简体   繁体   English

是否有一种算法(名称,实现)给出了两个相同顺序/内容但不同起始项的列表以测试是否相等?

[英]Is there an algorithm (name, implementation) that given two lists of the same ordering/content but different starting item to test for equality?

Surely there is something out there but googling doesn't give me what I'm looking for. 当然那里有些东西,但是谷歌搜索并没有给我我想要的东西。 Perhaps it's because I don't know the name of the algorithm to lookout for? 也许是因为我不知道要寻找的算法的名称吗?

Basically, I have two lists with the same content and size. 基本上,我有两个具有相同内容和大小的列表。

List1: {10, 30, 2, 4, 4}
List2: {4, 4, 10, 30, 2}

Notice that the sequence is the same for both lists. 请注意,两个列表的顺序相同。 ie: List2 can be seen as a starting from the previous to last position in List1 and continuing to iterate from the start of List1 until back at the starting position. 即:List2可以看作是从List1的前一个位置到最后一个位置的起点,并且从List1的起点开始一直进行迭代,直到返回起点为止。

List1: {10, 30, 2, 4, 4} 10, 30, 2
                   |  |  |   |   |
List2:            {4, 4, 10, 30, 2}

The two lists are then considered equivalent. 然后将这两个列表视为等效。

The following two lists are not: 以下两个列表不是:

List1: {10, 30, 2, 4, 3} 10, 30, 2
                   |  |  X   X   |
List2:            {4, 4, 30, 10, 2}

Update 更新资料

What I am doing right now is having List1 concatenated to itself and searching List2 inside of it. 我现在正在做的是将List1连接到自身并在其中搜索List2。

I feel this is innefficient. 我觉得这是无效的。

Suppose I want to iterate each list once? 假设我要重复每个列表一次?

Update 更新资料

Ok, in the end I went with the algo described at: Check if a string is rotation of another WITHOUT concatenating and adapted it to my data types. 好的,最后我使用了如下所述的算法: 检查一个字符串是否是另一个不带连接的旋转,并使其适应我的数据类型。

Search for List2 inside List1+List1. 在List1 + List1中搜索List2。 You can use KMP to do that in linear time. 您可以使用KMP在线性时间内执行此操作。

UPDATE: optimized the code to be at least as fast as other solutions in the best case and amazingly faster in the worst-case 更新:在最佳情况下将代码优化为至少与其他解决方案一样快,而在最坏情况下则要惊人地快

Some code. 一些代码。 The code uses a modified version of KMP, that receives List1, but actually considers as it was already doubled (for performance). 该代码使用KMP的修改版本,该版本接收List1,但实际上认为它已经加倍了(以提高性能)。

using System;
using System.Collections.Generic;
using System.Linq;

namespace Test
{
    public class CyclicKMP<T> {
        private readonly IList<T> P;
        private readonly int[] F;
        private readonly EqualityComparer<T> comparer;

        public CyclicKMP(IList<T> P) {
            this.comparer = EqualityComparer<T>.Default;
            this.P = P;
            this.F = new int[P.Count+1];

            F[0] = 0;  F[1] = 0;  
            int i = 1, j = 0;
            while(i<P.Count) {
                if (comparer.Equals(P[i], P[j]))
                    F[++i] = ++j;
                else if (j == 0)
                    F[++i] = 0;
                else
                    j = F[j];
            }
        }

        public int FindAt(IList<T> T, int start=0) {
            int i = start, j = 0;
            int n = T.Count, m = P.Count;

            while(i-j <= 2*n-m) {
                while(j < m) {
                    if (comparer.Equals(P[j], T[i%n])) {
                        i++; j++;
                    } else break;
                }
                if (j == m) return i-m;
                else if (j == 0) i++;
                j = F[j];
            }
            return -1;
        }
    }

    class MainClass
    {
        public static bool Check<T>(IList<T> list1, IList<T> list2) {
            if (list1.Count != list2.Count)
                return false;
            return new CyclicKMP<T> (list2).FindAt (list1) != -1;
        }

        public static void Main (string[] args)
        {
            Console.WriteLine (Check(new[]{10, 30, 2, 4, 4}, new[]{4, 4, 10, 30, 2}));
            Console.WriteLine (Check(new[]{10, 30, 2, 4, 3}, new[]{4, 4, 10, 30, 2}));
        }
    }
}

So to start with we need a method to shift a list some number of times, wrapping the items while doing so. 因此,首先我们需要一种方法来移动列表若干次,并同时包装项目。 This is actually fairly straightforward to do: 实际上,这很简单:

public static IEnumerable<T> Shift<T>(this IEnumerable<T> source, int number)
{
    return source.Skip(number)
        .Concat(source.Take(number));
}

Here we're able to iterate over a sequence with a given shift. 在这里,我们可以迭代具有给定移位的序列。

With this all we need to do is see if one of the lists, shifted to any number of times up to its size, is equal to the other. 有了这些,我们要做的就是查看列表中的一个,如果不超过其大小,可以移动任意次数,而另一个列表是否相等。

public static bool EqualWithShifting<T>(this IList<T> first, IList<T> second)
{
    return first.Count == second.Count &&
        Enumerable.Range(0, first.Count-1)
        .Any(shift => first.SequenceEqual(second.Shift(shift)));
}

Note that while this algorithm does have n^2 complexity in the worst case, due to the short circuiting of both SequenceEqual and Any this won't actually do nearly that much work for pretty much any real data. 请注意,尽管该算法在最坏的情况下确实具有n ^ 2的复杂度,但是由于SequenceEqualAny短路,对于几乎所有实际数据而言,这实际上都不会做那么多的工作。

How about: 怎么样:

void Main()
{

bool same = true;

var list1 = new List<int> {10, 30, 2, 4, 4};
var list2 = new List<int> {4, 4, 10, 30, 2};

var start = -1;

for(int i=0; i<list1.Count();i++)
{
 if(list1[0] == list2[i])
 { 
  start = i;
  break;
 }
}

start.Dump("Start");

if(-1 == start)
{ 
 same = false;
}
else
{
int x = 0;
int y = x + start;

int scanned = 0;

while(scanned < list2.Count)
{
 scanned++; 

 if(list1[x] == list2[y])
 {
    x++;
    y++;     if(y >= list2.Count)  {  y = 0; }
 }
 else
 {
   same = false;
   break;
 }
}
}

same.Dump("same");
}

// Define other methods and classes here

Update 更新资料

The more efficient algo (so far) when having a lot of unequal lists is the one from Juan. 当存在许多不平等列表时,效率更高的算法(到目前为止)是Juan提出的。

I updated the answer. 我更新了答案。


Here's a LinqPad code to show the perf comparison between several algorithm. 这是一个LinqPad代码,用于显示几种算法之间的性能比较。

It uses LinqPad extensions from @GitHub : zaus / LinqPad.MyExtensions.cs 它使用来自@GitHub的 LinqPad扩展名:zaus / LinqPad.MyExtensions.cs

void Main()
{
    var first = "abcabcdefabcabcabcabcdefabcabc".ToList();
    var second = "abcdefabcabcabcabcdefabcabcabc".ToList();

    var p = new Perf<bool>
    {
        { "AreEqualByRotation", n => AreEqualByRotation (first, second) },
        { "IsEqualWithShifting", n => IsEqualWithShifting (first, second) },
        { "EqualWithShifting", n => EqualWithShifting (first, second) },
        { "CheckIt", n => CheckIt (first, second) },
    }.Vs("Shifting", 100000);
}

