【问题标题】:How to compare 2 lists of ranges in bash?如何比较 bash 中的 2 个范围列表?
【发布时间】:2018-02-12 12:12:12
【问题描述】:

使用 bash 脚本(Ubuntu 16.04),我正在尝试比较 2 个范围列表:file1 中任何范围中的任何数字是否与 file2 中任何范围中的任何数字一致?如果是这样,打印第二个文件中的行。在这里,我将每个范围作为 2 个制表符分隔的列(在 file1 中,第 1 行表示范围 1-4,即 1、2、3、4)。真实的文件很大。

文件1:

1 4
5 7 
8 11
12 15

文件2:

3 4 
8 13 
20 24

期望的输出:

3 4 
8 13

我最好的尝试是:

awk 'NR=FNR { x[$1] = $1+0; y[$2] = $2+0; next}; 
{for (i in x) {if (x[i] > $1+0); then
{for (i in y) {if (y[i] <$2+0); then            
{print $1, $2}}}}}' file1 file2 > output.txt

这将返回一个空文件。

我认为脚本需要使用 if-then 条件进行范围比较,并遍历两个文件中的每一行。我找到了每个概念的示例,但不知道如何组合它们。

任何帮助表示赞赏!

【问题讨论】:

  • 在 Bash 中执行此操作有多重要,为什么?在 Python、Perl 等中会更直接。

标签: linux bash awk range genetics


【解决方案1】:

当然,这取决于您的文件有多大。如果它们不足以耗尽内存,你可以试试这个 100% bash 解决方案:

declare -a min=() # array of lower bounds of ranges
declare -a max=() # array of upper bounds of ranges

# read ranges in second file, store then in arrays min and max
while read a b; do
    min+=( "$a" );
    max+=( "$b" );
done < file2

# read ranges in first file    
while read a b; do
    # loop over indexes of min (and max) array
    for i in "${!min[@]}"; do
        if (( max[i] >= a && min[i] <= b )); then # if ranges overlap
            echo "${min[i]} ${max[i]}" # print range
            unset min[i] max[i]        # performance optimization
        fi
    done
done < file1

这只是一个起点。有许多可能的性能/内存占用改进。但它们在很大程度上取决于文件的大小和范围的分布。

EDIT 1:改进了范围重叠测试。

EDIT 2:重用 RomanPerekhrest 提出的出色优化(未设置已打印的范围来自 file2)。当范围重叠的概率较高时,性能应该会更好。

EDIT 3:与 RomanPerekhrest 提出的awk 版本的性能比较(在修复了最初的小错误之后):awk 在这个问题上比bash 快 10 到 20 倍.如果性能很重要,而您在awkbash 之间犹豫不决,请选择:

awk 'NR == FNR { a[FNR] = $1; b[FNR] = $2; next; }
    { for (i in a)
          if ($1 <= b[i] && a[i] <= $2) {
              print a[i], b[i]; delete a[i]; delete b[i];
          } 
    }' file2 file1

