如何检查两个列表在Python中是否循环相同

时间:2014-11-14 07:16:20

标签: python algorithm

例如,我有列表:

a[0] = [1, 1, 1, 0, 0]
a[1] = [1, 1, 0, 0, 1]
a[2] = [0, 1, 1, 1, 0]
# and so on

它们似乎有所不同,但如果假设开始和结束是连接的,则它们循环相同。

问题是,我拥有的每个列表的长度为55,并且只包含三个和52个零。没有循环条件,有26,235(55选3)列表。但是,如果条件“循环”存在,则存在大量循环相同的列表

目前,我通过以下方式检查循环身份:

def is_dup(a, b):
    for i in range(len(a)):
        if a == list(numpy.roll(b, i)): # shift b circularly by i
            return True
    return False

此功能在最坏的情况下需要55次循环移位操作。并且有26,235个列表可以相互比较。简而言之,我需要55 * 26,235 *(26,235 - 1)/ 2 = 18,926,847,225次计算。它差不多有20吉加!

有没有什么好方法可以用更少的计算来做到这一点?或者支持循环的任何数据类型?

18 个答案:

答案 0 :(得分:131)

首先,根据列表的长度,这可以在O(n)中完成 您可以注意到,如果您将复制列表2次([1, 2, 3])将为[1, 2, 3, 1, 2, 3],那么您的新列表肯定会包含所有可能的循环列表。

所以你需要的是检查你正在搜索的列表是否在你的起始列表的2倍之内。在python中,您可以通过以下方式实现此目的(假设长度相同)。

list1 = [1, 1, 1, 0, 0]
list2 = [1, 1, 0, 0, 1]
print ' '.join(map(str, list2)) in ' '.join(map(str, list1 * 2))

关于我的oneliner的一些解释: list * 2会将列表与自身合并,map(str, [1, 2])将所有数字转换为字符串,' '.join()将数组['1', '2', '111']转换为字符串'1 2 111'

正如一些人在评论中指出的那样,oneliner可能会产生一些误报,所以要涵盖所有可能的边缘情况:

def isCircular(arr1, arr2):
    if len(arr1) != len(arr2):
        return False

    str1 = ' '.join(map(str, arr1))
    str2 = ' '.join(map(str, arr2))
    if len(str1) != len(str2):
        return False

    return str1 in str2 + ' ' + str2

P.S.1 在谈到时间复杂度时,值得注意的是,如果在O(n)时间内找到子字符串,将会实现O(n)。它并非总是如此,取决于您的语言的实现(例如although potentially it can be done in linear时间KMP)。

P.S.2 对于害怕字符串操作的人而言,由于这个事实认为答案不好。复杂性和速度至关重要。此算法可能会在O(n)时间和O(n)空间中运行,这使得它比O(n^2)域中的任何内容都要好得多。要自己看这个,你可以运行一个小的基准测试(创建一个随机列表弹出第一个元素并将其追加到最后,从而创建一个循环列表。你可以自由地进行自己的操作)

from random import random
bigList = [int(1000 * random()) for i in xrange(10**6)]
bigList2 = bigList[:]
bigList2.append(bigList2.pop(0))

# then test how much time will it take to come up with an answer
from datetime import datetime
startTime = datetime.now()
print isCircular(bigList, bigList2)
print datetime.now() - startTime    # please fill free to use timeit, but it will give similar results
在我的机器上

0.3秒。不是很久。现在尝试将其与O(n^2)解决方案进行比较。在进行比较时,您可以从美国前往澳大利亚(最有可能乘坐游轮)

答案 1 :(得分:38)

在Python中没有足够的知识来满足您所要求的语言,但在C / C ++中,根据您的问题的参数,我将零和1转换为位并将它们推到最低位一个uint64_t。这将允许您一举比较所有55位 - 1个时钟。