// Define other methods and classes here
bool AreEqualByRotation<T> (List<T> s1, List<T> s2)
{
    if (s1.Count != s2.Count)
        return false;

    for (int i=0; i<s1.Count; ++i)
    {
        bool res = true;
        int index = i;
        for (int j=0; j<s1.Count; ++j)
        {
            if (!s1[j].Equals(s2[index]))
            {
                res = false;
                break;
            }
            index = (index+1)%s1.Count;
        }

        if (res == true)
            return true;
    }
    return false;
}

bool IsEqualWithShifting<T> (List<T> list1, List<T> list2)
{
    if (list1.Count != list2.Count) 
        return false;

    for (var i = 0; i < list1.Count; i++)
    {
        for (var j = 0; j < list2.Count; j++)
        {
            int countFound = 0;

            while (list1[(i + countFound) % list1.Count].Equals(list2[(j + countFound) % list2.Count]) && countFound < list1.Count)
                countFound++;

            if (countFound == list1.Count)
                return true;
        }
    }

    return false;
}

public static IEnumerable<T> Shift<T>(IEnumerable<T> source, int number)
{
    return source.Skip(number)
        .Concat(source.Take(number));
}

public static bool EqualWithShifting<T>(IList<T> first, IList<T> second)
{
    return first.Count == second.Count &&
        Enumerable.Range(0, first.Count-1)
        .Any(shift => first.SequenceEqual(Shift(second, shift)));
}

public class KMP<T> {
   private readonly IList<T> P;
   private readonly int[] F;

   public KMP(IList<T> P) {
       this.P = P;
       this.F = new int[P.Count+1];

       F[0] = 0;  F[1] = 0;  
       int i = 1, j = 0;
       while(i<P.Count) {
           if (Object.Equals(P[i], P[j]))
               F[++i] = ++j;
           else if (j == 0)
               F[++i] = 0;
           else
               j = F[j];
       }
   }

   public int FindAt(IList<T> T, int start=0) {
       int i = start, j = 0;
       int n = T.Count, m = P.Count;

       while(i-j <= n-m) {
           while(j < m) {
               if (Object.Equals(P[j], T[i])) {
                   i++; j++;
               } else break;
           }
           if (j == m) return i-m;
           else if (j == 0) i++;
           j = F[j];
       }
       return -1;
   }
}

public static bool CheckIt<T> (IList<T> list1, IList<T> list2)
{
    return Check (list1, list2) != -1;
}

public static int Check<T>(IList<T> list1, IList<T> list2)
{
    if (list1.Count != list2.Count)
        return -1;
    return new KMP<T> (list2).FindAt (new List<T>(Enumerable.Concat(list1, list1)));
}

Result is : 结果是:

|      Algorithm      |              Time spent             | Result |  
|:-------------------:|:-----------------------------------:|--------|  
| AreEqualByRotation  | 1262852 ticks elapsed (126.2852 ms) | True   |  
| IsEqualWithShifting | 1508527 ticks elapsed (150.8527 ms) | True   |  
| EqualWithShifting   | 4691774 ticks elapsed (469.1774 ms) | True   |  
| CheckIt             | 4079400 ticks elapsed (407.94 ms)   | True   |  

winner: AreEqualByRotation 赢家:AreEqualByRotation

Update 更新资料

Juan Lopes remarked that his solution is faster when the strings are not equal. Juan Lopes表示,当字符串不相等时,他的解决方案会更快。

Here's with this input and the results for 1000 iterations: 以下是此输入以及1000次迭代的结果:

var first = new string('a', 200).ToList();
var second = (new string('a', 199)+'b').ToList();

|      Algorithm      |              Time spent                | Result |  
|:-------------------:|:--------------------------------------:|--------|  
| AreEqualByRotation  | 187613414 ticks elapsed (18761.3414 ms)| False  |  
| IsEqualWithShifting | Too long !!!                           | False  |  
| EqualWithShifting   | 766858561 ticks elapsed (76685.8561 ms)| False  |  
| CheckIt             | 28222332 ticks elapsed (2822.2332 ms)  | False  |  

winner: CheckIt 赢家:CheckIt

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM