Hanoi tornyai

Használható segédanyagok: Haskell könyvtárainak dokumentációja, (lokális!) Hoogle, a tárgy honlapja és a BE-AD rendszerbe feltöltött beadandók. Ha bármilyen kérdés, észrevétel felmerül, azt a felügyelőknek kell jelezni, NEM a diáktársaknak!

Tekintve, hogy a tesztesetek, bár odafigyelés mellett íródnak, nem fedik le minden esetben a függvény teljes működését, határozottan javasolt még külön próbálgatni a megoldásokat beadás előtt, vagy megkérdezni a felügyelőket!

Részpontszámokat csak az elégséges szint elérése után lehet kapni!

A feladat összefoglaló leírása

A hanoi tornyai matematikai játék, amihez a hasonnevű matematikai feladvány kapcsolódik. Ez úgy is ismert, mint Brahma tornyai, vagy világ vége feladvány. A játék szabályai szerint az első rúdról az utolsóra kell átrakni a korongokat úgy, hogy minden lépésben egy korongot lehet áttenni, nagyobb korong nem tehető kisebb korongra, és ehhez összesen három rúd áll rendelkezésre. (forrás: Wikipedia)

Vizuálisan (az illusztráció a Wikipediáról származik):

Tower_of_Hanoi
Tower_of_Hanoi

A feladat során a Hanoi tornyai feladvánnyal fogunk foglalkozni, de egy egyszerűsített változatával. A feladat folyamán annyi megkötést teszünk, hogy kezdetben az összes korong az első rúdon van és a második rúdra való átpakolás is megoldásnak számít.

Az egyszerűség kedvéért a korongokat (Disk) számokkal reprezentáljuk, a korongok méretét pedig a számok értéke reprezentálja.

A rudakat (Rod) listákkal adjuk meg, a lista elején lévő elem adja a rúdon legfelül szereplő elemet, az utolsó pedig a legalsót.

A feladványban három rudat és a rajuk lévő korongokat a Puzzle típus definiálja.

type Disk = Int
type Rod = Int
type Puzzle = [[Disk]]

Kezdeti konfiguráció (1 pont)

Adjuk meg azt a függvényt, amely előállítja egy kezdeti konfigurációt n darab korongból!

newPuzzle :: Int -> Puzzle

Test>
[[1, 2], [], []] :: Puzzle
Test>
[[1, 2, 3], [], []] :: Puzzle
Test>
[[1, 2, 3, 4], [], []] :: Puzzle

Korong értékének lekérdezése (1 pont)

Adjuk meg azt a függvényt, amely lekérdezi a megadott rúdon legfelül szereplő korongot! A függvénynek csak jó konfigurációkon kell működnie.

getDisk :: Puzzle -> Rod -> Disk

Test>
1 :: Disk
Test>
2 :: Disk
Test>
1 :: Disk

Korong eltávolítása (1 pont)

Adjuk meg azt a függvényt, amely leveszi a legfelső korongot a megadott rúdról.!A függvénynek csak jó konfigurációkon kell működnie.

removeDisk :: Puzzle -> Rod -> Puzzle

Test>
[[2, 3], [], []] :: Puzzle
Test>
[[], [], [2, 3]] :: Puzzle

Megengedett lépés (2 pont)

Adjuk meg azt a függvényt, amely megvizsgálja, hogy legális-e a kérdezett lépés! A függvény paraméterül kapja, hogy melyik rúdról hová szeretnénk korongot áthelyezni.

isLegalMove :: Puzzle -> Rod -> Rod -> Bool

Test>
True :: Bool
Test>
True :: Bool
Test>
True :: Bool
Test>
False :: Bool
Test>
False :: Bool

Korong elhelyezés (1 pont)

Adjuk meg azt a függvényt, amely elhelyezi a megadott korongot a megadott rúdra! A függvénynek csak jó konfigurációkon kell működnie!

pushDisk :: Puzzle -> Rod -> Disk -> Puzzle

Test>
[[1, 2, 3], [], []] :: Puzzle
Test>
[[3], [2], [1]] :: Puzzle
Test>
[[3], [2], [1]] :: Puzzle

Korong mozgatása (3 pont)

Adjuk meg azt a műveletet, amely áthelyez az egyik rúdról a másikra egy korongot! Ha a mozgatás nem lehetséges, akkor adjon vissza hibát az error függvény felhasználásával.

moveDisk :: Puzzle -> Rod -> Rod -> Puzzle

Test>
[[2, 3, 4], [1], []] :: Puzzle
Test>
[[2, 3, 4], [], [1]] :: Puzzle
Test>
⊥₁ :: Puzzle
⊥₁: moveDisk: illegal move
CallStack (from HasCallStack):
  error, called at ./Hanoi.lhs:141:27 in main:Hanoi
