在Python中给出n个元素列出所有定向循环的最有效方法

时间:2014-01-02 17:44:28

标签: python combinations permutation combinatorics

我有一个很大的元素列表(100多个元素):elements = [a, b, c, d, e, f, g...]

我需要构建所有可能的定向循环列表,考虑到序列 [a,b,c,d,e], [b,c,d,e,a], [c,d,e,a,b], [d,e,a,b,c], [e,a,b,c,d]被认为是相同的,因为它们是同一定向循环的不同表示。只有起点不同。

此外,由于方向很重要,[a,b,c,d,e][e,d,c,b,a]不同。

我正在寻找所有长度的所有定向循环,从2到len(elements)。使用内置permutationscombinations等优化的最佳pythonic方法是什么?

4 个答案:

答案 0 :(得分:6)

也许我错过了什么,但这对我来说似乎很简单:

def gen_oriented_cycles(xs):
    from itertools import combinations, permutations
    for length in range(2, len(xs) + 1):
        for pieces in combinations(xs, length):
            first = pieces[0],  # 1-tuple
            for rest in permutations(pieces[1:]):
                yield first + rest

然后,例如,

for c in gen_oriented_cycles('abcd'):
    print c

显示:

('a', 'b')
('a', 'c')
('a', 'd')
('b', 'c')
('b', 'd')
('c', 'd')
('a', 'b', 'c')
('a', 'c', 'b')
('a', 'b', 'd')
('a', 'd', 'b')
('a', 'c', 'd')
('a', 'd', 'c')
('b', 'c', 'd')
('b', 'd', 'c')
('a', 'b', 'c', 'd')
('a', 'b', 'd', 'c')
('a', 'c', 'b', 'd')
('a', 'c', 'd', 'b')
('a', 'd', 'b', 'c')
('a', 'd', 'c', 'b')

这是否缺少了您正在寻找的一些基本属性?

修改

我认为可能会遗漏这部分标准:

  

此外,由于方向很重要,[a,b,c,d,e]和[e,d,c,b,a]不同。

但第二个想法我觉得它符合这个要求,因为[e,d,c,b,a][a,e,d,c,b]相同。

答案 1 :(得分:0)

有没有什么好的理由在记忆中使用规范表示?这将是巨大的,并且可能有任何用于此的用例可能有更好的方法来处理它。

对于您的源材料,您会使用X元素的任意组合,甚至不一定是同类的元素? (即你有(a,e,g,x,f)等)。然后,我会这样做一个嵌套循环。外部将按长度选择,并选择要使用的整个列表的子集。内部组件将构建子集的组合,然后丢弃匹配项。无论你怎么做,它都会很慢,但是我会使用一个带有冷冻集的字典作为关键字(项目,用于不变性和快速查找),并且这些项目是已经检测到的周期列表。无论你怎么做,它都会很慢/长时间运行,但这是一种方式。

首先,您需要一种方法来确定两个元组(或列表)是否代表相同的循环。你可以这样做:

def match_cycle(test_cycle, ref_cycle):
    try: 
        refi = ref_cycle.index(test_cycle[0])
        partlen = len(ref_cycle) - refi
        return not (any(test_cycle[i] - ref_cycle[i+refi] for i in range(partlen)) or 
           any(test_cycle[i+partlen] - ref_cycle[i] for i in range(refi)))
    except:
        return False

然后,其余的。

def all_cycles(elements):
    for tuple_length in range(2, len(elements)):
        testdict = defaultdict(list)
        for outer in combinations(elements, tuple_length):
            for inner in permutations(outer):
                testset = frozenset(inner)
                if not any(match_cycle(inner, x) for x in testdict[testset]):
                    testdict[testset].append(inner)
                    yield inner

这为长度为5的elements生成了60个项目,这似乎是正确的,从检查看起来还不错。请注意,这将是指数虽然....长度(5)花了1.34毫秒/循环。长度(6)花了22.1毫秒。长度(7)花费703毫秒,长度(8)花费33.5秒。长度(100)可能会在你退休之前完成,但我不会赌它。

可能有更好的方法,可能是,但一般来说,100个元素中的子集数量非常大,即使减少了一些周期。因此,这可能不是解决您要解决的任何问题的正确方法。

答案 2 :(得分:0)

这可能有效:

import itertools
import collections

class Cycle(object):
    def __init__(self, cycle):
        self.all_possible = self.get_all_possible(cycle)
        self.canonical = self.get_canonical(self.all_possible)

    def __eq__(self, other):
        return self.canonical == other.canonical

    def __hash__(self):
        return hash(self.canonical)

    def get_all_possible(self, cycle):
        output = []
        cycle = collections.deque(cycle)
        for i in xrange(len(cycle)):
            cycle.rotate(1)
            output.append(list(cycle))
        return output

    def get_canonical(self, cycles):
        return min(map(tuple, cycles), key=lambda item: hash(item))

    def __repr__(self):
        return 'Cycle({0})'.format(self.canonical)

def list_cycles(elements):
    output = set()
    for i in xrange(2, len(elements) + 1):
        output.update(set(map(Cycle, itertools.permutations(elements, i))))
    return list(output)

def display(seq):
    for cycle in seq:
        print cycle.canonical
        print '\n'.join('  ' + str(item) for item in cycle.all_possible)

def main():
    elements = 'abcdefghijkl'
    final = list_cycles(elements)
    display(final)


if __name__ == '__main__':
    main()

它创建一个表示任何给定循环的类,它将进行散列并检查与循环的规范表示的相等性。这样可以将Cycle对象放在一个集合中,该集合将自动过滤掉任何重复项。不幸的是,它不会高​​效,因为它首先产生每一个可能的排列。

答案 3 :(得分:0)

对于长度为2到len(元素)的循环,这应该给你正确的答案。可能不是最快的方法。我使用了qarma的旋转提示,始终以最小的元素开始。

from itertools import permutations

def rotate_min(l):
    '''Rotates the list so that the smallest element comes first '''
    minIndex = l.index(min(l))
    rotatedTuple = l[minIndex:] + l[:minIndex]
    return rotatedTuple


def getCycles(elements):
    elementIndicies = tuple(range(len(elements)))  #tupple is hashable so it works with set
    cyclesIndices = set()
    cycles = []

    for length in range(2, len(elements)+1):
        allPermutation = permutations(elementIndicies, length)
        for perm in allPermutation:
            rotated_perm = rotate_min(perm)
            if rotated_perm not in cyclesIndices:
                #If the cycle of indices is not in the set, add it.
                cyclesIndices.add(rotated_perm)
                #convert indicies to the respective elements and append
                cycles.append([elements[i] for i in rotated_perm])

    return cycles