【问题标题】:Faster solution for ascending sublist combinations升序子列表组合的更快解决方案
【发布时间】:2021-06-25 09:51:38
【问题描述】:

这是我上一个问题的后续问题:Retrieve all possible combinations of ascending integers from sublists

解决这个问题的代码对于更大的列表来说真的很慢。 到目前为止,这是我的解决方案:

from typing import List, Tuple
from itertools import product, combinations

def is_ascending(t: tuple) -> bool:
    return sorted(t) == list(t)

def get_ascending_combinations(a: List[List[int]]) -> List[Tuple, ...]]:
    all_combs = []
    add_comb = all_combs.append
    input_length = len(a) + 1
    for comb_length in range(2, input_length):
        for main_comb in combinations(a, comb_length):
            for comb in product(*main_comb):
                if is_ascending(comb):
                    add_comb(comb)
    return all_combs

example_list = [[0], [1, 4], [2]]
get_ascending_combinations(example_list)
>> [(0, 1), (0, 4), (0, 2), (1, 2), (0, 1, 2)]

example_list1 = [[0], [1, 4, 6], [2]]
get_ascending_combinations(example_list1)
>> [(0, 1), (0, 4), (0, 2), (1, 2), (0, 1, 2), (0, 6)]

example_list2 = [[0], [1, 4], [2], [5, 6]]
get_ascending_combinations(example_list2)
>> [(0, 1), (0, 4), (0, 2), (0, 5), (0, 6), (1, 2), (1, 5), (1, 6), (4, 5), (4, 6), (2, 5),
    (2, 6), (0, 1, 2), (0, 1, 5), (0, 1, 6), (0, 4, 5), (0, 4, 6), (0, 2, 5), (0, 2, 6),
    (1, 2, 5), (1, 2, 6), (0, 1, 2, 5), (0, 1, 2, 6)]

如示例中所示,它只能按升序进行组合。另一个限制是只允许与每个唯一子列表中的一个项目进行组合。

但是,此代码在较大的列表上确实很慢,因为可能的组合数量要大得多。如何制作一个更快的算法来做到这一点?请参阅下面的示例列表。

large_example = [[19, 67], [21, 43], [64], [47, 65, 69, 90], [48, 70], [0, 27, 63], [1], [2], [3], 
                 [4, 53], [5], [6], [7, 57], [8, 58], [9], [10], [11, 82], [12, 37], [13], 
                 [14, 77], [15], [16], [17], [18, 74], [20], [22], [23], [24, 30], [25], [26], 
                 [28], [29], [31], [32], [33], [34], [35], [36], [38], [39], [40], [41], [78], 
                 [79], [80], [46, 84], [85], [86], [87], [88], [89], [91], [50], [93]]

【问题讨论】:

  • 每个子列表是否按升序排列?如果您利用已排序的子列表,那么长子列表的时间复杂度会更好,因此您不必将一个子列表的每个值与另一个子列表的每个值并列。如果您想使用长子列表并且子列表尚未排序,那应该是get_ascending_combinations 的第一阶段。
  • 子列表是否互斥,所以如果一个整数出现在一个子列表中,则相同的整数不会出现在任何其他子列表中?
  • 它并不是真正的升序,因为它代表了两个匹配文本的索引。子列表的顺序是标记在两个文本中出现的顺序
  • 子列表因此不是互斥的,因为我想找到所有符合有效顺序的标记组合。这就是组合列表应该按升序排列的原因

标签: python python-3.x multithreading multiprocessing itertools


【解决方案1】:

好的,我把这段代码原样放在这里,虽然它可能需要进一步检查。

接近

我的方法是,首先构建所有 order-2 组合,然后通过将每个可接受的数字附加到每个 order-(n-1) 组合中来创建 order-n 组合 (n = 3, 4, ..., length_of_list)每个后续子列表 1. 在该组合中没有数字,并且 2. 在该组合中使用的最后一个子列表之后。

性能

我用您的 example_list2 检查了结果,没关系,但我的实现速度慢了大约 2 倍,因为它有一些开销。然后我创建了一个mid_example 列表,其中包含来自您的large_example 的前14 个子列表,结果令人惊叹。在我的笔记本电脑上:

>>> timeit("get_ascending_combinations(mid_example)", number=50, globals=globals())
40.589324300000044

>>> a=AscendingCombos(mid_example)
>>> timeit("a.calculate()", number=50, globals=globals())
0.23768389999997908

注意事项

我没有检查输出,尽管我有理由相信它是正确的。

我无法执行 large_example 上的两个过程,因为它们都阻止了我正在执行的其他作业,尽管我再次希望确认速度加快

代码

class AscendingCombos():

    def __init__(self, mainlist):
        self.mainlist = mainlist
        self.maindict = {k:v for k,v in enumerate(mainlist)}
        self.mainrange = set(range(len(self.mainlist)))
        self.out = {}
        
    def calculate(self):
        ##order 2
        self.out[2] = []
        for combo in combinations(self.maindict.keys(), 2):
            for first in self.maindict[combo[0]]:
                for second in self.maindict[combo[1]]:
                    if second > first:
                        self.out[2].append([list(combo),[first,second]])
        ##order 3+        
        for order in range(3,len(self.mainlist)+1):
            self.out[order] = []
            for prevcombo in self.out[order-1]:
                cands = self.mainrange.difference(
                    prevcombo[0]).difference(
                        range(prevcombo[0][-1]))
                for cand in cands:
                    ##i = self.index[prevcombo[0]].append(self.mainlist.index(self.mainlist[cand]))
                    for nextnum in self.maindict[cand]:
                        if nextnum > prevcombo[1][-1]:
                            self.out[order].append([prevcombo[0]+[cand],prevcombo[1]+[nextnum]])
        return self.out

    def tolist(self):
        for order in range(2,len(self.mainlist)+1):
            print(order, [c[1] for c in self.out[order]]) 

【讨论】:

  • 感谢您的回答!我今天测试一下
  • 输出包含组合中的整数,甚至不在输入列表中
  • 这应该是不可能的。您可能已经注意到了,但为了确定……每个组合,即a.out[x] 中的每个项目都包含 两个 列表,其中第二个是真正的组合:第一个只是一个助手,它存储第二个中的数字属于哪个输入子列表。要查看真正的组合列表,您也需要运行 a.tolist()
  • 无论如何我会检查代码,看看是否有一些错误溜进来:)
  • 你的 tolist 函数给出了 KeyError
【解决方案2】:

使用递归函数可以做到:

def get_ascending_combinations(a):
    if len(a) == 0:
        return []
    for left in get_ascending_combinations(a[:-1]):
        yield left
        for right in a[-1]:
            if left[-1] < right:
                yield left + [right]
    for right in a[-1]:
        yield [right]
>>> for x in get_ascending_combinations([[0], [1, 4], [2]]):
...     print(x)
[0]
[0, 2]
[0, 1]
[0, 1, 2]
[0, 4]
[1]
[1, 2]
[4]
[2]

但是由于组合的数量是指数的,因此不可能将所有组合都作为一个列表:

>>> it = get_ascending_combinations(large_example)
>>> for x in range(10):
...     print(next(it))
[19]
[19, 93]
[19, 50]
[19, 50, 93]
[19, 91]
[19, 91, 93]
[19, 89]
[19, 89, 93]
[19, 89, 91]
[19, 89, 91, 93]

【讨论】:

  • 这个想法很有趣,但例如example_list2 的结果是错误的。
  • 当您删除大小为 1 的结果时,我没有发现差异。但根据first question,这无关紧要。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-23
  • 1970-01-01
  • 2013-11-19
  • 2017-09-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多