Stonelee's Blog

如无必要,勿增实体

DotNetBar中ComboTree显示整条路径信息

分享到: 更多

背景

ComboTree是个可以显示树状下拉菜单的ComboBox控件,弹出菜单如下所示:

1
2
3
Tree
     — node
     — node

点击节点node,默认显示结果为node,即仅仅是节点信息。 现在需求是要显示Tree node,即返回整条路径信息。

思路

看上去很简单的样子,在选择事件中得到该节点的父路径信息,然后设置ComboTree的Text值即可。 嗯嗯!我擦!Text没用!!

换个思路

更改显示节点,即SelectedNode的Text,这下行了。ComboBox显示成功,但问题是原来节点的信息也被更改了,导致树变得很难看。没办法,在弹出菜单出现时再改回去,弹出菜单出现时改回来吧!真是个丑陋的做法!

无代码无真相

主要是用Tag来存储内部信息,没什么技术含量~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//显示整个路径信息  
private void showFullText()
{
    string strPaths = "";
    comboTreeDate.SelectedNode.FullPath.Split(';').ToList().ForEach(s => strPaths += s);
    Node thisNode = comboTreeDate.SelectedNode;
    //存储原值  
    comboTreeDate.Tag = thisNode;
    thisNode.Tag = thisNode.Text;
    //更改text  
    thisNode.Text = strPaths;
}

private void comboTreeDate_PopupShowing(object sender, EventArgs e)
{
    //改回原值  
    Node thisNode= comboTreeDate.Tag as Node;
    if (thisNode!=null)
        thisNode.Text = thisNode.Tag.ToString();
}

private void comboTreeDate_PopupClose(object sender, EventArgs e)
{
    showFullText();
}

连续票号剔除算法

分享到: 更多

问题场景

现有票据0-999,单位A领取前100张(0-99),单位B领取之后100张(100-199),单位C也领取100张(400-499),求剩下的票号段

1
2
3
4
原来: 0-999
领取: 0-99 100-199 400-499
===========运算求解======================
剩余: 200-399 500-999

分析

如果单纯分析这个问题,此时票据只有1000张,数量很少,因此只需要将每个票号放到一个set中,然后进行简单的集合运算即可求出结果。

但是现实系统中票据量往往很大(9位票据就是近10亿条记录),每张票据一条记录进行存储是不现实的,因此需要进行分段存储,即将一次领取的几本票据(票号连续)作为一条记录,只记录起始票号和终止票号,运算时因为内存空间有限,也不能整体集合求解,需要另辟蹊径。

既然是边界条件判断,如果用各种if应该可以解决问题,但是这样太繁琐,非常容易出错。今天早上忽然想到,这个问题中票号是连续的,因此只要找到前后几个关键点,整段票号就能确定,而段内的票号是不重要的。

算法

1.找到全部关键点

原来的号:0 999

每段领取号各向外扩展一个号,作为可能的关键点: -1 100 99 200 399 500

2.将上两种关键点放到一个数组中,去重复排序,得到: -1 0 99 100 200 399 500 999

3.去掉领取号中重复的点,剩余: -1 200 399 500 999

4.只取原来范围内的点,剩余 200 399 500 999

5.两两一组,即为所求解 (200,399),(500,999)

c#技巧

  • HashSet可以去重复,也可使用Distinct
  • 引入Linq后,数组可以进行集合运算。交集Intersect,差集Except,联集Union
  • 数组只能存储相同类型的数据
  • 二维数组和交错数组是不同的,二维数组形如int[,],GetLength(0)可获得第0维长度,依次类推
  • 需要筛选操作的话,使用交错数组string[][],而不要使用多维数组int[,]