邪恶的速度,整个事情将适合片上缓存(209,880字节)。用于同时移动所有55个列表成员的硬件支持仅在CPU的寄存器中可用。同样比较所有55个成员也是如此。这允许将问题映射到软件解决方案。 (并使用SIMD / SSE 256位寄存器,如果需要,最多256个成员)因此,代码对读者来说是显而易见的。

你可能能够在Python中实现这一点,我只是不太了解它是否可能或性能可能是什么。

在睡觉之后,一些事情变得明显,并且一切都变得更好。

1。)使用Dali旋转圆形链表非常容易,Dali非常聪明的技巧是不必要的。在64位寄存器内部,标准位移动将非常简单地完成旋转,并试图通过使用算术而不是位操作使这更加友好。

2.)使用除以2可以轻松完成位移。

3.)通过模2可以轻松地检查列表的结尾是否为0或1。

4。)"移动"从尾部开始的列表的头部可以通过除以2来完成。这是因为如果实际移动了零,那么它将使第55位为假,这已经完全没有了。

5。)"移动"从尾部开始的列表1的头部可以通过除以2并添加18,014,398,509,481,984来完成 - 这是通过将第55位标记为真而其余全部为假而创建的值。

6。)如果在任何给定的旋转之后锚和组合uint64_t的比较为TRUE,则断开并返回TRUE。

我会将整个列表数组转换为前面的uint64_ts数组,以避免重复进行转换。

在花了几个小时试图优化代码之后,研究汇编语言我能够节省20%的运行时间。我应该补充一点,O / S和MSVC编译器也在昨天中午更新了。无论出于何种原因,C编译器生成的代码质量在更新后(2014年11月15日)都得到了显着改善。运行时间现在〜 70个时钟,17纳秒来组成和比较一个测试环的所有55圈的锚环和所有其他环的NxN在 12.5秒< /强>

这段代码太紧了,但是有4个寄存器在99%的时间里无所事事。汇编语言几乎与行代码匹配。非常容易阅读和理解。一个伟大的装配项目,如果有人在教自己。

硬件是Hazwell i7,MSVC 64位,完全优化。

#include "stdafx.h"
#include "stdafx.h"
#include <string>
#include <memory>
#include <stdio.h>
#include <time.h>

const uint8_t  LIST_LENGTH = 55;    // uint_8 supports full witdth of SIMD and AVX2
// max left shifts is 32, so must use right shifts to create head_bit
const uint64_t head_bit = (0x8000000000000000 >> (64 - LIST_LENGTH)); 
const uint64_t CPU_FREQ = 3840000000;   // turbo-mode clock freq of my i7 chip

const uint64_t LOOP_KNT = 688275225; // 26235^2 // 1000000000;

// ----------------------------------------------------------------------------
__inline uint8_t is_circular_identical(const uint64_t anchor_ring, uint64_t test_ring)
{
    // By trial and error, try to synch 2 circular lists by holding one constant
    //   and turning the other 0 to LIST_LENGTH positions. Return compare count.

    // Return the number of tries which aligned the circularly identical rings, 
    //  where any non-zero value is treated as a bool TRUE. Return a zero/FALSE,
    //  if all tries failed to find a sequence match. 
    // If anchor_ring and test_ring are equal to start with, return one.

    for (uint8_t i = LIST_LENGTH; i;  i--)
    {
        // This function could be made bool, returning TRUE or FALSE, but
        // as a debugging tool, knowing the try_knt that got a match is nice.
        if (anchor_ring == test_ring) {  // test all 55 list members simultaneously
            return (LIST_LENGTH +1) - i;
        }

        if (test_ring % 2) {    //  ring's tail is 1 ?
            test_ring /= 2;     //  right-shift 1 bit
            // if the ring tail was 1, set head to 1 to simulate wrapping
            test_ring += head_bit;      
        }   else    {           // ring's tail must be 0
            test_ring /= 2;     // right-shift 1 bit
            // if the ring tail was 0, doing nothing leaves head a 0
        }
    }
    // if we got here, they can't be circularly identical
    return 0;
}
// ----------------------------------------------------------------------------
    int main(void)  {
        time_t start = clock();
        uint64_t anchor, test_ring, i,  milliseconds;
        uint8_t try_knt;

        anchor = 31525197391593472; // bits 55,54,53 set true, all others false
        // Anchor right-shifted LIST_LENGTH/2 represents the average search turns
        test_ring = anchor >> (1 + (LIST_LENGTH / 2)); //  117440512; 

        printf("\n\nRunning benchmarks for %llu loops.", LOOP_KNT);
        start = clock();
        for (i = LOOP_KNT; i; i--)  {
            try_knt = is_circular_identical(anchor, test_ring);
            // The shifting of test_ring below is a test fixture to prevent the 
            //  optimizer from optimizing the loop away and returning instantly
            if (i % 2) {
                test_ring /= 2;
            }   else  {
                test_ring *= 2;
            }
        }
        milliseconds = (uint64_t)(clock() - start);
        printf("\nET for is_circular_identical was %f milliseconds."
                "\n\tLast try_knt was %u for test_ring list %llu", 
                        (double)milliseconds, try_knt, test_ring);

        printf("\nConsuming %7.1f clocks per list.\n",
                (double)((milliseconds * (CPU_FREQ / 1000)) / (uint64_t)LOOP_KNT));

        getchar();
        return 0;
}

