【问题标题】:Sort a column by number of identical occurrences - using awk, sort, tr or uniq?按相同出现次数对列进行排序 - 使用 awk、sort、tr 还是 uniq?
【发布时间】:2013-02-08 00:53:15
【问题描述】:

假设我有一些制表符分隔的数据:

Peter   5
Joe     8
Peter   7
Peter   8
Joe     4
Laura   3

我想按名称在第一列中出现的次数对其进行排序(最大到最小) 所以我们会有 Peter(3 次出现)Joe(2 次出现)和 Laura(1 次出现)。

Peter   5
Peter   7
Peter   8
Joe     8
Joe     4
Laura   3

它只需要按第一列排序,而不是第二列。我一直在阅读 sort 的文档,但我认为它没有这个功能。谁有简单的方法?

【问题讨论】:

  • 是不是表示第2栏与本题无关?
  • 正确。该列中的数据只需要按照给定名称保持顺序即可。
  • 在结果集中,Peter 的条目按第 2 列值的升序列出,但 Joe 的条目按相反的顺序列出,这有关系吗?
  • @JonathanLeffler:不 - OP 希望在第一列出现时按降序排序,保持第二列在文件中出现的顺序。据推测,重复出现的顺序(第一列)并不重要,只要第二列保持给定名称的顺序即可。请参阅上面的 OP 评论。 HTH。
  • 对于您对史蒂夫解决方案的评论,在问题中提及文件大小会很有用。也许可以在 a 数组中排序的数据量有一些限制?你的比赛范围是多少?最多三个?最大50?什么?

标签: unix sorting awk tr uniq


【解决方案1】:

不性感,但适用于您的示例:

 awk  'NR==FNR{a[$1]++;next}{ print a[$1],$0}' file file|sort -nr|sed -r 's/[0-9]* //'

用你的数据测试:

kent$  cat n.txt
Peter   5
Joe     8
Peter   7
Peter   8
Joe     4
Laura   3

kent$  awk  'NR==FNR{a[$1]++;next}{ print a[$1],$0}' n.txt n.txt|sort -nr|sed -r 's/[0-9]* //'
Peter   8
Peter   7
Peter   5
Joe     8
Joe     4
Laura   3

【讨论】:

  • 数据(字段 2)需要按照问题下方的 cmets 保持有序。
  • -r 选项是不必要的;使用的正则表达式是基本的sed 正则表达式。
  • @ChrisJ.Vargo 是的,您可以删除“-r”选项。它也应该可以工作。
  • 哇。我刚刚在我的 BSD 盒子上运行了这个,效果很好。此外,在 3800 万行的列表上非常有效。肯特,非常感谢!
  • @ChrisJ.Vargo 很高兴为您提供帮助。它跑得很快,因为你有强大的 CPU。 :) 我的代码中没有 hack 算法
【解决方案2】:

这行得通:

for person in $(awk '{print $1}' file.txt | sort | uniq -c | sort -dnr | awk '{print $2}');
do grep -e "^$person[[:space:]]" file.txt;
done

【讨论】:

  • 所以,为了简化示例数据,我没有提到 $2 中偶尔会提到来自 $1 的名称。这可以通过在 $person 之后添加 /t 在您的代码中避免。你觉得:grep '$person\| ' 会工作吗?
【解决方案3】:

这是使用GNU awk 的一种方式。运行如下:

awk -f script.awk file

script.awk的内容:

BEGIN {
    FS="\t"
}

{
    c[$1]++
    r[$1] = (r[$1] ? r[$1] ORS : "") $0
}

END {

    for (i in c) {
        a[c[i],i] = i
    }

    n = asorti(a)

    for (i=1;i<=n;i++) {
        split(a[i], b, SUBSEP)
        x[++j] = b[2]
    }

    for (i=n;i>=1;i--) {
        print r[x[i]]
    }
}

结果:

Peter   5
Peter   7
Peter   8
Joe     8
Joe     4
Laura   3