Test>
⊥₁ :: Puzzle
⊥₁: moveDisk: illegal move
CallStack (from HasCallStack):
  error, called at ./Hanoi.lhs:141:27 in main:Hanoi

Lehetséges mozgatások (1 pont)

Adjuk meg azt a műveletet, amely megadja az összes lehetséges lépést, mely az előző lépés ismeretében előállhat! Az eredmény csak valós lépéseket tartalmazzon, azaz nem szeretnénk, hogy a levett korong visszakerülhessen ugyanarra a rúdra és nem szeretnénk visszalépni az előző állapotba! Például: az (1,1),(2,2) és a (3,3) nem kell, hogy megjelenjen az eredményben, illetve ha az előző lépés (1,2) volt, akkor a (2,1) se legyen az eredményben.

possibleSteps :: (Rod,Rod) -> [(Rod, Rod)]

Test>
[(1, 2), (1, 3), (2, 3), (3, 1), (3, 2)] :: [(Rod, Rod)]
Test>
[(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)] :: [(Rod, Rod)]

Megengedett lépések (2 pont)

Definiáljuk azt a függvényt, amely a jelenlegi állásból és az utolsó ismert lépésből megadja, hogy mely pozíciókról hová helyezhetünk át korongot anélkül, hogy megszegnénk a játék szabályait! Tartsuk szem előtt, hogy kisebb korongra nem kerülhet nagyobb korong, illetve ne lépjünk vissza egy korábbi állapotba! Azaz, ha az utolsó lépés az (1,2) volt, akkor a (2,1) nem megengedett lépés.

legalSteps :: Puzzle -> (Rod, Rod) -> [(Rod, Rod)]

Test>
[(1, 3), (2, 3)] :: [(Rod, Rod)]

Legutóbbi lépés (4 pont)

Adjuk meg azt a műveletet, amely két (egymás utáni) állapotból kiszámolja, hogy melyik rúdról hová helyeztünk el elemet!

lastStep :: Puzzle -> Puzzle -> (Rod, Rod)

Test>
(1, 2) :: (Rod, Rod)
Test>
(1, 3) :: (Rod, Rod)
Test>
(3, 1) :: (Rod, Rod)

Lépés végrehajtása (5 pont)

Adjuk meg azt a függvényt, amely megadja az összes lehetséges lépés által előállított tábla állást!

A függvény egy rendezett párt kap paraméterül, amelynek első eleme az aktuális állapot, második eleme pedig a korábbi állapotokat tartalmazza (történet).

A megoldásnál szükség lesz a következő lépésekre:

step :: (Puzzle, [Puzzle]) -> [(Puzzle, [Puzzle])]

Test>
[([[2], [1], []], [[[1, 2], [], []]]), ([[2], [], [1]], [[[1, 2], [], []]])] :: [(Puzzle, [Puzzle])]
Test>
[([[2, 3], [1], []], [[[1, 2, 3], [], []]]), ([[2, 3], [], [1]], [[[1, 2, 3], [], []]])] :: [(Puzzle, [Puzzle])]

Lépések végrehajtása (1 pont)

A korábbi függvény segítségével adjuk meg azt a függvényt, amely több állapotnak is képes előállítani azok folytatásait!

steps :: [(Puzzle, [Puzzle])] -> [(Puzzle, [Puzzle])]

Test>
[([[2], [1], []], [[[1, 2], [], []]]), ([[2], [], [1]], [[[1, 2], [], []]])] :: [(Puzzle, [Puzzle])]
Test>
[([[], [1], [2]], [[[2], [1], []], [[1, 2], [], []]]), ([[2], [], [1]], [[[2], [1], []], [[1, 2], [], []]])] :: [(Puzzle, [Puzzle])]

Összes lehetséges megoldás (4 pont)

Definiáljuk azt a függvényt, amely kiszámítja az adott feladvány összes lehetséges megoldását! A kezdőállapotból addig kell ismételgetni a lépéseket, amíg két üres rúd nem található az összes aktuális állapotban.

solve :: Puzzle -> [(Puzzle, [Puzzle])]

Test>
2 :: Int
Test>
16 :: Int

Legrövidebb megoldás (3 pont)

Definiáljuk azt a függvényt, amely a megoldások közül kiválasztja a legkevesebb lépésben előállítható megoldást a megadott célrúdon!

bestSolution :: Puzzle -> Rod -> (Puzzle, [Puzzle])

Test>
([[], [1, 2], []], [[[], [2], [1]], [[2], [], [1]], [[1, 2], [], []]]) :: (Puzzle, [Puzzle])
Test>
([[], [], [1, 2]], [[[], [1], [2]], [[2], [1], []], [[1, 2], [], []]]) :: (Puzzle, [Puzzle])
Test>
([[], [1], []], [[[1], [], []]]) :: (Puzzle, [Puzzle])

Pontozás