【问题标题】:Language Independant: Check if a string consists of a multiple of a certain substring语言无关:检查字符串是否由某个子字符串的倍数组成
【发布时间】:2016-10-05 05:23:20
【问题描述】:

我希望通用算法找出一个字符串是否包含重复模式,并且字符串的任何部分都没有被排除在重复模式之外。

例如,看看这些示例字符串:

abcabcabc - true
abcabcabcx - false
cucumbercucumber - true
cucumber - false
abaaabaaabaa - true

我查看了this answer,它解决了少数情况下的问题,但在cucumber 示例的情况下会失败。我需要在所有情况下都有效的东西。

【问题讨论】:

  • 确实很有趣。到目前为止,我了解到的是,对于每个regex,都有一个finite automata。第一个问题应该是Is there a finite autonata for dynamic pattern recognition?。如果是这样,那么您可以将其转换为正则表达式,反之亦然。
  • 如果支持反向引用,可以通过正则表达式:^(.+)\1+$
  • 解释为什么这是正确的例子:abaaabaaabaa 你认为重复模式是什么?
  • @IraBaxter abaa 重复。
  • @SebastianProske:啊。是的。好。这就是为什么您需要算法而不是人类来执行此操作的原因。或者至少为什么你想要一个算法而不是我做它:-}

标签: regex language-agnostic pattern-matching


【解决方案1】:

一个受https://stackoverflow.com/a/2553533/1763356启发的Python解决方案是

s in (s + s)[1:-1]

假设str.__contains__ 的有效实现,这需要O(n) 时间。

【讨论】:

  • 也许我不明白这是做什么的。这与 s == "a" 有什么关系?
  • s + s == "aa",所以(s + s)[1:-1] == ""(空字符串)。因此s in (s + s)[1:-1] 返回False
  • 啊,它是零原点索引。 (我不是 Python 专家)。有趣的。所以这个想法应该可以用字符串concat、substring和find的任何语言实现。
【解决方案2】:

这似乎是显而易见的方法:

String s = "abaaabaabaa" ; // string to test

for (int repeating_pattern_length=1; 
     repeating_pattern_length<=s.length/2;
     repeating_pattern_length++)
{  if (modulo(s.length,repeating_pattern_length)==0)
   { // can fit exactly N times
     String proposed_subpattern=s.substring(0,repeating_pattern_length);
     for (nth_instance=2; // don't need to check 1st occurrence
          nth_instance<=s.length/repeating_pattern_length;
          nth_instance++)
     { // check nth occurrence
       if (!proposed_subpattern.equal(
           s.substring((nth_instance-1)*repeating_pattern_length,
                       repeating_pattern_length)
          cycle repeating_pattern_length; // nth occurrence doesn't match
     }
     return true;
   }
}
return false;

[未经测试。这是 Java,但我不是 Java 编码专家。原谅我的过错]。

这可以说具有 O(s.length) 的复杂度和一个小的常数因子。

人们可能会考虑构建一个后缀树(也是线性时间),然后检查该树是否具有适当的循环。我怀疑上述算法在实践中相当不错。

【讨论】:

    【解决方案3】:

    由于您不要求特定语言,我建议您查看 Repeating String 的 Rosetta 代码页面。你可以找到并研究一堆解决问题的算法。 尽管 Rosetta Code 中针对 1 和 0 说明了问题,但大多数解决方案都应该适用于任何可能的字符串。

    我写了一个通用的 Common Lisp 递归解决方案,这里是注释代码:

    (ql:quickload :alexandria)
    (defun rep-stringv (a-str &optional (max-rotation (floor (/ (length a-str) 2))))
      ;; Exit condition if no repetition found.
      (cond ((< max-rotation 1) "Not a repeating string")
            ;; Two checks:
            ;; 1. Truncated string must be equal to rotation by repetion size.
            ;; 2. Remaining chars (rest-str) are identical to starting chars (beg-str)
            ((let* ((trunc (* max-rotation (truncate (length a-str) max-rotation)))
                    (truncated-str (subseq a-str 0 trunc))
                    (rest-str (subseq a-str trunc))
                    (beg-str (subseq a-str 0 (rem (length a-str) max-rotation))))
               (and (string= beg-str rest-str)
                    (string= (alexandria:rotate (copy-seq truncated-str) max-rotation)
                             truncated-str)))
             ;; If both checks pass, return the repeting string.
             (subseq a-str 0 max-rotation))
            ;; Recurse function reducing length of rotation.
            (t (rep-stringv a-str (1- max-rotation)))))
    

    测试:

    CL-USER> (rep-stringv "cucumber")
    "Not a repeating string"
    CL-USER> (rep-stringv "abaaabaaabaa")
    "abaa"
    

    最好的解决方案可以使用字符串的suffix tree 来实现,正如您现在可能已经知道的那样 - 因为这是随处描述的常见问题,例如Wikipedia

    除非你真的需要性能,否则实施它对我来说似乎有点过头了。在任何情况下,都可以在here 找到后缀树的示例(多种语言)。

    【讨论】:

      【解决方案4】:

      这里有一些完成这项工作的基本 C++ 代码:

      bool IsRepeating( std::string in ) {
      
          int totalLength = in.length();
          for (int subLength = 1; subLength <= totalLength / 2; subLength++ ) {
              if (totalLength % subLength != 0) continue;
      
              for (int startPos = 0; startPos < subLength; startPos++) {
                  char startChar =in[startPos];
                  bool mismatchFound = false;
                  for (int delta = subLength; delta < totalLength-startPos; delta += subLength) {
                      if (in[startPos+delta] != startChar ) {
                          mismatchFound = true;
                          break;
                      }
                  }
                  if (mismatchFound) {
                      break;
                  }
                  return true;
              }
          }
          return false;
      }
      

      它利用了子字符串长度必须是总字符串长度的除数这一事实。

      最坏情况的时间复杂度非常糟糕,类似于 O(n^2 log(log(n))),但我不确定。 (最坏的情况是字符串由两个完全相同的子字符串组成。)我仍然相信平均而言它应该表现得相当好,因为大多数外部循环体只针对字符串长度的除数执行,而内部循环会尽快中止发现不匹配。

      编辑:@Veedrac 的解决方案不仅更优雅,而且在大多数情况下也更高效。为了直接比较,这里是 C++ 版本:

      bool IsRepeating( const std::string& in ) {
          if (in.length() < 1) return false;
          return (in + in).substr(1, 2 * in.length() - 2).find(in) != std::string::npos;
      }
      

      但是它确实使用了更多的内存。而且,如果您不知道该功能的用途,则可能很难弄清楚。但这也适用于我的原始版本。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-04
        • 2021-10-31
        • 2019-09-13
        • 2019-08-05
        • 2021-12-14
        • 2012-03-13
        相关资源
        最近更新 更多