【问题标题】:Python: translating a printing recursive function into a generatorPython:将打印递归函数转换为生成器
【发布时间】:2017-05-16 01:20:10
【问题描述】:

我找到了这个功能:

def hanoi(pegs, start, target, n):
    assert len(pegs[start]) >= n, 'not enough disks on peg'
    if n == 1:
        pegs[target].append(pegs[start].pop())
        print '%i -> %i: %s' % (start, target, pegs)
    else:
        aux = 3 - start - target  # start + target + aux = 3
        hanoi(pegs, start, aux, n-1)
        hanoi(pegs, start, target, 1)
        hanoi(pegs, aux, target, n-1)

Trying to implement recursive Tower of Hanoi algorithm with arrays 在 StackOverflow 上。

现在我需要对其进行修改,以便每次迭代都生成 pegs 变量,而不是打印 start, target, pegs

例如,我希望这个输出来自新函数(打印得很漂亮):

>>> list( hanoi([[120, 90, 60, 30], [], []]) )
[ [[120, 90, 60], [30], []],
  [[120, 90], [30], [60]],
  [[120, 90], [], [60, 30]],
  [[120], [90], [60, 30]],
  [[120, 30], [90], [60]],
  [[120, 30], [90, 60], []],
  [[120], [90, 60, 30], []],
  [[], [90, 60, 30], [120]],
  [[], [90, 60], [120, 30]],
  [[60], [90], [120, 30]],
  [[60, 30], [90], [120]],
  [[60, 30], [], [120, 90]],
  [[60], [30], [120, 90]],
  [[], [30], [120, 90, 60]],
  [[], [], [120, 90, 60, 30]],
]

这是我尝试修改它的方式:

def hanoi(pegs, start, target, n):
    assert len(pegs[start]) >= n, 'not enough disks on peg'
    if n == 1:
        pegs[target].append(pegs[start].pop())
        yield pegs
    else:
        aux = 3 - start - target  # start + target + aux = 3
        hanoi(pegs, start, aux, n-1)
        hanoi(pegs, start, target, 1)
        hanoi(pegs, aux, target, n-1)

但是,(由于图形目的,输入的钉子数字有点大):

>>> pegs = [[120, 90, 60, 30], [], []]
>>> print(list(hanoi(pegs, 0, 2, 4)))
[]

输出只是一个空列表。

试图通过[:] 复制列表没有帮助,我很困惑,也许print 总是可以打印出来,但是yield 在深度递归级别中“卡住”了,所以它是产生了较少的深度递归,而不是outside。同样使用带有append 的列表也不起作用:

def hanoi(pegs, start, target, n):
    assert len(pegs[start]) >= n, 'not enough disks on peg'
    out = []
    if n == 1:
        pegs = pegs[:]
        pegs[target].append(pegs[start].pop())
        out.append( pegs )
    else:
        aux = 3 - start - target  # start + target + aux = 3
        hanoi(pegs, start, aux, n-1)
        hanoi(pegs, start, target, 1)
        hanoi(pegs, aux, target, n-1)
    return out

我也尝试过听从Python: using a recursive algorithm as a generator的建议

def hanoi(pegs, start, target, n):
    assert len(pegs[start]) >= n, 'not enough disks on peg'
    if n == 1:
        pegs = pegs[:]
        pegs[target].append(pegs[start].pop())
        yield pegs
    else:
        aux = 3 - start - target  # start + target + aux = 3
        for i in hanoi(pegs, start, aux, n-1): yield i
        for i in hanoi(pegs, start, target, 1): yield i
        for i in hanoi(pegs, aux, target, n-1): yield i

通过从嵌套的for 循环产生,但它失败了。

我如何编写这样的生成器(我需要用于图形目的)?

生成器会这样使用:

pegs = [[120, 90, 60, 30], [], []]
positions = hanoi(pegs, 0, 2, 4)

for position in positions:
    screen.fill((255, 255, 255)) 

    print(index, peg_history[index])
    for i, pegs in enumerate(position):
        display_pegs(pegs, 100 + 180*i, 300, screen)
    pygame.display.update()
    time.sleep(0.5)

