碎碎念

2006, March 30

字母加总的AWK脚本

Filed under: awk




Up: (dir)

字母加总

awk中的关联数组十分灵活方便。这一篇中将会涉及关联数组的用法。同时还示范了将FS设为空的用法:-0

–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。所以这一次我们要写一个脚本来计算英文单词字母的总和。如果有字典文件还可以找出和为特定值的所有单词和词组。





Next: ,
Up: Top

任务分析

根据前面引用的签名档,字母A到Z将分别由数字1到26来表示——A=1,B=2,…,Z=26。不分大小写所以A=a=1。

  • 首先,要对字母进行编号。方法有很多种,这里我们会使用awk中常用的技巧来完成编号。
  • 其次,读入字母,并根据相应的编号进行加总。

只要两步——看来是个简单任务!现在开始想一想具体怎么用awk实现?或者(如果你对awk不是很熟的话)用其他编程语言怎么实现?





Next: ,
Previous: 任务分析,
Up: Top

计算总和的脚本

首先,对字母编号。awk中为数组元素分配多个值常用的方法是用`split(STRING, ARRAY [, FIELDSEP])‘函数。它的返回值是数组元互素的个数。需要注意的是:数组的下标是从`1‘,而不是从`0‘开始的。

	n=split("ABCDEFGHIJKLMNOPQRSTUVWXYZ",alpha,"")

这条语句会生成alpha数组,内容如下:alpha[1]="A";alpha[2]="B";…;alpha[26]="Z"。但我们需要的是以字母为索引找到对应的数值,而不是相反。所以还要进一步加工:

	while(n) { num_alpha[alpha[n]]=n; n--}

现在得到了一个关联数组num_alpha[]。内容如下:

num_alpha["A"]=1
num_alpha["B"]=2
...
num_alpha["Z"]=3

现在只要以输入的字母为索引就能得到对应的数值了。假设输入"ADD",只要分别以“A”、“D”、“D”为索引加总:

  num_alpha["A"] + num_alpha["D"] + num_alpha["D"]
=       1        +      4         +       4
=       9

加总的脚本如下:

     {
       # 每读入一条记录时,先将总和归0
       sum=0
       # 将所有栏位的字母加总。非字母字符将被忽略,
       # 因为我们并未为非字母字符同值
       # 注意:这里用了toupper()。在gawk中还可以设置
       # IGNORECASE使之不区分大小写。
       for (i=1;i<=NF;i++) sum+=num_alpha[toupper($i)]
       # 输出计算结果
       print sum
     }

上面的脚本中并未对输入进行限定。如果输入含有非字母字符,脚本仅是忽略它们——因为在数组中没有相应的索引,所以不会对运算结果造成影响。但你可能希望对结果进行限定,使得只有当输入中只含字母和空格(词组)时才进行加总。为此我们可以用正则表达式对输入做简单的筛选:`/^[a-zA-Z ]+$/1。注意后面的空格。
具体实现如下:

gawk 'BEGIN{
   FS=""
   # split()的最后一个参数可省略,因为而FS是一样的
   n=split("ABCDEFGHIJKLMNOPQRSTUVWXYZ",alpha,"")
   while(n) { num_alpha[alpha[n]]=n; n-- }
  }
/^[a-zA-Z ]+$/{
   sum=0
   for (i=1;i<=NF;i++) sum+=num_alpha[toupper($i)]
   print sum
      }'

另一种实现方法

不过我们还可以将这个脚本写得更紧凑一点,我们需要有一种不同的思路。上一个脚本,我们预先计算每个字母对应的数值,但我们其实可以边加总边计算,即省略了数组这一中间过程。awk提供了一个函数`index(IN, FIND)‘,用来返回`FIND‘在`IN‘中的位置。而“ABCD…Z”中字母的位置与各自的编号是一致的,因而可以以输入的字母为索引通过`index()‘来实时计算总和。

gawk -F '' '{
   sum=0               # 每读入一条记录时将sum置0
   for(i=1;i<=NF;i++)  # 将所有栏位相加
     sum+=index("ABCDEFGHIJKLMNOPQRSTUVWXYZ",toupper($i))
   print sum
 }'

提示:命令行选项-FFS设置为空字串,这样每个字母刚好是作为一栏。这与在脚本的BEGIN段使用`FS=""‘语句是一样的。-F ''也可以写成-v FS=''的形式。下面是一些运行结果:

$ echo fortune|gawk -F '' '{sum=0;for(i=1;i<=NF;i++)sum+=index("ABCDEFGHIJKLMNOP
QRSTUVWXYZ",toupper($i));print sum}'
99
	

财富的确是很重要,看来比爱重要很多——用“love”运行的结果是“54”。不过建议试一下“love and care”——光有爱是不行的还要懂得关怀!





Previous: 另一种实现方法,
Up: Top

显示特定总和值单词/词组的脚本。

如果你有看上一篇并有找到合适的英汉字典的话,我们还可以让gawk替我们找出所有和为特定值的单词或短语。为此需要增加一条判断语句:

gawk -F '' '{
   sum=0
   for(i=1;i<=NF;i++)
     sum+=index("ABCDEFGHIJKLMNOPQRSTUVWXYZ",toupper($i))
   # 总和为100的话则输出当前记录($0),当然你也可以改成其他值
   if (sum==100) print
 }'

当然我们还需要有字典文件,对我们之前用过的字典文件dict.txt2进行加工:
`cut -f1 dict.txt >voca.txt
如果用的是Windows的话,你可能没有cut工具,那就用awk:

gawk -F "\t" "{print $1}" dict.txt >voca.txt

确保字生成的字典文件与脚本在同一目录中,然后运行前面的脚本就大功告成了。

gawk -F '' '{...省略...}' voca.txt

这是脚本在Windows下的运行结果3

e:\>gawk -F "" "{sum=0;for(i=1;i<=NF;i++)sum+=index(\"ABCDEFGHIJKLMNOPQRSTUVWXYZ
\",toupper($i));if(sum==100)print}" voca.txt
...省略
American oil
Anchicodium
Anglophobia
Anthozoa
Astralon
AutoMark
Avernus
Bar Draught
Bear steady!
Berlin blue
Bingham body
Bombay hemp
Bouma cycle
Butoxide
C battery cab
Carbolan dye
...省略

当然如果你不想输入那么长的脚本的话,可以将它放到单独的脚本文件中。

[–结束–]

Footnotes

[1] 也可以使用字母类[[:alpha:]]
[2] 见“gawk显示指定内容”
[3] 看来总和为100的单词和词组还真不少。用我的字典,删除重复项后,共有3177条条目总和为100

Comments »

The URI to TrackBack this entry is: http://blah.blogsome.com/2006/03/30/awk-sumup/trackback/

No comments yet.

RSS feed for comments on this post.

Leave a comment

Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>



请输入验证码。

Get free blog up and running in minutes with Blogsome
Theme designed by Jay of onefinejay.com