【讨论】:

  • 哇。史蒂夫,这是你这么多天来回答我的第二个问题。你推荐什么资源来学习这样的脚本?仅供参考:我在一个大文本文件上运行它。我得到这个错误:awk: calls undefined function asorti input record number 38823587, file input.txt source line number 16
  • @ChrisJ.Vargo:看起来我应该研究一下你之前的一些问题。据我所知,您使用的似乎是BSD/OSX awk,不幸的是它不支持使用GNU awk 时可用的数组排序函数(asortasorti)。我强烈推荐安装和使用 GNU 实用程序;特别是GNU awkGNU sed - 你只会在未来为自己节省很多时间和麻烦。 Re 资源:SO 可能是使用awk 学习算法开发的最佳场所。 TBH,我还没有遇到更好的资源。通过实践学习。 HTH。
  • 非常感谢。我会这样做的。
【解决方案4】:

这是一个非常难的排序标准。这段代码有效,但很丑:

data=${1:-data}
awk '{ print $1 }' $data |
sort |
uniq -c |
sort -k2 |
join -1 2 -2 2 -o 1.1,2.1,2.2,2.3 - <(awk '{ print NR, $0 }' $data | sort -k2) |
sort -k1,1nr -k3,3 -k2n |
awk 'BEGIN{OFS="\t"} { print $3, $4 }'

它假定bash 4.x 用于“进程替换”,但不使用awk 内置的任何排序(与POSIX awk 相比,这是一个GNU 扩展)。使用显式临时文件,它可以在 shell 中工作而无需进程替换。

data=${1:-data}                  # File named on command line, or uses name 'data'
awk '{ print $1 }' $data |       # List of names
sort |                           # Sorted list of names
uniq -c |                        # Count occurrences of each name
sort -k2 |                       # Sort in name order
join -1 2 -2 2 -o 1.1,2.1,2.2,2.3 - <(awk '{ print NR, $0 }' $data | sort -k2) |
# The process substitution numbers each record in sequence and sorts in name order
# The join matches the names (column 2) and outputs the frequency, record number, name, value
sort -k1,1nr -k3,3 -k2n |        # Sort on frequency reversed, name, original line number
awk 'BEGIN{OFS="\t"} { print $3, $4 }'   # Print name and value

使用带有内置排序的 GNU awk,或者 Perl 或 Python,可能比这更好。

对于原始数据,输出为:

Peter   5
Peter   7
Peter   8
Joe     8
Joe     4
Laura   3

鉴于此数据的扩展版本:

Peter   5
Joe     8
Peter   7
Peter   8
Joe     4
Laura   3
Peter   50
Joe     80
Peter   70
Peter   80
Joe     40
Laura   30
Peter   700
Peter   800
Peter   7002
Peter   8002
Peter   7000
Peter   8000
Peter   7001
Peter   8001
Pater   50
Jae     80
Pater   70
Pater   80
Jae     40
Laura   30

输出是:

Peter   5
Peter   7
Peter   8
Peter   50
Peter   70
Peter   80
Peter   700
Peter   800
Peter   7002
Peter   8002
Peter   7000
Peter   8000
Peter   7001
Peter   8001
Joe     8
Joe     4
Joe     80
Joe     40
Laura   3
Laura   30
Laura   30
Pater   50
Pater   70
Pater   80
Jae     80
Jae     40

-k3,3 排序项对于该数据集是必需的;它将 Laura 的条目排在 Pater 的条目之前(省略时,这两个列表交错排列)。

【讨论】:

    【解决方案5】:

    这是另一个使用 awk 的:

    awk '{ a[ $1, ++b[$1] ]=$0 ; if(b[$1]>max) max=b[$1] }

       END{ for(x=max;x>=1;x--)
             for( k in b )
               if( a[k,x] )
                  for(y=1;y<=x;y++) {
                        print a[k,y]
                        delete a[k,y]
                   }
       }' filename
    

    它适用于 gawk 和 POSIX awk。 END 语句中存在三个循环可能会影响大文件的性能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-21
      • 2020-10-24
      • 1970-01-01
      • 2023-03-11
      • 1970-01-01
      • 2013-07-21
      相关资源
      最近更新 更多