【问题标题】:Sort a list by presence of items in another list根据另一个列表中的项目的存在对列表进行排序
【发布时间】:2020-02-13 15:27:22
【问题描述】:

假设我有两个列表:

a = ['30', '10', '90', '1111', '17']
b = ['60', '1201', '30', '17', '900']

你会如何最有效地排序,这样:

列表b 相对于a 排序。 b 中的唯一元素应放在排序列表的末尾。 a 中的唯一元素可以忽略。

示例输出:

c = ['30', '17', '60', '1201', '900']

抱歉,这是一个简单的问题。我的尝试停留在十字路口。

intersection = sorted(set(a) & set(b), key = a.index)

【问题讨论】:

  • 所以您只想保留a 中与b 重复的那些,然后将其余的b 添加到它们?
  • 如果我对问题的理解正确,sorted(b, key=(a+b).index) 应该这样做。
  • @jasonharper 是的,但这是二次时间,因为对每个元素都执行 .index
  • c = [i for i in a if i in b]+[i for i in b if i not in a] 。请注意,尽管您没有进行任何排序

标签: python list sorting


【解决方案1】:

这里不需要实际排序。您希望a 中的元素在b 中,与它们在a 中的顺序相同;紧随其后的是b 中不在a 中的元素,其顺序与b 中相同。

我们可以用两个过滤器来做这件事,使用集合进行快速成员测试:

>>> a = ['30', '10', '90', '1111', '17']
>>> b = ['60', '1201', '30', '17', '900']
>>> a_set = set(a)
>>> b_set = set(b)
>>> [*filter(lambda x: x in b_set, a), *filter(lambda x: x not in a_set, b)]
['30', '17', '60', '1201', '900']

或者,如果您更喜欢推导式:

>>> [*(x for x in a if x in b_set), *(x for x in b if x not in a_set)]
['30', '17', '60', '1201', '900']

两者都需要线性时间,这比排序要好。

【讨论】:

  • 不错,不知道filter(b_set.__contains__, a)是否更好
  • @Chris_Rands 当然;那应该稍微快一点。
  • 排序布尔值实际上也是线性时间。我挑战你找到ab,你的解决方案比我的更快,或者让我的看起来非线性:-)
  • 不错的一个;我稍后会尝试你的挑战,但更多的是出于好奇而不是竞争;我认为即使在最坏的情况下,您的解决方案也应该是线性时间。
  • 如果我理解正确,您的意思是 Timsort 保持两个(或更多?)运行并尝试将下一个元素附加到其中一个?它不这样做。那可能会变成 kayasort :-)。 Timsort 只是找到现有的运行,如果它们太小(很可能是随机数据),则进行二进制插入排序,然后合并它们。我现在已经在我的答案中添加了基准。
【解决方案2】:

您可以创建一个自定义字典,键是a 中的条目,值是它们的位置。然后根据字典中的值对b进行排序。您可以使用dict.get 进行查找,如果该值不存在,则使用inf

a = ['30', '10', '90', '1111', '17']
b = ['60', '1201', '30', '17', '900']

d = {i:ix for ix, i in enumerate(a)}
#{'30': 0, '10': 1, '90': 2, '1111': 3, '17': 4}
sorted(b, key=lambda x: d.get(x, float('inf')))
#['30', '17', '60', '1201', '900']

