字符串

时间:2018-01-11 22:43:51

标签: string perl sed match

我想找到一种方法来对字符串进行部分匹配。

我有两个50位二进制输入。如果任何输入与数据库(数组)中至少5位的数据匹配,我会打印输入。

假设我的输入是这样的。 X是一个“不在乎”的位;它会更改为.

11XX1100100010110111110110101001000010110101111111

数据库中的数据是

11001100100010110111110110101001000010110101111111
11001011011101001000001001010110111101001010000000
00110011011101001000001001010110111101001010000111

第一行数据与输入完全匹配,因此我将打印出来。

第二行数据与输入不完全匹配,但第一个5位匹配,所以我也会打印它。

第三行数据与输入不完全匹配,但第二和第三位是匹配的,因为无关条件且最后一个3位匹配。因此,5位(第2 + 3 +最后3位)匹配,所以我将打印这个。

我有一个Perl脚本仅用于完全匹配的情况,但我不知道如何为部分匹配的情况修改它。

input.txt中

11XX1100100010110111110110101001000010110101111111
1000011000111101001011110111001100100101111000010X

search.pl

#!/usr/bin/perl

use warnings;
use strict;

# Read input
open my $input_fh, '<', 'input.txt' or die $! ;
chomp ( my @input = <$input_fh> );

# input
#   11XX11001000101101111101101010010000101101011111X1
#   1000011000111101001011110111001100100101111000010X

# Replace 'X' with '.' which is the regex "don't care" character.                 
s/X/./g for @input;

# Compile a regex made of these two patterns. 
my $search = join ( "|", @input ); 
$search = qr/$search/;      

# Iterate database ( pasted in 'data' block for illustrative purposes )
while ( <DATA> ) {
    my ( $id, $target, @rest ) = split;
    # print if the target line matches
    print if $target =~ /$search/;
}

# Currently, only fully matched ones are printed 

__DATA__
11001100100010110111110110101001000010110101111101
11001011011101001000001001010110111101001010011111
00110011011101001000001001010110111101001010000111

3 个答案:

答案 0 :(得分:1)

你需要逐个字符地检查,所以为什么不打破字符串并计算

sub is_match {
    my ($target, $search, threshold) = @_;
    return if length($target) != length($search);
    $treshold //= 5;

    my @tgt = split //, $target;
    my @sr  = split //, $search;

    for my $i (0..$#tgt) {
        ++$m if $tgt[$i] eq $sr[$i] or $sr[$i] eq 'X';
    }

    return $m >= $treshold ? $m : 0;
}

我返回完整的计数,因为这可能会派上用场。但是,如果你只关心1/0,如果字符串可能很大或比较很多次,那么提前返回可能是有意义的

    ...
    for my $i (0..$#tgt) {
        ++$m if $tgt[$i] eq $sr[$i] or $sr[$i] eq 'X';
        return 1 if $m == $treshold;
    }
    return 0;

请注意,从循环中返回通常不是一种好习惯,因为多次(隐藏)返回会使程序流程难以遵循。以后也很容易被忽视。

我只添加了一个字符串长度相同的基本检查。在这种情况下返回的undef可以简单地用作'false',如果这种情况可以接受的话。如果不是,你可以改为die

答案 1 :(得分:0)

以下是快速解决方案。当大多数字符串匹配时,它表现最佳。

sub is_match { ( ( $_[0] ^ $_[1] ) =~ tr/\x00\x68\x69// ) >= 5 }

while (<DATA>) {
   my ( undef, $target ) = split;
   for my $query (@inputs) {
      if (is_match($query, $target)) {
         print;
         last;             
      }
   }
}

工作原理:

    Hex of characters
    =================

    30 30 31 31 58 58  "0011XX"
    30 31 30 31 30 31  "010101"
XOR -----------------
    00 01 01 00 68 69
    ^^       ^^ ^^ ^^  4 matches

如果其中一个字符串比另一个字符串短,则此解决方案甚至可以工作(因为XOR将为额外字符生成303158

答案 2 :(得分:-1)

以下是快速解决方案。当有许多查询和/或大多数字符串不匹配时,它表现最佳。

use Algorithm::Loops qw( NestedLoops );
use Regexp::Assemble qw( );

sub make_matcher {
   my $ra = Regexp::Assemble->new( flags => 's' );
   for (@_) {
      my $query = tr/X/./r;
      my @query = split //, $query;     #/
      my $extra = @query - 5;
      if ($extra <= 0) {
         $ra->add("^$query") if $extra == 0;
         next;
      }

      NestedLoops(
         [
            [ 0..$#query ],
            ( sub { [ $_+1..$#query ] } ) x ( $extra - 1 ),
         ],
         sub {
            local @query[@_] = (".") x @_;
            $ra->add( "^" . ( join("", @query) =~ s/\.+\z//r ) );
         },
      );
   }

   return $ra->re();
}

my $re = make_matcher(@inputs);
while (<DATA>) {
   my ( undef, $target ) = split;
   print if $target =~ $re;
}

它的工作原理是创建一个类似于以下内容的大型正则表达式:

# For @inputs = qw( 000000 111111 );
my $re = qr/
      ^.00000
   |  ^0.0000
   |  ^00.000
   |  ^000.00
   |  ^0000.0
   |  ^00000
   |  ^.11111
   |  ^1.1111
   |  ^11.111
   |  ^111.11
   |  ^1111.1
   |  ^11111
/xs;

实际模式:

my $re = qr/(?s:^(?:0(?:0(?:0(?:0.?|.0)|.00)|.000)0|1(?:1(?:1(?:1.?|.1)|.11)|.111)1|.(?:00000|11111)))/;

如果其中一个字符串比另一个短,则该解决方案甚至可以工作。

请注意,make_matcher可以进行优化。我并不认为这很重要,因为它只被召唤过一次。