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


145

例如,我有以下列表:

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,并且仅包含三个1和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 Giga!

有什么好方法可以减少计算量吗?还是支持循环的任何数据类型?


只是预感:我觉得后缀树可能会有所帮助。en.wikipedia.org/wiki/Suffix_tree。要构建一个,请访问en.wikipedia.org/wiki/Ukkonen%27s_algorithm
Rerito 2014年

1
@Mehrdad但是运行时间比转换为规范形式的任何答案都要差,运行时间比转换为整数更差,并且运行时间比David Eisenstat差得多。
Veedrac

2
所有尝试解决一般问题的答案,但是在这种特殊情况下,只有3个,您可以用3个数字表示每个列表,其中3个数字是1到0之间的数字。问题列表可以表示为[0,0,2],[0,2,0],[2,0,0]。您可以只运行一次缩小列表,然后检查缩小列表。如果它们“在圆周上相同”,则原稿也是如此。
abc667 2014年

1
我猜然后Stack Overflow不需要投票。我们需要的是在所有解决方案中运行代码,并按其完成顺序显示它们。
达伍德·伊本·卡里姆

2
由于迄今尚未提及,@ abc667,Veedrac和Eisenstat所指的“规范形式”称为运行长度编码en.wikipedia.org/wiki/Run-length_encoding
David Lovell

Answers:


133

首先,这可以根据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

PS1在谈到时间复杂性时,值得注意的是,O(n)如果可以及时找到子字符串,则可以实现O(n)。它并非总是如此,并且取决于您所用语言的实现方式(尽管可能可以例如在线性时间KMP中完成)。

PS2针对那些害怕弦乐操作的人,由于这个事实,他们认为答案并不理想。重要的是复杂性和速度。该算法可能会在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)解决方案进行比较。在进行比较时,您可以从美国到澳大利亚旅行(很可能乘游轮旅行)


3
只需添加填充空格(每个字符串之前1和之后1)就可以解决问题。无需使用正则表达式使事情复杂化。(当然,我假设我们比较的是相同长度的列表)
Rerito

2
@Rerito,除非任何一个列表都包含字符串,而字符串本身可能具有前导或尾随空格。仍然可能导致碰撞。
亚当·史密斯

12
我不喜欢这个答案。字符串操作胡说八道使我不喜欢它,而David Eisenstat的回答让我很乐于投票。可以使用字符串在O(n)时间内完成此比较,但是也可以使用整数 [需要10k作为自删除值] 在O(n)时间内完成此比较,速度更快。尽管如此,David Eisenstat的答案表明进行任何比较都是毫无意义的,因为答案不需要。
Veedrac

7
@Veedrac你在跟我开玩笑吗?您听说过计算复杂性吗?Davids的答案仅花费O(n ^ 2)的时间和O(n ^ 2)的空间即可生成他的所有重复内容,即使对于小输入10 ^ 4的长度也要花费22秒,并且谁知道有多少ram。更不用说我们现在还没有开始搜索任何东西(我们只是生成了所有循环旋转)。我的字符串废话在不到0.5秒的时间内为您提供了10 ^ 6之类的输入的完整结果。它还需要O(n)空间来存储它。因此,在得出结论之前,请花一些时间了解答案。
Salvador Dali

1
@SalvadorDali您似乎非常注重(软)时间;-)
e2-e4

38

在Python中没有足够的知识来以您所请求的语言来回答这个问题,但是在C / C ++中,考虑到问题的参数,我会将零和一转换为位,然后将其压入uint64_t的最低有效位。这样一来,您就可以比较所有55位-1个时钟。

速度极快,整个过程都可以放入片上缓存(209,880字节)。仅在CPU的寄存器中提供对所有55个列表成员同时右移的硬件支持。同时比较所有55个成员时也是如此。这样可以将问题一对一地映射到软件解决方案。(并使用SIMD / SSE 256位寄存器,如果需要,最多可使用256个成员),因此,该代码对于读者而言立即显而易见。

您也许可以在Python中实现此功能,但我只是不太了解它,无法知道这是否可行或性能如何。

在它上睡觉之后,一些事情变得显而易见,并且一切都变得更好。

1.)使用位旋转循环链接列表非常容易,因此不必使用Dali的巧妙技巧。在64位寄存器内部,标准移位将非常简单地完成旋转,并通过使用算术而非位操作来使这一切对Python更友好。

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

3.)通过模2可以很容易地检查列表末尾的0或1。

4.)通过从2的尾部将0从列表的尾部“移动”到列表的头部,这是因为,如果实际移动了0,则将使第55位为false,这已经是绝对不做的了。

5.)将a从尾移到列表的头可以通过除以2并加上18,014,398,509,481,984-这是通过将第55位标记为true并将其余所有标记为false来创建的值。

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

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

在花了几个小时尝试优化代码之后,研究了汇编语言,我能够节省20%的运行时间。我还应该补充说,O / S和MSVC编译器也在昨天中午更新。无论出于何种原因,C编译器生成的代码质量在更新(11/15/2014)后都得到了显着改善。现在,运行时间约为70个时钟,约17纳秒,可在12.5秒内完成并比较锚环与测试环的所有55圈,并将所有环的NxN与其他环进行比较。

这段代码是如此紧凑,除了4个寄存器外,其余的99%的时间都无所作为。汇编语言几乎逐行匹配C代码。非常容易阅读和理解。如果有人自学的话,这是一个很棒的组装项目。

硬件是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;
}

在此处输入图片说明


23
人们一直在谈论“萨尔瓦多·达利的解决方案”,而我只是困惑地坐在这里,想知道同名画家是否也是以某种重要方式对古典算法做出了贡献的数学家。然后我意识到那是发布最受欢迎答案的人的用户名。我不是一个聪明人。
Woodrow Barlow 2014年

对于拥有10k代表的任何人,此处可以使用Numpy和矢量化实现。<10k的要点镜子。我删除了我的答案,因为David Eisenstat的答案指出您根本不需要进行比较因为您可以立即生成唯一列表,我想鼓励人们使用他的更好的答案。
Veedrac

@RocketRoy为什么您认为Python不会进行位操作?哎呀,我在链接的代码中使用位操作。我仍然认为基本上不需要这个答案(David Eisenstat的回答整个过程需要1毫秒),但是我发现该说法很奇怪。FWIW是Numpy中用于搜索262M-“列表”的类似算法,在我的计算机上大约需要15秒钟的时间(假设未找到匹配项),只有列表的旋转发生在外循环中,而不发生在内部循环中。
Veedrac

@Quincunx,感谢您所做的编辑,以使C ++的语法颜色正确。非常感激!

@RocketRoy没问题。当您继续回答很多问题时 PPCG,您将学习如何进行语法着色。
贾斯汀

33

在两行之间阅读时,听起来好像您正在尝试枚举具有3个1和52个0的字符串的每个圆形等效类的代表。让我们从一个密集的表示转换为一个稀疏的表示(中的三个数字的集合range(55))。在此表示形式中,sby 的循环移位k由理解力给出set((i + k) % 55 for i in s)。类中的词典最小代表通常包含位置0。给定一组形式{0, i, j}为的0 < i < j,该类中其他最小候选者为{0, j - i, 55 - i}{0, 55 - j, 55 + i - j}。因此,我们需要(i, j) <= min((j - i, 55 - i), (55 - j, 55 + i - j))使原件最小。这是一些枚举代码。

def makereps():
    reps = []
    for i in range(1, 55 - 1):
        for j in range(i + 1, 55):
            if (i, j) <= min((j - i, 55 - i), (55 - j, 55 + i - j)):
                reps.append('1' + '0' * (i - 1) + '1' + '0' * (j - i - 1) + '1' + '0' * (55 - j - 1))
    return reps

2
@SalvadorDali您误解了答案(在他指出之前,我也是这么做的!)。这将直接生成“具有3个1和52个0的字符串的每个圆形等效类的代表”。他的代码不会生成所有周期性的循环。原始费用¹为T(55²·26235²)。您的代码将55²提高到55,T(55 *26235²)也是如此。大卫Eisenstat的回答是55²和55³之间的整个事情。55³≪ 55·26235²。¹在所有情况下,此处不以大O项作为O(1)中的实际成本。
Veedrac

1
@Veedrac但是将来有99%的读者会遇到这个问题,他不会受到限制,我相信我的回答会更适合他们。在不使讨论变得肿的情况下,我将留给OP解释他到底想要什么。
萨尔瓦多·达利

5
@SalvadorDali OP似乎已成为XY问题的牺牲品。幸运的是,问题本身清楚地表明了标题中没有的标题,David能够在两行之间阅读。如果确实如此,那么正确的做法是更改标题并解决实际问题,而不是回答标题并忽略问题。
亚伦·迪富

1
@SalvadorDali,在您的Python代码的幕后,调用了C的strstr()等效项,后者在字符串中搜索子字符串。依次调用strcmp(),它运行for()循环,比较string1和string2中的每个字符。因此,假设搜索失败,看起来像O(n)的就是O(n * 55 * 55)。高级语言是一把两刃剑。他们向您隐藏了实施细节,但随后又向您隐藏了实施细节。FWIW,将列表连接起来的见解很棒。速度与uint8一样快,而与位一样快-在硬件中可以轻松旋转。

2
@AleksandrDubinsky对计算机而言更简单,对人类而言则更复杂。它足够快了。
David Eisenstat 2014年

12

重复第一个数组,然后使用Z算法(O(n)时间)在第一个数组中找到第二个数组。

(注意:您不必物理复制第一个数组。只需在匹配过程中进行环绕即可。)

Z算法的优点是,与KMP,BM等相比,它非常简单。
但是,如果您有雄心壮志,则可以在线性时间和恒定空间中进行字符串匹配strstr,例如,这样做。但是,实施它会更加痛苦。


6

紧随Salvador Dali的非常聪明的解决方案之后,处理该问题的最佳方法是确保所有元素的长度相同,并且两个LIST的长度相同。

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建议的正则表达式解决方案的内容,该内容为:

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))

1
Wiki之所以这样,是因为我基本上只是调整了Salvador Dali的答案并格式化了Ashwini的更改。实际上,这很少是我的。
亚当·史密斯

1
谢谢你的反馈。我认为我在编辑的解决方案中涵盖了所有可能的情况。让我知道是否缺少什么。
Salvador Dali

@SalvadorDali啊,是的。。。检查字符串长度是否相同。我认为这比遍历列表来寻找最长的元素然后调用str.format n时间格式化结果字符串要容易。我支持.... :)
亚当·斯密

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)未能对他的相似答案表示歉意。


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

3

首先将每一个列表元素(在副本如有必要),以旋转的版本,是词法最大。

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


2

Sa带@SalvadorDali观察在b + b中任何a长度大小的切片中寻找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

第二种方法:[已删除]


第一个版本为O(n²),第二个版本不适用于rollmatch([1, 0, 1, 1], [0, 1, 1, 1])
Veedrac

不错,我将其删除!
PaulMcG 2014年

1

这不是一个完整的,独立的答案,但是在通过减少比较来优化的主题上,我也正在考虑归一化的表示形式。

即,如果您输入的字母为{0,1},则可以大大减少允许的排列数量。将第一个列表旋转为(伪)归一化形式(考虑到问题中的分布,我将选择其中一个1位之一位于最左端,而0个位之一位于最右端的一个)。现在,在每次比较之前,先以相同的对齐方式在可能的位置上依次旋转其他列表。

例如,如果您总共有四个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

这可以概括为较大的字母和不同的对齐方式。主要的挑战是找到仅包含少量可能表示的良好规范化。理想情况下,这将是具有单个唯一表示形式的适当规范化,但是鉴于问题,我认为这是不可能的。


0

进一步建立在RocketRoy的答案上:将所有列表预先转换为无符号的64位数字。对于每个列表,将其旋转55位以找到最小的数值。

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

(从本质上讲,您为列表创建了一个不受列表元素轮换影响的标识值)如果列表中有任意多个ID,则该值甚至可以工作。


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());
        }
    }

希望对您有所帮助!


0

简化问题

  • 问题包括订购商品清单
  • 价值域是二进制 (0,1)
  • 我们可以通过将连续的1s 映射到count中来减少问题
  • 和连续0s为负数

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 ]
  • 此过程要求第一个项目和最后一个项目必须不同
  • 这将减少总体比较量

检查流程

  • 如果我们假设它们是重复的,那么我们可以假设我们正在寻找什么
  • 基本上,第一个列表中的第一个项目必须存在于另一个列表中的某个位置
  • 其次是第一个列表中的后续内容,并且以相同的方式
  • 前面的项目应该是第一个列表中的最后一个项目
  • 由于是圆形的,因此顺序相同

握力

  • 这里的问题是从哪里开始,技术上称为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):LISTE在列表中存在元素的地方返回索引列表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参数以降低通过线性检查的可能性


0

可以将有关列表的有效,快速计算的“规范形式”导出为:

  • 计算一个之间的零数目(忽略环绕),以获得三个数字。
  • 旋转三个数字,以便最大的数字位于第一位。
  • 第一个数字(a)必须在18和之间52(包括)。重新编码为0和之间34
  • 第二个数字(b)必须在0和之间26,但没关系。
  • 删除第三个数字,因为它只是一个52 - (a + b),不添加任何信息

规范形式是整数b * 35 + a,介于0和之间936(包括在内),并且非常紧凑(477总共有圆唯一列表)。


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;
    }
}

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)

请参阅:此片段以获取更多测试/示例。


0

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

我将使用多项式哈希函数来计算列表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。


-1

要粘合到最pythonic的方式,请使用set!

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

这也将匹配数目相同的0和1(不一定以相同的顺序)
GeneralBecos 2014年

GeneralBecos:只需选择这些字符串并在第二步中检查顺序即可
路易(Louis)

它们的线性顺序不同。它们处于相同的“循环”顺序。您所说的第2步是原始问题。
GeneralBecos
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.