Python中的递归列表推导式?

41

在Python中是否可能定义一个递归的列表推导式?

可能是一个简单的例子,但可以像这样:

nums = [1, 1, 2, 2, 3, 3, 4, 4]
willThisWork = [x for x in nums if x not in self] # self being the current comprehension

有没有类似这样的可能性?


4
如果顺序不重要,可以使用 list(set(num))。否则,请查看 http://docs.python.org/library/itertools.html 中的 unique_everseen - kennytm
泄漏的抽象警告。在我看来,理解不应该被视为循环,即使它们可能在CPython中作为循环实现。 - wim
7个回答

34

不,没有(文档化的、可靠的、稳定的......;-)方法来引用“当前理解”。你可以使用一个循环:

res = []
for x in nums:
  if x not in res:
    res.append(x)

当然,这非常耗费资源(O(N的平方)),所以您可以使用辅助set进行优化(我假设保持res中项目顺序与nums中项目顺序一致,否则set(nums)将为您服务;-)...

res = []
aux = set()
for x in nums:
  if x not in aux:
    res.append(x)
    aux.add(x)

这对于非常长的列表来说速度快得多(O(N)而不是N平方)。

编辑:在Python 2.5或2.6中,vars()['_[1]']实际上可能会在您想要的self角色(对于非嵌套的列表推导式)中起作用......这就是为什么我限定了我的陈述,并澄清没有文档化,稳定的方法来访问“正在建立的列表” - 那个奇特的、未记录的“名称”'_[1]'(故意选择不是一个有效的标识符;-)是“实现工件”的顶点,任何依赖它的代码都应该被淘汰。;-)


2
集合操作使其为O(n log(n)),我非常确定。 - dash-tom-bang
2
据我所知,Python中的@dash-tom-bang集合并不像STL那样实现为红黑树,而是使用哈希表,因此时间复杂度为O(N)。 - Justin Peel
1
@Justin 是正确的 - Python 的集合和字典是经过优化的哈希表,添加元素的平摊成本为 O(1),查找的成本也为 O(1)。 - Alex Martelli

13

Python 3.8 开始,引入了赋值表达式(PEP 572):= 运算符),这使得我们有可能通过更新列表推导式中的变量来引用已经看到的项目:

# items = [1, 1, 2, 2, 3, 3, 4, 4]
acc = []; [acc := acc + [x] for x in items if x not in acc]
# acc = [1, 2, 3, 4]

这个函数:

  • 初始化一个列表acc,代表已经被查看过的元素列表
  • 对于每个元素,检查它是否已经在acc列表中;如果不是:
    • 使用赋值表达式将该项添加到acc中(acc := acc + [x])
    • 同时使用acc的新值作为该项的映射值

1
非常棒,将来一定会使用。 - Sebastian Serrano

10

实际上你是可以的!这个例子及其解释应该会很好地说明如何做到。

定义递归例子,只有当数字大于等于5时才获取它,如果不是,则将其增加并再次调用“check”函数。重复此过程直到它达到5,此时返回5。

print [ (lambda f,v: v >= 5 and v or f(f,v+1))(lambda g,i: i >= 5 and i or g(g,i+1),i) for i in [1,2,3,4,5,6] ]

结果:

[5, 5, 5, 5, 5, 6]
>>> 

本质上,这两个匿名函数是这样互动的:

let f(g,x) = {  
                 expression, terminal condition
                 g(g,x), non-terminal condition
             }

let g(f,x) = {  
                 expression, terminal condition
                 f(f,x), non-terminal condition
             }

让函数g,f“相同”,除了在一个或两个函数中都添加一个子句,使得参数被修改以导致达到终止条件和执行f(g,x),通过这种方式g成为f的副本,变得像:

f(g,x) = {  
                 expression, terminal condition
                 {
                    expression, terminal condition,
                    g(g,x), non-terminal codition
                 }, non-terminal condition
             }

你需要这样做,因为在执行时无法访问匿名函数本身。

(lambda f,v: somehow call the function again inside itself )(_,_)

所以在这个例子中,令A为第一个函数,B为第二个。我们称将B传递给A的过程为f,i作为v。现在由于B本质上是A的一个副本并且它是一个被传递的参数,你可以像调用A一样调用B。

这将生成一个阶乘列表。

print [ (lambda f,v: v == 0 and 1 or v*f(f,v-1))(lambda g,i: i == 0 and 1 or i*g(g,i-1),i) for i in [1,2,3,5,6,7] ]

[1, 2, 6, 120, 720, 5040]
>>> 

2
克隆 lambda 是不必要的;您可以使用通用代理作为第一个 lambda,以允许任何类型的第二个 lambda 调用自身。 (lambda f,arg: f(f,arg))(lambda self,v: .... , firstvalue) - Sean Gugler

2

不确定这是否是您想要的,但您可以编写嵌套的列表推导式:

xs = [[i for i in range(1,10) if i % j == 0] for j in range(2,5)]
assert xs == [[2, 4, 6, 8], [3, 6, 9], [4, 8]]

根据您提供的代码示例,您似乎想要简单地消除重复项,这可以使用集合来完成:

xs = sorted(set([1, 1, 2, 2, 3, 3, 4, 4]))
assert xs == [1, 2, 3, 4]

1

不。

但是看起来你正在尝试制作nums中唯一元素的列表。

你可以使用一个set

unique_items = set(nums)

请注意,nums 中的项需要是可哈希的。
您也可以执行以下操作。这是我能接近您原始想法的方式。但这不如创建一个 set 高效。
unique_items = []
for i in nums:
    if i not in unique_items:
        unique_items.append(i)

1
做这个:
nums = [1, 1, 2, 2, 3, 3, 4, 4]
set_of_nums = set(nums)
unique_num_list = list(set_of_nums)

或者甚至是这个:

unique_num_list = sorted(set_of_nums)

列表推导式是不必要的。unique_num_list = list(set_of_nums)sorted(set_of_nums) 返回一个列表。 - Gary Kerr

1

不行,因为列表推导式执行时没有 self 可以引用。

当然,主要原因是列表推导式不是为此设计的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接