【问题标题】:How should I find repeated word sequences我应该如何找到重复的单词序列
【发布时间】:2010-12-10 22:12:56
【问题描述】:

我需要检测是否存在多个柱状数据块,仅给出它们的标题。除了标题词之外,对数据一无所知,每组数据的标题词都是不同的。

重要的是,事先不知道每个块中有多少单词,因此也不知道有多少块。

同样重要的是,单词列表总是相对较短——不到 20 个。

因此,给定一个标题词列表或数组,例如:

Opt
Object
Type
Opt
Object
Type
Opt
Object
Type

确定它完全由重复序列组成的最有效处理方法是什么:

Opt
Object
Type

它必须是完全匹配,所以我的第一个想法是搜索[1+]寻找与[0]的匹配,称它们为索引n,m,...然后如果它们是等距检查[1] == [n+1] == [m+1] 和 [2] == [n+2] == [m+2] 等等。

编辑:它必须适用于其中一些单词本身在一个块中重复的单词集,所以

Opt
Opt
Object
Opt
Opt
Object

是一组 2

Opt
Opt
Object

【问题讨论】:

  • 您知道要寻找的重复序列是什么吗?或者您想知道给定字符串中是否存在重复序列?

标签: java algorithm repeat


【解决方案1】:

如果列表由 x 个重复组组成,每个组包含 n 个元素...

我们知道至少有 1 个组,因此我们将查看是否有 2 个重复组,通过比较列表的前半部分和后半部分进行测试。

1) 如果以上为真,我们知道解是 2 的因数

2) 如果上述情况为假,我们将移动到下一个可被单词总数整除的最大素数...

在每个步骤中,我们检查列表之间的相等性,如果我们找到它,那么我们知道我们有一个包含该因素的解决方案。

我们想要返回一个单词列表,其中我们在子列表中找到相等的第一个素数的最大因子。

所以我们知道所有子列表都是相等的,所以我们在子列表上应用上述公式......因此,最好递归求解。那就是我们只需要孤立地考虑当前的子列表。


如果加载一个简短的素数表,该解决方案将非常有效......在此之后,将有必要计算它们,但如果即使是只有几十个素数的列表,该列表也必须是不平凡的考虑到。

【讨论】:

  • 啊,我明白了。这与我的迭代前向方法不同——我将不得不考虑它(并写下来看看它是如何比较的)。似乎它可能非常好。递归解决方案的一个很好的例子。
  • 每个素数都是列表划分的数量,所以它从 1 开始(因为如果没有重复组,那么只有整个列表,一组)显然不需要测试,所以我们直接跳到 2,如果两个列表不相等,我们尝试 3... ,这就是为什么我们只需要关心素数...如果说列表可以被 3 整除...然后我们递归应用相同的逻辑来找出该列表中有多少倍数(如果有的话)。跨度>
  • 你可能应该创建一个 PrimeFinder 类,它为你返回素数,说 primeFinder.nextAfter(int currentPrime) 将返回给定素数之后的下一个素数......有很多算法可以找到素数,但你要求高效,因此应在 PrimeFinder 中预加载尽可能多的素数:primes.utm.edu/lists/small/10000.txt
  • @Quaternion - 这是有道理的,现在你把它拼出来了。
  • 接受这一点,因为在没有编码的情况下,它似乎是概念上最好的解决方案。
【解决方案2】:

单元序列可以包含它自己的重复吗?你知道单元序列的长度吗?

例如

ABCABCABCDEFABCABCABCDEFABCABCABCDEF

其中单元序列为ABCABCABCDEF

如果答案是肯定的,我认为你遇到了一个难题,除非你知道单元序列的长度(在这种情况下,解决方案是微不足道的,你只需制作一个首先存储单元序列的状态机,然后验证序列的每个元素其余部分对应于单元序列的每个元素)。

如果答案是否定的,则使用此变体Floyd's cycle-finding algorithm 来识别单元序列:

  • 将指针 P1 和 P2 初始化为序列的开头。
  • 对于每个新元素,每次递增指针 P1,并每隔一段时间递增指针 P2(保留一个计数器来执行此操作)。
  • 如果 P1 指向 P2 的相同元素,则您找到了一个单元序列。

  • 现在重复序列的其余部分以验证它是否包含重复项。


更新:您已经澄清了您的问题,说明单元序列可能包含其自身的重复。在这种情况下,使用循环查找算法,但它只能保证找到潜在的循环。让它在整个序列中运行,并使用以下状态机,从状态 1 开始:

状态 1:没有找到有效的循环;继续寻找。当循环查找算法找到一个潜在循环时,验证您是否从 P 中获得了 2 个初步单元序列副本,然后进入状态 2。如果到达输入的末尾,则进入状态 4。

状态 2:找到初步的单元序列。只要循环重复相同,就遍历输入。如果到达输入的末尾,则进入状态3。如果找到与单元序列的对应元素不同的输入元素,则返回状态1。

状态 3:如果输入的结尾包含单元序列的完整重复,则输入是单元序列的重复。 (如果它在一个单元序列的中间,例如ABCABCABCABCAB,则找到一个单元序列,但它不包含完整的重复。)

