perl 当第一列相同时,如何按第二列中的最早日期对列表重新排序?

ou6hu8tu  于 2022-11-24  发布在  Perl
关注(0)|答案(7)|浏览(135)

我有很多类似下面的列表,第一列是ID号,第二列是分数,第三列是出生日期,格式为DDMMYYYY。

111 100 01012011
222 90 01012001
333 90 01012013
444 80 01012015
555 80 01012014
666 70 01012016
777 60 01012017
888 50 01012018

当有多个行具有相同的分数时,我希望将它们重新排序,最新的日期位于顶部,示例的结果将是:

111 100 01012011
333 90 01012013
222 90 01012001
555 80 01012014
444 80 01012015
666 70 01012016
777 60 01012017
888 50 01012018

如您所见,分数相同的行已重新排列,最新日期位于顶部。
我首先尝试选择最早的日期,我可以用以下方法来完成:

sort -k1.5 -k1.1,1.2 -k1.3,1.4 | tail -n 1

但是我不确定我怎样才能达到这个结果。我怎样才能达到这个结果?

4ioopgfo

4ioopgfo1#

当前sort尝试的问题是,当您想要解析第三个字段(-k3.)时,您正在尝试解析第一个字段(-k1.)。
设置,添加一些日期不是DDMM == 0101的条目:

$ cat raw.dat
111 100 01012011
222 90 01012001
333 90 01012013
444 80 01012015
555 80 01012014
666 70 01012016
777 60 01012017
888 50 01012018
aaa 35 01082022
bbb 35 23012022
ccc 35 12112022
ddd 35 10122022

一种方法,假设第一次排序是针对第二个字段(score),以递减数字顺序进行:

$ sort -t' ' -k2,2nr -k3.5,3.8nr -k3.3,3.4nr -k3.1,3.2nr raw.dat
111 100 01012011
333 90 01012013
222 90 01012001
444 80 01012015
555 80 01012014
666 70 01012016
777 60 01012017
888 50 01012018
ddd 35 10122022
ccc 35 12112022
aaa 35 01082022
bbb 35 23012022

其中:

  • -t ' '-将分隔符定义为空格(覆盖默认值,即非空白到空白的转换,这将导致前导空格被计为字段的一部分)
  • -k2,2nr-第一次排序(score),以字段2开始,以字段2结束,按n数字和r反向(即降序)顺序排序
  • -k3.5,3.8nr-第二次排序(YYYY),以字段3和第5个字符开始,以字段3和第8个字符结束,按n数值排序,并按r相反顺序排序(-k3.3,3.4nr-k3.1,3.2nr分别在MMDD上执行rn相反的数值排序)
    ***注意:**OP的预期输出显示ID=555YYYY=2014)列在ID=444YYYY=2015)之前;我假设这是一个打字错误,应该先列出ID=444

把艾德Morton的评论放到这个答案里...

  • -b放弃所有前导空格(即替换-t ' '),但...
  • 在键级别应用标志时,必须在每个键上应用-b选项;或者...
  • 只要没有索引键层级旗标,-b选项就可以当做全域旗标套用(索引键层级旗标会优先于全域层级旗标,并且会否定全域层级旗标)

应用这些规则,我们得到以下结果之一:

# all global-level flags

sort -nrb -k2,2 -k3.5,3.8 -k3.3,3.4 -k3.1,3.2 raw.dat

# all key-level flags ('start' only needs '-b` flag)

sort -k2b,2bnr -k3.5b,3.8bnr -k3.3b,3.4bnr -k3.1b,3.2bnr raw.dat

# all key-level flags (overkill on the 'start' flags)

sort -k2bnr,2bnr -k3.5bnr,3.8bnr -k3.3bnr,3.4bnr -k3.1bnr,3.2bnr raw.dat

这些都产生:

111 100 01012011
333 90 01012013
222 90 01012001
444 80 01012015
555 80 01012014
666 70 01012016
777 60 01012017
888 50 01012018
ddd 35 10122022
ccc 35 12112022
aaa 35 01082022
bbb 35 23012022
2vuwiymt

2vuwiymt2#

使用以下强制POSIX工具的任何版本进行修饰-排序-取消修饰:

$ awk -v OFS='\t' '{print substr($3,5) substr($3,3,2) substr($3,1,2), $0}' file |
    sort -rn -k3,3 -k1,1 | cut -f2-
