分类: Python/Ruby
2012-09-23 13:35:06
Code kata为提高编程能力的一系列练习,是关于数据抽取的,其给出了两个编程题目,要求分别实现,然后把共同部分抽象出来,重新实现这两个程序。
题目一: weather.dat为某个地方6月的温度数据,要求输出温差最大的天,Dy为Day,MxT为该日最大温度,MnT为该日最小温度。
weather.dat
题目二:football.dat为某个联赛各个队伍的比赛成绩,要求输出第二列与第三列差异最大的队伍。
football.dat
href="">sunsite.tut.fi/rec/riku/soccer_data/tab/93_94/table.eng0.01_02.html Team P W L D F A Pts 1. Arsenal 38 26 9 3 79 - 36 87 2. Liverpool 38 24 8 6 67 - 30 80 3. Manchester_U 38 24 5 9 87 - 45 77 4. Newcastle 38 21 8 9 74 - 52 71 5. Leeds 38 18 12 8 53 - 37 66 6. Chelsea 38 17 13 8 66 - 38 64 7. West_Ham 38 15 8 15 48 - 57 53 8. Aston_Villa 38 12 14 12 46 - 47 50 9. Tottenham 38 14 8 16 49 - 53 50 10. Blackburn 38 12 10 16 55 - 51 46 11. Southampton 38 12 9 17 46 - 54 45 12. Middlesbrough 38 12 9 17 35 - 47 45 13. Fulham 38 10 14 14 36 - 44 44 14. Charlton 38 10 14 14 38 - 49 44 15. Everton 38 11 10 17 45 - 57 43 16. Bolton 38 9 13 16 44 - 62 40 17. Sunderland 38 10 10 18 29 - 51 40 ------------------------------------------------------- 18. Ipswich 38 9 9 20 41 - 64 36 19. Derby 38 8 6 24 33 - 63 30 20. Leicester 38 5 13 20 30 - 64 28 |
问题一的思路:
1. 遍历文件,如果该行的第一个单词为天数(数字),那么取得天数、最大温度、最小温度并放到返回列表中;
2. 返回列表排序,最大温度-最小温度最大的元素放在最前面;
3. 取得列表头的第一个元素;
问题二的思路:
1. 遍历该文件,如果该行的第一个单词为数字+点,那么取得队伍、P分数、W分数,并放入到返回列表中;
2. 对返回列表进行排序,P分数-W分数绝对值最大的放在最前面;
3. 取得列表头的第一个元素。
显然,两者的共同点在于整体处理流程的相似,而在每个流程中处理细节又能不同,如两者都需要遍历文件,抽取所需数据列,并排序得到最终结果,而它们的排序方法是不同的。
最终得到的通用部分如下:
-module(file_common). -export([process/4, process_stream/4, is_int_string/1]). process(FileName, SortPred, IsDataLine, LineParser) -> {ok, Stream} = file:open(FileName, read), Line = io:get_line(Stream, ''), ResultList = process_stream(Stream, Line, IsDataLine, LineParser), hd(lists:sort(SortPred, ResultList)). process_stream(_Stream, eof, _IsDataLine, _LineParser)-> []; process_stream(Stream, Line, IsDataLine, LineParser) -> NextLine = io:get_line(Stream, ''), case IsDataLine(Line) of false -> process_stream(Stream, NextLine, IsDataLine, LineParser); true -> Result = LineParser(Line), [Result | process_stream(Stream, NextLine, IsDataLine, LineParser)] end. is_int_string(Str) -> try list_to_integer(Str) of _Int -> true catch error:_ -> false end. |
这里非常有趣的是,抽取得到的通用部分分成两种情况,一是整个处理流程,如上面的process,二是一些通用的辅助小函数,如上面的is_int_string,其用来判断某个字符串是否为数字字符串。对于第二种抽象,是非常容易的,这里就不详细说明了。
对于整体处理流程的抽象,必须先定义出解决该问题的通用结构,并通过回调函数如SortPred,IsDataLine,LineParser来实现不通用部分。这意味着,必须先定义出问题的通用处理流程,每个步骤的输入、输出。
在这里,通用处理流程抽象如下:
1. 遍历文件,生成结果列表; 1.1 遍历文件的每一行; 1.2 如果该行是数据行,那么解析该行得到结果tuple,并加入到返回列表中,处理下一行; 1.3 如果改行不是数据行,那么接着处理; 2. 对结果列表进行排序; 2.1 使用排序谓词对列表进行排序; 3. 返回排序后列表的第一个元素; |
在没有对通用部分进行抽象的时候,并没有判断该行是否为数据行的概念,而是把其当做行解析的一部分,在对通用部分进行抽象过后,得到:把判断该行是否未数据行当做单独的函数,而不是耦合在解析函数内。从这个角度上来说,把程序泛化能够改进程序的结构。下面为处理weather.dat第一版的程序和第二版即抽取通用部分后的程序,如下
第一版:解析行的函数包括了判断该行是否为数据行 processLine(Line) -> |
第二版:由于需要通用公共部分,发现判断该行是否是数据行是个通用的功能,不仅仅在题目一中需要用到,在题目二中也需要用到,因此把其抽象出来。 IsDataLine = fun(Line) -> |
本文章的几个程序放在。