【问题标题】:Optimizing RegEx Pattern Built Using List of 1000 Phrases优化使用 1000 个短语列表构建的 RegEx 模式
【发布时间】:2014-10-03 15:26:10
【问题描述】:

我是使用 RegEx 的新手。

我有一个公司短语列表(1000 多个),我在运行时将其转换为正则表达式模式。

这是我构建模式的方法:

    ListOfEntries.Sort()

    For i As Integer = 0 To (ListOfEntries.Count - 1)
        ListOfRegExEntries.Add("(\b(?i)" & ListOfEntries(i) & "\b)")
    Next

    RegExPatternString = "(" & String.Join("|", ListOfRegExEntries) & ")"

    RegExPattern = New Regex(RegExPatternString)

条目全部大写。

匹配的字符串是全名字段。我只是想知道字符串是否包含公司关键字。

我可以做些什么来优化匹配过程?如果有人需要更多信息,请随时询问!

【问题讨论】:

  • 可能不是解决问题的最佳方法。即使你优化了它,表达式仍然是低效的/\b(?:[fp]oo|ba[rt]|gr(?:eat|ape))\b/(匹配 foo、poo、bar、bat、great、grape..但是要匹配葡萄它仍然需要尝试许多其他字符)。
  • 如果短语交替出现,唯一的问题是大量重复的起始字母会导致难以置信的分支测试。您可以对短语进行预处理,先对它们进行排序,然后分解出 1 或 2 个字母,例如 g(?:ood day|ame on)。这至少会将初始分支数减少到 26 个以下。无论如何,引擎可能会将其优化为 trie。最好的办法是在 Perl 下试用它,使用 use re 'debug'; 来查看什么会被纳入 trie。
  • 您只是评估一个全名字段吗?您实际上将如何使用它。

标签: .net regex vb.net


【解决方案1】:

对于其他一些答案/cmets,RegEx 似乎不是最佳选择。我决定改用这段代码

Private Function ContainsOrganizationKeywordTest2() As Boolean

    With Output

        Dim BuiltFullName As String = UCase(String.Join(Space, {.PrimaryFirstName, .PrimaryMiddleName, .PrimaryLastName}))
        Dim NameParts As List(Of String) = BuiltFullName.Split(Space).ToList
        NameParts.Sort()

        For i As Integer = 0 To (NameParts.Count - 1)
            If (Not String.IsNullOrWhiteSpace(NameParts(i))) Then
                Dim Result As Integer = _OrganizationKeywords.ListOfEntries.BinarySearch(NameParts(i))
                If (Result > -1) Then
                    Return True
                End If
            End If
        Next

        Return False

    End With

End Function

