例如,我有列表:
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吉加!
有没有什么好方法可以用更少的计算来做到这一点?或者支持循环的任何数据类型?
答案 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;
}
答案 2 :(得分:32)
在这些行之间进行读取,听起来好像是在尝试枚举每个循环等价类的一个代表,其中包含3个1和52个零。让我们从密集表示切换到稀疏表示(range(55)
中的三个数字的集合)。在此表示中,s
到k
的循环移位由理解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
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
<强>功能强>
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
)必须介于18
和52
之间(含)。在0
和34
之间重新编码。b
)必须介于0
和26
之间,但这并不重要。52 - (a + b)
并且不添加任何信息规范形式是整数b * 35 + a
,介于0
和936
之间(包括),相当紧凑(有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