【问题标题】:Python: list comprehension to produce consecutive sublists of even numbersPython:列表推导生成偶数的连续子列表
【发布时间】:2018-04-23 13:22:20
【问题描述】:

我正在尝试练习 Python 练习,但使用列表推导来解决问题,而不是书中显示的初学者风格循环。有一个例子,它要求将一个数字列表放入一个偶数列表中,但是它们必须在子列表中,这样如果数字一个接一个地跟随而没有被奇数打断,它们应该被放入一起进入一个子列表:

my_list = [2,3,5,7,8,9,10,12,14,15,17,25,31,32]
desired_output = [[2],[8],[10,12,14],[32]]

因此,您可以在上面的所需输出中看到,10、12、14 是偶数,它们彼此接续而不会被奇数中断,因此它们一起放入子列表中。 8 的两边都有一个奇数,因此在移除赔率后它会单独放入一个子列表中。

我可以使用下面这样的列表理解轻松组合一个偶数列表,但我不知道如何将它放入子列表中,如所需的输出显示。有人可以建议使用列表理解(或生成器,我不介意哪个,因为我目前正在尝试学习两者)。谢谢!

evens = [x for x in my_list if x%2==0]
print(evens)
[2, 8, 10, 12, 14, 32]

【问题讨论】:

  • for 循环没有什么“初学者风格”。对于这个特殊问题,请使用itertools.groupby()
  • 我建议先学习旧循环,这是基础。 (假设您从 Comprehensions 开始)

标签: python python-3.x list list-comprehension


【解决方案1】:

正如 cmets 中所解释的,列表推导不应被视为“适合初学者” - 首先专注于使用简单的 for 循环编写逻辑。

准备就绪后,您可以查看基于理解的方法。这是一个:

from itertools import groupby

my_list = [2,3,5,7,8,9,10,12,14,15,17,25,31,32]

condition = lambda x: all(i%2==0 for i in x)
grouper = (list(j) for _, j in groupby(my_list, key=lambda x: x%2))

res = filter(condition, grouper)

print(list(res))

# [[2], [8], [10, 12, 14], [32]]

在此解决方案中要注意的要点是,在您调用 list(res) 之前不会计算任何内容。这是因为filter 和生成器推导是惰性的。

【讨论】:

  • 非常感谢您添加此解决方案。我的书在第 4 章中介绍了列表理解,因此与书后更可怕的主题相比,我认为它仍然是初学者(而且我也有点不耐烦)。这本书已经介绍了lambda和keys,但是我还没有见过groupby(虽然我习惯在SQL中使用groupby)。
  • @Shaken_not_stirred,您可能会发现itertools.groupby 上的docs 很有用。
【解决方案2】:

你提到还想学习生成器,所以这里有一个更易读的版本,恕我直言。

from itertools import groupby


def is_even(n):
    return n%2 == 0


def runs(lst):
    for even, run in groupby(lst, key=is_even):
        if even:
            yield list(run)


if __name__ == '__main__':
    lst = [2, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 25, 31, 32]
    res = list(runs(lst))
    print(res)

顺便说一句,如果您绝对、肯定地想要将其实现为列表推导式,那么这个解决方案很自然地不属于上述解决方案:

[list(run) for even, run in groupby(lst, key=is_even) if even]

【讨论】:

  • 这也是一个不错的选择。我理解定义函数和产量的概念。我只需要再次阅读 name 和 main (我见过它们,只是还没有使用它们)。
【解决方案3】:

如果您不想使用 itertools,还有另一种使用列表推导式的方法。

首先,取奇数元素的索引:

[i for i,x in enumerate(my_list) if x%2==1]

并添加两个哨兵:[-1] 之前和 [len(my_list)] 之后:

odd_indices = [-1]+[i for i,x in enumerate(my_list) if x%2==1]+[len(my_list)]
# [-1, 1, 2, 3, 5, 9, 10, 11, 12, 14]

你现在有这样的东西:

 [2,3,5,7,8,9,10,12,14,15,17,25,31,32]
^---^-^-^---^-----------^--^--^--^----^

你可以看到你的序列。现在,获取这些索引之间的元素。为此,请自行压缩odd_indices 以获取元组形式的区间:

zip(odd_indices, odd_indices[1:])
# [(-1, 1), (1, 2), (2, 3), (3, 5), (5, 9), (9, 10), (10, 11), (11, 12), (12, 14)]    

even_groups = [my_list[a+1:b] for a,b in zip(odd_indices, odd_indices[1:])]
# [[2], [], [], [8], [10, 12, 14], [], [], [], [32]]

您只需要过滤非空列表:

even_groups = [my_list[a+1:b] for a,b in zip(odd_indices, odd_indices[1:]) if a+1<b]
# [[2], [8], [10, 12, 14], [32]]

您可以将这两个步骤合并为一个理解列表,但这有点不可读:

>>> my_list = [2,3,5,7,8,9,10,12,14,15,17,25,31,32]
>>> [my_list[a+1:b] for l1 in [[-1]+[i for i,x in enumerate(my_list) if x%2==1]+[len(my_list)]] for a,b in zip(l1, l1[1:]) if b>a+1]
[[2], [8], [10, 12, 14], [32]]

正如@jpp 所指出的,在您感到舒服之前,请选择基本循环。并且也许永远避免那些嵌套列表推导......

【讨论】:

  • 感谢您将其分解为易于消化的块。它使它更容易理解。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-14
  • 1970-01-01
  • 2016-06-29
  • 2019-06-30
  • 2016-11-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多