【讨论】:

    【解决方案2】:

    awk解决方案:

    awk 'NR==FNR{ a[$1]=$2; next }
         { for(i in a) 
               if (($1>=i+0 && $1<=a[i]) || ($2<=a[i] && $2>=i+0)) { 
                   print i,a[i]; delete a[i];
               } 
         }' file2 file1
    

    输出:

    3 4
    8 13
    

    【讨论】:

      【解决方案3】:
      awk 'FNR == 1 && NR == 1 { file=1 } FNR == 1 && NR != 1 { file=2 } file ==1 { for (q=1;q<=NF;q++) { nums[$q]=$0} } file == 2 { for ( p=1;p<=NF;p++) { for (i in nums) { if (i == $p) { print $0 } } } }' file1 file2
      

      分解:

      FNR == 1 && NR == 1 { 
                        file=1 
                        }
      FNR == 1 && NR != 1 { 
                        file=2 
                        }
      file == 1 { 
                 for (q=1;q<=NF;q++) { 
                            nums[$q]=$0
                      } 
                }
      file == 2 {
            for ( p=1;p<=NF;p++) {
               for (i in nums) {
                   if (i == $p) {
                            print $0
                   }
                }
            }
      }
      

      基本上我们在处理第一个文件时设置file = 1,在处理第二个文件时设置file = 2。当我们在第一个文件中时,将该行读入以该行的每个字段为键的数组中。当我们在第二个文件中时,处理数组(nums)并检查行上的每个字段是否有一个条目。如果有,打印出来。

      【讨论】:

      • 感谢您的帮助!不幸的是,该命令没有返回任何内容。但是,上一个答案中的脚本有效。
      • 该脚本的前 6 行可以仅替换为 FNR==1{++file}
      【解决方案4】:

      对于 GNU awk,我正在控制 for 扫描顺序以优化时间:

      $ cat program.awk
      BEGIN {
          PROCINFO["sorted_in"]="@ind_num_desc"
      }
      NR==FNR {                                         # hash file1 to a
          if(($2 in a==0) || $1<a[$2])                  # avoid collisions
              a[$2]=$1
          next
      }
      {
          for(i in a) {                                 # in desc order
              # print "DEBUG: For:",$0 ":", a[i], i     # remove # for debug
              if(i+0>$1) {                              # next after
                  if($1<=i+0 && a[i]<=$2) {
                      print
                      next
                  }
              }
              else
                  next
          }
      }
      

      测试数据:

      $ cat file1
      0 3 # testing for completely overlapping ranges
      1 4
      5 7 
      8 11
      12 15
      $ cat file2
      1 2 # testing for completely overlapping ranges
      3 4 
      8 13 
      20 24
      

      输出:

      $ awk -f program.awk file1 file2
      1 2
      3 4 
      8 13 
      

      $ awk -f program.awk file2 file1
      0 3
      1 4
      8 11
      12 15
      

      【讨论】:

      • 与 RomanPerekhrest 回答中的问题相同:1) 范围重叠测试不完整,例如它错过了 file2 中的范围严格包含在 file1 范围内的重叠情况。 2) 如果两个file1 范围具有相同的上限,则a 关联数组中将发生键冲突。
      • @RenaudPacalet 无论如何(经过一个不眠之夜),我对那个测试不太满意,反正看起来像是个黑客。您能否提供一个示例来证明失败?
      • 这个例子应该解决 2 个问题:file1=1 5\n1 2(2 个范围),file2=3 4\n0 6(2 个范围)。输出应该是3 4\n0 6
      • 谢谢。稍后我会看看它。
      • 两个问题:1) 你应该删除你的if($2&gt;a[i]),和2) 如果两个file1 范围具有相同的上限,那么你的a 关联数组中将会发生键冲突,你可能错过了一些有效的结果。请参阅我对自己的答案所做的最后一次编辑。
      【解决方案5】:

      如果首选 Perl 解决方案,那么下面的单行将起作用

      /tmp> cat marla1.txt
      1 4
      5 7
      8 11
      12 15
      /tmp> cat marla2.txt
      3 4
      8 13
      20 24
      /tmp> perl -lane ' BEGIN { %kv=map{split(/\s+/)} qx(cat marla2.txt) } { foreach(keys %kv) { if($F[0]==$_ or $F[1]==$kv{$_}) { print "$_ $kv{$_}" }} } ' marla1.txt
      3 4
      8 13
      /tmp>
      

      【讨论】:

        【解决方案6】:

        如果范围是根据其下限排序的,我们可以使用它来提高算法的效率。这个想法是交替处理 file1 和 file2 中的范围。更准确地说,当我们在file2 中有一定范围R 时,我们会在file1 中越来越远的范围,直到我们知道这些范围是否与R 重叠。一旦我们知道这一点,我们就切换到file2 中的下一个范围。

        #!/bin/bash
        
        exec 3< "$1"  # file whose ranges are checked for overlap with those ...
        exec 4< "$2"  # ... from this file, and if so, are written to stdout
        
        l4=-1  # lower bound of current range from file 2 
        u4=-1  # upper bound
        # initialized with -1 so the first range is read on the first iteration
        
        echo "Ranges in $1 that intersect any ranges in $2:"
        while read l3 u3; do  # read next range from file 1
          if (( u4 >= l3 )); then
            (( l4 <= u3 )) && echo "$l3 $u3"
          else  # the upper bound from file 2 is below the lower bound from file 1, so ...
            while read l4 u4; do  # ... we read further ranges from file 2 until ...
              if (( u4 >= l3 )); then  # ... their upper bound is high enough
                (( l4 <= u3 )) && echo "$l3 $u3"
                break
              fi
            done <&4
          fi
        done <&3
        

        脚本可以用./script.sh file2 file1调用

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多