2

I have found this function:

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)

At Trying to implement recursive Tower of Hanoi algorithm with arrays here on StackOverflow.

Now I need to modify this so that instead of the start, target, pegs being printed, the pegs variable is yielded out each iteration.

For example I expect this output out of the new function (pretty printed):

>>> 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]],
]

This is how I tried to modify it:

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)

But, (input pegs numbers are a bit bigger because of graphical purposes):

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

The output is just an empty list.

Trying to make copies of the lists via [:] did not help, and I am left very confused, maybe print can always print out, but yield gets "stuck" inside deep recursion levels, so it is yielded to less deep recursion and not to the outside. Also using a list with append does not work:

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

I also tried following advice from 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

By yielding from nested for loops, but it failed.

How can I write such generator (that I need for graphical purposes)?

The generator will be used like this:

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)
Community
  • 1
  • 1
Caridorc
  • 6,222
  • 2
  • 31
  • 46

1 Answers1

1

a generator version could look like this:

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)

with the output:

[[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]]

the only 'trick' here is to yield from hanoi_yield because hanoi_yield is a generator.

the down-side: this returns a reference to the same list all the time and changes the input list pegs (which is just the return value)! that may not be wished or useful... more below:


a version that does not change the first argument (pegs) and returns an individual list every time (and can therefore be used in the list constructor). i had to add an auxiliary variable _work_pegs as the algorithm needs to change the this list. pegs is now unchanged. i also yield a deepcopy of the result (we are handling lists of lists here; a regular copy would not work):

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)

finally this works:

pegs = [[120, 90, 60, 30], [], []]
lst = list(hanoi_yield(pegs, 0, 2, 4))
print(lst)
hiro protagonist
  • 44,693
  • 14
  • 86
  • 111
  • I tried your version (using explicit `for` because I am in Python 2 but i got `>>>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]]]` – Caridorc Jan 01 '17 at 19:51
  • That is calling `list` is not working as intended, how could I fix that? – Caridorc Jan 01 '17 at 19:52
  • I understand that Python treats lists in a counter-intuitive way, pointing to outdated objects, so you need to `yield a deepcopy of the restult (we are handling lists of lists here; a regular copy would not work):` but why did this problem only show when using `list` and not when traversing the items one by one? – Caridorc Jan 01 '17 at 20:45
  • the first version changed (the global) `pegs` all the time. there was only 1 list in play while the function was called again and again; you could watch its evolution with the print statement. if you `print(id(item), item)` in the first version you see, that you always print the same list - just its content has changed now. `list(...)` then generated a list with several references to the same list inside. – hiro protagonist Jan 01 '17 at 20:50
  • Thanks hiro, for the detailed explanation (I suggest you move it into the answer as comments are temporary and may be deleted). Just to double check, I understood that this very similar to C pointers, I point to a list into memory, and yield always that same object, but its contents vary and in the end all pointers refer to the same collection of values. Correct? – Caridorc Jan 01 '17 at 21:04
  • 1
    @Caridorc: yes, that is correct! made some small adaptions to my answer; feel free to change things to your liking if you feel something is missing or unclear. grazie e buona notte! – hiro protagonist Jan 01 '17 at 21:08
  • Buona notte, gentile compatriota :) – Caridorc Jan 01 '17 at 21:21