enter image description here

答案 2 :(得分:32)

在这些行之间进行读取,听起来好像是在尝试枚举每个循环等价类的一个代表,其中包含3个1和52个零。让我们从密集表示切换到稀疏表示(range(55)中的三个数字的集合)。在此表示中,sk的循环移位由理解set((i + k) % 55 for i in s)给出。类中的词典最小代表总是包含位置0.给定{0, i, j}形式的0 < i < j形式的集合,其中最小的候选者是{0, j - i, 55 - i}和{{1} }。因此,我们需要{0, 55 - j, 55 + i - j}来使原作最小。这是一些枚举代码。

(i, j) <= min((j - i, 55 - i), (55 - j, 55 + i - j))

答案 3 :(得分:12)

重复第一个数组,然后使用Z algorithm(O(n)时间)查找第一个数组中的第二个数组。

(注意:你不必在物理上复制第一个数组。你可以在匹配期间环绕。)

与KMP,BM等相比,Z算法的优点是它的非常简单<
em> 但是,如果您感觉雄心勃勃,可以在线性时间和常量空间进行字符串匹配 - strstr,例如,这样做。然而,实施它会更痛苦。

答案 4 :(得分:6)

跟进萨尔瓦多·达利非常聪明的解决方案,处理它的最佳方法是确保所有元素的长度相同,并且两个LISTS的长度相同。

def is_circular_equal(lst1, lst2):
    if len(lst1) != len(lst2):
        return False
    lst1, lst2 = map(str, lst1), map(str, lst2)
    len_longest_element = max(map(len, lst1))
    template = "{{:{}}}".format(len_longest_element)
    circ_lst = " ".join([template.format(el) for el in lst1]) * 2
    return " ".join([template.format(el) for el in lst2]) in circ_lst

不知道这是否比AshwiniChaudhary在Salvador Dali的回答中推荐的正则表达式解决方案更快或更慢,其中包括:

import re

def is_circular_equal(lst1, lst2):
    if len(lst2) != len(lst2):
        return False
    return bool(re.search(r"\b{}\b".format(' '.join(map(str, lst2))),
                          ' '.join(map(str, lst1)) * 2))

答案 5 :(得分:3)

鉴于您需要进行许多比较,您可能值得在初始通过列表时将它们转换为某种可以轻松比较的规范形式吗?

您是否尝试获取一组循环唯一列表?如果是这样,你可以在转换为元组后将它们放入一个集合中。

def normalise(lst):
    # Pick the 'maximum' out of all cyclic options
    return max([lst[i:]+lst[:i] for i in range(len(lst))])

