【问题标题】:How to sort column A uniquely based on descending order of column B in unix/per/tcl?如何根据unix / per / tcl中B列的降序对A列进行唯一排序?
【发布时间】:2015-09-21 19:07:36
【问题描述】:

我有一个如下所示的 csv 文件。

Column A, Column B
cat,30
cat,40
dog,10
elephant,23
dog,3
elephant,37

如何根据最大对应值对 A 列进行唯一排序 B列?

我想要得到的结果是,

Column A, Column B
cat,40
elephant,37
dog,10

【问题讨论】:

  • 没有引用字段,字段中没有逗号?
  • 您将使用哪种语言?用多种语言询问它是结束这个问题的一个很好的理由……

标签: perl unix tcl


【解决方案1】:

求救!

$ sort -t, -k1,1 -k2,2nr filename | awk -F, '!a[$1]++'
Column A, Column B
cat,40
dog,10
elephant,37

如果您想要特定的输出,由于标题行,它需要更多的编码。

$ sort -t, -k1,1 -k2nr filename | awk -F, 'NR==1{print "999999\t"$0;next} !a[$1]++{print $2"\t"$0}' | sort -k1nr | cut -f2-
Column A, Column B
cat,40
elephant,37
dog,10

另一种方法是预先删除标题并在最后添加它

$ h=$(head -1 filename); sed 1d filename | sort -t, -k1,1 -k2nr | awk -F, '!a[$1]++' | sort -t, -k2nr | sed '1i'"$h"''

【讨论】:

    【解决方案2】:

    Perlishly:

    #!/usr/bin/env perl
    use strict;
    use warnings;
    
    #print header row
    print scalar <>;
    my %seen;
    #iterate the magic filehandle (file specified on command line or 
    #stdin - e.g. like grep/sed)
    while (<>) {
        chomp; #strip trailing linefeed
        #split this line on ','
        my ( $key, $value ) = split /,/;
    
        #save this value if previous is lower or non existant
        if ( not defined $seen{$key}
            or $seen{$key} < $value )
        {
            $seen{$key} = $value;
        }
    }
    
    #sort, comparing values in %seen 
    foreach my $key ( sort { $seen{$b} <=> $seen{$a} } keys %seen ) {
        print "$key,$seen{$key}\n";
    }
    

    【讨论】:

      【解决方案3】:

      我已经 +1 了 karakfa 的回答。它简单而优雅。

      我的答案是对 karakfa 的标头处理的扩展。如果你喜欢它,请随时 +1 我的答案,但“最佳答案”应该去 karakfa。 (当然,除非您更喜欢其他答案之一!:])

      如果您的输入与您在问题中描述的一样,那么我们可以通过看到 $2 不是数字来识别标题。因此,以下内容不考虑标题:

      $ sort -t, -k1,1 -k2,2nr filename | awk -F, '!a[$1]++'
      

      您可以交替使用以下内容剥离标题:

      $ sort -t, -k1,1 -k2,2nr filename | awk -F, '$2~/^[0-9]+$/&&!a[$1]++'
      

      这会大大减慢速度,因为正则表达式的计算时间可能比简单的数组赋值和数值测试要长。我正在使用正则表达式进行数字测试,以允许 0,否则将评估为“假”。

      接下来,如果您想保留标头,但先打印它,您可以在流的末尾处理您的输出:

      $ sort -t, -k1,1 -k2,2nr filename | awk -F, '$2!~/^[0-9]+$/{print;next} !a[$1]++{b[$1]=$0} END{for(i in b){print b[i]}}'
      

      在不将额外数组存储在内存中的情况下实现相同效果的最后一个选项是再次处理您的输入。这在 IO 方面成本更高,但在内存方面成本更低:

      $ sort -t, -k1,1 -k2,2nr filename | awk -F, 'NR==FNR&&$2!~/^[0-9]+$/{print;nextfile} $2~/^[0-9]+$/&&!a[$1]++' filename -
      

      【讨论】:

        【解决方案4】:

        另一个perl

        perl -MList::Util=max -F, -lane '
            if ($.==1) {print; next}
            $val{$F[0]} = max $val{$F[0]}, $F[1];
        } {
            print "$_,$val{$_}" for reverse sort {$val{$a} <=> $val{$b}} keys %val;
        ' file
        

        【讨论】:

          【解决方案5】:

          一种可能的 Tcl 解决方案:

          # read the contents of the file into a list of lines
          set f [open data.csv]
          set lines [split [string trim [chan read $f]] \n]
          chan close $f
          
          # detach the header
          set lines [lassign $lines header]
          
          # map the list of lines to a list of tuples
          set tuples [lmap line $lines {split $line ,}]
          
          # use an associative array to get unique tuples in a flat list
          array set uniqueTuples [concat {*}[lsort -index 1 -integer $tuples]]
          
          # reassemble the tuples, sorted by name
          set tuples [lmap {a b} [lsort -stride 2 -index 0 [array get uniqueTuples]] {list $a $b}]
          
          # map the tuples to csv lines and insert the header
          set lines [linsert [lmap tuple $tuples {join $tuple ,}] 0 $header]
          
          # convert the list of lines into a data string
          set data [join $lines \n]
          

          此解决方案假定没有引用元素的简化数据集。如果有引用的元素,则应使用csv 模块而不是split 命令。

          另一个受 Perl 解决方案启发的解决方案:

          puts [gets stdin]
          set seen [dict create]
          
          while {[gets stdin line] >= 0} {
              lassign [split $line ,] key value
              if {![dict exists $seen $key] || [dict get $seen $key] < $value} {
                  dict set seen $key $value
              }
          }
          
          dict for {key val} [lsort -stride 2 -index 0 $seen] {
              puts $key,$val
          }
          

          文档:chanconcatdictgetsifjoinlassignlinsertlmap、@9876543331@4、替换opensetsplitstringwhile

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2015-03-05
            • 1970-01-01
            • 2015-07-30
            • 2020-11-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多