【问题标题】:gsub many columns simultaneously based on different gsub conditions?根据不同的gsub条件同时gsub多列?
【发布时间】:2017-05-17 20:32:25
【问题描述】:

我有一个包含以下数据的文件-

输入-

A B C D E F
A B B B B B
C A C D E F
A B D E F A
A A A A A F
A B C B B B

如果从第 2 行开始的任何其他行与第 1 行具有相同的字母,则应将它们更改为 1。基本上,我试图找出任何行与第一行的相似程度。

期望的输出-

1 1 1 1 1 1
1 1 B B B B
C A 1 1 1 1
1 1 D E F A
1 A A A A 1
1 1 1 B B B

第一行全为 1,因为它与自身相同(显然)。在第二行中,第一列和第二列与第一行 (A B) 相同,因此它们变为 1 1。其他行以此类推。

我已经编写了以下代码来进行这种转换-

for seq in {1..1} ; #Iterate over the rows (in this case just row 1)
do 
    for position in {1..6} ; #Iterate over the columns
    do 
        #Define the letter in the first row with which I'm comparing the rest of the rows
        aa=$(awk -v pos=$position -v line=$seq 'NR == line {print $pos}' f) 
        #If it matches, gsub it to 1 
        awk -v var=$aa -v pos=$position '{gsub (var, "1", $pos)} 1' f > temp
        #Save this intermediate file and now act on this
        mv temp f 
    done 
done

您可以想象,这真的很慢,因为嵌套循环很昂贵。我的真实数据是一个 60x10000 的矩阵,这个程序在上面运行大约需要 2 个小时。

我希望您能帮助我摆脱内部循环,以便我可以一步完成所有 6 个 gsub。也许将它们放在自己的数组中?我的awk 技能还不是很好。

【问题讨论】:

    标签: bash loops awk gsub


    【解决方案1】:

    您可以使用这个更简单的 awk 命令来完成这项工作,因为我们避免了 shell 中的嵌套循环,并且还在嵌套循环中重复调用 awk:

    awk '{for (i=1; i<=NF; i++) {if (NR==1) a[i]=$i; if (a[i]==$i) $i=1} } 1' file
    
    1 1 1 1 1 1
    1 1 B B B B
    C A 1 1 1 1
    1 1 D E F A
    1 A A A A 1
    1 1 1 B B B
    

    编辑:

    根据下面的 cmets,您可以执行以下操作来获取每行中每列的总和:

    awk '{sum=0; for (i=1; i<=NF; i++) { if (NR==1) a[i]=$i; if (a[i]==$i) $i=1; sum+=$i}
          print $0, sum}' file
    
    1 1 1 1 1 1 6
    1 1 B B B B 2
    C A 1 1 1 1 4
    1 1 D E F A 2
    1 A A A A 1 2
    1 1 1 B B B 3
    

    【讨论】:

    • ++ 为简单起见!
    • 我相信它会更快:)
    • 这就像一个魅力。使我的整个程序快了大约 3.5 倍。我有程序的第二部分,它将行中的数字相加。 IE。对于此输出,您将获得 6、2、4、2、2、3。您的程序是否可以在此步骤本身进行调整以获取这些值?我应该将此作为一个单独的问题提出吗?
    【解决方案2】:

    输入

    $ cat f
    A B C D E F
    A B B B B B
    C A C D E F
    A B D E F A
    A A A A A F
    A B C B B B
    

    所需的o/p

    $ awk 'FNR==1{split($0,a)}{for(i=1;i<=NF;i++)if (a[i]==$i) $i=1}1' f
    1 1 1 1 1 1
    1 1 B B B B
    C A 1 1 1 1
    1 1 D E F A
    1 A A A A 1
    1 1 1 B B B
    

    说明

    • FNR==1{ .. }

    awk读取当前文件的第一条记录时,做大括号内的事情

    split(string, array [, fieldsep [, seps ] ])

    将字符串分成由fieldsep分隔的片段并存储片段 in 数组和 seps 数组中的分隔符字符串。

    • split($0,a)

    将当前记录或行($0)按字段分割(默认空格,如 我们没有提供第三个参数)并将这些片段存储在数组a 所以数组a 包含第一行的数据

           a[1] = A 
           a[2] = B
           a[3] = C 
           a[4] = D  
           a[5] = E  
           a[6] = F
    
    • for(i=1;i&lt;=NF;i++)

    遍历文件的每条记录的所有字段,直到文件结束。

    • if (a[i]==$i) $i=1

    如果当前索引的第一行的列值(i)等于 当前行的当前列值设置当前列值=1(表示修改当前列值)

    现在我们修改了列值接下来只是打印修改后的行

    • }1

      1 总是评估为真,它执行默认操作{print $0}

    关于评论的更新请求

    同样的问题,我有程序的第二部分加起来 行中的数字。 IE。你会得到 6, 2, 4, 2, 2, 3 输出。可以调整您的程序以在此获取这些值吗 自己踩?

    $ awk 'FNR==1{split($0,a)}{s=0;for(i=1;i<=NF;i++)if(a[i]==$i)s+=$i=1;print $0,s}' f
    1 1 1 1 1 1 6
    1 1 B B B B 2
    C A 1 1 1 1 4
    1 1 D E F A 2
    1 A A A A 1 2
    1 1 1 B B B 3
    

    【讨论】:

    • 这非常有效,与@anubhava 的解决方案一样快。 +1。
    • @VarunM 很高兴知道。
    • 同样的问题,我有程序的第二部分将行中的数字相加。 IE。对于此输出,您将获得 6、2、4、2、2、3。是否可以调整您的程序以在此步骤中获取这些值?
    • 接受了这个,因为答案中的解释很深入。阿努巴瓦的回答同样好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-26
    • 2019-10-21
    • 1970-01-01
    • 2021-04-04
    • 1970-01-01
    相关资源
    最近更新 更多