我想找到一种方法来对字符串进行部分匹配。
我有两个50位二进制输入。如果任何输入与数据库(数组)中至少5位的数据匹配,我会打印输入。
假设我的输入是这样的。 X
是一个“不在乎”的位;它会更改为.
,
11XX1100100010110111110110101001000010110101111111
数据库中的数据是
11001100100010110111110110101001000010110101111111
11001011011101001000001001010110111101001010000000
00110011011101001000001001010110111101001010000111
第一行数据与输入完全匹配,因此我将打印出来。
第二行数据与输入不完全匹配,但第一个5位匹配,所以我也会打印它。
第三行数据与输入不完全匹配,但第二和第三位是匹配的,因为无关条件且最后一个3位匹配。因此,5位(第2 + 3 +最后3位)匹配,所以我将打印这个。
我有一个Perl脚本仅用于完全匹配的情况,但我不知道如何为部分匹配的情况修改它。
11XX1100100010110111110110101001000010110101111111
1000011000111101001011110111001100100101111000010X
#!/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
答案 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将为额外字符生成30
,31
或58
。
答案 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
可以进行优化。我并不认为这很重要,因为它只被召唤过一次。