【讨论】:

    【解决方案3】:

    您的标题实际上比您的描述更清晰,可以直接翻译成代码:

    根据另一个列表中是否存在项目对列表进行排序

    代码:

    >>> sorted(b, key=set(a).__contains__, reverse=True)
    ['30', '17', '60', '1201', '900']
    

    >>> sorted(b, key=lambda x, s=set(a): x not in s)
    ['30', '17', '60', '1201', '900']
    

    排序布尔值实际上与线性时间没有区别,并且这些解决方案比您的示例数据以及我尝试使用数百万个随机数的示例数据(其中大约一半 b 的元素)都比公认的解决方案更快在a)。


    基准测试

       n    b in a   kaya1    kaya2    heap1    heap2    heap3
    ----------------------------------------------------------
       1024 53.12%  0.00046  0.00033  0.00020  0.00067  0.00018
       2048 51.03%  0.00142  0.00069  0.00048  0.00071  0.00060
       4096 50.34%  0.00226  0.00232  0.00127  0.00183  0.00125
       8192 50.42%  0.00938  0.00843  0.00328  0.00471  0.00351
      16384 50.38%  0.02010  0.01647  0.00776  0.00992  0.00839
      32768 49.96%  0.03987  0.03165  0.01661  0.02326  0.01951
      65536 50.20%  0.08002  0.06548  0.03326  0.04828  0.03896
     131072 50.04%  0.16118  0.12863  0.06671  0.09642  0.07840
     262144 50.06%  0.32698  0.26757  0.13477  0.19342  0.15828
     524288 50.08%  0.66735  0.54627  0.27378  0.38365  0.32496
    1048576 50.00%  1.34095  1.08972  0.54703  0.78028  0.65623
    2097152 50.03%  2.68957  2.20556  1.13797  1.60649  1.33975
    4194304 50.01%  5.36141  4.33496  2.25494  3.18520  2.70506
    8388608 49.99% 10.72588  8.74114  4.56061  6.35421  5.36515
    

    注意:

    • nb 的大小。
    • a 在对功能进行基准测试之前准备为set,以便专注于它们的差异。 a 的大小始终为 8388608 以保持 in a 检查恒定时间(即使 sets 变大时会变慢)。
    • b in ab 的元素在a 中的百分比。我把它们做成了大约 50%。
    • kaya1kaya2 来自@kaya3 接受的答案,经过修改以便他们完成我认为的任务(通过a 中的项目的存在对b 进行排序,而不是“a & b”后跟“ b \ a")。
    • heap1heap2 是我上面使用sorted 的两个解决方案。
    • heap3 是我能够编写的没有 sorted 的最快解决方案。
    • 结果以秒为单位。

    基准代码:

    from timeit import repeat
    import random
    
    def kaya1(a_set, b):
        return [*filter(lambda x: x in a_set, b), *filter(lambda x: x not in a_set, b)]
    
    def kaya2(a_set, b):
        return [*(x for x in b if x in a_set), *(x for x in b if x not in a_set)]
    
    def heap1(a_set, b):
        return sorted(b, key=a_set.__contains__, reverse=True)
    
    def heap2(a_set, b):
        return sorted(b, key=lambda x: x not in a_set)
    
    def heap3(a_set, b):
        not_in_a = []
        append = not_in_a.append
        in_a = [x for x in b if x in a_set or append(x)]
        in_a.extend(not_in_a)
        return in_a
    
    print('   n    b in a   kaya1    kaya2    heap1    heap2    heap3')
    print('----------------------------------------------------------')
    
    A = random.sample(range(2**24), 2**23)
    B = random.sample(range(2**24), 2**23)
    a_set = set(A)
    
    for e in range(10, 24):
        n = 2**e
        b = B[:n]
        print('%7d %5.2f%%' % (n, 100 * len(set(b) & a_set) / len(b)), end='')
        expect = None
        for sort in kaya1, kaya2, heap1, heap2, heap3:
            t = min(repeat(lambda: sort(a_set, b), number=1))
            print('%9.5f' % t, end='')
            output = sort(a_set, b)
            if expect is None:
                expect = output
            else:
                assert output == expect
        print()
    

    【讨论】:

    • 我突然想到我们两种解决方案的行为有所不同;我的保留列表a 中的相关订单,而您的保留列表b 中的相关订单。前者与 OP 尝试的代码相匹配,尽管从问题文本中不清楚是否有意使用其中一个。
    • @kaya3 是的,这就是我在写我对你的修改时的意思,以及为什么我引用标题并说这是最清楚的。他们的示例允许两种解释,我倾向于不相信人们的代码尝试,如果a 中有重复项,那么a.index 可能会很糟糕。也许您的解释是 OP 想要的,也许他们都可以,但我认为我的解释“更好”,或者至少更有可能是其他人在搜索并找到该标题时所寻找的。​​span>
    • 是的,我同意,而且我不会撤回我的投票:-p
    【解决方案4】:

    正如您暗示使用 set 时,在我看来这两个列表包含非重复项。然后你可以简单地做列表理解:

    c = [x for x in a if x in b] + [x for x in b if x not in a]
    

    然而,这是 O(n^2)。如果您的列表很大并且想要使其更快,请尝试分别构建一组ab 并使用它们进行成员检查。

    【讨论】:

    • 简单的一个,第一个想到的,
    • 你可以通过这样做来达到线性时间` [x for x in a if x in set(b)] + [x for x in b if x not in set(a)]`跨度>
    • @Ch3steR 这并不是线性的。这使它变慢了二次方。
    【解决方案5】:

    也许这应该可行。

    intersection = sorted(set(a) & set(b), key=a.index)
    intersection.extend([ele for ele in b if ele not in intersection])
    

    【讨论】:

      猜你喜欢
      • 2019-02-16
      • 1970-01-01
      • 1970-01-01
      • 2018-11-15
      • 1970-01-01
      • 1970-01-01
      • 2023-04-01
      • 2021-05-02
      相关资源
      最近更新 更多