具有唯一值的排列

Posted

技术标签:

【中文标题】具有唯一值的排列【英文标题】:permutations with unique values 【发布时间】:2011-09-11 04:20:27 【问题描述】:

itertools.permutations 生成其元素根据其位置而不是其值被视为唯一的位置。所以基本上我想避免这样的重复:

>>> list(itertools.permutations([1, 1, 1]))
[(1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1)]

因为在我的情况下排列的数量太大,所以无法进行过滤。

有人知道合适的算法吗?

非常感谢!

编辑:

我基本上想要的是以下内容:

x = itertools.product((0, 1, 'x'), repeat=X)
x = sorted(x, key=functools.partial(count_elements, elem='x'))

这是不可能的,因为sorted 创建了一个列表并且 itertools.product 的输出太大。

对不起,我应该描述实际问题。

【问题讨论】:

什么太大了? TOTAL 排列或 UNIQUE 排列或两者兼而有之? 给定here,有一个比公认答案(Knuth 算法 L 的实现)更快的解决方案 您正在寻找 Multisets 的排列。请参阅下面 Bill Bell 的 answer。 你试过for x in permutation() set.add(x)吗? 也许这个问题的一个更好的标题是“不同的排列”。更好的是,“具有重复的列表的不同排列”。 【参考方案1】:
class unique_element:
    def __init__(self,value,occurrences):
        self.value = value
        self.occurrences = occurrences

def perm_unique(elements):
    eset=set(elements)
    listunique = [unique_element(i,elements.count(i)) for i in eset]
    u=len(elements)
    return perm_unique_helper(listunique,[0]*u,u-1)

def perm_unique_helper(listunique,result_list,d):
    if d < 0:
        yield tuple(result_list)
    else:
        for i in listunique:
            if i.occurrences > 0:
                result_list[d]=i.value
                i.occurrences-=1
                for g in  perm_unique_helper(listunique,result_list,d-1):
                    yield g
                i.occurrences+=1




a = list(perm_unique([1,1,2]))
print(a)

结果:

[(2, 1, 1), (1, 2, 1), (1, 1, 2)]

编辑(这是如何工作的):

我重写了上面的程序,使其更长但更易读。

我通常很难解释某件事是如何工作的,但让我试试吧。 为了理解它是如何工作的,你必须理解一个类似但更简单的程序,它会产生所有重复的排列。

def permutations_with_replacement(elements,n):
    return permutations_helper(elements,[0]*n,n-1)#this is generator

def permutations_helper(elements,result_list,d):
    if d<0:
        yield tuple(result_list)
    else:
        for i in elements:
            result_list[d]=i
            all_permutations = permutations_helper(elements,result_list,d-1)#this is generator
            for g in all_permutations:
                yield g

这个程序显然要简单得多: d 代表 permutations_helper 中的深度,有两个功能。一个函数是我们递归算法的停止条件,另一个是传递的结果列表。

我们没有返回每个结果,而是产生它。如果没有函数/运算符yield,我们将不得不在停止条件点将结果推送到某个队列中。但是这样,一旦满足停止条件,结果就会通过所有堆栈传播到调用者。这就是for g in perm_unique_helper(listunique,result_list,d-1): yield g的目的 所以每个结果都会传播给调用者。

回到原来的程序: 我们有一个独特元素的列表。在我们可以使用每个元素之前,我们必须检查其中有多少仍然可以推送到 result_list。使用这个程序与permutations_with_replacement 非常相似。不同之处在于每个元素的重复次数不能超过 perm_unique_helper 中的次数。

【讨论】:

我试图了解这是如何工作的,但我很困惑。你能提供一些评论吗? @Nathan 我编辑了答案并改进了代码。随时发布您的其他问题。 一段不错的代码。你重新实现了itertools.Counter,对吧? 我对itertools Counter不熟悉。由于性能问题,此代码更多地用于示例和教育目的,但用于生产则较少。如果需要更好的解决方案,我建议使用源自 Narayana Pandita 的迭代/非递归解决方案,并由 Donad Knuth 在 计算机编程艺术 中进行解释,并在 ***.com/a/12837695/429982 处可能实现 python 我用itertools.Counter 重新创建了这个,但您的代码似乎更快:)【参考方案2】:

