【问题标题】:Sorting a list of dicts based on another list of dicts in Python根据 Python 中的另一个 dicts 列表对 dicts 列表进行排序
【发布时间】:2017-12-27 21:49:38
【问题描述】:

我有 2 个列表

A = [{'g': 'goal'}, {'b': 'ball'}, {'a': 'apple'}, {'f': 'float'}, {'e': 'egg'}]
B = [{'a': None}, {'e': None}, {'b': None}, {'g': None}, {'f': None}]

我想根据 B 对 A 进行排序。我问这个的原因是,我不能简单地将 B 的内容复制到 A 并用 None 覆盖 A 的对象值。我想保留 A 的值,但按照 B 的顺序对其进行排序。

我如何实现这一目标?更喜欢 Python 中的解决方案

【问题讨论】:

  • 为什么你有一个一键字典的列表呢?这听起来像是一个 OrderedDict 的工作,或者可能是一个元组列表。

标签: python arrays dictionary


【解决方案1】:
spots = {next(iter(d)): i for i, d in enumerate(B)}
sorted_A = [None] * len(A)
for d in A:
    sorted_A[spots[next(iter(d))]] = d

平均情况线性时间。将每个 dict 直接放到它需要去的地方,而不需要缓慢的 index 调用,甚至调用 sorted

【讨论】:

    【解决方案2】:

    您可以将键的索引存储在字典中并使用sorting function 中的索引。这将在O(n log(n)) 时间起作用:

    >>> keys = {next(iter(v)): i for i, v in enumerate(B)}
    >>> keys
    {'a': 0, 'e': 1, 'b': 2, 'g': 3, 'f': 4}    
    >>> A.sort(key=lambda x: keys[next(iter(x))])
    >>> A
    [{'a': 'apple'}, {'e': 'egg'}, {'b': 'ball'}, {'g': 'goal'}, {'f': 'float'}]
    

    【讨论】:

    • 不是 O(n),因为它正在调用 sorted。不过仍然优于 O(n**2)。
    • 是的,它是O(n log(n))。但是,对于小的输入,使用 sorted() 可能比纯 Python O(n) 解决方案更快。
    【解决方案3】:

    您可以通过迭代B 中现有的有序键来避免排序:

    1. 将列表 A 合并到单个查找字典中
    2. 根据B 中的顺序构建一个新列表,使用查找字典查找与每个键匹配的值

    代码:

    import itertools
    
    merged_A = {k: v for d in A for k, v in d.items()}
    sorted_A = [{k: merged_A[k]} for k in itertools.chain.from_iterable(B)]
    # [{'a': 'apple'}, {'e': 'egg'}, {'b': 'ball'}, {'g': 'goal'}, {'f': 'float'}]
    

    如果需要,您可以保留来自 A 的原始 dict 对象,而不是构建新对象:

    keys_to_dicts = {k: d for d in A for k in d}
    sorted_A = [keys_to_dicts[k] for k in itertools.chain.from_iterable(B)]
    

    【讨论】:

    • sum 是一种非常糟糕的(二次时间)连接列表的方式。不过,从 A 构建查找表,然后在 B 上的传递中使用它比我对从 B 构建并在 A 上的传递中使用的查找表所做的更好。
    • 使用itertools 很好。我认为chain.from_iterable 会使该列表变平。感谢您的编辑。
    • 啊,哎呀。我一直忘记这一点。谢谢你提醒我。
    【解决方案4】:

    这个怎么样?在A 上创建一个查找字典,然后使用B 的键以正确的顺序创建一个新列表。

    In [103]: lookup_list = {k : d for d in A for k in d}
    
    In [104]: sorted_list = [lookup_list[k] for d in B for k in d]; sorted_list
    Out[104]: [{'a': 'apple'}, {'e': 'egg'}, {'b': 'ball'}, {'g': 'goal'}, {'f': 'float'}]
    

    性能

    设置:

    import random
    import copy
    
    x = list(range(10000)) 
    random.shuffle(x)
    
    A = [{str(i) : 'test'} for i in x] 
    B = copy.deepcopy(A)
    random.shuffle(B)
    
    # user2357112's solution
    %%timeit
    spots = {next(iter(d)): i for i, d in enumerate(B)}
    sorted_A = [None] * len(A)
    for d in A:
        sorted_A[spots[next(iter(d))]] = d
    
    # Proposed in this post
    %%timeit
    lookup_list = {k : d for d in A for k in d}
    sorted_list = [lookup_list[k] for d in B for k in d]; sorted_list
    

    结果:

    100 loops, best of 3: 9.27 ms per loop
    100 loops, best of 3: 4.92 ms per loop
    

    比原来的 O(n) 提速 45%,空间复杂度是原来的两倍。

    【讨论】:

    • 所有这些index 调用使此运行总体上以二次时间运行。
    • @user2357112 在 O(n) 时间内找到了更好的解决方案。
    猜你喜欢
    • 2018-07-12
    • 1970-01-01
    • 2021-02-04
    • 1970-01-01
    • 1970-01-01
    • 2021-11-25
    • 2018-11-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多