unix按其关联的最大值排序组?

时间:2018-03-07 00:26:02

标签: bash sorting unix grouping gnu-coreutils

假设我有输入文件49142202.txt

A   5
B   6
C   3
A   4
B   2
C   1

是否可以按第2列中的值对第1列中的组进行排序?所需的输出如下:

B   6 <-- B group at the top, because 6 is larger than 5 and 3
B   2 <-- 2 less than 6
A   5 <-- A group in the middle, because 5 is smaller than 6 and larger than 3
A   4 <-- 4 less than 5
C   3 <-- C group at the bottom, because 3 is smaller than 6 and 5
C   1 <-- 1 less than 3

以下是解决方案

join -t$'\t' -1 2 -2 1 \
 <(cat 49142202.txt | sort -k2nr,2 | sort --stable -k1,1 -u | sort -k2nr,2 \
  | cut -f1 | nl | tr -d " " | sort -k2,2) \
 <(cat 49142202.txt | sort -k1,1 -k2nr,2) \
| sort --stable -k2n,2 | cut -f1,3

按列2排序的join的第一个输入是:

2   A
1   B
3   C

按列1排序的join的第二个输入是:

A   5
A   4
B   6
B   2
C   3
C   1

join的输出是:

A   2   5
A   2   4
B   1   6
B   1   2
C   3   3
C   3   1

然后按第2列中的nl行号排序,然后原始输入列1和3保留cut

我知道用例如Python的pandas groupby可以做得更容易,但有更优雅的方法,同时坚持使用GNU Coreutils,例如{ {3}},sortjoincuttr?我希望避免使用内存效率低的awk解决方案,但请分享这些解决方案。谢谢!

3 个答案:

答案 0 :(得分:2)

正如评论中所解释的,我的解决方案尝试减少pipes,不必要的cat命令的数量,尤其是管道sort操作的数量,因为排序是复杂/耗时的操作

我达到了以下解决方案:f_grp_sort是输入文件:

for elem in $(sort -k2nr f_grp_sort | awk '!seen[$1]++{print $1}')
do 
   grep $elem <(sort -k2nr f_grp_sort) 
done

<强>输出:

B       6
B       2
A       5
A       4
C       3
C       1

说明:

sort -k2nr f_grp_sort将生成以下输出:

B       6
A       5
A       4
C       3
B       2
C       1

sort -k2nr f_grp_sort | awk '!seen[$1]++{print $1}'将生成输出:

B
A
C

awk将以相同的顺序生成临时输出的第一列的1个唯一元素。

然后for elem in $(...)do grep $elem <(sort -k2nr f_grp_sort); done 对于包含grep然后B的行,AC,然后sort -k2nr f_grp_sort将提供所需的输出。

现在作为增强功能,您可以使用临时文件来避免执行$ sort -k2nr f_grp_sort > tmp_sorted_file && for elem in $(awk '!seen[$1]++{print $1}' tmp_sorted_file); do grep $elem tmp_sorted_file; done && rm tmp_sorted_file 次操作两次:

{{1}}

答案 1 :(得分:1)

因此,这不适用于所有情况,但如果第一列中的值可以转换为bash变量,我们可以使用动态命名的数组来执行此操作而不是一堆连接。它应该很快。

第一个while块读取文件内容,获取前两个空格分隔的字符串并将它们放入col1col2。然后,我们创建一系列名为ARR_AARR_B的数组,其中AB是第1列的值(但仅当$col1仅包含字符时可以在bash变量名中使用)。该数组包含与这些第1列值相关联的第2列值。

我使用您喜欢的排序链来获取我们希望将列1值打印出来的顺序,我们只是遍历它们,然后对于每个列1数组,我们对值进行排序并回显第1列和第2列。

dynamc变量位可能难以遵循,但对于第1列中的正确值,它将起作用。同样,如果任何字符不能成为第1列中bash变量名称的一部分,则此解决方案将无效。

file=./49142202.txt

while read col1 col2 extra
do
  if [[ "$col1" =~ ^[a-zA-Z0-9_]+$ ]]
  then
    eval 'ARR_'${col1}'+=("'${col2}'")'
  else
    echo "Bad character detected in Column 1:  '$col1'"
    exit 1
  fi
done < "$file"

sort -k2nr,2 "$file" | sort --stable -k1,1 -u | sort -k2nr,2 | while read col1 extra
do 
  for col2 in $(eval 'printf "%s\n" "${ARR_'${col1}'[@]}"' | sort -r)
  do
    echo $col1 $col2
  done
done 

这是我的测试,比你提供的例子稍微复杂一点:

$ cat 49142202.txt
A 4
B 6
C 3
A 5
B 2
C 1
C 0

$ ./run
B 6
B 2
A 5
A 4
C 3
C 1
C 0

答案 2 :(得分:1)

非常感谢@JeffBreadner和@Allan!我提出了另一个解决方案,它与我的第一个解决方案非常相似,但提供了更多控制,因为它允许使用for循环更容易嵌套:

for x in $(sort -k2nr,2 $file | sort --stable -k1,1 -u | sort -k2nr,2 | cut -f1); do
 awk -v x=$x '$1==x' $file | sort -k2nr,2
done

你介意,如果我不接受你的任何一个答案,那么在我有时间评估你的解决方案的时间和内存性能之前?否则我可能只会去@Allan的awk解决方案。