a_normalised = map(normalise,a)
a_tuples = map(tuple,a_normalised)
a_unique = set(a_tuples)

向David Eisenstat道歉,因为他没有发现他的v.similar答案。

答案 6 :(得分:3)

您可以像这样滚动一个列表:

list1, list2 = [0,1,1,1,0,0,1,0], [1,0,0,1,0,0,1,1]

str_list1="".join(map(str,list1))
str_list2="".join(map(str,list2))

def rotate(string_to_rotate, result=[]):
    result.append(string_to_rotate)
    for i in xrange(1,len(string_to_rotate)):
        result.append(result[-1][1:]+result[-1][0])
    return result

for x in rotate(str_list1):
    if cmp(x,str_list2)==0:
        print "lists are rotationally identical"
        break

答案 7 :(得分:3)

首先将每个列表元素(如果需要,在副本中)转换为 旋转版本,该版本在词法上最大。

然后对结果列表进行排序(将索引保留在原始列表位置)并统一排序列表,根据需要标记原始列表中的所有重复项。

答案 8 :(得分:2)

在@ SalvadorDali关于在b + b中查找任何一个加长大小的切片中a的匹配的观察时,抄袭这里是一个仅使用列表操作的解决方案。

def rollmatch(a,b):
    bb=b*2
    return any(not any(ax^bbx for ax,bbx in zip(a,bb[i:])) for i in range(len(a)))

l1 = [1,0,0,1]
l2 = [1,1,0,0]
l3 = [1,0,1,0]

rollmatch(l1,l2)  # True
rollmatch(l1,l3)  # False

第二种方法:[删除]

答案 9 :(得分:1)

不是一个完整的,独立的答案,但关于通过减少比较进行优化的主题,我也在考虑规范化的表示。

即,如果输入字母为{0,1},则可以显着减少允许的排列数。将第一个列表旋转为(伪)规范化形式(给定问题中的分布,我会选择一个位于最左侧的1位中的一个,并且0位中的一个位于最右侧)。现在,在每次比较之前,通过具有相同对齐模式的可能位置连续旋转另一个列表。

例如,如果总共有4个1位,则此对齐最多可以有4个排列,如果有相邻1位的簇,则此类簇中的每个附加位都会减少位置数。

List 1   1 1 1 0 1 0

List 2   1 0 1 1 1 0  1st permutation
         1 1 1 0 1 0  2nd permutation, final permutation, match, done

这推广到更大的字母和不同的对齐模式;主要的挑战是找到一个很好的规范化,只有几个可能的表示。理想情况下,这将是一个适当的规范化,只有一个独特的表示,但考虑到问题,我不认为这是可能的。

答案 10 :(得分:0)

进一步构建RocketRoy的答案: 将前面的所有列表转换为无符号的64位数字。 对于每个列表,旋转这些55位以找到最小的数值。

现在,每个列表都有一个无符号的64位值,您可以直接与其他列表的值进行比较。函数is_circular_identical()不再需要了。

(实际上,您为列表创建了一个不受列表元素轮换影响的标识值) 如果您的列表中有一个任意数字,那甚至可以工作。

答案 11 :(得分:0)