状态 4:未找到单元序列。

在我的示例中(重复 ABCABCABCDEF),该算法首先找到 ABCABC,这会将其置于状态 2,然后它会一直停留在那里,直到遇到第一个 DEF,这会将其放回状态 1,然后可能在状态 1 和状态 2 之间来回跳转,直到到达第 2 个 ABCABCABCDEF,此时它会重新进入状态 2,输入结束时会进入状态 3。

【讨论】:

    【解决方案3】:

    比我的另一个更好的答案:一个有效的 Java 实现,应该易于理解并且是通用的:

    package com.example.algorithms;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Iterator;
    import java.util.List;
    
    interface Processor<T> {
        public void process(T element);
    }
    
    public class RepeatingListFinder<T> implements Processor<T> {
    
        private List<T> unit_sequence = new ArrayList<T>();
        private int repeat_count = 0;
        private int partial_matches = 0;
        private Iterator<T> iterator = null;
    
        /* Class invariant:
         * 
         * The sequence of elements passed through process()
         * can be expressed as the concatenation of
         *        the unit_sequence repeated "repeat_count" times,
         *   plus the first "element_matches" of the unit_sequence.
         * 
         * The iterator points to the remaining elements of the unit_sequence,
         * or null if there have not been any elements processed yet.
         */
    
        public void process(T element) {
            if (unit_sequence.isEmpty() || !iterator.next().equals(element))
            {
                revise_unit_sequence(element);
                iterator = unit_sequence.iterator();
                repeat_count = 1;
                partial_matches = 0;
            }
            else if (!iterator.hasNext())
            {
                iterator = unit_sequence.iterator();
                ++repeat_count;
                partial_matches = 0;
            }
            else
            {
                ++partial_matches;
            }
        }
    
        /* Unit sequence has changed. 
         * Restructure and add the new non-matching element. 
         */
        private void revise_unit_sequence(T element) {
            if (repeat_count > 1 || partial_matches > 0)
            {
                List<T> new_sequence = new ArrayList<T>();
                for (int i = 0; i < repeat_count; ++i)
                    new_sequence.addAll(unit_sequence);
                new_sequence.addAll(
                        unit_sequence.subList(0, partial_matches));
    
                unit_sequence = new_sequence;
            }
            unit_sequence.add(element);
        }
    
        public List<T> getUnitSequence() { 
            return Collections.unmodifiableList(unit_sequence);
        }
        public int getRepeatCount() { return repeat_count; }
        public int getPartialMatchCount() { return partial_matches; }
        public String toString()
        {
            return "("+getRepeatCount()
            +(getPartialMatchCount() > 0 
                ? (" "+getPartialMatchCount()
                    +"/"+unit_sequence.size())
                : "")
            +") x "+unit_sequence;
        }
    
        /********** static methods below for testing **********/
    
        static public List<Character> stringToCharList(String s)
        {
            List<Character> result = new ArrayList<Character>();
            for (char c : s.toCharArray())
                result.add(c);
            return result;
        }
    
        static public <T> void test(List<T> list)
        {
            RepeatingListFinder<T> listFinder 
                = new RepeatingListFinder<T>();
            for (T element : list)
                listFinder.process(element);
            System.out.println(listFinder);
        }
    
        static public void test(String testCase)
        {
            test(stringToCharList(testCase));
        }
    
        static public void main(String[] args)
        {
            test("ABCABCABCABC");
            test("ABCDFTBAT");
            test("ABABA");
            test("ABACABADABACABAEABACABADABACABAEABACABADABAC");
            test("ABCABCABCDEFABCABCABCDEFABCABCABCDEF");
            test("ABABCABABCABABDABABDABABC");      
        }
    }
    

    这是一种面向流的方法(执行时间为 O(N),最坏情况下的空间要求为 O(N));如果要处理的List&lt;T&gt; 已经存在于内存中,则应该可以重写此类以处理List&lt;T&gt;,而无需任何额外的空间要求,只需跟踪重复计数和部分匹配计数,使用 List.subList( ) 创建一个单元序列,它是输入列表的前 K 个元素的视图。

    【讨论】:

      【解决方案4】:

      我的解决方案可以按预期工作,但可能很幼稚。它确实具有简单的优点。

      String[]                            wta;                                    // word text array
      ...
      INTERVAL:
      for(int xa=1,max=(wta.length/2); xa<=max; xa++) {
          if((wta.length%xa)!=0) { continue; }                                    // ignore intervals which don't divide evenly into the words
          for(int xb=0; xb<xa; xb++) {                                            // iterate the words within the current interval
              for(int xc=xb+xa; xc<wta.length; xc+=xa) {                          // iterate the corresponding words in each section
                  if(!wta[xb].equalsIgnoreCase(wta[xc])) { continue INTERVAL; }   // not a cycle
                  }
              }
          ivl=xa;
          break;
          }
      

      【讨论】:

      • @Quaternion:你是什么意思?它应该找到第一个也是最短的重复出现。
      猜你喜欢
      • 2020-04-26
      • 2015-07-05
      • 2021-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多