【问题标题】:bash subsequence matching speed-upbash 子序列匹配加速
【发布时间】:2019-11-21 15:48:33
【问题描述】:

我想知道是否有一种简单的方法可以检查一个字符串是否是 bash 中另一个字符串的子序列,实际上是一个带有额外规则的子序列。我会解释的。

“apple”的一些子序列是“aple”、“al”、“pp”和“ale”。我想得到的带有额外规则的子序列是那些以与字符串相同的字母开头和结尾的子序列,所以只有“aple”和“ale”符合我的愿望。

我做了以下程序:

#!/bin/bash
while read line
do
    search=$(echo "$line" | tr -s 'A-Za-z' | sed 's/./\.\*&/g;s/^\.\*//' )
    expr match "$1" "$search" >/dev/null && echo "$line"
done

执行如下:

./program.sh greogdgedlqfe < words.txt

这个程序可以运行,但是速度很慢。

它获取文件的每一行,将其修改为正则表达式,然后检查它们是否匹配,然后打印原始行。比如:

其中一行包含 google

$search 变成 g.*o.*g.*l.*e (重复的字母被压缩,额外的规则)

然后我们使用给定的参数检查该表达式,如果匹配,我们打印以下行:google

这工作正常,但是当文件 words.txt 变得太大时,这个程序变得太慢。我怎样才能加快我的程序,可能通过更快的匹配子序列。

在可能的 Kamilcuk 解决方案后编辑

该解决方案为字符串“qwertyuihgfcvbnhjk”返回 quick,quiff,quin,qwerty,并且只应返回 quick,所以它几乎是正确的,但还不完全正确。

【问题讨论】:

  • 你能从 words.txt 和示例输出中发布一些摘录吗?我无法测试您的脚本,某些匹配和不匹配某些输入的单词会有所帮助。 appppppppleapple 的子序列吗?因为你的脚本会匹配到apple
  • 如果我理解正确的话,apple 的有效后续只有 4 个:aealeapeaple。对吗?
  • 是的,但 apppppppppppe 也会在我的程序中匹配,这是有意的。
  • 哦?那么a&lt;anythinghere&gt;pple也是苹果的后续吗? “后续”看起来不像“子序列”,而是像扩展。所以一个子序列就是任何与正则表达式匹配的东西,由一个单词的字母组成,字母之间有.*,故事结束了吗?
  • 不,appppe 是苹果的后续,因为我的程序首先挤压重复的字符。所以appppe变成了猿,这是苹果的后续。

标签: bash performance subsequence


【解决方案1】:

像这样尝试:

grep -x "$(<<<"$1" tr -s 'A-Za-z' | sed 's/./&*/g;s/\*$//;s/\*//1')" words.txt

测试:

set -- apple  
cat >words.txt <<EOF
aple
al
pp
ale
fdafda
apppppppple
apple
google
EOF

输出:

aple
ale
apppppppple
apple

对于set -- greogdgedlqfe,它只输出google

如果我理解正确,apple 的“后续”就是 ap*l*e 的所有数学运算。

Tested on repl

【讨论】:

  • 非常好,正是我想要实现的 Swype 或 SwiftKey
  • 其实不是,它应该匹配a * p * l * e,但是没有空格但需要输出
  • 小记:'greogdgedlqfe'的grep模式是'greogdgedlqfe'。我相信要求的“额外”规则是让模式为 'greogdgedlqf*e'(以 g 开头,以 e 结尾)。
  • @KamilCuk 几乎是正确的,我用更多示例编辑了我的问题并回答了为什么你的问题是错误的
  • 我认为一个小改动: 'grep -x "$(qwertyuihgfcvbnhjk 错误。
【解决方案2】:

您可以使用模式而不是正则表达式。只需在每个单词的每个字母后插入星号(最后一个字母除外)并使用正常的模式匹配。

#!/bin/bash
while read line
do
    pattern=""
    for ((i=${#line}-1 ; i>=0 ; --i)) ; do
        pattern="${line:i:1}*"$pattern
    done
    pattern=${pattern%'*'}

    if [[ "$1" == $pattern ]] ; then
        echo "$line"
    fi
done

【讨论】:

    【解决方案3】:

    bash 不需要使用expr(外部程序)进行正则表达式匹配;它提供对系统库的内置访问。

    #!/bin/bash
    while read line
    do
        search=$(echo "$line" | tr -s 'A-Za-z' | sed 's/./\.\*&/g;s/^\.\*//' )
        [[ $1 =~ $search ]] && echo "$line"
    done
    

    【讨论】:

    • 如何在我的程序中使用grep?
    • 抱歉,忘记将参数提供给grep
    • grep 的问题,它只会输出$1 的内容。我们想从words.txt 输出这些行,对吧?我们需要让 grep 输出它与 $1 匹配的正则表达式
    • 好点。不过,更快的解决方案是在单个 awk 进程中完成所有这些操作。尝试几次失败后,我会看看我是否能做到这一点。
    【解决方案4】:

    用正则表达式很难击败perl

    性能

    性能的关键是避免分叉额外的进程。此处介绍的大多数 bash 解决方案(基于 KamilCuk grep 的解决方案除外,这并不总是正确的)将需要多次调用 sed、tr 等。Perl 将优于这些解决方案。即使可以实现纯 bash 解决方案(使用 bash RE、模式),当单词列表的大小很大时,Perl 也可能会胜过它。

    考虑program.pl appl &lt; words.txt

    #! /usr/bin/perl
    use strict ;
    
    my $word = shift @ARGV ;
    
    while ( <> ) {
        chomp ;
        my $p = $_ ;
        tr/A-Za-z//s ;
        s/(.)/.*$1/g ;
        s/^\.\*// ;
        print $p, "\n" if $word =~ "^$_\$" ;
    } ;
    

    更新 1:KamilCuk 解决方案的 Perl 实现 + 修复。

    经过小修后,我相信可以在基于 grep 的解决方案中使用这个想法来创建一个更快的 Perl 程序。它创建一个单一的 REGEXP,并测试单词列表文件中的每个单词。我认为这是 Perl 的最佳选择。

    #! /usr/bin/perl
    use strict ;
    
    $_ = shift @ARGV ;
    tr/A-Za-z//s ;
    s/(.)/$1*/g ;
    s/\*// ;
    s/\*$// ;
    my $re = "^$_\$" ;
    print "RE=$re\n" ;
    
    while ( <> ) {
            chomp ;
            print $_, "\n" if /$re/ ;
    } ;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-27
      • 2014-02-05
      • 2020-04-02
      • 1970-01-01
      • 1970-01-01
      • 2013-01-09
      相关资源
      最近更新 更多