【问题标题】:python3.5 - how can I yield from an arbitrary depth of recursion?python3.5 - 如何从任意深度的递归中产生?
【发布时间】:2020-12-17 13:27:34
【问题描述】:

我已经编写了一个函数来创建任意长度的输入组合,因此递归似乎是显而易见的方法。虽然一个小玩具示例返回结果列表是可以的,但我想代替它们。我读过yield from,但不完全理解它是如何使用的,这些示例似乎没有涵盖我的用例,并且希望将它插入我的代码还没有产生任何有效的东西。请注意,编写此递归代码是我 Python 能力的极限,因此需要大量调试打印语句。

这是工作列表返回代码,我希望的非工作收益已被注释掉。

def allposs(elements, output_length):
    """
    return all zero insertion paddings of elements up to output_length maintaining order

    elements -         an iterable of length >= 1
    output_length      >= len(elements)

    for instance allposs((3,1), 4) returns
    [[3,1,0,0], [3,0,1,0], [3,0,0,1], [0,3,1,0], [0,3,0,1], [0,0,3,1]]
    """

    output_list = []

    def place_nth_element(nth, start_at, output_so_far):
        # print('entering place_nth_element with nth =', nth,
        #      ', start_at =', start_at,
        #      ', output_so_far =', output_so_far)
        
        last_pos = output_length - len(elements) + nth
        # print('iterating over range',start_at, 'to', last_pos+1)
        for pos in range(start_at, last_pos+1):
            output = list(output_so_far)           
            # print('placing', elements[nth], 'at position', pos)
            output[pos] = elements[nth]

            if nth == len(elements)-1:
                # print('appending output', output)
                output_list.append(output)
                # yield output    
            else:
                # print('making recursive call')
                place_nth_element(nth+1, pos+1, output)
   
    place_nth_element(0, 0, [0]*output_length)
    return output_list

if __name__=='__main__':
    for q in allposs((3,1), 4):
        print(q)

使用yield from 让我的列表一次生成一个组合的语法是什么?

【问题讨论】:

  • 在您的函数中,您对 place_nth_element 进行了递归调用,但随后您丢弃了它返回的任何结果。您通常需要 yield from place_nth_element() 以便此调用返回递归调用返回的任何内容。
  • @FrankYellin 我认为这是我遇到困难的地方,递归调用不返回任何内容,它附加到顶层列表

标签: python recursion yield


【解决方案1】:

递归生成器是一个强大的工具,我很高兴您正在努力研究它们。

使用 yield from 让我的列表一次生成一个组合的语法是什么?

你把yield from放在表达式from前面,结果应该是yielded;在你的情况下,递归调用。因此:yield from place_nth_element(nth+1, pos+1, output)。这个想法是每个结果from递归调用的生成器被迭代(在幕后)和yielded 在这个过程中的这一点。

请注意,要使其正常工作:

  • 你需要yield递归基础级别的单个结果

  • 要从生成的生成器“收集”结果,您需要遍历顶层调用的结果。幸运的是,很多地方都内置了迭代。例如,您可以调用list,它会为您迭代。

与其将递归生成器嵌套在一个包装函数中,我更愿意将它写成一个单独的辅助函数。由于不再需要从递归中访问output_list,因此不需要形成闭包;和flat is better than nested 正如他们所说。然而,这确实意味着我们需要通过递归传递elements。我们不需要传递output_length,因为我们可以重新计算它(output_so_far 的长度在整个递归过程中是恒定的)。

此外,我发现在执行此类算法时,尽可能从功能上进行思考(在范式意义上 - 即避免副作用和可变性,并通过创建新对象继续进行)是有帮助的。您使用list 制作副本的方法可行(尽管使用.copy 方法更清晰),但我认为有一种更简洁的方法,如下所示。

所有这些建议都引导我们:

def place_nth_element(elements, nth, start_at, output_so_far):        
    last_pos = len(output_so_far) - len(elements) + nth
    for pos in range(start_at, last_pos+1):
        output = output_so_far[:pos] + (elements[nth],) + output_so_far[pos+1:]
        if nth == len(elements)-1:
            yield output    
        else:
            yield from place_nth_element(elements, nth+1, pos+1, output)


def allposs(elements, output_length):
    return list(place_nth_element(elements, 0, 0, (0,)*output_length))

但是,我不会那样解决问题 - 因为标准库已经提供了一个简洁的解决方案:我们可以找到值应该去的索引的itertools.combinations,然后插入它们。现在我们不再需要递归思考,我们可以继续改变值:)

from itertools import combinations

def place_values(positions, values, size):
    result = [0] * size
    for position, value in zip(positions, values):
        result[position] = value
    return tuple(result)


def possibilities(values, size):
    return [
        place_values(positions, values, size)
        for positions in combinations(range(size), len(values))
    ]

【讨论】:

  • 感谢您修复产量。我在其他地方使用 itertools,但没有发现此用例的组合使用。
  • 这只是你在一段时间后记住的那些技巧之一,真的。我不希望大多数人会不自觉地想到。
猜你喜欢
  • 2016-06-18
  • 1970-01-01
  • 1970-01-01
  • 2020-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-20
相关资源
最近更新 更多