111 100 01012011
333 90 01012013
222 90 01012001
444 80 01012015
555 80 01012014
666 70 01012016
777 60 01012017
888 50 01012018

我假设第二列80的过帐预期输出顺序为:

555 80 01012014
444 80 01012015

是错误的。

pxiryf3j

pxiryf3j3#

使用Perl

perl -0777 -wnE'say for 
    map { join " ", @{$_}[0,1], reverse unpack "A4A2A2", $_->[2] } 
    sort { $b->[1] <=> $a->[1] or $b->[2] <=> $a->[2] } 
    map { 
        my @line = split; 
        [ @line[0,1], join "", reverse unpack "A2A2A4", $line[2] ] 
    } 
    split "\n"
' file

获取整个文件(通过-0777开关),然后将其拆分为行,并将其输入Schwartzian变换(decorate-sort-undecorate),在此过程中反转日期格式--

  • split将每行转换为单词,并对该列表进行引用([]),但首先将日期"反转"为yyyymmdd格式,以便以后可以方便地对其进行排序
  • sort(数字)通过数组引用的第二个-然后(或)-第三个元素对这些行进行排序
  • 将每行向后连接,首先将日期倒过来

我假设在结束日期需要在ddmmyyyy格式,否则调整。
或者,从概念上来说,首先准备--读取所有行并使用单词列表的数组引用构建一个数组--然后对其进行排序可能更简单