因为有时新问题会被标记为重复问题,并且它们的作者会被引用到这个问题,所以可能需要提及 sympy 有一个用于此目的的迭代器。

>>> from sympy.utilities.iterables import multiset_permutations
>>> list(multiset_permutations([1,1,1]))
[[1, 1, 1]]
>>> list(multiset_permutations([1,1,2]))
[[1, 1, 2], [1, 2, 1], [2, 1, 1]]

【讨论】:

这是唯一明确识别 OP 真正寻找什么的答案(即 Multisets 的排列)。【参考方案3】:

这依赖于实现细节,即已排序迭代的任何排列都是按排序顺序排列的,除非它们是先前排列的重复。

from itertools import permutations

def unique_permutations(iterable, r=None):
    previous = tuple()
    for p in permutations(sorted(iterable), r):
        if p > previous:
            previous = p
            yield p

for p in unique_permutations('cabcab', 2):
    print p

给予

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

【讨论】:

效果很好,但比公认的解决方案慢。谢谢! 在较新版本的 Python 中并非如此。例如,在 Python 3.7.1 中,list(itertools.permutations([1,2,2], 3)) 返回 [(1, 2, 2), (1, 2, 2), (2, 1, 2), (2, 2, 1), (2, 1, 2), (2, 2, 1)] @KirkStrauser:你是对的。 “排序后的迭代的任何排列都按排序顺序”这句话甚至不适用于旧版本的 Python。我通过 2.7 测试了 Python 版本,发现你的结果是准确的。有趣的是,它不会使算法无效。它确实会产生排列,使得在任何点只有最大排列是原始的。 @KirkStrauser:我必须修改。你不正确。我去编辑我的答案并更仔细地阅读我写的内容。我的陈述有一个限定词使它正确:“排序迭代的任何排列都是按排序顺序排列的除非它们是先前排列的重复。”【参考方案4】:

与 Luka Rahne 的回答大致一样快,但更短更简单,恕我直言。

def unique_permutations(elements):
    if len(elements) == 1:
        yield (elements[0],)
    else:
        unique_elements = set(elements)
        for first_element in unique_elements:
            remaining_elements = list(elements)
            remaining_elements.remove(first_element)
            for sub_permutation in unique_permutations(remaining_elements):
                yield (first_element,) + sub_permutation

>>> list(unique_permutations((1,2,3,1)))
[(1, 1, 2, 3), (1, 1, 3, 2), (1, 2, 1, 3), ... , (3, 1, 2, 1), (3, 2, 1, 1)]

它通过设置第一个元素(遍历所有唯一元素)并遍历所有剩余元素的排列来递归地工作。

让我们通过 (1,2,3,1) 的unique_permutations 看看它是如何工作的:

unique_elements 是 1,2,3 让我们遍历它们:first_element 从 1 开始。 remaining_elements 是 [2,3,1](即 1,2,3,1 减去第一个 1) 我们迭代(递归地)剩余元素的排列:(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3 , 1, 2), (3, 2, 1) 对于每个sub_permutation,我们插入first_element:(1,1,2,3), (1,1,3,2), ...并产生结果。 现在我们迭代到first_element = 2,并执行与上述相同的操作。 remaining_elements 是 [1,3,1](即 1,2,3,1 减去前 2) 我们遍历剩余元素的排列:(1, 1, 3), (1, 3, 1), (3, 1, 1) 对于每个sub_permutation,我们插入first_element:(2, 1, 1, 3), (2, 1, 3, 1), (2, 3, 1, 1)... 并产生结果。 最后,我们对first_element = 3 做同样的事情。