这与Salvador Dali的想法相同,但不需要字符串转换。背后是同样的KMP恢复想法,以避免不可能的轮班检查。他们只调用KMPModified(list1,list2 + list2)。

    public class KmpModified
    {
        public int[] CalculatePhi(int[] pattern)
        {
            var phi = new int[pattern.Length + 1];
            phi[0] = -1;
            phi[1] = 0;

            int pos = 1, cnd = 0;
            while (pos < pattern.Length)
                if (pattern[pos] == pattern[cnd])
                {
                    cnd++;
                    phi[pos + 1] = cnd;
                    pos++;
                }
                else if (cnd > 0)
                    cnd = phi[cnd];
                else
                {
                    phi[pos + 1] = 0;
                    pos++;
                }

            return phi;
        }

        public IEnumerable<int> Search(int[] pattern, int[] list)
        {
            var phi = CalculatePhi(pattern);

            int m = 0, i = 0;
            while (m < list.Length)
                if (pattern[i] == list[m])
                {
                    i++;
                    if (i == pattern.Length)
                    {
                        yield return m - i + 1;
                        i = phi[i];
                    }
                    m++;
                }
                else if (i > 0)
                {
                    i = phi[i];
                }
                else
                {
                    i = 0;
                    m++;
                }
        }

        [Fact]
        public void BasicTest()
        {
            var pattern = new[] { 1, 1, 10 };
            var list = new[] {2, 4, 1, 1, 1, 10, 1, 5, 1, 1, 10, 9};
            var matches = Search(pattern, list).ToList();

            Assert.Equal(new[] {3, 8}, matches);
        }

        [Fact]
        public void SolveProblem()
        {
            var random = new Random();
            var list = new int[10];
            for (var k = 0; k < list.Length; k++)
                list[k]= random.Next();

            var rotation = new int[list.Length];
            for (var k = 1; k < list.Length; k++)
                rotation[k - 1] = list[k];
            rotation[rotation.Length - 1] = list[0];

            Assert.True(Search(list, rotation.Concat(rotation).ToArray()).Any());
        }
    }

希望这有帮助!

答案 12 :(得分:0)

简化问题

  • 问题包括有序商品列表
  • 值域为二进制(0,1)
  • 我们可以通过将连续的1映射到计数
  • 来减少问题
  • 且连续0为负数

示例

A = [ 1, 1, 1, 0, 0, 1, 1, 0 ]
B = [ 1, 1, 0, 1, 1, 1, 0, 0 ]
~
A = [ +3, -2, +2, -1 ]
B = [ +2, -1, +3, -2 ]
  • 此过程要求第一项和最后一项必须不同
  • 这将减少整体比较的数量

检查流程

  • 如果我们假设它们是重复的,那么我们可以假设我们正在寻找什么
  • 基本上,第一个列表中的第一个项目必须存在于另一个列表中的某个位置
  • 接下来是第一个列表中的内容,并以相同的方式
  • 之前的项目应该是第一个列表中的最后一项
  • 由于它是循环的,顺序是相同的

The Grip

  • 这里的问题是从哪里开始,技术上称为lookuplook-ahead
  • 我们将通过第二个列表
  • 检查第一个列表的第一个元素的位置
  • 鉴于我们将列表映射到直方图
  • ,频繁元素的概率较低

<强>伪代码

FUNCTION IS_DUPLICATE (LIST L1, LIST L2) : BOOLEAN

    LIST A = MAP_LIST(L1)
    LIST B = MAP_LIST(L2)

    LIST ALPHA = LOOKUP_INDEX(B, A[0])

    IF A.SIZE != B.SIZE
       OR COUNT_CHAR(A, 0) != COUNT_CHAR(B, ALPHA[0]) THEN

        RETURN FALSE

    END IF

    FOR EACH INDEX IN ALPHA

        IF ALPHA_NGRAM(A, B, INDEX, 1) THEN

            IF IS_DUPLICATE(A, B, INDEX) THEN

                RETURN TRUE

            END IF

        END IF

    END FOR

    RETURN FALSE

END FUNCTION

FUNCTION IS_DUPLICATE (LIST L1, LIST L2, INTEGER INDEX) : BOOLEAN

    INTEGER I = 0

    WHILE I < L1.SIZE DO

        IF L1[I] != L2[(INDEX+I)%L2.SIZE] THEN

            RETURN FALSE

        END IF

        I = I + 1

    END WHILE

    RETURN TRUE

END FUNCTION

<强>功能

  • MAP_LIST(LIST A):LIST MAP保守元素作为新名单中的数据

  • LOOKUP_INDEX(LIST A, INTEGER E):LIST返回列表E中列出的元素A

  • COUNT_CHAR(LIST A , INTEGER E):INTEGER在一份清单E

  • 中列出了A元素多次出现的情况
  • ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEAN检查B[I]是否等同于A[0] N-GRAM两种方向


