【问题标题】:Is there more clever way to merge these two lists in Python? [duplicate]有没有更聪明的方法可以在 Python 中合并这两个列表? [复制]
【发布时间】:2021-01-23 00:48:09
【问题描述】:

我有两个列表,我正在尝试用它们创建一个大列表。 第一个列表只是给了我每个父母可以拥有的所有可能的孩子数量。将其视为标签。

num_of_children = [0, 1, 2, 3, 4, 5]

第二个列表告诉我有多少父母有多少孩子。例如,27 个父母有 0 个孩子,其中 22 个有 1 个孩子,以此类推。

number_of_parents = [27, 22, 30, 12, 7, 2]

使用这两个列表,我试图得到一个如下所示的列表:

parent_num_of_children = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 
3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5]

到目前为止,我能够做到这一点:

for number in num_of_children:
    parent_num_of_children.extend([number] * number_of_parents[number])

我的问题是:是否有另一种方法可以在不使用 for 循环的情况下获取此列表,只需使用 range 函数或其他巧妙的方法?

感谢您的回答!

【问题讨论】:

  • num_of_children 是否始终只是排序后的整数 0len(number_of_parents) - 1?或者这只是一个糟糕的示例和代码?

标签: python python-3.x


【解决方案1】:

有一些itertools

list(chain.from_iterable(map(repeat, num_of_children, number_of_parents)))

基准测试:

0.23 s  0.35 s  0.33 s  original
0.67 s  0.64 s  0.72 s  Bram_Vanroy
1.36 s  1.48 s  1.52 s  Fredericka
0.29 s  0.35 s  0.34 s  superb_rain

最后查看更多基准。

代码:

import timeit
from itertools import chain, repeat

def original(num_of_children, number_of_parents):
    parent_num_of_children = []
    for number in num_of_children:
        parent_num_of_children.extend([number] * number_of_parents[number])
    return parent_num_of_children

def Bram_Vanroy(num_of_children, number_of_parents):
    return [c for c, p in zip(num_of_children,number_of_parents) for _ in range(p)]

def Fredericka(num_of_children, number_of_parents):
    parent_num_of_children = []
    for i in range(len(number_of_parents)):
        for n in range(number_of_parents[i]):
            parent_num_of_children.append(num_of_children[i])
    return parent_num_of_children

def superb_rain(num_of_children, number_of_parents):
    return list(chain.from_iterable(map(repeat, num_of_children, number_of_parents)))

funcs = original, Bram_Vanroy, Fredericka, superb_rain
num_of_children = [0, 1, 2, 3, 4, 5]
number_of_parents = [27, 22, 30, 12, 7, 2]

# Correctness
expect = original(num_of_children, number_of_parents)
for func in funcs:
    result = func(num_of_children, number_of_parents)
    print(result == expect, func.__name__)
print()

# Speed
tss = [[] for _ in funcs]
for _ in range(4):
    for func, ts in zip(funcs, tss):
        t = min(timeit.repeat(lambda: func(num_of_children, number_of_parents), number=100000))
        ts.append(t)
        print(*('%.2f s ' % t for t in ts[1:]), func.__name__)
    print()

另一个基准测试,“更大”的情况num_of_children = [0, 1, 2, 3, 4, 5] * 100number_of_parents = [27, 22, 30, 12, 7, 2] * 100(和number=1000):

0.25 s  0.17 s  0.16 s  original
0.57 s  0.41 s  0.40 s  Bram_Vanroy
1.22 s  1.19 s  1.17 s  Fredericka
0.16 s  0.16 s  0.17 s  superb_rain

还有一个,我改为使用number_of_parents = [p * 100 for p in number_of_parents](以及number=1000)增加值:

0.09 s  0.09 s  0.09 s  original
0.46 s  0.38 s  0.38 s  Bram_Vanroy
1.27 s  1.56 s  1.22 s  Fredericka
0.07 s  0.07 s  0.09 s  superb_rain

根据@BramVanroy 的commentnum_of_children = [i for i in range(100)]; number_of_parents = [random.randint(500,1000) for _ in range(100)](和number=100)建议的数据:

0.06 s  0.05 s  0.05 s  original
0.27 s  0.25 s  0.25 s  Bram_Vanroy
0.91 s  0.89 s  0.90 s  Fredericka
0.05 s  0.05 s  0.05 s  superb_rain

【讨论】:

  • 为这么小的列表计时可能意义不大。
  • @juanpa.arrivillaga 也许吧。不久前,别人的二次时间解决方案击败了我的线性时间解决方案,因为事实证明 OP 的数据实际上总是很小。无论如何,建议一些更大的东西,我会测试它:-)。我怀疑 repeat-in-C 仍然会击败 repeat-in-Python 。
  • 这也是我的直觉,除非,比如说,如果没有重复,你有一些东西几乎不适合记忆,而重复会与重复交换
  • 您可以很容易地使用更大的列表进行测试,例如num_of_children = [i for i in range(100)]; number_of_parents = [random.randint(500,1000) for _ in range(100)]。 (可能需要及时减少试验次数才能在合理的时间内做到这一点。)在我测试的所有值中,结果都是相同的。如前所述,我对结果印象深刻。如果您有任何直觉,为什么结果会是这样(为什么 extend 这么快,为什么 chain.from_iterable 更快),我很高兴听到。 +1
  • @BramVanroy 列表重复,some_list * n非常快。它基本上是一个 C 级循环,预先分配缓冲区(因此无需重新调整大小)并使用指针算法快速复制原始 PyObject 指针数组中的指针。
【解决方案2】:

无需扩展现有列表(相对较慢)(我的立场已得到纠正,请参阅出色的 Rain 的回答),您可以在列表理解中执行以下操作。使用range 避免了以后需要展平子列表。

num_of_children = [0, 1, 2, 3, 4, 5]
number_of_parents = [27, 22, 30, 12, 7, 2]
parent_num_of_children = [c for c, p in zip(num_of_children,number_of_parents) for _ in range(p)]
# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5]

【讨论】:

  • 声称它们的扩展速度相对较慢,确实需要一个基准。
  • @superbrain 除了必须在扩展之前创建新列表之外,扩展现有列表肯定比列表理解要慢。我会对此下注,但没有时间编写基准代码。不过,很想被证明是错误的。
  • @BramVanroy .extend 相对较慢。正如您所指出的,可能会减慢速度的是它们在扩展之前创建了一个列表
  • @BramVanroy 但是您的列表理解在 Python 代码中创建了每个元素,而列表重复和列表扩展在 C 代码中完成。现在在我的答案中查看基准。
  • @superbrain 是的,列表重复是最快的方式来完成它的工作
【解决方案3】:

这是一个简单的解决方案:

for i in range(len(number_of_parents)):
    for n in range(number_of_parents[i]):
        parent_num_of_children.append(num_of_children[i])

【讨论】:

  • OP 明确要求在没有 for 循环的情况下执行此操作,您的解决方案包括...两个 for 循环。
【解决方案4】:

此列表推导式无需任何额外工具即可解决问题:

parent_num_of_children = [num_of_children[i] for i in range(len(number_of_parents)) for _ in range(number_of_parents[i])]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-03
    • 2023-03-21
    • 2020-08-28
    • 1970-01-01
    • 2017-12-11
    • 2021-11-17
    • 1970-01-01
    • 2011-12-22
    相关资源
    最近更新 更多