2017-01-24 30 views
1

對於下面的輸入數據,優化AWK腳本對大型數據集

Chr C rsid D A1 A2 ID1_AA ID1_AB ID1_BB ID2_AA ID2_AB ID2_BB ID3_AA ID3_AB ID3_BB ID4_AA ID4_AB ID4_BB ID5_AA ID5_AB ID5_BB 
10 p rsid1 q A G 0.00 0.85 0.15 0.70 0.10 0.20 0.40 0.50 0.10 0.30 0.30 0.40 0.10 0.20 0.80 
10 p rsid2 q C T 0.90 0.10 0.00 0.80 0.10 0.10 0.70 0.10 0.20 0.30 0.40 0.30 0.30 0.20 0.40 
10 p rsid3 q A G 0.40 0.50 0.10 0.80 0.20 0.00 0.20 0.30 0.50 0.50 0.30 0.20 0.20 0.30 0.40 

我需要生成以下的輸出數據。

rsid  ID1   ID2   ID3   ID4   ID5 
rsid1  2.15  1.50  1.70  2.10  2.90 
rsid2  1.10  1.30  1.50  2.00  1.90 
rsid3  1.70  1.20  2.30  1.70  2.00 

表通過用一個常數因子乘以(1, 2, 3)每ID (ID1, ID2, ID3, etc)顯示3列(_AA, _AB & _BB)的總和。

Example: for rsID1 --> ID1 -> (ID1_AA*1 + ID1_AB*2 + ID1_BB*3) = (0.00*1 + 0.85*2 + 0.15*3) = 2.15 

我寫了下面的AWK腳本來建立任務,它工作得很好。

請注意:我是AWK的初學者。

awk '{ 
    if(NR <= 1) { # header line 
     str = $3; 
     for(i=7; i<=NF; i+=3) { 
      split($i,s,"_」); 
      str = str"\t"s[1] 
     } 
     print str 
    } else { # data line 
     k = 0; 
     for(i=7; i<=NF; i+=3) 
      arr[k++] = $i*1 + $(i+1)*2 + $(i+2)*3; 
     str=$3; 
     for(i=0; i<=(NF-6)/3; i++) 
      str = str"\t"arr[i]; 
     print str 
    } 
}' input.txt > out.txt 

後來有人告訴我,輸入數據可以是一樣大的60萬行& 300千列,這意味着輸出數據將是60Mx100K。如果我沒有錯,AWK每次只讀一行&,因此一瞬間內存中會有30萬列數據。這是個問題嗎?鑑於這種情況,我該如何改進我的代碼?

+1

有可能進行一些細微的變化,但目前還不能確定,他們將不得不對性能有顯著的影響。其他的調整,比如從每個分支中分解出公用代碼,並且使用慣用的'condition {action}'而不是'if' /'else',與風格相比,更多地與其他任何東西相關。我猜如果你的腳本能夠工作的話,這會更多地進入[codereview.se]的範圍。 –

+0

感謝您的意見@TomFenech,我會更新我的腳本。只是一個簡單的問題,我的一個同事指出我使用'by reference by reference'在'bash'中重寫腳本。我真的不明白這一點。有什麼建議麼? – DhiwaTdG

+0

我不知道_call by reference_的概念如何應用於這種情況,但是在bash中處理文本文件,特別是大文本文件幾乎肯定不是*要走的路。 –

回答

4

雖然兩種方法各有利弊/利弊,它們既可以處理,因爲它們只能存儲1行中的任何數量的行/列在內存中的時候,我會因爲每行有300,000列,所以他的方法要求你測試NR==1每行近10萬次,而下面的方法每行只執行1次測試,所以它應該明顯更有效:

$ cat tst.awk 
BEGIN { OFS="\t" } 
{ 
    printf "%s", $3 
    if (NR==1) { 
     gsub(/_[^[:space:]]+/,"") 
     for (i=7; i<=NF; i+=3) { 
      printf "%s%s", OFS, $i 
     } 
    } 
    else { 
     for (i=7; i<=NF; i+=3) { 
      printf "%s%.2f", OFS, $i + $(i+1)*2 + $(i+2)*3 
     } 
    } 
    print "" 
} 

$ awk -f tst.awk file 
rsid ID1  ID2  ID3  ID4  ID5 
rsid1 2.15 1.50 1.70 2.10 2.90 
rsid2 1.10 1.30 1.50 2.00 1.90 
rsid3 1.70 1.20 2.30 1.70 2.00 

我強烈建議您閱讀Arnold Robbins編寫的Effective Awk Programming第4版一書,以瞭解awk是什麼以及如何使用它。

+0

代碼看起來非常有效。這與@Tom Fenech提出的使用'condition {action}'邏輯的建議類似嗎? – DhiwaTdG

+0

不,我打算使用它,但後來決定不這樣做,我可以重用這兩種情況下圍繞循環的通用代碼。 –

+2

'^ 1'今天學到了一些新東西,小而有效 –

0
awk -v OFS="\t" ' 
      { 
       printf("%s",$3); 
       for(i=7;i<=NF; i+=3) 
       { 
       if(FNR==1) 
       { 
        sub(/_.*/,"",$i) 
        f = $i 
       }else 
       { 
        f = sprintf("%5.2f",$i*1 + $(i+1)*2 + $(i+2)*3) 
       } 
        printf("%s%s",OFS,f) 
       } 
       print "" 
      } 
    ' file 

輸出

rsid  ID1  ID2  ID3  ID4  ID5 
rsid1 2.15 1.50 1.70 2.10 2.90 
rsid2 1.10 1.30 1.50 2.00 1.90 
rsid3 1.70 1.20 2.30 1.70 2.00 
0

您是否認爲使用像C這樣的低級語言?

C++或C的自動運行速度並不比awk快,而且代碼的可讀性和易碎性也較差。

我示出了使用c++,另一種解決方案來比較

//p.cpp 
#include <stdio.h> 

//to modify this value 
#define COLUMNS 5 

int main() { 
    char column3[256]; 
    bool header=true; 
    while (scanf("%*s\t%*s\t%255s\t%*s\t%*s\t%*s\t", column3) == 1) { 
     printf("%s", column3); 
     if(header){ 
      header=false; 
      char name[256]; 
      for(int i=0; i<COLUMNS; ++i){ 
       scanf("%[^_]_%*s\t%*s\t%*s\t", name); 
       printf("\t%s", name); 
      } 
     }else{ 
      float nums[3]; 
      for(int i=0; i<COLUMNS; ++i){ 
       scanf("%f %f %f", nums, nums + 1, nums + 2); 
       float sum = nums[0]+nums[1]*2+nums[2]*3; 
       printf("\t%2.2f", sum); 
      } 
     } 
     printf("\n"); 
    } 
} 

運行它,像

g++ p.cpp -o p 
cat file | ./p 

基準

用1個米隆在輸入線和300列

  • 埃德莫頓解決方案2 :2M 34S

  • C++:1米19S