文本可以说是除了数据库外几乎最常见的数据存储形式,针对文本的计算非常重要。然而文本本身没有计算能力,不象数据库有SQL语法,这样对文本的计算就需要借助程序设计语言编码,而大多数用于文本处理的程序语言都没有集合化的,编写批量运算时很繁琐。比如用Java写个很简单地求和运算就要很多行,如果涉及到过滤分组这种运算就需要几百行代码。近年来新出来的perl,python,R等脚本语言在这些方面有所改善,但对批量结构化计算的支持仍然不足,而且集成性也较差。
还有一种方案是将文本数据导入数据库再利用SQL计算,但文本经常缺乏数据库要求的强数据类型特征,导入过程常常要伴随繁琐的数据整理,而且多了一个步骤,事务的处理效率也会受到严重影响。
作为集合化的动态脚本语言,集算器一定程度地弥补这方面的缺失。这里将列举一些文本计算中常见的情况,说明集算器实施这类计算的优势。
无结构运算
文本解析
文本T.txt的行内数据项由不确定数量的空格分隔开:
20010-8-13 991003 3166.63 3332.57 3166.63 3295.11
2010-8-10 991003 3116.31 3182.66 3084.2 3140.2
……
现在要计算每行最后四项数据的平均值列表。用集算器只要一句:
A |
|
1 |
=file("T.txt")[email protected]().([email protected](“”).to(-4).avg()) |
[email protected]()将文本读入成字串集合,[email protected](“”)将字串按不定数量的空白符拆成子串集合,@p将自动解析成合适的数据类型以便进一步计算(这里计算平均)。
将逗号分隔符文本T.csv中行内数据项数不少于8项的行的前8项写出成另一个文本R.txt,分隔符替换成|(某些银行系统采用的分隔符):
A |
|
1 |
=file("T.csv")[email protected]().(~.array(“,”)).select(~.len()>=8) |
2 |
>file(“R.txt”).write(A1.(~.to(8).string(“|”))) |
string()函数可将集合按指定分隔符再拼成字串。
文本T.txt中都是形如下行的串,需要按字符US前的州名(LA)分组拆分成多个文件。
COOP:166657,‘NEW IBERIA AIRPORT ACADIANA REGIONAL LA US‘,200001,177,553
……
A |
|
1 |
=file("T.txt")[email protected]() |
2 |
=A1.group(mid(~,pos(~," US‘")-2,2):state;~:data) |
3 |
>A2.run(file(state+".txt").export(data)) |
集算器也提供了对正则表达式的支持以应对复杂的拆解需求。不过由于正则表达式的使用难度较大且性能较差,一般建议仍然用常规方法实现。
结构化
日志S.log中每3行构成一段完整信息,需要将其解析成结构化数据后再到T.txt:
A |
B |
||
1 |
=file(“S.log”)[email protected]() |
||
2 |
=create(…) |
建立目标结果集 |
|
3 |
for A1.group((#-1)\3) |
… |
按行号分组,每3行一个单位 |
… |
… |
从A3(这3行)中解析出字段值 |
|
… |
>A2.insert(…) |
插入到目标结果集 |
|
… |
>file(“T.txt”).export(A2) |
写出结果 |
有了按行号分组的机制,就可以用循环每次处理一组数据,简化难度。
显然,更简单的单行情况是其特例。
如果S.log大到不能读入内存,也可以使用游标逐步读入并写出:
A |
B |
||
1 |
=file(“S.log”)[email protected]() |
创建游标用流式读入文件 |
|
2 |
=file(“T.txt”) |
结果文件 |
|
3 |
for A1,3 |
… |
每读入3行执行一轮循环 |
… |
… |
从A3(这3行)中解析出字段值 |
|
… |
>[email protected](...) |
追加写到文件中 |
熟悉的用户还可以优化代码,使得解析多条记录后一次写出,会有更好的性能。
日志S.log中每段完整信息均以”---start---“开头,包含行的数量不确定。这时只要将前面的A3格改成:
3 |
for [email protected](~==”---start---”) |
出现---start---时会产生一个新分组 |
类似地,大文本时也可以用游标处理,也是将上面A3格改成:
3 |
for A1;~==”---start—“:0 |
出现---start---时另起一轮循环 |
不定行还有一种情况,同一段信息的每一行都有相同的前缀(比如该段日志所属的用户号等),当这个前缀发生变化时就表示开始另一段信息了,这时仍然只要简单地修改A3代码即可处理:
3 |
for [email protected](left(~,6)) |
前6个字符变化时产生一个新组 |
3 |
for A1;left(~,6) |
前6个字符变化时另起一轮循环 |
前一小节的运算也可以改造成使用游标支持大文本。
查找统计
在目录下所有文本中找出含有指定单词的文件,并列出所在行内容及行号:
A |
||
1 |
[email protected](“*.txt”) |
|
2 |
=A1.conj(file(~)[email protected]().(if(pos(~,"xxx"),[A1.~,#,~].string())).select(~)) |
grep是常用的unix命令,但有些操作系统下没有,且在程序中实现也不简单。集算器提供了文件系统的遍历功能,结合文本计算能力,只要两句代码就能完成。
列出文本T.txt中所有出现过的单词及次数,忽略大小写:
A |
||
1 |
=lower(file(“T.txt”).read()).words().groups(~:word;count(1):count) |
WordCount是著名的练习题,集算器提供了words()函数将串拆分成单词,只要一句就可以完成这个运算。
列出文本T.txt包括字母a,b,c的所有单词,忽略大小写:
A |
||
1 |
=lower(file(“T.txt”).read()).words().select(~.array(“”).pos([“a”,”b”,”c”])) |
由于次序问题,判断字母包含不能用子串查找,要用array(“”)将串拆成单字符集合,再用集合从属去判断。有集合运算支持的集算器也只要一句即可。
这些运算都可以用分段或游标的方式简单改造以支持大文本。