【讨论】:

【参考方案5】:

您可以尝试使用 set:

>>> list(itertools.permutations(set([1,1,2,2])))
[(1, 2), (2, 1)]

设置已删除重复项的调用

【讨论】:

他可能需要 list(set(itertools.permutations([1,1,2,2]))) list(itertools.permutations(1,1,2,2)) 在 Python 3+ 或 Python 2.7 中,因为存在集合文字。尽管如果他不使用文字值,那么无论如何他只会使用set()。还有@ralu:再看看这个问题,事后过滤会很昂贵。 set(permutations(somelist)) != permutations(set(somelist)) 这个问题是我需要输出具有输入的长度。例如。 list(itertools.permutations([1, 1, 0, 'x'])) 但没有重复的地方被互换。 @JAB: 嗯,超过 12 个值需要很长时间......我真正想要的是类似 itertools.product((0, 1, 'x'), repeat=X) 但我需要先处理几个 'x' 的值(排序不适合,因为它正在生成列表并使用太多内存)。【参考方案6】:

这是我的 10 行解决方案:

class Solution(object):
    def permute_unique(self, nums):
        perms = [[]]
        for n in nums:
            new_perm = []
            for perm in perms:
                for i in range(len(perm) + 1):
                    new_perm.append(perm[:i] + [n] + perm[i:])
                    # handle duplication
                    if i < len(perm) and perm[i] == n: break
            perms = new_perm
        return perms


if __name__ == '__main__':
    s = Solution()
    print s.permute_unique([1, 1, 1])
    print s.permute_unique([1, 2, 1])
    print s.permute_unique([1, 2, 3])

--- 结果----

[[1, 1, 1]]
[[1, 2, 1], [2, 1, 1], [1, 1, 2]]
[[3, 2, 1], [2, 3, 1], [2, 1, 3], [3, 1, 2], [1, 3, 2], [1, 2, 3]]

【讨论】:

我喜欢这个解决方案 很高兴你喜欢这种方法 嗨@LittleRoys。我为PR in more-itertools 使用了稍微修改过的代码版本。你同意吗? 我很好奇,这门课有什么价值吗?为什么这不只是一个函数?【参考方案7】:

一种天真的方法可能是采用一组排列:

list(set(it.permutations([1, 1, 1])))
# [(1, 1, 1)]

但是,这种技术会浪费地计算重复排列并丢弃它们。更有效的方法是more_itertools.distinct_permutations,third-party tool。

代码

import itertools as it

import more_itertools as mit


list(mit.distinct_permutations([1, 1, 1]))
# [(1, 1, 1)]

性能

使用更大的迭代器,我们将比较幼稚技术和第三方技术之间的性能。

iterable = [1, 1, 1, 1, 1, 1]
len(list(it.permutations(iterable)))
# 720

%timeit -n 10000 list(set(it.permutations(iterable)))
# 10000 loops, best of 3: 111 µs per loop

%timeit -n 10000 list(mit.distinct_permutations(iterable))
# 10000 loops, best of 3: 16.7 µs per loop

我们看到more_itertools.distinct_permutations 快了一个数量级。


详情

从源头上看,递归算法(如已接受的答案所示)用于计算不同的排列,从而避免了浪费的计算。有关详细信息,请参阅source code。

【讨论】:

赞成。 @Bill Bell 的回答中,list(mit.distinct_permutations([1]*12+[0]*12)) 也比 list(multiset_permutations([1]*12+[0]*12)) 快了约 5.5 倍。【参考方案8】:

这是问题的递归解决方案。

def permutation(num_array):
    res=[]
    if len(num_array) <= 1:
        return [num_array]
    for num in set(num_array):
        temp_array = num_array.copy()
        temp_array.remove(num)
        res += [[num] + perm for perm in permutation(temp_array)]
    return res

arr=[1,2,2]
print(permutation(arr))

【讨论】:

【参考方案9】:

听起来您正在寻找 itertools.combinations() docs.python.org

list(itertools.combinations([1, 1, 1],3))
[(1, 1, 1)]

【讨论】:

不,组合会有同样的问题。 只按顺序给出,例如 [1, 2, 3] 会产生 [1, 2, 3] 但不会产生 [3, 2, 1] 或 [2, 3, 1] 等 【参考方案10】:

要生成 ["A","B","C","D"] 的唯一排列,我使用以下命令:

from itertools import combinations,chain

l = ["A","B","C","D"]
combs = (combinations(l, r) for r in range(1, len(l) + 1))
list_combinations = list(chain.from_iterable(combs))

生成:

[('A',),
 ('B',),
 ('C',),
 ('D',),
 ('A', 'B'),
 ('A', 'C'),
 ('A', 'D'),
 ('B', 'C'),
 ('B', 'D'),
 ('C', 'D'),
 ('A', 'B', 'C'),
 ('A', 'B', 'D'),
 ('A', 'C', 'D'),
 ('B', 'C', 'D'),
 ('A', 'B', 'C', 'D')]

注意,不会创建重复项(例如,不会生成与 D 组合的项目,因为它们已经存在)。

示例:这可用于通过 Pandas 数据帧中的数据为 OLS 模型生成高阶或低阶项。

import statsmodels.formula.api as smf
import pandas as pd

# create some data
pd_dataframe = pd.Dataframe(somedata)
response_column = "Y"

# generate combinations of column/variable names
l = [col for col in pd_dataframe.columns if col!=response_column]
combs = (combinations(l, r) for r in range(1, len(l) + 1))
list_combinations = list(chain.from_iterable(combs))

# generate OLS input string
formula_base = ' ~ '.format(response_column)
list_for_ols = [":".join(list(item)) for item in list_combinations]
string_for_ols = formula_base + ' + '.join(list_for_ols)

创建...

Y ~ A + B + C + D + A:B + A:C + A:D + B:C + B:D + C:D + A:B:C + A:B:D + A:C:D + B:C:D + A:B:C:D'

然后可以通过管道传送到您的OLS regression

model = smf.ols(string_for_ols, pd_dataframe).fit()
model.summary()

【讨论】:

【参考方案11】:

自己找东西的时候碰到这个问题!

这就是我所做的:

def dont_repeat(x=[0,1,1,2]): # Pass a list
    from itertools import permutations as per
    uniq_set = set()
    for byt_grp in per(x, 4):
        if byt_grp not in uniq_set:
            yield byt_grp
            uniq_set.update([byt_grp])
    print uniq_set

for i in dont_repeat(): print i
(0, 1, 1, 2)
(0, 1, 2, 1)
(0, 2, 1, 1)
(1, 0, 1, 2)
(1, 0, 2, 1)
(1, 1, 0, 2)
(1, 1, 2, 0)
(1, 2, 0, 1)
(1, 2, 1, 0)
(2, 0, 1, 1)
(2, 1, 0, 1)
(2, 1, 1, 0)
set([(0, 1, 1, 2), (1, 0, 1, 2), (2, 1, 0, 1), (1, 2, 0, 1), (0, 1, 2, 1), (0, 2, 1, 1), (1, 1, 2, 0), (1, 2, 1, 0), (2, 1, 1, 0), (1, 0, 2, 1), (2, 0, 1, 1), (1, 1, 0, 2)])

基本上,制作一组并不断添加。比制作​​需要太多内存的列表等更好.. 希望它可以帮助下一个关注的人:-) 注释掉函数中设置的“更新”以查看差异。

【讨论】:

, 4 应该被删除,这样它就可以处理任何长度的东西。即使固定,这也不是一个很好的解决方案。一方面,它一次将所有项目存储在内存中,从而破坏了生成器的一些优势。另一方面,它在时间方面仍然非常低效,在某些情况下它应该是即时的。试试for i in dont_repeat([1]*20+[2]): print i;这将需要永远。【参考方案12】:

