【问题标题】:number of unique words in a document文档中唯一单词的数量
【发布时间】:2015-12-20 03:33:06
【问题描述】:

我有一个非常大的 txt 文件 (500GiB),我想获取其唯一单词的数量。我试过this,但它似乎很慢,因为它确实排序:

grep -o -E '\w+' temp | sort -u -f | wc -l

有没有更好的方法?

【问题讨论】:

  • 您有 500 GiB 的备用磁盘空间吗?
  • 尝试awk(将单词放入 awk 关联数组),看看是否足够快。如果没有,我会用 C++(或类似的东西)编写一个自定义程序。
  • @rici 是的(这里还有几个字来满足回复的字符限制)
  • 对不起,我的想法并没有变得更快。
  • bash uniq 程序好像没有解决,但为什么呢?

标签: bash text grep


【解决方案1】:

awk 来救援!

$ awk -v RS=" " '{a[$0]++} END{for(k in a) sum++; print sum}' file

更新:

使用tr 进行预处理可能更好,让awk 经济地进行计数。您可能想用空格或换行符分隔单词。

例如:

$ tr ':;,?!\"' ' ' < file | tr -s ' ' '\n' | awk '!a[$0]++{c++} END{print c}'

【讨论】:

  • 一种有前途的方法,但是通过将 RS 设置为 " "(单个空格),您会错误地将行尾 \n 包含在每行的最后一个单词中,并且您还计算多个相邻空格之间的空词。
  • +1 用于优雅的 awk 脚本,但考虑到文件大小和性能问题,最好只使用 one tr 调用并在awk 中使用字段循环。另请注意,OP 希望不区分大小写。
  • 考虑到管道的工作原理,我认为尺寸并不重要。 tr 也快如闪电。如果所有唯一的单词都超出了内存可以容纳的范围,那么它可能是一个问题。
【解决方案2】:

您可以依靠awk 的默认行为通过空格将行拆分为单词,并使用其关联数组:

awk '{ for (i=1; i<=NF; ++i) a[tolower($i)]++ } END { print length(a) }' file

更新:正如@rici 在评论中指出的那样,空格分隔的标记可能包含_ 和其他字符以外的标点符号,因此不一定与@ 相同987654324@ 的\w+ 构造。因此@4ae1e1 建议使用类似于'[^[:alnum:]_]' 的字段分隔符。请注意,这将导致连字词的每个组成部分都被单独计算;同样,' 分隔单词。

awk -F '[^[:alnum:]_]+' '{ for (i=1; i<=NF; ++i) { a[tolower($i)]++ } }
        END { print length(a) - ("" in a) }' file
  • 关联数组a 的构建方式是计算输入中遇到的每个不同单词的出现次数,首先转换为小写以忽略大小写差异 - 如果您不想忽略大小写差异,只需删除tolower() 电话。
    • CAVEAT:似乎 Mawk 和 BSD Awk 无法识别区域设置,因此 tolower() 无法正常处理非 ASCII 字符。
  • 处理完所有单词后,a 的元素数等于唯一单词数。
    • 注意:print length(a) 的符合 POSIX 的重新表述为:for (k in a) ++count; print count

上述内容适用于 GNU Awk、Mawk (1.3.4+) 和 BSD Awk,即使它严格不符合 POSIX(POSIX 定义 length 函数仅用于字符串,而不是数组)。

【讨论】:

  • 这很好,但是 awk 字段通常与 grep 单词不同。 \w 是 [_[:alnum]] iirc,所以我认为期望是“不!不?不,不。”将包含一个唯一的单词,而不是四个。
  • @rici 很简单。将FS 设置为[^[:alnum:]]+,虽然I'm(实际上如果这是一个单词)和带有连字符的单词(可以将- 添加到char 类中,但仍然取决于是否有@ 987654341@ 没有被伪装成 em-dash 的空格包裹)那样会有问题。
  • @4ae1e1:谢谢;我添加了一个基于'[^[:alnum:]_]+' 的解决方案(这也需要忽略空字段)并添加了一个注释。
  • @mklement0 我想说不要检查空字段(这会导致每个字段都出现分支,并且肯定会影响性能 - 至少在理论上),只需从最终结果中减去一个.
  • @4ae1e1:好点;虽然您不能盲目地减去,但在END 块中简单检查a 中是否存在空字符串键优于检查每个输入字段 - 已更新。
【解决方案3】:

排序的一个重要特性是它可以识别语言环境,因此在 C 以外的任何语言环境中都更昂贵。由于您并不真正关心这里的顺序,您不妨告诉 sort 忽略该语言环境通过使用LC_ALL=C sort -u -f。如果您的语言环境设置为其他设置,那可能会将您的执行时间缩短一半。

此答案的原始版本建议您仅在不关心非 ascii 字符时才应这样做。但是,如果您使用的是 Gnu coreutils,那么这些东西在 UTF-8 语言环境中都不起作用。虽然 gnu sort 将在任何区域设置中进行区域设置感知字符串比较(使用 strxfrm 标准库函数),但 sort -f 仅在单字节区域设置中进行区域设置感知大小写折叠。 Gnu uniq -i 有同样的问题。而tr 只翻译单字节字符(按设计,afaik);理论上[:alpha:] 可以识别区域设置,但仅适用于可表示为单个字节的字符。

简而言之,如果要使用sort -u -f,不妨指定C 语言环境。对于非英文字母来说,这同样是破碎的,但至少破碎不会浪费时间。

Gnu awktolower() 函数显然适用于多字节语言环境。因此,如果您需要在 UTF-8 语言环境中工作,请查看 awk 答案之一。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-28
    • 2015-06-02
    • 2020-07-10
    • 2013-11-01
    相关资源
    最近更新 更多