ProstDev ProstDev
Challenges Mar 21, 2023 · 5 min read

DataWeave programming challenge #4: Solve the Tower of Hanoi mathematical puzzle

Move the disks, one disk at a time, from tower A to tower C following the Tower of Hanoi puzzle rules.

By Alex Martinez
Thumbnail: DataWeave programming challenge #4: Solve the Tower of Hanoi mathematical puzzle Read & copy the full video transcript

Note

This challenge is based on the Tower of Hanoi mathematical puzzle - this is a tough one!

Try to solve this challenge on your own to maximize learning. We recommend you refer to the DataWeave documentation only. Try to avoid using Google or asking others so you can learn on your own and become a DataWeave expert!

Solve on the Playground

Important

There are 3 different scenarios to test your code is working properly and to ensure there are no hardcoded values. However, only the first scenario is provided in the Playground link below. Please copy+paste the other two scenarios to make sure your solution is working properly.

Input

Consider the following JSON inputs:

SCENARIO 1

{
    "moves": 0,
    "disks": 3,
    "targetTower": "C",
    "towers": {
        "A": [
            1,
            2,
            3
        ],
        "B": [],
        "C": []
    }
}

SCENARIO 2

{
    "moves": 0,
    "disks": 4,
    "targetTower": "C",
    "towers": {
        "A": [
            1,
            2,
            3,
            4
        ],
        "B": [],
        "C": []
    }
}

SCENARIO 3

{
    "moves": 0,
    "disks": 7,
    "targetTower": "Y",
    "towers": {
        "X": [],
        "Y": [],
        "Z": [
            1,
            2,
            3,
            4,
            5,
            6,
            7
        ]
    }
}

Explanation of the problem

Create a DataWeave script to solve the Tower of Hanoi puzzle. The input payload contains the number of moves (starting at 0), the total number of disks in the game, the target tower where all the disks have to be moved, and the 3 towers containing each disk.

(You can use this link to play the game online)

Here are the rules of the game:

  • With each move, you can only move one disk at a time from its current stack to a different stack.
  • You can only place smaller disks on top of bigger disks. For example, you can place disk 1 on top of disk 3 but you can’t place disk 5 on top of disk 2.
  • You can only move the disk at the top of any stack.

Expected output

For each of the three scenarios from the input, these are the expected outputs:

SCENARIO 1

{
  "moves": 7,
  "disks": 3,
  "targetTower": "C",
  "towers": {
    "A": [
      
    ],
    "B": [
      
    ],
    "C": [
      1,
      2,
      3
    ]
  }
}

SCENARIO 2

{
  "moves": 15,
  "disks": 4,
  "targetTower": "C",
  "towers": {
    "A": [
      
    ],
    "B": [
      
    ],
    "C": [
      1,
      2,
      3,
      4
    ]
  }
}

SCENARIO 3

{
  "moves": 127,
  "disks": 7,
  "targetTower": "Y",
  "towers": {
    "X": [
      
    ],
    "Y": [
      1,
      2,
      3,
      4,
      5,
      6,
      7
    ],
    "Z": [
      
    ]
  }
}

Clues

If you’re stuck with your solution, feel free to check out some of these clues to give you ideas on how to solve it!

Clue #1

Read the wikipedia page to have more context from the algorithm perspective.

Clue #2

From the wikipedia page, take special attention to this part:

For an even number of disks:

make the legal move between pegs A and B (in either direction),

make the legal move between pegs A and C (in either direction),

make the legal move between pegs B and C (in either direction),

repeat until complete.

For an odd number of disks:

make the legal move between pegs A and C (in either direction),

make the legal move between pegs A and B (in either direction),

make the legal move between pegs B and C (in either direction),

repeat until complete.

Clue #3

If the previous clue wasn’t what you needed, take a look at these other rules from the wikipedia page:

Number the disks 1 through n (largest to smallest).

If n is odd, the first move is from peg A to peg C.

If n is even, the first move is from peg A to peg B.

Now, add these constraints:

No odd disk may be placed directly on an odd disk.

No even disk may be placed directly on an even disk.

There will sometimes be two possible pegs: one will have disks, and the other will be empty. Place the disk on the non-empty peg.

Never move a disk twice in succession.

Clue #4

If you see other solutions (in Python, C++, Java, etc) remember that those don’t necessarily apply to DataWeave. Try to come up with your own solution instead.

Answer

