如何在 .csv 文件中找到最常用的单词,忽略每行的重复项?

gin*_*ino 15 text-processing sort uniq

我需要在 .csv 文件中找到 10 个最常用的词。该文件的结构使得每一行都包含逗号分隔的单词。如果同一个词在同一行中重复出现多次,则应计为一个。所以,在下面的例子中:

green,blue,blue,yellow,red,yellow
red,blue,green,green,green,brown
Run Code Online (Sandbox Code Playgroud)

绿色、蓝色和红色应计为 2,黄色和棕色应计为 1

我知道以前有人问过类似的问题,一种解决方案是:

<file.csv tr -c '[:alnum:]' '[\n*]' | sort|uniq -c|sort -nr|head  -10
Run Code Online (Sandbox Code Playgroud)

但这将计算一个单词出现在同一行中的次数,如下所示:

  4 green
  3 blue
  2 yellow
  2 red
  1 brown
Run Code Online (Sandbox Code Playgroud)

这实际上不是我需要的。有什么帮助吗?此外,我将感谢对该命令的简短解释,以及为什么我在类似问题中找到的命令不能满足我的需要。

Sté*_*las 16

使用 GNUgrep或兼容:

$ grep -nEo '\w+' file.csv|sort -u|cut -d: -f2-|sort|uniq -c|sort -k1rn|head
      2 blue
      2 green
      2 red
      1 brown
      1 yellow
Run Code Online (Sandbox Code Playgroud)


ste*_*ver 11

我可能会使用 perl

  • 使用uniq来自List::Util模块的重复数据删除每一行。
  • 使用散列来计算结果出现的次数。

例如

perl -MList::Util=uniq -F, -lnE '
  map { $h{$_}++ } uniq @F 
  }{ 
  foreach $k (sort { $h{$b} <=> $h{$a} } keys %h) {say "$h{$k}: $k"}
' file.csv
2: red
2: green
2: blue
1: yellow
1: brown
Run Code Online (Sandbox Code Playgroud)

如果除了sortuniqcoreutils之外别无选择,那么您可以通过添加一个 shell 循环来实现类似的算法

while IFS=, read -a words; do 
  printf '%s\n' "${words[@]}" | sort -u
done < file.csv | sort | uniq -c | sort -rn
  2 red
  2 green
  2 blue
  1 yellow
  1 brown
Run Code Online (Sandbox Code Playgroud)

但是请参阅为什么使用 shell 循环来处理文本被认为是不好的做法?


bu5*_*man 9

您可以使用awk关联数组和简单的逻辑检查。

awk -F, '
  {split("", c); for (i=1; i<=NF; i++) 
      if (!c[$i]){c[$i]++;wds[$i]++}}
  END{for (wd in wds) print wds[wd], wd}' file
Run Code Online (Sandbox Code Playgroud)

输出

1 brown
2 red
1 yellow
2 blue
2 green
Run Code Online (Sandbox Code Playgroud)

演练

将字段分隔符设置为 ,

awk -F, '
Run Code Online (Sandbox Code Playgroud)

您将计算c一行中是否出现多个单词,因此请确保在每行开头的单词计数为空,然后使用delete c;orsplit("", c)遍历字段

      {split("", c); for (i=1; i<=NF; i++) 
Run Code Online (Sandbox Code Playgroud)

或者

      {delete c; for (i=1; i<=NF; i++) 
Run Code Online (Sandbox Code Playgroud)

如果你还没有$i在这一行看到这个词,!c[$i]那么增加那个词的计数器c[$i]++(增加到 1,这样条件测试失败,如果它再次出现在同一行),然后wds[$i]++ 当测试没有失败时增加那个词的总计数

      if (!c[$i]){c[$i]++;wds[$i]++}}
Run Code Online (Sandbox Code Playgroud)

文件完成后,只需遍历wds数组并打印计数wds[wd]和单词wd

      END{for (wd in wds) print wds[wd], wd}' file
Run Code Online (Sandbox Code Playgroud)

只是为了好玩

一个没有awk关联数组位的hacky

awk -F, '{for (i=1; i<=NF; i++) print NR, $i}' file | 
    sort | uniq | awk '{print $2}'| sort | uniq -c | sort -nr
Run Code Online (Sandbox Code Playgroud)

awk删除字段,以便它们前面是行号,然后sort | uniq丢失行重复,awk再次丢失编号,然后恢复到您的原始代码。


Kus*_*nda 8

使用awk

awk -F , '
        {
                delete seen
                for (i = 1; i <= NF; ++i) seen[$i]++ || ++count[$i]
        }
        END {
                for (word in count) printf "%d\t%s\n", count[word], word
        }' file |
sort -rn | head
Run Code Online (Sandbox Code Playgroud)

如果在当前行中之前没有出现过,则第一个块中的循环会计算一个单词。END块中的循环输出计数和字数。

对于那些喜欢“one-liners”的人:

awk -F, -v OFS="\t" '{delete s;for(i=1;i<=NF;++i)s[$i]++||++c[$i]} END {for(w in c)print c[w],w}' file | sort -rn | head
Run Code Online (Sandbox Code Playgroud)

zshshell中的类似方法:

awk -F, -v OFS="\t" '{delete s;for(i=1;i<=NF;++i)s[$i]++||++c[$i]} END {for(w in c)print c[w],w}' file | sort -rn | head
Run Code Online (Sandbox Code Playgroud)

这会将每个以逗号分隔的行读入一个数组,words,该数组保持唯一(仅将每个单词的第一个副本添加到数组中)。

对于每行读取,通过增加关联数组中的相应条目来计算唯一字count

阅读完所有单词后,输出累积的单词及其计数,并根据计数进行排序。使用 将输出截断为 10 行head

${(kv)count}膨胀将计算为从键和值的列表count关联数组。这些用于printf将值和键打印为换行符分隔的对。所使用的格式字符串printf第一个挑选出来的值,那么关键,但由于这些都从错误的顺序${(kv)count},我们使用的扩展2$1$选择参数失灵。


ImH*_*ere 5

有一个脚本可以执行 awk 中主要要求的操作:

awk -F, '
{ 
       i = split( "" , seen ) ;
       while( ++i <= NF ) if( ++seen[$i] == 1 ) count[$i]++; 
}END{
       for( word in count ) print count[word] , word
}'     file | sort -rn | head
Run Code Online (Sandbox Code Playgroud)

它的工作原理是:

  • 对于输入文件上的每一行:
  • 重新初始化i为零并seen为每个新行清除数组i=split("",seen)
  • seen为每个字段生成数组++seen[$i]
  • 在第一次(在这一行)看到 a 字段时,计算它。( count[$i]++).
  • 处理完所有行后END
  • 对于已计算的每个单词for( word in count )
  • 打印所有单词及其计数print count[word] , word
  • 最后,在 awk 生成其输出后,按数字对其进行排序 sort -rn
  • 并选择前 10 行head

我们可以用稍微更神秘的一行代码来写:

awk -F, '{i=split("",a);while(++i<=NF)a[$i]++||c[$i]++}END{for(i in c)print c[i],i}' file|sort -rn|head
Run Code Online (Sandbox Code Playgroud)