我见过的这个问题的最佳解决方案是使用 Knuth 的“算法 L”(正如 Gerrat 之前在原帖的 cmets 中指出的那样):http://***.com/questions/12836385/how-can-i-interleave-or-create-unique-permutations-of-two-stings-without-recurs/12837695

一些时间安排:

排序[1]*12+[0]*12(2,704,156 个唯一排列): 算法 L → 2.43 s Luke Rahne 的解决方案 → 8.56 sscipy.multiset_permutations() → 16.8 s

【讨论】:

【参考方案13】:

您可以创建一个函数,使用 collections.Counter 从给定序列中获取唯一项目及其计数,并使用 itertools.combinations 为每个递归调用中的每个唯一项目选择索引组合,并将索引映射回选择所有索引时的列表:

from collections import Counter
from itertools import combinations
def unique_permutations(seq):
    def index_permutations(counts, index_pool):
        if not counts:
            yield 
            return
        (item, count), *rest = counts.items()
        rest = dict(rest)
        for indices in combinations(index_pool, count):
            mapping = dict.fromkeys(indices, item)
            for others in index_permutations(rest, index_pool.difference(indices)):
                yield **mapping, **others
    indices = set(range(len(seq)))
    for mapping in index_permutations(Counter(seq), indices):
        yield [mapping[i] for i in indices]

这样[''.join(i) for i in unique_permutations('moon')] 返回:

['moon', 'mono', 'mnoo', 'omon', 'omno', 'nmoo', 'oomn', 'onmo', 'nomo', 'oonm', 'onom', 'noom']

【讨论】:

【参考方案14】:

这是我的尝试,不使用 set / dict,作为使用递归的生成器,而是使用字符串作为输入。输出也按自然顺序排序:

def perm_helper(head: str, tail: str):
    if len(tail) == 0:
        yield head
    else:
        last_c = None
        for index, c in enumerate(tail):
            if last_c != c:
                last_c = c
                yield from perm_helper(
                    head + c, tail[:index] + tail[index + 1:]
                )


def perm_generator(word):
    yield from perm_helper("", sorted(word))

示例:

from itertools import takewhile
word = "POOL"
list(takewhile(lambda w: w != word, (x for x in perm_generator(word))))
# output
# ['LOOP', 'LOPO', 'LPOO', 'OLOP', 'OLPO', 'OOLP', 'OOPL', 'OPLO', 'OPOL', 'PLOO', 'POLO']

【讨论】:

【参考方案15】:

前几天在解决我自己的问题时遇到了这个问题。我喜欢 Luka Rahne 的方法,但我认为在集合库中使用 Counter 类似乎是一个适度的改进。这是我的代码:

def unique_permutations(elements):
    "Returns a list of lists; each sublist is a unique permutations of elements."
    ctr = collections.Counter(elements)

    # Base case with one element: just return the element
    if len(ctr.keys())==1 and ctr[ctr.keys()[0]] == 1:
        return [[ctr.keys()[0]]]

    perms = []

    # For each counter key, find the unique permutations of the set with
    # one member of that key removed, and append the key to the front of
    # each of those permutations.
    for k in ctr.keys():
        ctr_k = ctr.copy()
        ctr_k[k] -= 1
        if ctr_k[k]==0: 
            ctr_k.pop(k)
        perms_k = [[k] + p for p in unique_permutations(ctr_k)]
        perms.extend(perms_k)

    return perms

此代码将每个排列作为列表返回。如果你给它一个字符串,它会给你一个排列列表,每个排列都是一个字符列表。如果您希望输出为字符串列表(例如,如果您是一个糟糕的人,并且想滥用我的代码来帮助您在 Scrabble 中作弊),只需执行以下操作:

[''.join(perm) for perm in unique_permutations('abunchofletters')]

【讨论】:

【参考方案16】:

在这种情况下,我使用 itertools.product 提出了一个非常合适的实现(这是一个您想要所有组合的实现

unique_perm_list = [''.join(p) for p in itertools.product(['0', '1'], repeat = X) if ''.join(p).count() == somenumber]

这本质上是 n = X 和 somenumber = k 的组合(n 大于 k) itertools.product() 从 k = 0 迭代到 k = X,随后使用 count 进行过滤,确保仅将具有正确数量的排列转换为列表。当您计算 n over k 并将其与 len(unique_perm_list) 进行比较时,您可以很容易地看到它有效

【讨论】:

【参考方案17】:

适用于去除递归,使用字典和 numba 以获得高性能,但不使用 yield/generator 样式,因此内存使用不受限制:

import numba

@numba.njit
def perm_unique_fast(elements): #memory usage too high for large permutations
    eset = set(elements)
    dictunique = dict()
    for i in eset: dictunique[i] = elements.count(i)
    result_list = numba.typed.List()
    u = len(elements)
    for _ in range(u): result_list.append(0)
    s = numba.typed.List()
    results = numba.typed.List()
    d = u
    while True:
        if d > 0:
            for i in dictunique:
                if dictunique[i] > 0: s.append((i, d - 1))
        i, d = s.pop()
        if d == -1:
            dictunique[i] += 1
            if len(s) == 0: break
            continue
        result_list[d] = i
        if d == 0: results.append(result_list[:])
        dictunique[i] -= 1
        s.append((i, -1))
    return results
import timeit
l = [2, 2, 3, 3, 4, 4, 5, 5, 6, 6]
%timeit list(perm_unique(l))
#377 ms ± 26 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

ltyp = numba.typed.List()
for x in l: ltyp.append(x)
%timeit perm_unique_fast(ltyp)
#293 ms ± 3.37 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

assert list(sorted(perm_unique(l))) == list(sorted([tuple(x) for x in perm_unique_fast(ltyp)]))

快了大约 30%,但由于列表复制和管理,仍然会受到一些影响。

或者没有 numba 但仍然没有递归并使用生成器来避免内存问题:

def perm_unique_fast_gen(elements):
    eset = set(elements)
    dictunique = dict()
    for i in eset: dictunique[i] = elements.count(i)
    result_list = list() #numba.typed.List()
    u = len(elements)
    for _ in range(u): result_list.append(0)
    s = list()
    d = u
    while True:
        if d > 0:
            for i in dictunique:
                if dictunique[i] > 0: s.append((i, d - 1))
        i, d = s.pop()
        if d == -1:
            dictunique[i] += 1
            if len(s) == 0: break
            continue
        result_list[d] = i
        if d == 0: yield result_list
        dictunique[i] -= 1
        s.append((i, -1))

【讨论】:

【参考方案18】:
ans=[]
def fn(a, size): 
    if (size == 1): 
        if a.copy() not in ans:
            ans.append(a.copy())
            return

    for i in range(size): 
        fn(a,size-1); 
        if size&1: 
            a[0], a[size-1] = a[size-1],a[0] 
        else: 
            a[i], a[size-1] = a[size-1],a[i]

https://www.geeksforgeeks.org/heaps-algorithm-for-generating-permutations/

【讨论】:

【参考方案19】:

怎么样

np.unique(itertools.permutations([1, 1, 1]))

问题是排列现在是 Numpy 数组的行,因此使用更多内存,但您可以像以前一样循环遍历它们

perms = np.unique(itertools.permutations([1, 1, 1]))
for p in perms:
    print p

【讨论】:

以上是关于具有唯一值的排列的主要内容,如果未能解决你的问题,请参考以下文章

python itertools 具有绑定值的排列

如何从现有排名值创建具有递增排名值的运行范围的表?

生成具有重复元素的列表排列

具有排列数组的通用合并排序

是否有一种算法可以生成多重集的所有唯一循环排列?

如何在剔除坏序列时生成排列?