【问题讨论】:

    标签: python recursion generator towers-of-hanoi


    【解决方案1】:

    生成器版本可能如下所示:

    def hanoi_yield(pegs, start, target, n):
        # pegs will be modified!
        assert len(pegs[start]) >= n, 'not enough disks on peg'
    
        if n == 1:
            pegs[target].append(pegs[start].pop())
            yield pegs
        else:
            aux = 3 - start - target  # start + target + aux = 3
            yield from hanoi_yield(pegs, start, aux, n-1)
            yield from hanoi_yield(pegs, start, target, 1)
            yield from hanoi_yield(pegs, aux, target, n-1)
    
    pegs = [[120, 90, 60, 30], [], []]
    for item in hanoi_yield(pegs, 0, 2, 4):
        print(item)
    

    输出:

    [[120, 90, 60], [30], []]
    [[120, 90], [30], [60]]
    [[120, 90], [], [60, 30]]
    [[120], [90], [60, 30]]
    [[120, 30], [90], [60]]
    [[120, 30], [90, 60], []]
    [[120], [90, 60, 30], []]
    [[], [90, 60, 30], [120]]
    [[], [90, 60], [120, 30]]
    [[60], [90], [120, 30]]
    [[60, 30], [90], [120]]
    [[60, 30], [], [120, 90]]
    [[60], [30], [120, 90]]
    [[], [30], [120, 90, 60]]
    [[], [], [120, 90, 60, 30]]
    

    这里唯一的“技巧”是yield from hanoi_yield,因为hanoi_yield 是一个生成器。

    缺点:这会一直返回对同一个列表的引用并更改输入列表pegs(这只是返回值)!这可能不希望或有用...更多如下:


    不更改第一个参数 (pegs) 并且每次都返回一个单独的列表的版本(因此可以在 list 构造函数中使用)。我不得不添加一个辅助变量_work_pegs,因为算法需要更改此列表。 pegs 现在没有改变。我还 yield a deepcopy 的结果(我们在这里处理列表列表;常规副本不起作用):

    from copy import deepcopy
    
    def hanoi_yield(pegs, start, target, n, _work_pegs=None):
    
        if _work_pegs is None:
            _work_pegs = deepcopy(pegs)
            # or (this way pegs could be a tuple of tuples):
            # _work_pegs = [list(item) for item in pegs]
    
        assert len(_work_pegs[start]) >= n, 'not enough disksdef on peg'
    
        if n == 1:
            _work_pegs[target].append(_work_pegs[start].pop())
            yield deepcopy(_work_pegs)
            # or (returning tuples might be nice...):
            # yield tuple(tuple(item) for item in _work_pegs)
        else:
            aux = 3 - start - target  # start + target + aux = 3
            yield from hanoi_yield(pegs, start, aux, n-1, _work_pegs)
            yield from hanoi_yield(pegs, start, target, 1, _work_pegs)
            yield from hanoi_yield(pegs, aux, target, n-1, _work_pegs)
    

    终于成功了:

    pegs = [[120, 90, 60, 30], [], []]
    lst = list(hanoi_yield(pegs, 0, 2, 4))
    print(lst)
    

    【讨论】:

    • 我试过你的版本(使用明确的for,因为我在Python 2中,但我得到了>>>pegs = [[120, 90, 60, 30], [], []]>>>print(list(hanoi(pegs, 0, 2, 4)))[[[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]], [[], [], [120, 90, 60, 30]]]
    • 调用list 没有按预期工作,我该如何解决?
    • 我知道 Python 以一种违反直觉的方式处理列表,指向过时的对象,所以你需要 yield a deepcopy of the restult (we are handling lists of lists here; a regular copy would not work): 但为什么这个问题只在使用 list 时出现,而不是在遍历一项一项?
    • 第一个版本一直在变化(全球)pegs。当函数被一次又一次地调用时,只有 1 个列表在播放;你可以通过 print 语句观察它的演变。如果您在看到的第一个版本中print(id(item), item),您总是打印相同的列表 - 只是它的内容现在发生了变化。 list(...) 然后生成了一个列表,其中包含对同一列表的多个引用。
    • @Caridorc:是的,没错!对我的回答做了一些小的修改;如果您觉得缺少某些内容或不清楚,请随意更改您喜欢的内容。 grazie e buona notte!
    猜你喜欢
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-06
    • 2016-01-06
    • 1970-01-01
    • 1970-01-01
    • 2016-04-03
    相关资源
    最近更新 更多