无码无真相

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//从父表票号段中剔除子表中的票号段  
public static int[,] getLeftNums(int[,] fathers, int[,] sons)
{
    //填入所有可能的关键点  
    //HashSet可去重复  
    HashSet<int> possibleSets = new HashSet<int>();
    foreach (int i in fathers)
        possibleSets.Add(i);
    for (int i = 0; i < sons.GetLength(0); i++)
    {
        possibleSets.Add(sons[i, 0] - 1);
        possibleSets.Add(sons[i, 1] + 1);
    }

    //去掉sons中已经使用过的点  
    HashSet<int> sonSets = new HashSet<int>();
    foreach (int i in sons)
        sonSets.Add(i);
    IEnumerable<int> withoutSonsSets = possibleSets.Except(sonSets);

    //只取fathers范围内的点  
    HashSet<int> inFatherSets = new HashSet<int>();
    foreach (int value in withoutSonsSets)
    {
        for (int i = 0; i < fathers.GetLength(0); i++)
        {
            if (value >= fathers[i, 0] && value <= fathers[i, 1])
            {
                inFatherSets.Add(value);
                break;
            }
        }
    }

    //排序  
    List<int> sortedLists = inFatherSets.OrderBy(i => i).ToList<int>();

    //两两一组  
    int[,] results = new int[sortedLists.Count() / 2, 2];
    for (int i = 0; i < sortedLists.Count(); i++)
    {
        int index = i / 2;
        if (i % 2 == 0)
            results[index, 0] = sortedLists[i];
        else
            results[index, 1] = sortedLists[i];
    }
    return results;
}

测试用例

1
2
3
4
5
6
7
8
9
10
[Test]
public void getLeftNum2()
{
    int[,] fathers = new int[1, 2] { { 0, 999 } };
    int[,] sons = new int[3, 2] { { 0, 99 }, { 100, 199 }, { 400, 499 } };
    int[,] results = frmQueryBillDistribute.getLeftNums(fathers, sons);

    int[,] asserts = new int[2, 2] { { 200, 399 }, { 500, 999 }};
    Assert.AreEqual(results, asserts);
}

LINQ技巧小结

分享到: 更多

LINQ可不是简单的SQL语句的替代品,它将繁琐的数据操作(排序、求和、分组、投影、打包)统一起来,极大地简化了代码。

查询所有以ABC开头的字符串

1
2
3
string[] xx={"ABCEFG","ABCASDFASDF","12312ADSFASD"};
xx.Where(x=>x.StartsWith("ABC")).ToList()
  .ForEach(x=>Console.WriteLine(x));

使用正则来解决

1
2
3
string[] xx = { "ABCEFG", "abcASDFASDF", "12312ADSFASD" };
xx.Where(s => Regex.IsMatch(s, "^abc",RegexOptions.IgnoreCase))
  .ToList().ForEach(s =>Console.WriteLine(s));

简化字符

1
2
3
4
string source = @"西瓜,1,a
苹果,2,b
橙子,3,c";
结果数组:西瓜,苹果,橙子
1
2
string[] array1 = source.Split(new char[] { '/r', '/n' })
                  .Select(x => x.Split(',')[0]).ToArray();

LINQ方式实现学生成绩汇总(右连接、聚合函数)

分享到: 更多

需求

从数据表students中取得所有学生姓名,然后将其scores表对应中的分数相加,显示一个“姓名:分数”的列表

  • 取得所有姓名(包括没有成绩的)
  • 分数是该学生所有成绩的总和

先看效果图:

数据库结构简化模拟如下:

1
2
3
4
5
6
7
8
9
10
class Student
{
    public int id { get; set; }
    public string name { get; set; }
}
class Score
{
    public int StudentId { get; set; }
    public int score { get; set; }
}

插入测试数据:

1
2
3
4
5
6
7
8
Student p1 = new Student() { id = 1, name = "张三" };
Student p2 = new Student() { id = 2, name = "李四" };
Student p3 = new Student() { id = 3, name = "王五" };
Score s1 = new Score() { StudentId = 1, score = 80 };
Score s2 = new Score() { StudentId = 1, score = 90 };
Score s3 = new Score() { StudentId = 2, score = 70 };
List<Student> students = new List<Student>(new Student[] { p1, p2, p3 });
List<Score> scores = new List<Score>(new Score[] { s1, s2, s3 });

查询,注意:

  • LINQ中没有left join和right join之分,只有关键词join来控制连接,join之前表的作为完整的表连接,join之后的表需要使用DefaultIfEmpty来进行处理,才能正常的显示null值
  • group中的key是分组的字段,分组后就可以使用聚合函数了
  • LINQ就像生产流水线,可以分阶段进行数据排序、汇总、投影等操作,最后组装起来就能达到预定的效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//left join,返回左边每一个元素  
var studentsTable = from student in students
                    join score in scores on student.id equals score.StudentId into joinedTable
                    from j in joinedTable.DefaultIfEmpty()//处理右边为空的情况  
                    select new
                    {
                        name = student.name,
                        score = j != null ? j.score : 0
                    };