<强>最后

如果列表大小非常大或者我们开始检查循环的元素通常很高,那么我们可以执行以下操作:

  • 在第一个列表中查找以

  • 开头的最不频繁的项目
  • 增加n-gram N参数以降低通过线性检查的概率

答案 13 :(得分:0)

高效,快速计算&#34;规范形式&#34;对于有问题的列表可以推导为:

  • 计算两者之间的零数(忽略环绕),得到三个数字。
  • 旋转这三个数字,使最大数字为第一个。
  • 第一个号码(a)必须介于1852之间(含)。在034之间重新编码。
  • 第二个号码(b)必须介于026之间,但这并不重要。
  • 删除第三个号码,因为它只是52 - (a + b)并且不添加任何信息

规范形式是整数b * 35 + a,介于0936之间(包括),相当紧凑(有477个循环唯一列表总)。

答案 14 :(得分:0)

我写了一个直截了当的解决方案,它比较了两个列表,只是增加(并包装)每次迭代的比较值的索引。

我不太了解python,所以我用Java编写它,但它非常简单,因此应该很容易使它适应任何其他语言。

通过这个你也可以比较其他类型的列表。

public class Main {

    public static void main(String[] args){
        int[] a = {0,1,1,1,0};
        int[] b = {1,1,0,0,1};

        System.out.println(isCircularIdentical(a, b));
    }

    public static boolean isCircularIdentical(int[] a, int[]b){
        if(a.length != b.length){
            return false;
        }

        //The outer loop is for the increase of the index of the second list
        outer:
        for(int i = 0; i < a.length; i++){
            //Loop trough the list and compare each value to the according value of the second list
            for(int k = 0; k < a.length; k++){
                // I use modulo length to wrap around the index
                if(a[k] != b[(k + i) % a.length]){
                    //If the values do not match I continue and shift the index one further
                    continue outer;
                }
            }
            return true;
        }
        return false;
    }
}

答案 15 :(得分:0)

正如其他人所提到的,一旦找到列表的标准化轮换,就可以比较它们。

这是一些工作代码, 基本方法是为每个列表找到一个标准化的旋转并进行比较:

  • 计算每个列表上的标准化旋转索引。
  • 使用偏移量循环两个列表,比较每个项目,如果它们不匹配则返回。

请注意,此方法不依赖于数字,您可以传入字符串列表(可以比较的任何值)。

我们不是在列表中进行列表搜索,而是希望列表以最小值开始 - 所以我们可以遍历最小值,搜索直到找到哪个具有最低的连续值,然后存储进一步比较,直到我们有最好的。

在计算索引时有很多机会提前退出,有些优化的细节。

  • 当只有一个时,跳过搜索最佳最小值。
  • 当前一个值也是最小值时,跳过搜索最小值(它永远不会是一个更好的匹配)。
  • 当所有值相同时跳过搜索。
  • 列表具有不同的最小值时提前失败。
  • 当偏移匹配时使用常规比较。
  • 调整偏移量以避免在比较期间将索引值包装在其中一个列表上。

请注意,在Python中,列表中的列表搜索可能会更快,但我有兴趣找到一种有效的算法 - 也可以在其他语言中使用。此外,避免创建新列表也有一些优势。

