使用 Shell - 统计单词和字母

作者:Dave Taylor

我知道我一直在写关于在 shell 脚本中使用变量的基础知识,但我将偏离主题,解答我最近收到的一个问题。可以吗?(并且,嘿,请给我写信。)

“亲爱的 Dave,我希望在下次玩Hangman或其他文字游戏时能获得优势。我想知道英语中最常见的单词是什么,以及书面材料中最常见的字母是什么。如果您能展示如何用 shell 脚本做到这一点,那对您的专栏很有用,如果不能,您能给我指出一个在线资源吗?谢谢,Mike R.”

好的,Mike,我可以先告诉你,玩Hangman的秘诀是确保你有足够的猜测机会,在陷入困境之前猜对至少 30% 的字母。哦,这不是你想要的,是吗? 总是第一个要猜的字母是 E,它是英语中最常见的字母。 如果你有一套Scrabble,你也可以算出字母的频率,因为单个字母的分数与它们的频率成反比。 也就是说,E 值一分,而 Q 和 Z——英语中两个非常罕见的字母——每个值十分。

但是,让我们编写一个 shell 脚本来验证和证明这一切,好吗?

第一步是找到一些书面材料进行分析。 这很容易做到,只需访问我在网上最喜欢的地方之一,古腾堡计划。 你也可以在那里访问 www.gutenberg.org

有成千上万本免费、可下载的书籍,我们只选取三本:Bram Stoker 的Dracula,Charles A. Beard 和 Mary Ritter Beard 的History of the United States,以及 Jane Austen 的Pride and Prejudice。 它们显然都有些年代了,但这对于我们的目的来说没问题。 为了方便起见,我将以纯文本格式下载它们,并将古腾堡计划的书呆子介绍也留在每个文件的顶部,只是为了增加单词的多样性,嗯,因为我比较懒。 亲爱的读者,您觉得可以吗?

以下是关于这三本书的简要介绍

$ wc *txt
   16624  163798  874627 dracula.txt
   24398  209289 1332539 history-united-states.txt
   13426  124576  717558 pride-prejudice.txt
   54448  497663 2924724 total

好的,所以我们有 54,448 行文本,代表 497,663 个单词和 2,924,724 个字符。 这真是大量的文本。

单词频率

找出任何我们想要的统计数据的关键在于认识到我们需要使用的基本策略是将内容分解成更小的片段,对它们进行排序,然后使用强大的uniq -c功能,它可以删除输入流中的重复项,并在执行过程中计算频率。 作为 shell 管道,我们讨论的是sort | uniq -c,再加上我们需要的任何命令来分解各个实体。

对于这项任务,我将使用tr,像这样,将空格转换为换行符

$ cat *txt | tr ' ' '\
' | head        
The
Project
Gutenberg
EBook
of
Dracula,
by
Bram
Stoker

好的,那么当我们真正在我们合并后的文本的所有 54,448 行上释放这个“野兽”时会发生什么呢?

$ cat *txt | tr ' ' '\
> ' | wc -l
  526104

这很奇怪。 在某种程度上,我原本期望按空格分隔符分解每一行应该与wc的单词计数非常接近,但很可能文档中有诸如“the end. The next”之类的标点符号,其中双空格变成两行,而不是一行。 不过,不用担心,一旦我们采取下一步,这一切都会消失。

现在我们有能力将文档分解成单个单词,让我们对其进行排序和“uniq”操作,看看我们看到了什么

$ cat *txt | tr ' ' '\
' | sort | uniq | wc -l
   52407

但是,那是不对的。 你知道为什么吗?

如果你说:“伙计!你需要考虑大小写!”,你就走对了方向。 实际上,我们需要将所有内容音译为小写。 我们还需要去除所有的标点符号,因为现在它将“cat,”和“cat”算作两个不同的单词——这不好。

首先,音译最好使用字符组而不是字母范围来完成。 在 tr 中,使用 [::] 表示法有点奇怪

$ echo "Hello" | tr '[:upper:]' '[:lower:]'
hello

去除标点符号稍微棘手一点,但不多。 同样,我们可以在 tr 中使用字符类

$ echo "this, and? that! for sure." | tr -d '[:punct:]'
this and that for sure

很酷,是吧? 我敢打赌你不知道你可以这样做! 现在,让我们把它们放在一起

$ cat *txt | tr ' ' '\
' | tr '[:upper:]' '[:lower:]' | 
tr -d '[:punct:]' | sort | uniq | wc -l
   28855

所以,这将单词数从 52,407 减少到 28,855——对我来说有道理。 不过,还需要进行一次转换。 让我们去除所有不包含字母字符的行,以消除数字。 这可以通过简单的grep -v '[^a-z]'":

$ cat *txt | tr ' ' '\
' | tr '[:upper:]' '[:lower:]' | 
tr -d '[:punct:]' | grep -v '[^a-z]' | 
sort | uniq | wc -l
   19,820

顺便说一句,如果你只分析Dracula,结果表明整本书只有 9,434 个独特的单词。 有用,是吧?

现在,最后,让我们稍微调整一下,看看这个语料库中最常见的十个单词

$ cat *txt | tr ' ' '\
' | tr '[:upper:]' '[:lower:]' | 
tr -d '[:punct:]' | grep -v '[^a-z]' | 
sort | uniq -c | sort -rn | head
29247 the
19925 
16995 of
14715 and
13010 to
9293 in
7894 a
6474 i
5724 was
5206 that

现在,您知道了。

下个月,我将通过展示如何分析单个字母的出现次数来结束本文,最后,我将提供一种方法来找到一些很棒的Hangman单词来难倒你的朋友。

Dave Taylor 是一位拥有 26 年 UNIX 经验的资深人士,The Elm Mail System 的创建者,也是最近畅销书Wicked Cool Shell ScriptsTeach Yourself Unix in 24 Hours(在他的 16 本技术书籍中)的作者。 他的主要网站是 www.intuitive.com,他还在 AskDaveTaylor.com 上提供技术支持。 您也可以通过 twitter.com/DaveTaylor 在 Twitter 上关注 Dave。

加载 Disqus 评论