【问题标题】:How to locate the data column instead of character position NOT using loop to search in Perl and Bash?如何定位数据列而不是字符位置而不是使用循环在 Perl 和 Bash 中搜索?
【发布时间】:2019-04-23 14:43:01
【问题描述】:

我想知道的是如何使用 PERL 或 Bash 在字符串/数组中定位原子 H 位置(作为列号而不是字符号)?我试图避免不必要的循环来搜索 H,因为我的数据有超过一百万行。

我的研究数据如下所示

FRAM_#     20000000      5000000(fs)  CN= 1 PRMRYTGT     16652      O      16654      H  1.036      8140     CA  2.586      7319     AL  1.963

其中,有 O、H、CA 和 AL 原子。第一个原子是目标原子氧,其余原子是与目标氧结合的邻居。除第一个原子(氧)外,每个原子前面的整数为原子ID,后面的浮点数为与第一个原子的键长O(ID=16652)。

$line = 'FRAM_#     20000000      5000000(fs)  CN= 1    PRMRYTGT     16652'
        . '      O     16654      H  1.036      8140     CA  2.586'
        . '     7319     AL  1.963';
@values = split(/\s+/, $line);
my $bondlength;
my $neighbor_ID;
for (my $i = 10; $i <= $#values; $i = $+3) {
  if ($values[$i] eq 'H') {
    $neighbor_ID = $values[$i-1];
    $bondlength = $values[$i+1];
  } else {
    next;
  }

我可以使用循环在数组@values 中搜索H 的位置。但是,是否有任何其他方式(不是循环),如正则表达式或 BASH 脚本,来获取 H 在数组中的位置?如果您能给我额外的建议和帮助,我将不胜感激。

我想找到 H 和目标氧之间的氢键(键长大于 1.5 埃)。所以,我必须得到 H 的原子 ID 和相关的键长。所以,首先,我需要找到 H 的位置。然后找到原子 ID 和相关的键长。然后我可以做进一步的数据分析。

注意:我有超过100万的数据线,因此,我必须考虑代码效率。在这个例子中,H 是我的目标原子。在数据线中,H的数量可以是多种多样的。

【问题讨论】:

  • 你想要什么,H之后的值?
  • 您需要向我们提供更多详细信息。对于这行输入,你想要什么输出?
  • 我需要获取原子 ID(这里是 H)和 H 与目标原子(这里是氧)之间的键长。
  • 我再问一次。对于这行输入,你想要什么输出?

标签: regex bash perl


【解决方案1】:

目前尚不清楚给定输入的预期结果究竟是什么。 如果是字母H前后的一对数字,下面就可以了。

sed -E 's/.*\s+(\S+)\s+H\s+(\S+)\s.*/\1,\2/' < input.txt

示例输入:

FRAM_#     20000000      5000000(fs)  CN= 1 PRMRYTGT     16652      O      16654      H  1.036      8140     CA  2.586      7319     AL  1.963

样本输出:

16654,1.036

【讨论】:

    【解决方案2】:
    open(FH, "data.txt") or die "Can’t open data.txt: $!";
    
    while(<FH>)
    {
     if (@d=/\bO\s+(\d+)\s+H\s+(1\.[5-9]\d*|[2-9][\d.]*)/) {print "$_\n" for @d}
    $ID=$d[0]
    $len=$d[1]
    }
    

    每条数据线只产生ID,放入$d[0],和键长,如果大于/等于1.5,则放入@d数组中的$d[1]

    【讨论】:

      【解决方案3】:

      perl:使用来自List::MoreUtils的firstidx

      use List::MoreUtils  qw/ firstidx /;
      
      my $line = '...';
      my @values = split ' ', $line;
      my $h_idx = firstidx {$_ eq 'H'} @values;
      print "H appears at index $h_idx\n";
      

      这将使用循环under the hood。我不明白你怎么能避免它。如果您的列表已排序,您可以使用二进制搜索,但您的列表未排序。

      另一方面,您根本不需要将行拆分到列表中,这样可以节省一些时间:

      my ($neighbour_id, $bond_length) = $line =~ /(\d+) \s+ H \s+ (\S+)/x;
      return $neighbour_id if $bond_length > 1.5;
      

      【讨论】:

        【解决方案4】:

        这是您的代码,为了便于阅读,稍微重新格式化(空格并不短缺!)

        $line = "FRAM_#     20000000      5000000(fs)  CN= 1  PRMRYTGT     16652      O      16654      H  1.036      8140     CA  2.586      7319     AL  1.963";
        
        @values = split(/\s+/, $line);
        
        my ($bondlength, $neighbor_ID);
        
        for (my $i = 10; $i <= $#values; $i += 3) {
          if ($values[$i] eq 'H') {
            $neighbor_ID = $values[$i-1];
            $bondlength = $values[$i+1];
          }
          else {
            next;
          }
        }
        

        循环中的else 子句完全没有必要。无论如何,循环将继续进行下一次迭代。

        一个明显的优化是一旦找到氢原子就停止寻找。所以你的代码看起来像这样:

        $line = "FRAM_#     20000000      5000000(fs)  CN= 1  PRMRYTGT     16652      O      16654      H  1.036      8140     CA  2.586      7319     AL  1.963";
        
        @values = split(/\s+/, $line);
        
        my ($bondlength, $neighbor_ID);
        
        for (my $i = 10; $i <= $#values; $i = $+3) {
          if ($values[$i] eq 'H') {
            $neighbor_ID = $values[$i-1];
            $bondlength = $values[$i+1];
            last; # stop looking once you've found it
          }
        }
        

        我不知道它是否足以解决你所有的问题,但这是一个开始。

        【讨论】:

        • 感谢您的改造。是的。在这种情况下变得更加清晰。不幸的是,这不是我想要的。我希望找到另一种提高代码效率的方法。这个搜索是 O(n)。对于个人搜索,这不是问题。然而,对于数百万的数据,它会花费更多的时间。这就是为什么我想找到另一种方法来避免这种搜索循环。
        猜你喜欢
        • 1970-01-01
        • 2021-10-15
        • 1970-01-01
        • 1970-01-01
        • 2014-11-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-30
        相关资源
        最近更新 更多