def normalize_rotation_index(ls, v_min_other=None):
    """ Return the index or -1 (when the minimum is above `v_min_other`) """

    if len(ls) <= 1:
        return 0

    def compare_rotations(i_a, i_b):
        """ Return True when i_a is smaller.
            Note: unless there are large duplicate sections of identical values,
            this loop will exit early on.
        """
        for offset in range(1, len(ls)):
            v_a = ls[(i_a + offset) % len(ls)]
            v_b = ls[(i_b + offset) % len(ls)]
            if v_a < v_b:
                return True
            elif v_a > v_b:
                return False
        return False

    v_min = ls[0]
    i_best_first = 0
    i_best_last = 0
    i_best_total = 1
    for i in range(1, len(ls)):
        v = ls[i]
        if v_min > v:
            v_min = v
            i_best_first = i
            i_best_last = i
            i_best_total = 1
        elif v_min == v:
            i_best_last = i
            i_best_total += 1

    # all values match
    if i_best_total == len(ls):
        return 0

    # exit early if we're not matching another lists minimum
    if v_min_other is not None:
        if v_min != v_min_other:
            return -1
    # simple case, only one minimum
    if i_best_first == i_best_last:
        return i_best_first

    # otherwise find the minimum with the lowest values compared to all others.
    # start looking after the first we've found
    i_best = i_best_first
    for i in range(i_best_first + 1, i_best_last + 1):
        if (ls[i] == v_min) and (ls[i - 1] != v_min):
            if compare_rotations(i, i_best):
                i_best = i

    return i_best


def compare_circular_lists(ls_a, ls_b):
    # sanity checks
    if len(ls_a) != len(ls_b):
        return False
    if len(ls_a) <= 1:
        return (ls_a == ls_b)

    index_a = normalize_rotation_index(ls_a)
    index_b = normalize_rotation_index(ls_b, ls_a[index_a])

    if index_b == -1:
        return False

    if index_a == index_b:
        return (ls_a == ls_b)

    # cancel out 'index_a'
    index_b = (index_b - index_a)
    if index_b < 0:
        index_b += len(ls_a)
    index_a = 0  # ignore it

    # compare rotated lists
    for i in range(len(ls_a)):
        if ls_a[i] != ls_b[(index_b + i) % len(ls_b)]:
            return False
    return True


assert(compare_circular_lists([0, 9, -1, 2, -1], [-1, 2, -1, 0, 9]) == True)
assert(compare_circular_lists([2, 9, -1, 0, -1], [-1, 2, -1, 0, 9]) == False)
assert(compare_circular_lists(["Hello" "Circular", "World"], ["World", "Hello" "Circular"]) == True)
assert(compare_circular_lists(["Hello" "Circular", "World"], ["Circular", "Hello" "World"]) == False)

有关更多测试/示例,请参阅:this snippet

答案 16 :(得分:0)

您可以很容易地检查列表A是否等于预期O(N)时间内列表B的循环移位。

我会使用多项式散列函数来计算列表A的散列,以及列表B的每个循环移位。如果列表B的移位具有与列表A相同的散列,我将实际元素与实际元素进行比较看他们是否平等。

这个快速的原因是使用多项式散列函数(非常常见!),您可以在恒定时间内计算每个循环移位的散列,因此您可以计算所有循环移位的散列值在O(N)时间。

它的工作原理如下:

我们假设B有N个元素,那么使用素数P的B的散列是:

Hb=0;
for (i=0; i<N ; i++)
{
    Hb = Hb*P + B[i];
}

这是评估P中多项式的优化方法,相当于:

Hb=0;
for (i=0; i<N ; i++)
{
    Hb += B[i] * P^(N-1-i);  //^ is exponentiation, not XOR
}

注意每个B [i]如何乘以P ^(N-1-i)。如果我们将B向左移动1,则每个B [i]将乘以额外的P,除了第一个。由于乘法在加法上分布,我们可以通过乘以整个散列来立即乘以所有分量,然后修正第一个元素的因子。

B左移的哈希只是

Hb1 = Hb*P + B[0]*(1-(P^N))

第二个左移:

Hb2 = Hb1*P + B[1]*(1-(P^N))

依旧......

注意:上面的所有数学运算都是以某​​些机器字大小为模,你只需计算一次P ^ N.

答案 17 :(得分:-1)

要粘贴到最pythonic的方式,使用集合!

from sets import Set
a = Set ([1, 1, 1, 0, 0])
b = Set ([0, 1, 1, 1, 0]) 
c = Set ([1, 0, 0, 1, 1])
a==b
True
a==b==c
True