簡體   English   中英

當第一列相同時,如何按第二列中的最早日期重新排序列表?

[英]How can i reorder a list by the earliest date in the second column when the first column is the same?

我有很多類似下面的列表,第一列是 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

但我不確定我如何才能達到結果。 我怎樣才能達到結果?

當前sort嘗試的問題是,當您想要解析第三個字段( -k3 -k1. )時,您正試圖解析第一個字段( -k3. )。

設置,添加一些日期不是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 - r一次排序( score ),從字段2開始,以字段2結束,按數字排序並按n (也稱為降序)
  • -k3.5,3.8nr - 第二次排序( YYYY ),從字段3和第5個字符開始,以字段3r 8個字符結束,按數字和-k3.3,3.4nr n -k3.1,3.2nr分別對MM n DD執行反向數字排序r
  • 注意: OP 的預期輸出顯示ID=555 ( YYYY=2014 ) 在ID=444 ( YYYY=2015 ) 之前列出; 我假設這是一個錯字,應該首先列出ID=444

將 Ed 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

使用這些強制性 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

是錯的。

使用 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)中,在此過程中反轉日期格式 -

  • 將每一行拆分為單詞並對該列表進行引用[] ),但首先將日期“反轉”為 yyyymmdd 格式,以便以后可以輕松對其進行排序

  • 按數組引用的第二個(或)第三個元素對這些行進行排序(數字)

  • 加入每一行,首先反轉日期

我假設最終日期需要采用 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的行,而不是如圖所示的相反(由問題的描述:“最新日期在頂部”)

使用sort

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

使用 gawk:

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可以為每個鍵指定不同的標志。

這適用於我的 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

設置,添加一些日期不是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
# 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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM