如何检查两个列表是否在Python中循环相同
例如,我有名单:
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
该function在最坏的情况下需要55次循环移位操作。 有26,235个名单可以相互比较。 总之,我需要55 * 26,235 *(26,235 – 1)/ 2 = 18,926,847,225的计算。 这大约是20千兆!
有没有什么好办法用较less的计算来做到这一点? 或者支持循环的任何数据types?
首先,这可以在O(n)
中按照列表的长度来完成。您可以注意到,如果您将复制您的列表2次( [1, 2, 3]
)将会是[1, 2, 3, 1, 2, 3]
那么你的新列表肯定会包含所有可能的循环列表。
所以你只需要检查你所search的列表是否在你的起始列表的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])
将所有数字转换为string, ' '.join()
将转换数组['1', '2', '111']
变成一个string'1 2 111'
。
正如评论中的一些人所指出的,在线人可能会给出一些误报,所以要涵盖所有可能的边缘情况:
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)
时间内可以find子串,那么O(n)
将会被实现。 这并不总是如此,取决于您的语言的实施( 尽pipe可能在线性时间KMP,例如可能会完成 )。
PS2对于那些害怕string操作的人来说,由于这个事实认为答案不好。 复杂性和速度是多么重要。 这个algorithm可能运行在O(n)
时间和O(n)
空间,这使得它比O(n^2)
域中的任何事物都好得多。 要自己看看,你可以运行一个小基准(创build一个随机列表popup第一个元素,并将其追加到最后,从而创build一个循环列表,你可以自由地操作)
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)
解决scheme进行比较。 当它比较时,你可以从美国旅行到澳大利亚(最有可能通过游轮)
在Python中没有足够的知识来回答你所要求的语言,但在C / C ++中,给定你的问题的参数,我会把零和1转换成位,并将它们推到uint64_t的最低位。 这将使您能够一举比较所有55位 – 1个时钟。
速度非常快,整个事情将适合芯片caching(209,880字节)。 硬件支持同时移动全部55个列表成员只能在CPU寄存器中使用。 所有55名成员同时进行比较也是如此。 这允许将问题与软件解决scheme进行一对一映射。 (并且使用SIMD / SSE 256位寄存器,如果需要的话,最多可以有256个成员)结果代码对于读者来说是立即显而易见的。
你可能可以在Python中实现这一点,我只是不知道这是否可能或性能可能。
在睡觉之后,有几件事变得明显起来,一切都变好了。
1.)使用Dali的非常聪明的技巧不需要旋转循环链表是很容易的。 在一个64位的寄存器中,标准的位移将很简单地完成旋转,并且试图通过使用算术而不是位操作来更加友好的使用Python。
2.)使用2分频可以很容易地实现位移。
3.)检查列表的末尾是否为0或1可以通过模2很容易地完成。
4.)将“0”从尾部移动到列表的头部可以通过除以2来完成。这是因为如果实际移动了零,则会使第55位变成虚假的,这已经完全没有任何作用。
5.)将“1”从尾部移动到列表的头部可以通过除以2并添加18,014,398,509,481,984来完成 – 这是通过将第55位标记为真并且其余为假而创build的值。
6.)如果在任何给定的旋转之后锚点和组合uint64_t的比较为TRUE,则中断并返回TRUE。
我会将整个列表数组转换成uint64_ts数组,以避免必须重复进行转换。
花了几个小时试图优化代码,学习汇编语言,我能够削减20%的运行时间。 我应该补充说,O / S和MSVC编译器昨天中午也得到了更新。 无论出于何种原因,C编译器所产生的代码的质量在更新后(11/15/2014)得到了显着提高。 运行时间现在是〜70 个时钟,17纳秒来组成一个testing环的所有55个环锚环并比较,所有其他环的N×N在12.5秒内完成。
这段代码非常紧密,但是有四个寄存器在99%的时间里都没有任何function。 汇编语言几乎匹配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; }
在行之间阅读,听起来好像你试图枚举每个循环等价类的一个代表性的3个和52个string的string。 让我们从密集表示切换到稀疏表示( range(55)
的三个数字集合)。 在这个表示中, s
的循环移位由理解set((i + k) % 55 for i in s)
。 在一个类中的词典最小代表总是包含位置0.给定一个0 < i < j
的forms{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
重复第一个数组,然后使用Zalgorithm (O(n)时间)查找第一个数组中的第二个数组。
(注意:你不必物理复制第一个数组,你可以在匹配过程中换行)
Zalgorithm的好处是与KMP,BM等相比非常简单
但是,如果你觉得雄心勃勃,你可以在线性时间和恒定空间中进行string匹配 – 例如, strstr
。 但是实施它将会更加痛苦。
遵循萨尔瓦多•达利(Salvador Dali)非常聪明的解决scheme,处理问题的最佳方法是确保所有元素的长度相同,并且两个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
如果这个速度比AshwiniChaudharybuild议的正则expression式在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))
鉴于你需要做这么多的比较,可能值得你一边通过你的列表初步通过将它们转换成某种规范的forms,可以轻松地比较?
你是否想要获得一组循环唯一的列表? 如果是这样,你可以把它们转换成元组后抛出一个集合。
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)
向大卫·艾森斯塔特道歉,因为没有发现他的类似答案。
你可以像这样滚动一个列表:
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
首先将每个列表元素(如有必要,复制一份)转换为词法最大的旋转版本。
然后对结果列表进行sorting(将索引保留在原始列表位置)并统一sorting列表,根据需要标记原始列表中的所有重复项。
捎带@ SalvadorDali在b + b中查找任何长度较长的片段的匹配的观察,这里是使用列表操作的解决scheme。
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
第二种方法:[删除]
不是一个完整的,独立的答案,而是通过减less比较来优化的话题,我也正在考虑规范化的表示。
也就是说,如果您的input字母是{0,1},则可以显着减less允许的排列数量。 将第一个列表旋转到(伪)规范化的forms(给定分布在你的问题中,我会select其中一个位在最左边,其中一个位在最右边)。 现在,在每次比较之前,通过相同的alignment模式的可能位置依次旋转另一个列表。
例如,如果总共有4个1位,则alignment时最多可以有4个排列,如果您有相邻1个位的集群,那么这个集群中的每个附加位都会减less位置的数量。
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
这推广到更大的字母和不同的alignment模式; 主要的挑战是只有几个可能的表示find一个很好的规范化。 理想情况下,这将是一个适当的正常化,有一个唯一的代表性,但考虑到这个问题,我不认为这是可能的。
进一步build设RocketRoy的答案:将所有的列表前面转换为无符号的64位数字。 对于每个列表,旋转这些55位来查找最小的数值。
现在,您将为每个列表留下一个无符号的64位值,您可以直接与其他列表的值进行比较。 函数is_circular_identical()不再是必需的。
(实际上,您为列表创build一个不受列表元素旋转影响的标识值)如果您的列表中有任意数量的列表,那么这甚至可以工作。
这是萨尔瓦多·达利的想法,但不需要string转换。 背后是同样的KMP恢复理念,以避免不可能的换class检查。 他们只打电话给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,1)
- 我们可以通过将连续的
1
秒映射到一个计数来减less这个问题 - 并连续
0
s为负数
例
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 ]
- 这个过程要求第一个项目和最后一个项目必须是不同的
- 这将减less总体比较的数量
检查过程
- 如果我们假设他们是重复的,那么我们可以假设我们正在寻找的东西
- 基本上,第一个列表中的第一个项目必须存在于另一个列表中的某处
- 接下来是第一个列表中的内容,方式也是如此
- 以前的项目应该是第一个列表中的最后一个项目
- 既然是循环的,顺序是一样的
抓地力
- 这里的问题是从哪里开始,技术上称之为
lookup
和look-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
function
-
MAP_LIST(LIST A):LIST
映射消息元素在新列表中计算 -
LOOKUP_INDEX(LIST A, INTEGER E):LIST
元素E
存在于列表A
LOOKUP_INDEX(LIST A, INTEGER E):LIST
返回列表 -
COUNT_CHAR(LIST A , INTEGER E):INTEGER
COUNT多less次元素出现在列表A
-
ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEAN
检查如果B[I]
等同于两个方向上的A[0]
N-GRAM
最后
如果列表大小将非常大,或者我们开始检查周期的元素经常很高,那么我们可以执行以下操作:
-
首先查找第一个列表中最不频繁的项目
-
增加n-gram N参数以降低经过线性检查的概率
有效的,快速计算的“规范forms”的列表可以被推导为:
- 计算两个数之间的零(忽略环绕),得到三个数字。
- 旋转三个数字,使最大的数字是第一个。
- 第一个数字(
a
)必须在18
和52
之间(含)。 重新编码为0
到34
之间。 - 第二个数字(
b
)必须在0
到26
之间,但是并不重要。 - 放下第三个数字,因为它只是
52 - (a + b)
,并没有添加任何信息
规范forms是整数b * 35 + a
,介于0
和936
(含)之间,相当紧凑(总共有477
循环唯一列表)。
我写了一个简单的解决scheme,比较两个列表,并且仅仅增加(并且环绕)每次迭代的比较值的索引。
我不太了解python,所以我用Java编写了它,但它非常简单,所以它很容易适应任何其他语言。
通过这个,你也可以比较其他types的列表。
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; } }
正如其他人所提到的,一旦你find一个列表的规范化轮换,你可以比较它们。
下面是一些工作代码,这样做,基本的方法是为每个列表find一个标准化的旋转和比较:
- 计算每个列表上的归一化旋转索引。
- 在两个列表中循环使用偏移量,比较每个项目,如果不匹配则返回。
请注意,这个方法不依赖于数字,你可以传入string列表(任何可以比较的值)。
我们知道我们希望列表以最小值开始 – 所以我们可以遍历最小值,search直到我们find哪一个具有最低的连续值,将其存储用于进一步比较直到我们拥有最好的。
计算索引时有很多机会退出,有些优化的细节。
- 当只有一个时,跳过search最佳的最小值。
- 当前一个值也是最小值时,跳过search最小值(这绝不会是更好的匹配)。
- 当所有值相同时跳过search。
- 列表具有不同的最小值时提前失败。
- 抵消匹配时使用常规比较。
- 调整偏移以避免在比较过程中将索引值包装在其中一个列表上。
请注意,在Python中,list-in-listsearch可能会更快,但是我有兴趣find一种有效的algorithm – 也可以用于其他语言。 而且,避免创build新列表也有一些优点。
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)
See: this snippet for some more tests/examples.
You can check to see if a list A is equal to a cyclic shift of list B in expected O(N) time pretty easily.
I would use a polynomial hash function to compute the hash of list A, and every cyclic shift of list B. Where a shift of list B has the same hash as list A, I'd compare the actual elements to see if they are equal.
The reason this is fast is that with polynomial hash functions (which are extremely common!), you can calculate the hash of each cyclic shift from the previous one in constant time, so you can calculate hashes for all of the cyclic shifts in O(N) time.
它是这样工作的:
Let's say B has N elements, then the the hash of B using prime P is:
Hb=0; for (i=0; i<N ; i++) { Hb = Hb*P + B[i]; }
This is an optimized way to evaluate a polynomial in P, and is equivalent to:
Hb=0; for (i=0; i<N ; i++) { Hb += B[i] * P^(N-1-i); //^ is exponentiation, not XOR }
Notice how every B[i] is multiplied by P^(N-1-i). If we shift B to the left by 1, then every every B[i] will be multiplied by an extra P, except the first one. Since multiplication distributes over addition, we can multiply all the components at once just by multiplying the whole hash, and then fix up the factor for the first element.
The hash of the left shift of B is just
Hb1 = Hb*P + B[0]*(1-(P^N))
The second left shift:
Hb2 = Hb1*P + B[1]*(1-(P^N))
等等…
NOTE: all math above is performed modulo some machine word size, and you only have to calculate P^N once.
To glue to the most pythonic way to do it, use sets !
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