perl -wnE'
    @ln = split; 
    push @lines, [ @ln[0,1], join "", reverse unpack "A2A2A4", $ln[2] ] 
    }{ 
    say for 
        map { join " ", sprintf "%3d %3d %08d", 
            @{$_}[0,1], join "", reverse unpack "A4A2A2" $_->[2]
        } 
        sort { $b->[1] <=> $a->[1] or $b->[2] <=> $a->[2] }
        @lines
' file

其中}{ ...语法开始END块,相当于END { ... }。所有END块在程序中的所有处理完成之后运行(参见perlmod);所以这里是在所有行都被读取之后。这里我还使用sprintf对齐输出。
对于Linux上的上述问题,使用系统的sort的解决方案显然要简单得多。当有其他原因需要引入编程语言时(即有更多工作要做),或者如果操作系统没有工具从命令行(Windows)执行此操作时,这里的方法可能会很有用。
这不需要任何系统工具,而且它是高效的,因为它在排序之前预处理日期时间一次(并且在排序期间不为每个比较解析它们)。
注意:问题中的444555行对(80在第二列)排序错误--应该是先有444的行,然后是有555的行,而不是相反,如图所示(根据问题描述:"* 最新日期在顶部 *")

plupiseo

plupiseo4#

咱家呆呆:

gawk '{split($3, a, ""); print $1,$2,a[5]a[6]a[7]a[8]a[3]a[4]a[1]a[2]}' score-file |
sort -t ' ' -rnk 2,2 -k 3,3 |
gawk '{split($3, a, ""); print $1,$2,a[7]a[8]a[5]a[6]a[1]a[2]a[3]a[4]}'
  • 将日期拆分为多个字符,以最大-最小单位(ID SCORE YYYYMMDD)排列。
  • 这是一种可以先按分数再按日期排序的格式,格式为sort
  • 将多个-k标志指定为sort将按这些字段排序,排序顺序为标志指定的顺序(即先按字段2排序,再按字段3排序)。
  • -n =数值排序,-r =逆序(高-低),-t =字段分隔符(空格)。
  • 第二个awk重新排列为原始格式。

如果你可以得到/使用ID SCORE YYYYMMDD的输入格式,你可以用同样的排序命令(没有gawk)简单地排序它。
编辑:如果你想让分数高-低,日期低-高(旧-新),即不同的顺序,你可以用sort -t ' ' -k 2,2rn -k 3,3n代替上面的sort命令。sort可以为每个键指定不同的标志。

hzbexzde

hzbexzde5#

这在我的MacOS终端上工作。
说明:我首先将日期格式转换为YYMMDD,然后进行排序。

cat input.txt|\
awk '{m=substr($3,1,2);d=substr($3,3,2);y=substr($3,5,4); str=sprintf("%s%s%s",y,m,d);print $1,$2,str}'|\
sort -nrk2,2 -k3,3
olqngx59

olqngx596#

安装程序添加了一些日期不是DDMM == 0101的条目,添加了一些具有重复$2/$3对的行:

$ cat raw.dat
111 100 01012011
222 90 01012001
333 90 01012013
444 80 01012015
555 80 01012014
666 70 01012016
777 60 01012017
888 50 01012018
aaa 35 01082022     # different DDMM values; should be 3/4
bbb 35 23012022     # different DDMM values; should be 4/4
ccc 35 12112022     # different DDMM values; should be 2/4
ddd 35 10122022     # different DDMM values; should be 1/4
456 40 17052019     # duplicate $2/$3 (and $1); 1/3
123 40 17052019     # duplicate $2/$3         : 2/3
456 40 17052019     # duplicate $2/$3 (and $1); 3/3

**注意:**文件中包含注解

一个GNU awk创意:

awk '
    { # a [score] [dob as YYYYMMDD] [unique sequence] ; unique sequence required in case score/dob is not unique

      a[$2][substr($3,5,4) substr($3,3,2) substr($3,1,2)][--c]=$0
    }
END { PROCINFO["sorted_in"]="@ind_num_desc"      # sort all indices as numbers in descending order
      for (score in a)
          for (dob in a[score])
              for (seq in a[score][dob])
                  print a[score][dob][seq]
    }
' raw.dat

备注:

  • 多维数组(也称为数组的数组)和PROCINFO["sorted_in"]支持需要GNU awk
  • 在重复$2/$3对的情况下,我们选择保持输入顺序;这可以根据OP关于如何在这种情况下继续进行的额外输入进行修改(例如,通过$1进一步排序?删除重复行?)

这会产生:

111 100 01012011
333 90 01012013
222 90 01012001
444 80 01012015
555 80 01012014
666 70 01012016
777 60 01012017
888 50 01012018
456 40 17052019     # duplicate $2/$3 (and $1); 1/3
123 40 17052019     # duplicate $2/$3         : 2/3
456 40 17052019     # duplicate $2/$3 (and $1); 3/3
ddd 35 10122022     # different DDMM values; should be 1/4
ccc 35 12112022     # different DDMM values; should be 2/4
aaa 35 01082022     # different DDMM values; should be 3/4
bbb 35 23012022     # different DDMM values; should be 4/4
mkshixfv

mkshixfv7#

# Using markp-fuso data
$ cat raw.dat
111 100 01012011
222 90 01012001
333 90 01012013
444 80 01012015
555 80 01012014
666 70 01012016
777 60 01012017
888 50 01012018
aaa 35 01082022
bbb 35 23012022
ccc 35 12112022
ddd 35 10122022

$ while IFS=" " read -r n s d; do
>    echo "${s}${d:4:4}${d:2:2}${d:0:2} ${n} ${s} ${d}"
> done < raw.dat|sort -nr|cut -d" " -f2-

111 100 01012011
333 90 01012013
222 90 01012001
444 80 01012015
555 80 01012014
666 70 01012016
777 60 01012017
888 50 01012018
ddd 35 10122022
ccc 35 12112022
aaa 35 01082022
bbb 35 23012022

$ sed -E 's/(.*) (.*) ([0-9]{2})([0-9]{2})([0-9]{4})/\2\5\4\3 \1 \2 \3\4\5/' raw.dat|sort -nr|cut -d" " -f2-

111 100 01012011
333 90 01012013
222 90 01012001
444 80 01012015
555 80 01012014
666 70 01012016
777 60 01012017
888 50 01012018
ddd 35 10122022
ccc 35 12112022
aaa 35 01082022
bbb 35 23012022

awk '
    {
        match($0, /(.*) (.*) ([0-9]{2})([0-9]{2})([0-9]{4})/, m)
        arr[NR]=m[2]m[5]m[4]m[3]" "$0
    }
    END{ 
        asorti(arr, sorted_arr, "@val_num_desc") 
        for (i in sorted_arr){
            row=arr[sorted_arr[i]]
            sub(/^[^ ]* /,"",row)
            print row
        } 
    }
' raw.dat

111 100 01012011
333 90 01012013
222 90 01012001
444 80 01012015
555 80 01012014
666 70 01012016
777 60 01012017
888 50 01012018
ddd 35 10122022
ccc 35 12112022
aaa 35 01082022
bbb 35 23012022

相关问题