//聚合函数  
var data = from student in studentsTable
           group student by student.name into g
           select new
           {
               name = g.Key,
               score = g.Sum(student => student.score)
           };
foreach (var d in data)
{
    Console.WriteLine("name:" + d.name + ";score:" + d.score.ToString());
}
Console.ReadLine();

看看效果:

1
2
3
4
5
foreach (var d in data)
{
    Console.WriteLine("name:" + d.name + ";score:" + d.score.ToString());
}
Console.ReadLine();

数据抓包及分析经验小结

分享到: 更多
  • 分析网络包——Wireshark
  • 查看AMF数据结构工具——AMF Explorer
  • 查看cookie工具——Firecookie
  • cookie操作
  • cookie分析

1.分析网络包——Wireshark

  • ubuntu下需要sudo wireshark启动
  • Capture-interfaces:Start有ip的Device
  • 本机网络包无法抓取
  • filter中填写:
    • http and ip.src==192.168.1.103只显示本机发送的http协议包
    • http and ip.addr==192.168.1.103只显示本机发送和接收的http协议包

2.查看AMF数据结构工具——AMF Explorer

AMF Explorer基于Firebug的AMF查看插件

3.查看cookie工具——Firecookie

Firecookie基于Firebug的cookie查看、删除修改等操作插件,还可以下断点

4.cookie操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import urllib2
import cookielib
#自动记录cookie
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
request = urllib2.Request('http://www.pythonchallenge.com/pc/def/linkedlist.php?busynothing=12345')
response = opener.open(request)

#获取服务器返回的cookie
cookies = cj.make_cookies(response, request) # extract all cookies that this request-response pair added to the jar

#修改cookie
cookie = cookies[0]
cookie.value = 'the flowers are on their way' # 将其值设为要发送的,不必自己转码
request = urllib2.Request('http://www.pythonchallenge.com/pc/stuff/violin.php')
cj.set_cookie(cookie)
cj.add_cookie_header(request) #将cookies放入请求头部
print urllib2.urlopen(request).read()

5.cookie分析

1
Connection   keep-alive

说明使用的是长连接,应该使用Httplib2而不是Urllib2

网站登录信息往往保存在基于session的cookie里,因此在发送数据包的时候提供

服务端Response Headers set-cookie发送给客户端cookie然后

客户端Request Headers Cookie才能保存

如何解析word中存在合并单元格的表格

分享到: 更多

对于word中存在合并单元格的表格:

下图是对Cells遍历的结果,True表示该行该列的单元格存在,False表示不存在。

可以看到,word表格中行和列的合并单元格(Merge)机制是不一样的。 Cells数组在计算时会无视行中的合并,将合并单元格作为普通格,依次进行存储,合并后空出的项排在本行的最后。 而在计算列时,会在第一次遇到合并单元格时将其进行存储,其后遇到时保留其指针,但是将该格剔除(不太好描述,可以理解为将其作为None值保存)。这是符合我们一般理解的。

问题来了: 在存在合并单元格的行中,如何知晓哪些行被合并?

从网上搜索解决方案,在 这篇帖子中提到,在word可以支持合并单元格操作之前,VBA中就实现了table对象,但是一直没有升级。因此当表格中存在合并单元格时,表格内容无法完整获取。

目前想到的思路是判断每个单元格的宽度,用单元格数量完整的行做参照,判断单元格数量不全的行每个单元格的宽度,但是这个方案只对标准的合并单元格有效,宽度略微有所调整就不行了。

Excel没有这种问题,难道Word的表格就是个纯粹的杯具吗?

解决方案待续~,期待大牛解答。

Lxml中xpath的text()陷阱

分享到: 更多

lxml中使用xpath方法获取html标签中的内容时经常会使用text()

1
2
tree = etree.HTML(html)
s = tree.xpath('//p[@id="lk"]/a[3]/text()')[0]

这段代码是从百度首页html中解析hao123这段文字。

使用一下这个变量s,很普通吧~

1
2
3
4
>>> print s
hao123
>>> s=='hao123'
True

但是如果想把这个变量使用pickle序列化

1
2
import pickle
pickle.dumps(s)

报错了~

1
TypeError:can't pickle _Element objects

为什么呢?

我们被s==’hao123’骗了,这个s并不是str类型,而是lxml.etree._ElementStrinResult类型,比str多了getparent、is_text等多个方法,pickle对这个类型不能识别,因此转换一下类型即可。

自己写个转换类型函数

