Python 多个排序列表的最快联合删除重复项并获得有序结果

Python 多个排序列表的最快联合删除重复项并获得有序结果,python,python-3.x,Python,Python 3.x,当有一个所有子列表都已排序的列表列表时,例如: [[1,3,7,20,31],[1,2,5,6,7],[2,4,25,26] 获得这些列表的并集而又不包含重复项的最快方法是什么,以及获得有序结果的最快方法是什么? 因此,得出的清单应该是: [1,2,3,4,5,6,7,20,25,26,31]。 我知道我可以在不重复的情况下合并它们,然后对它们进行排序,但是python中有没有更快的方法(比如:在合并时进行排序) 编辑: 建议的答案是否比使用所有子列表成对执行以下算法更快? 您可以使用: def

当有一个所有子列表都已排序的列表列表时,例如:
[[1,3,7,20,31],[1,2,5,6,7],[2,4,25,26]
获得这些列表的并集而又不包含重复项的最快方法是什么,以及获得有序结果的最快方法是什么? 因此,得出的清单应该是:
[1,2,3,4,5,6,7,20,25,26,31]
。 我知道我可以在不重复的情况下合并它们,然后对它们进行排序,但是python中有没有更快的方法(比如:在合并时进行排序)

编辑:

建议的答案是否比使用所有子列表成对执行以下算法更快?

您可以使用:

def mymerge(v):
    from heapq import merge
    last = None
    for a in merge(*v):
        if a != last:  # remove duplicates
            last = a
            yield a

print(list(mymerge([[1,3,7,20,31], [1,2,5,6,7], [2,4,25,26]])))
# [1, 2, 3, 4, 5, 6, 7, 20, 25, 26, 31]

可以在Python-3中使用集合

mylist = [[1,3,7,20,31], [1,2,5,6,7], [2,4,25,26]]

mynewlist = mylist[0] + mylist[1] + mylist[2]

print(list(set(mynewlist)))
输出:

[1, 2, 3, 4, 5, 6, 7, 20, 25, 26, 31]
首先使用列表添加合并所有子列表

然后将其转换为set对象,在该对象中,它将删除所有重复项,这些重复项也将按升序排序

将其转换回列表。它提供您所需的输出

希望它能回答你的问题。

(编辑)

解决这个问题的最佳渐进理论方法是使用优先级队列,例如,在
heapq.merge()
中实现的队列(感谢@kaya3指出这一点)

然而,在实践中,许多事情可能会出错。例如,复杂性分析中的常数因子足够大,以至于在现实生活场景中,理论上最优的方法速度较慢。 这从根本上取决于实施情况。 例如,Python会因为显式循环而遭受一些速度损失

这样,让我们考虑几个方法,以及如何执行一些具体的输入。< /P> 方法 为了让您了解我们正在讨论的数字,以下是一些方法:

  • merge\u sorted()
    使用最简单的方法将序列展平,将其简化为
    set()
    (删除重复项)并根据需要对其进行排序

  • merge\u heapq()
    。请注意,
    itertools.groupby()
    的变化速度稍快(小于~1%)

  • merge\u bisect\u set()
    基本上与
    merge\u sorted()
    的算法相同,只是现在使用高效的
    bisect
    模块显式构造排序插入的结果。由于
    sorted()
    基本上做的是相同的事情,但在Python中是循环的,所以这不会更快

  • merge\u bisect\u cond()
    类似于
    merge\u bisect\u set()
    ,但现在使用最终的
    列表显式完成非重复约束。然而,这比仅仅使用
    set()
    (事实上它太慢了,以至于被排除在绘图之外)要昂贵得多

  • merge\u pairwise()
    显式实现了理论上高效的算法,与您在问题中概述的类似

基准 通过使用以下命令生成输入:

def gen_input(n, m=100, a=None, b=None):
    if a is None and b is None:
        b = 2 * n * m
        a = -b
    return tuple(tuple(sorted(set(random.randint(int(a), int(b)) for _ in range(n)))) for __ in range(m))
可以绘制不同
n
的计时:

请注意,通常情况下,
n
(每个序列的大小)和
m
(序列的数量)的不同值,以及
a
b
(生成的最小和最大数量)的不同值,性能会有所不同。
为简洁起见,本回答中未对此进行探讨,但请随意使用它,其中还包括一些其他实现,特别是一些使用Cython的尝试性加速,但仅部分成功。

它在哪些方面不是Pythonic?@AlexandrShurigin这怎么不是Pythonic?它很好地使用了标准库,并且优雅地使用了生成器。“我得说它很像蟒蛇。”亚历山大·舒里金“看起来很丑”?看起来不错。简单、自文档化和Pythonic。这恰好是正确的…@blhsing一个好的解决方案是使用
last=object()
而不是
last=None
您可以使用
itertools.groupby
来消除重复项-
[x代表x,在itertools.groupby(merge(*v))]
是一个单行程序。这是非常低效的二次时间(不要使用连接来合并列表列表)这是不正确的,因为它不是sorted,我认为您可以通过将给定列表解包到set构造函数中来实现,但是您仍然需要将解包后的列表转换为单个迭代器。然后将final语句中的“list”更改为
sorted
,返回列表。请用i的转录替换图像t、 “如果您追求的是理论上最有效的算法,即只遍历输入一次并在经过时构造输出的成对连接,那么它可能是最有效的(假设是O(1)追加操作)。”否;迭代地将多个列表合并到输出中意味着您最终将合并长度为O(N)的列表其中N是项目的总数,因此时间将是O(Nm),其中m是列表的数量。使用优先级队列的解决方案,例如
heapq.merge
,需要O(N log m)时间。为了进行比较,简单的
排序(set(…)
方法是O(N log N)。@kaya3我相信
heapq.merge
实际上是
O(Nm log Nm)
No,
heapq.merge
不会一次将所有N个元素插入堆中;它只需要每个序列中当前最小的
m
元素。您可以阅读源代码:它构建一个大小为m的堆,然后为每个项调用
\u heapreplace
,这会同时执行插入和移除操作,将堆保留为大小为m。不,成对连接是O(Nm)和
heapq。merge
是O(N log m)。后者总是渐进地更好。好吧,我忽略了
heapq.merge
的内部堆的大小是
m
。我现在确实确信:-)
import heapq


def i_merge_heapq(seqs):
    last_item = None
    for item in heapq.merge(*seqs):
        if item != last_item:
            yield item
            last_item = item

def merge_heapq(seqs):
    return list(i_merge_heapq(seqs))
import itertools
import bisect


def merge_bisect_set(seqs):
    result = []
    for item in set(itertools.chain.from_iterable(seqs)):
        bisect.insort(result, item)
    return result
def merge_bisect_cond(seqs):
    result = []
    for item in itertools.chain.from_iterable(seqs):
        if item not in result:
            bisect.insort(result, item)
    return result
def join_sorted(seq1, seq2):
    result = []
    i = j = 0
    len1, len2 = len(seq1), len(seq2)
    while i < len1 and j < len2:
        if seq1[i] < seq2[j]:
            result.append(seq1[i])
            i += 1
        elif seq1[i] > seq2[j]:
            result.append(seq2[j])
            j += 1
        else:  # seq1[i] == seq2[j]
            result.append(seq1[i])
            i += 1
            j += 1
    if i < len1:
        result.extend(seq1[i:])
    elif j < len2:
        result.extend(seq2[j:])
    return result


def merge_pairwise(seqs):
    result = []
    for seq in seqs:
        result = join_sorted(result, seq)
    return result
def merge_loop(seqs):
    result = []
    lengths = list(map(len, seqs))
    idxs = [0] * len(seqs)
    while any(idx < length for idx, length in zip(idxs, lengths)):
        item = min(
            seq[idx]
            for idx, seq, length in zip(idxs, seqs, lengths) if idx < length)
        result.append(item)
        for i, (idx, seq, length) in enumerate(zip(idxs, seqs, lengths)):
            if idx < length and seq[idx] == item:
                idxs[i] += 1
    return result
def gen_input(n, m=100, a=None, b=None):
    if a is None and b is None:
        b = 2 * n * m
        a = -b
    return tuple(tuple(sorted(set(random.randint(int(a), int(b)) for _ in range(n)))) for __ in range(m))