【问题标题】:Partial match of strings [closed]字符串的部分匹配[关闭]
【发布时间】:2018-06-21 08:52:03
【问题描述】:

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

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

假设我的输入是这样的。 X 是“无关紧要”位;会改成.

11XX1100100010110111110110101001000010110101111111

数据库中的数据是

11001100100010110111110110101001000010110101111111
11001011011101001000001001010110111101001010000000
00110011011101001000001001010110111101001010000111

第一行数据完全匹配输入,所以我会打印出来。

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

第三行数据与输入不完全匹配,但第二和第三位由于不关心条件匹配,最后三位匹配。因此,5 位(2nd + 3rd + 最后 3 位)是匹配的,所以我会打印这个。

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

input.txt

11XX1100100010110111110110101001000010110101111111
1000011000111101001011110111001100100101111000010X

搜索.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

【问题讨论】:

  • 您的需求本质上是重要的。你不能简单地依靠正则表达式来做到这一点,这意味着你需要更复杂的过程?
  • 我明白了,那么我认为基于字符的搜索是唯一的方式,就像 zdim 的回复一样。
  • 您的代码甚至没有尝试遵循您描述的规则。当您不想再尝试时,Stack Overflow 并不是为了完成您的工作。正则表达式无法做到这一点,您应该设计和实现一种算法来满足您的需求。如果您进行了真正的尝试并且仍在苦苦挣扎,请在此处发布您的代码以寻求帮助,以了解您错过了什么。就目前而言,您只是要求免费编写代码,这是不可原谅的。
  • 嗨,对不起,我知道规则。如果我的问题和代码不遵守规则,我会删除我的问题。对此感到抱歉。
  • 我试图删除这篇文章,因为我的鳕鱼没有遵守规则。我无法删除此帖子

标签: string perl sed match


【解决方案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 可以简单地用作“假”,如果这种情况是可以接受的。如果没有,您可以改为使用die

【讨论】:

  • // 这样的空模式通常会尝试匹配最近成功匹配的模式。但split 不受此限制,因此split // 将正确地将字符串划分为字符列表。我认为总是最好使用/作为分割模式的分隔符,强调参数是一个正则表达式模式,至少要区分split ' 'split / /这两个非常不同。
  • @Borodin Eh ...我只是无法决定。起初我有//,但后来我喜欢''“强调”它使用文字字符串。不过,这可能很愚蠢(而且没有实际意义,我认为它无论如何都会运行正则表达式)。 (另外,我确实在正则表达式中对// 进行了一些抽搐,但这在split 中很清楚。)重新考虑...
【解决方案2】:

以下是一个快速的解决方案。当大多数字符串匹配时,它的性能最好。

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)。

【讨论】:

  • 经过测试。修复了问题。
【解决方案3】:

以下是一个快速的解决方案。当有很多查询和/或大多数字符串不匹配时,它的性能最好。

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 可以进行优化。我认为它并不重要,因为它只被调用过一次。

【讨论】:

  • 经过测试。修复了问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-15
  • 2019-10-18
  • 1970-01-01
  • 2017-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多