If you haven’t solved this challenge yet, we encourage you to keep trying! It’s ok if it’s taking longer than you thought. We all have to start somewhere ✨ Check out the clues and read the docs before giving up. You got this!! 💙

There are many ways to solve this challenge, but you can find here my solution. I’m sure you all can make it better! :) Mine is super long 😂

Solution

%dw 2.0
import update from dw::util::Values
import drop, take from dw::core::Arrays
output application/json
var towerNames:Array<String> = namesOf(payload.towers)
var targetTower:String = payload.targetTower
var initialTower:String = payload.towers
    pluck (if (!isEmpty($)) $$ else "")
    filter ($ != "") then $[0] as String
var auxTower:String = (towerNames -- [targetTower, initialTower])[0]
fun moveDisk(towers:Object, fromTower:String, toTower:String) = do {
    var from = towers[fromTower]
    var to = towers[toTower]
    ---
    towers 
    update log("Moved from", fromTower) with (from drop 1)
    update log("to",toTower) with (from take 1)[0] >> to
}
fun swapDisks(towers:Object, tower1:String, tower2:String) = do {
    var disk1 = towers[tower1][0] default (payload.disks + 1)
    var disk2 = towers[tower2][0] default (payload.disks + 1)
    ---
    if (disk1 < disk2)
        moveDisk(towers, tower1, tower2)
    else
        moveDisk(towers, tower2, tower1)
}
fun play(game:Object, moveNumber:Number = 1) = do {
    var isEvenGame = isEven(game.disks)
    var isOddGame = isOdd(game.disks)
    @Lazy
    var newTowers = 
        if (
            (isEvenGame and moveNumber == 1)
            or (isOddGame and moveNumber == 2)
        ) swapDisks(game.towers, initialTower, auxTower)
        else if (
            (isEvenGame and moveNumber == 2)
            or (isOddGame and moveNumber == 1)
        ) swapDisks(game.towers, initialTower, targetTower)
        else swapDisks(game.towers, auxTower, targetTower)
    @Lazy
    var newMoveNumber = if (moveNumber == 3) 1 else moveNumber + 1
    var newGame = game 
        update "towers" with newTowers
        update "moves" with game.moves + 1
    ---
    if (sizeOf(game.towers[targetTower]) == game.disks)
        game
    else
        play(newGame, newMoveNumber)
}
---
play(payload)

I also recorded myself coming up with the solution, but without explanations. Just some lo-fi music and a screen :) you can leave the video in the background while working! :D let me know if you find this useful.

Subscribe to receive notifications as soon as new content is published ✨

Reader notes

