字母加总
awk中的关联数组十分灵活方便。这一篇中将会涉及关联数组的用法。
——by hq00e
曾经看过这样的一个签名档:
如果将英语的26个字母由A到Z分别编上1到26的分数,
你的知识(KNOWLEDGE)只能得到11+14+15+23+12+5+4+7+5=96分。
你努力工作(HARDWORK)也只能得到8+1+18+4+23+15+18+11=98分。
只有你的态度(ATTITUDE)才是左右你生命全部的1+20+20+9+20+21+4+5=100分。
想知道还有哪些单词的总和是100吗?这知道答案很容易,只要你会用awk。所以这一次我们要写一个脚本来计算英文单词字母的总和。如果有字典文件还可以找出和为特定值的所有单词和词组。
任务分析
根据前面引用的签名档,字母A到Z将分别由数字1到26来表示——A=1,B=2,…,Z=26。不分大小写所以A=a=1。
- 首先,要对字母进行编号。方法有很多种,这里我们会使用awk中常用的技巧来完成编号。
- 其次,读入字母,并根据相应的编号进行加总。
只要两步——看来是个简单任务!现在开始想一想具体怎么用awk实现?或者(如果你对awk不是很熟的话)用其他编程语言怎么实现?
计算总和的脚本
首先,对字母编号。awk中为数组元素分配多个值常用的方法是用split(STRING, ARRAY [, FIELDSEP])
函数。它的返回值是数组元互素的个数。需要注意的是:数组的下标是从’1’,而不是从’0’开始的。
1 | n=split("ABCDEFGHIJKLMNOPQRSTUVWXYZ",alpha,"") |
这条语句会生成alpha数组,内容如下:alpha[1]=”A”;alpha[2]=”B”;…;alpha[26]=”Z”。但我们需要的是以字母为索引找到对应的数值,而不是相反。所以还要进一步加工:
1 | while(n) { num_alpha[alpha[n]]=n; n–} |
现在得到了一个关联数组num_alpha[]。内容如下:
1 | num_alpha["A"]=1 |
现在只要以输入的字母为索引就能得到对应的数值了。假设输入”ADD”,只要分别以”A”、”D”、”D”为索引加总:
1 | num_alpha["A"] + num_alpha["D"] + num_alpha["D"] |
加总的脚本如下:
1 | { |
上面的脚本中并未对输入进行限定。如果输入含有非字母字符,脚本仅是忽略它们——因为在数组中没有相应的索引,所以不会对运算结果造成影响。但你可能希望对结果进行限定,使得只有当输入中只含字母和空格(词组)时才进行加总。为此我们可以用正则表达式对输入做简单的筛选:/^[a-zA-Z ]+$/
(也可以使用字母类[[:alpha:]]
)。注意后面的空格。
具体实现如下:
1 | gawk 'BEGIN{ |
另一种实现方法
不过我们还可以将这个脚本写得更紧凑一点,我们需要有一种不同的思路。上一个脚本
,我们预先计算每个字母对应的数值,但我们其实可以边加总边计算,即省略了数组这一中间过程。awk提供了一个函数index(IN, FIND)
,用来返回FIND
在’IN’中的位置。而”ABCD…Z”中字母的位置与各自的编号是一致的,因而可以以输入的字母为索引通过 index()
来实时计算总和。
1 | gawk -vFS="" '{ |
下面是一些运行结果:
1 | $ echo fortune|gawk -vFS="" '{sum=0;for(i=1;i<=NF;i++)sum+=index("ABCDEFGHIJKLMNOPQRSTUVWXYZ",toupper($i));print sum}' |
财富的确是很重要,看来比爱重要很多——用”love”运行的结果是”54”。不过建议试一下”love and care”——光有爱是不行的还要懂得关怀!
显示特定总和值单词/词组的脚本。
如果你有看上一篇并有找到合适的英汉字典的话,我们还可以让gawk替我们找出所有和为特定值的单词或短语。为此需要增加一条判断语句:
1 | gawk -vFS="" '{ # 将FS设为空字串,这样每个字母都会被视为一个栏位。 |
当然我们还需要有字典文件,对我们之前用过的字典文件dict.txt2进行加工:cut -f1 dict.txt >voca.txt
如果用的是Windows的话,你可能没有cut工具,那就用awk:
1 | gawk -v FS="t" "{print $1}" dict.txt >voca.txt |
运行前面的脚本:
1 | gawk -vFS="" '{…省略…}' voca.txt |
这是脚本在Windows下的运行结果(看来总和为100的单词和词组还真不少。用我的字典,删除重复项后,共有3177条条目总和为100):
1 | e:> gawk -vFS="" "{sum=0;for(i=1;i<=NF;i++)sum+=index("ABCDEFGHIJKLMNOPQRSTUVWXYZ",toupper($i));if(sum==100)print}" voca.txt |
当然如果你不想输入那么长的脚本的话,可以将它放到单独的脚本文件中。