【讨论】:

    【解决方案2】:

    几个问题

    • 创建一个List然后字符串连接效率低下
      使用 StringBuilder 一次性完成
    • 为什么 (?i) 可以多次设置一次,即使在 Regex 上也是如此
    • 如果它可以是第一个,为什么要建立一个完整的列表
    • 是的,我会先测试 str.Contains,因为它更快
      如果它不在字符串中,请继续

    【讨论】:

      【解决方案3】:

      除了 Perl,我不知道有任何正则表达式引擎可以进行调试。
      因此,作为类比,我使用 Perl 示例代码来展示如何节省大量时间
      在做这样的正则表达式时。

      我相信您可以将此代码翻译成 vb。
      它基本上只是分解每个短语的第一个字母并创建一个
      连接在一起的那些短语的数组。我使用哈希来做到这一点,
      但可以通过对所有短语进行排序,然后循环遍历来轻松完成
      每个都代表相等的第一个字母。

      首先,拥有 +1000 个短语可能会包含大部分字母
      作为短语中某处的起始字符,因此普通的 trie 不会有太大帮助
      在平面正则表达式中。

      然后,在平面正则表达式的情况下,必须测试每个短语,直到匹配。
      这是源字符串中每个字符 +1000 次测试。相当多的开销。

      当您将每个短语的第一个字母分解出来时,您可以立即将其除以 26。
      当您这样做时,将为每个字母打开一个辅助 trie 类,进一步减少
      开销很大的因素。

      如果您为 2 个字符执行此操作,则开销几乎可以忽略不计。

      下面仅显示了 FLAT 1 级 (trie) 正则表达式的调试,
      和单个字符级别因子之一。

      要分析正则表达式,请按照每个TRIEC-EXACTF[..] 中的路径表示一个终止
      点(通过或失败)。

      您可以看到 路径 显着减少。

      Perl 代码:

      use strict;
      use warnings;
      use Data::Dumper;
      
      use re 'debug';
      
      my @Flat_Rx_ary = ();
      my @rx_ary = ();
      
      
      my %LetterHash = ();
      
      while (my $line = <DATA>)
      {
          chomp( $line );
          next if ( length($line) == 0);
      
          push ( @Flat_Rx_ary, $line );
      
          my $first_char = substr( $line, 0, 1);
          my $remainder = substr( $line, 1 );
          if ( !defined( $LetterHash{ $first_char } )) {
              $LetterHash{ $first_char } = [];
          }
          push ( @{$LetterHash{ $first_char }}, $remainder );
      }
      
      print Dumper(\%LetterHash);
      
      # Factored regex ..
      my @rx_parts = ();
      foreach my $rx_key ( keys %LetterHash )
      {
          @{$LetterHash{ $rx_key }} = sort @{$LetterHash{ $rx_key }};
          my $rx_val = join ( '|', @{$LetterHash{ $rx_key }} );
          push ( @rx_parts, '(?:' . $rx_key . '(?:' . $rx_val . '))' );
      }
      my $total_rx = '(?i)\b(' . join( '|', @rx_parts ) . ')\b'; 
      print $total_rx, "\n\n\n";
      my $CompiledRx = qr /$total_rx/;
      
      # Flat regex ..
      @Flat_Rx_ary = sort ( @Flat_Rx_ary );
      my $Flat_Total_Rx = '(?i)\b(' . join( '|',@Flat_Rx_ary) . ')\b'; 
      print "\n\n\n", $Flat_Total_Rx, "\n\n\n";
      my $CompiledFlatRx = qr /$Flat_Total_Rx/;
      
      __DATA__
      hello world
      this is cool
      good day
      one day beyond
      a very fine time
      the end of the season
      the trial of the centurn
      total eclipse
      game on
      hello LA
      

      输出:

      $VAR1 = {
                'a' => [
                         ' very fine time'
                       ],
                'h' => [
                         'ello world',
                         'ello LA'
                       ],
                'g' => [
                         'ood day',
                         'ame on'
                       ],
                'o' => [
                         'ne day beyond'
                       ],
                't' => [
                         'his is cool',
                         'he end of the season',
                         'he trial of the centurn',
                         'otal eclipse'
                       ]
              };
      (?i)\b((?:a(?: very fine time))|(?:h(?:ello LA|ello world))|(?:g(?:ame on|ood da
      y))|(?:o(?:ne day beyond))|(?:t(?:he end of the season|he trial of the centurn|h
      is is cool|otal eclipse)))\b
      
      
      Compiling REx "(?i)\b((?:a(?: very fine time))|(?:h(?:ello LA|ello world))|"...
      Final program:
         1: BOUND (2)
         2: OPEN1 (4)
         4:   TRIEC-EXACTF[AGHOTaghot] (74)
              <a very fine time> (74)
              <h> (15)
        15:     EXACTF <ello > (18)
        18:     TRIE-EXACTF[LWlw] (74)
                <LA>
                <world>
              <g> (28)
        28:     TRIE-EXACTF[AOao] (74)
                <ame on>
                <ood day>
              <one day beyond> (74)
              <t> (48)
        48:     TRIEC-EXACTF[HOho] (74)
                <he end of the season>
                <he trial of the centurn>
                <his is cool>
                <otal eclipse>
        74: CLOSE1 (76)
        76: BOUND (77)
        77: END (0)
      stclass BOUND minlen 7
      
      
      
      (?i)\b(a very fine time|game on|good day|hello LA|hello world|one day beyond|the
       end of the season|the trial of the centurn|this is cool|total eclipse)\b
      
      
      Compiling REx "(?i)\b(a very fine time|game on|good day|hello LA|hello worl"...
      Final program:
         1: BOUND (2)
         2: OPEN1 (4)
         4:   TRIEC-EXACTF[AGHOTaghot] (60)
              <a very fine time>
              <game on>
              <good day>
              <hello LA>
              <hello world>
              <one day beyond>
              <the end of the season>
              <the trial of the centurn>
              <this is cool>
              <total eclipse>
        60: CLOSE1 (62)
        62: BOUND (63)
        63: END (0)
      stclass BOUND minlen 7
      Freeing REx: "(?i)\b((?:a(?: very fine time))|(?:h(?:ello LA|ello world))|"...
      Freeing REx: "(?i)\b(a very fine time|game on|good day|hello LA|hello worl"...
      

      【讨论】:

      • 感谢您的回复。这种技术可能对我正在维护的应用程序的其他领域有用。
      猜你喜欢
      • 2018-08-10
      • 2013-10-21
      • 1970-01-01
      • 1970-01-01
      • 2014-07-25
      • 2019-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多