Helpful comments preserved from the original post.

  • armandomejiam · Mar 23, 2023

    One of my favorites mathematical puzzles! Here's a simple solution, without really moving individual disks :)

    %dw 2.0
    output application/json
    
    fun minMoves (n: Number) = (2 pow n) - 1
    var sourceTower = payload.towers filterObject !isEmpty($) mapObject (value: $)
    ---
    {
        "moves": minMoves(payload.disks),
        "disks": payload.disks,
        "targetTower": payload.targetTower,
        "towers": payload.towers mapObject ((value, key, index) -> {
            (key): if (key ~= payload.targetTower) sourceTower.value else []
        })
    }
  • Reply to armandomejiam

    Alex Martinez · Mar 23, 2023

    hahaha yeah I didn't even thought of that when I was thinking of it tbh :D Felix did the same thing in a short answer lol but try to move them!

  • Felix Schnabel · Mar 21, 2023

    Really cool challenge!😄 I think there are some open questions which should be described in the problem statement: - Are there always 3 towers? - Is the initial state of the towers always like in your sample inputs? So all disks are in order on another tower than the targetTower. - Is it possible that there are two rings with the same number? (Probably not, but should be stated explicitly IMO) I will try my luck with the challenge and post my solution (if I solve it 😂).

  • Reply to Felix Schnabel

    Felix Schnabel · Mar 21, 2023

    Here is my solution😄

    %dw 2.0
    output application/json
    import * from dw::core::Arrays
    
    type State = {|
        moves: Number,
        disks: Number,
        targetTower: String,
        towers: {|
            _: Array<Number>
        |}
    |}
    
    fun free(a: String, b: String, state: State): String = (namesOf(state.towers) -- [a, b])[0]
    
    fun move(a: String, b: String, height: Number, state: State): State = height match {
        case 0 -> state
        else -> do {
            var c = free(a, b, state)
            ---
            move(a, c, height - 1, state) then (state) ->
            state update {
                case .moves -> $ + 1
                case .towers."$a" -> $ drop 1
                case .towers."$b" -> state.towers[a][0] >> $
            } then (state) ->
            move(c, b, height - 1, state)
        }
    }
    
    fun findStartingTower(state): String = namesOf(state.towers filterObject !isEmpty($))[0]
    
    fun simulate(state: State): State = move(
        findStartingTower(state), //a
        state.targetTower, //b
        state.disks, //height
        state
    )
    ---
    simulate(payload)
  • Reply to Felix Schnabel

    Felix Schnabel · Mar 21, 2023

    And my super super short solution (which is a bit like cheating):

    %dw 2.0
    output application/json
    var startTower = namesOf(payload.towers filterObject !isEmpty($))[0]
    ---
    payload update {
        case .moves -> pow(2, payload.disks) - 1
        case .towers."$(startTower)" -> []
        case .towers."$(payload.targetTower)" -> payload.towers[(startTower)]
    }
  • Reply to Felix Schnabel

    Alex Martinez · Mar 21, 2023

    Thank you so much for your suggestions! I thought it was clear from the inputs, but I'll add those points too :) - yes, always 3 towers - the disks always start in order and the initial tower could be anything (except the targetTower) - it's not possible to have two rings with the same number

  • Reply to Felix Schnabel

    Alex Martinez · Mar 21, 2023

    whoops! your solutions are not working for the 3 scenarios 😄 In the inputs section, there are 3 different payloads to try out :)

  • Reply to Alex Martinez

    Felix Schnabel · Mar 21, 2023

    Are you sure? For me they work with my scripts 🤔

  • Reply to Felix Schnabel

    Alex Martinez · Mar 22, 2023

    Ahh!! you're right. I was taking the wrong input 😂

  • Reply to Felix Schnabel

    armandomejiam · Mar 23, 2023

    I like your usage of "update" on this one

FAQs

Frequently asked questions about this post.

  • What are the rules of the Tower of Hanoi challenge?

    There are three rules: with each move you can only move one disk at a time from its current stack to a different stack, you can only place smaller disks on top of bigger disks (you can put disk 1 on disk 3 but not disk 5 on disk 2), and you can only move the disk at the top of any stack.

  • What does the input payload contain?

    The input payload contains the number of moves (starting at 0), the total number of disks in the game (disks), the target tower where all the disks have to be moved (targetTower), and the 3 towers each containing their disks (towers).

  • How many moves does it take to solve each scenario?

    The expected outputs show 7 moves for scenario 1 (3 disks), 15 moves for scenario 2 (4 disks), and 127 moves for scenario 3 (7 disks), with all disks ending up in order on the target tower.

  • Why should I test against all three scenarios instead of just the Playground one?

    There are 3 different scenarios so you can confirm your code works properly and has no hardcoded values, but only the first scenario is provided in the Playground link, so you should copy and paste the other two to make sure your solution works.

  • How do I get unstuck if I can't solve the puzzle?

    The post offers four clues: read the Tower of Hanoi Wikipedia page for algorithm context, study the even-versus-odd legal-move sequence between pegs, apply the constraints about not stacking same-parity disks and never moving a disk twice in succession, and remember that solutions written in Python, C++, or Java don't necessarily apply to DataWeave so try to come up with your own.

  • Where can I solve this challenge?

    You can solve it on the DataWeave Playground via the link in the post at https://dataweave.mulesoft.com/learn/playground , or play the puzzle online first at https://www.mathsisfun.com/games/towerofhanoi.html to understand it.

More from this series

DataWeave Programming Challenges· Part 4 of 8
  1. 1.DataWeave programming challenge #1: Add numbers separated by paragraphs and get the max number
  2. 2.DataWeave programming challenge #2: Rock Paper Scissors game score system
  3. 3.DataWeave programming challenge #3: Count palindrome phrases using the Strings module
  4. 4.DataWeave programming challenge #4: Solve the Tower of Hanoi mathematical puzzle
  5. 5.DataWeave programming challenge #5: Reverse a phrase's words, but keep the punctuation
  6. 6.DataWeave programming challenge #6: Using tail-recursion to get the factorial of a number
  7. 7.DataWeave programming challenge #7: Modify certain values from a JSON structure
  8. 8.DataWeave programming challenge #8: Sum all digits to get a 1-digit number
Search

Loading search…