1
2
def get_text(nodes):
    return [unicode(node.xpath('text()')[0]) for node in nodes]

以后需要使用text()的时候,改为调用这个函数了:

1
2
nodes = tree.xpath('//p[@id="lk"]/a[3]')
s = get_text(nodes)[0]

总结

在pickle时,请谨慎使用text()

Python字符串格式化小结

分享到: 更多

将python字符串格式化方法以例子的形式表述如下:

定义宽度

1
2
>>> '%*s' %(5,'some')
' some'

左对齐

1
2
>>> '%-*s' %(5,'some')
'some '

最小宽度为6的2位精度的浮点小数,位数不够时前补空格

1
2
>>> '%6.2f' %8.123    
'  8.12'

字典形式,可在正数前显示加号,位数不够时前面补0

1
2
>>> '%(name)s = %(num)+06.2f' %{'name':'a','num':8.123}
'a = +08.12'

在八进制数前面显示零(‘0’),在十六进制前面显示’0x’或者’0X’(取决于用的是’x’还是’X’)

1
2
>>> 'dec: %d/oct: %#o/hex: %#X' % (123,123,123)
'dec: 123/oct: 0173/hex: 0X7B'

科学计数法

1
2
>>> '%e' % 1234.567890
'1.234568e+03'

非专业python编程方法小结

分享到: 更多

本人是非计算机专业毕业的非专业程序员,毕业后也是在非专业软件公司从事业余编码工作,总结的这套编程方法可能会不入某些牛人的法眼,请指点~

1.明确需求

搞清楚到底想做什么,即程序最终要实现的功能。需求越简单明确越好,功能越单一越好。

Simple is better than complex

2.功能分解

将目标分解出可以实现的步骤,然后将每个步骤细化为函数(function),如果某些函数显而易见是一个性质的,可以归结到一个类中,那就没什么说的,就放到一个类中。不提前设计类,不做总体类划分。

3.命令行尝试相关API的用法

由于本人非专业程序猿,记忆力不咋地,偏偏兴趣广泛,桌面网络数据库游戏服务器算法都略懂,因此经常需要在命令行中配合API及相关文档写些脚本来尝试其用法。专家及熟手可以省略此步骤。

4.快速开发

终于可以开始写代码了,首先打开最喜欢的文本编辑器,本人是Vim,因为够轻便够好用。把第三步尝试得出的脚本填到第二步分解出来的每个函数中,有重复的坏味道出现时就抽出来函数之,有新的类冒出来时就类之。一切都是顺其自然,让代码影响程序的结构。

5.单元测试

对于辅助性质的函数,比如文字串变换或列表调整之类的,都直接在doctest里写测试,对于功能性质的函数,比如类中的各接口函数,新建个test文件测试之,之后nosetests即可。

6.重构!

由于没有类设计阶段(鄙人认为这是基本上根本不需要的,第二步功能分解实际上就是类设计阶段的简化版),代码自己随心所欲产生出来的程序往往结构上有问题,在项目越来越大,函数越来越多的时候会产生坏味道,这就是重构的好机会了。通过重构将函数归到类中,将类分到不同包中,在单元测试基础上的重构是很开心的,每次重构都是对项目又一次深入理解的机会,都是项目脱胎换骨的机会。当然,世上没有完美,在满足项目需求的前提下做到适当扩展就可以了,要适合而止。应该牢记“如无必要,勿增实体”,重构需谨慎,全靠自己把握。

写代码应该是很自然,很美好的事情,应该没有那么多刻板的步骤,没有必要套用那么多设计模式、类的条条框框。顺其自然,随心而变就好。

Windows下编译开源代码中的sphinx文档

分享到: 更多

许多python开源代码clone下来后会发现里面附带docs,有许多是Sphinx格式的帮助文件,可以将其编译为html以便在本地查看。

编译sphinx文档的方法很简单,首先安装Sphinx

1
easy_install Sphinx

然后在docs目录下编译

1
make html

这样就会在build生成html样式的帮助文档。

在windows下如果目录中没有make.bat文件,会报错:

1
'make’不是内部或外部命令,也不是可运行的程序或批处理文件。

解决方案:新建目录

1
2
md temp
cd temp

生成sphinx脚手架

1
sphinx-quickstart

然后将生成的make.bat复制到原来文档目录下,然后make html。

有时候会报错

1
Error:Cannot find source directory

这是因为配置路径有问题,按照提示进行相应配置即可。