詳細な実装
一見すると、現在のソースから現在のターゲットに直接移動できる場合と、最大のディスクを2つのステップで移動する必要がある場合を区別する必要があるように見えます。次の実装はそれを行います:
class Stack(object):
def __init__(self, index, name, disks):
self.index = index
self.name = name
self.disks = disks
def __str__(self):
return self.name
def __repr__(self):
return 'Stack(%r, %r, %r)' % (self.index, self.name, self.disks)
def is_adjacent(self, other):
return other.index in (self.index + 1, self.index - 1)
def push(self, disk):
assert len(self.disks) == 0 or self.disks[-1] > disk
self.disks.append(disk)
def pop(self):
return self.disks.pop()
class Hanoi(object):
def __init__(self, n):
source = Stack(0, "source", range(n, 0, -1))
helper = Stack(1, "helper", [])
target = Stack(2, "target", [])
self.stacks = [source, helper, target]
self.hanoi(n, source, target)
def hanoi(self, n, source, target):
"""Move n disks from source to target using remaining stack"""
helper = self.stacks[3 - source.index - target.index]
if n == 0:
return
if source.is_adjacent(target):
self.hanoi(n - 1, source, helper)
self.move(source, target)
self.hanoi(n - 1, helper, target)
else:
assert helper.is_adjacent(source) and helper.is_adjacent(target)
self.hanoi(n - 1, source, target)
self.move(source, helper)
self.hanoi(n - 1, target, source)
self.move(helper, target)
self.hanoi(n - 1, source, target)
def move(self, s, d):
assert s.is_adjacent(d)
disk = s.pop()
print "moving %d from %s to %s" % (disk, s, d)
d.push(disk)
Hanoi(5)
このStack
オブジェクトは、スタックの1つに関するすべてのもの(名前、位置、隣接関係、およびディスクの現在のシーケンス)をまとめるのに役立ちます。そのインデックスを保持するために3番目の要素を追加した場合はタプルを使用できますが、OOPの方が直感的だと思います。
Hanoi
クラスはスタックのセットをまとめます。これにより、hanoi
メソッドはソースとターゲットを指定するだけで、3番目のスタックが推測されます。これを別の方法でコーディングすることもできますが、ヘルパーを省略すると、これらの再帰呼び出しがはるかに理解しやすくなります。シングルディスクmove
とマルチディスクの両方で、hanoi
ソースと宛先を指定し、3番目のスタックはありません。
簡素化の余地
ここでよく見ると、再帰呼び出しは常に隣接していないスタックに対するものであることがわかります。したがって、スタックの順序が実際にソースとターゲットが隣接していないようなものである場合、すべての再帰呼び出しはelse
私のコードのブランチを使用し、大文字と小文字の区別を避けるために短縮して、常にそのelse
ブランチを使用できます。ただし、上記のより冗長なコードは、何が起こっているのかという点で理解しやすいと思います。