繁体   English   中英

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

[英]Fastest union for multiple sorted lists removing duplicates and get an ordered result

当有一个列表列表时,其中所有子列表都被排序,例如: [[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 中的更快的方法(比如:在做联合的同时进行排序)?

编辑:

建议的答案是否比对所有子列表成对执行以下算法更快? 在此处输入图片说明

您可以为此使用heapq.merge

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]

(已编辑)

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

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

因此,让我们考虑几种方法以及 do 如何针对某些具体输入执行。

方法

为了让您对我们正在讨论的数字有所了解,这里有一些方法:

  • merge_sorted()使用最朴素的方法展平序列,将其缩减为set() (删除重复项)并根据需要对其进行排序
import itertools


def merge_sorted(seqs):
    return sorted(set(itertools.chain.from_iterable(seqs)))

  • merge_heapq()本质上是@arshajii 的回答 请注意, itertools.groupby()变化略快(小于 ~1%)。
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))

  • merge_bisect_set()基本上与merge_sorted()算法相同,除了现在使用高效的bisect模块显式构造结果以进行排序插入。 由于sorted()基本上是在做同样的事情,但在 Python 中循环,这不会更快。
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

  • merge_bisect_cond()类似于merge_bisect_set()但现在非重复约束是使用 final list明确完成的。 然而,这比仅使用set()要昂贵得多(实际上它太慢以至于被排除在绘图之外)。
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

  • merge_pairwise()明确实现了理论上有效的算法,类似于您在问题中概述的算法。
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

  • merge_loop()实现了上述的泛化,现在 pass 只对所有序列执行一次,而不是成对执行。
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))

可以绘制变化n的时间:

bm_full bm_zoom

请注意,一般而言,性能会因n (每个序列的大小)和m (序列数量)以及ab (生成的最小和最大数量)的不同值而异。 为简洁起见,本答案中未对此进行探讨,但请在此处随意使用它,其中还包括一些其他实现,尤其是 Cython 的一些试探性加速,但仅部分成功。

您可以在 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]

首先使用列表添加合并所有子列表。

然后将其转换为一个集合对象,它将删除所有重复项,这些重复项也将按升序排序。

将其转换回列表。它提供您想要的输出。

希望它能回答你的问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM