Designing algorithm to solve Ball Sort Puzzle - GeeksforGeeks

22 Nov, 2021
In Ball Sort Puzzle game, we have p balls of each colour and n different colours, for a total of p×n balls, arranged in n stacks. In addition, we have 2 empty stacks. A maximum of p balls can be in any stack at a given time. The goal of the game is to sort the balls by colour in each of the n stacks.
Rules:
Only the top ball of each stack can be moved.
A ball can be moved on top of another ball of the same colour
A ball can be moved in an empty stack.
Refer to the following GIF for an example game play (Level-7):
Level 7 Gameplay
Approach I [Recursion and BackTrack]:
From the given rules, a simple recursive algorithm could be generated as below:Start with the given initial position of all the ballsCreate an initial empty Queue.loop:If the current position is sorted:returnelseEnqueue all possible moves in a Queue.Dequeue the next move from the Queue.Go to loop.
Start with the given initial position of all the balls
Create an initial empty Queue.
loop:If the current position is sorted:returnelseEnqueue all possible moves in a Queue.Dequeue the next move from the Queue.Go to loop.
If the current position is sorted:return
return
elseEnqueue all possible moves in a Queue.Dequeue the next move from the Queue.Go to loop.
Enqueue all possible moves in a Queue.
Dequeue the next move from the Queue.
Go to loop.
However, the approach looks simple and correct, it has few caveats:
Incorrect:We might end up in an infinite loop if there are >1 moves in the Queue which lead to the same position of balls.
We might end up in an infinite loop if there are >1 moves in the Queue which lead to the same position of balls.
Inefficient:We might end up visiting the same position multiple times.
We might end up visiting the same position multiple times.
Thus, eliminating the above-mentioned bottlenecks would solve the issue.
Approach II [Memoization using HashMap]:
Assumptions:We’ll represent ball positions as a vector of strings: {“gbbb”, “ybry”, “yggy”, “rrrg”}
We’ll represent ball positions as a vector of strings: {“gbbb”, “ybry”, “yggy”, “rrrg”}
Create a set called Visited of <String> which will contain the visited positions as one long string.
Create an empty vector for Answer which will store positions<a, b> of the tubes to move the top ball from tube a to and put it in tube b.
Initialise grid with the initial settings of the balls.
func solver(grid):add grid to Visitedloop over all the stacks (i):loop over all the stacks (j):If move i->j is valid, create newGrid with that move.if the balls are sorted in newGrid,update Answer;return;if newGrid is NOT in Visitedsolver(newGrid)if solved:update Answer
add grid to Visited
loop over all the stacks (i):loop over all the stacks (j):If move i->j is valid, create newGrid with that move.if the balls are sorted in newGrid,update Answer;return;if newGrid is NOT in Visitedsolver(newGrid)if solved:update Answer
loop over all the stacks (j):If move i->j is valid, create newGrid with that move.if the balls are sorted in newGrid,update Answer;return;if newGrid is NOT in Visitedsolver(newGrid)if solved:update Answer
If move i->j is valid, create newGrid with that move.if the balls are sorted in newGrid,update Answer;return;if newGrid is NOT in Visitedsolver(newGrid)if solved:update Answer
if the balls are sorted in newGrid,update Answer;return;
update Answer;
return;
if newGrid is NOT in Visitedsolver(newGrid)if solved:update Answer
solver(newGrid)
if solved:update Answer
update Answer
Sample Game Input I:
Level 3
Sample Input I: 
5
ybrb
byrr
rbyy
Sample Output I:
Move 1 to 4 1 times
Move 1 to 5 1 times
Move 1 to 4 1 times
Move 2 to 5 2 times
Move 1 to 2 1 times
Move 3 to 1 1 times
Move 1 to 2 1 times
Move 3 to 1 1 times
Move 2 to 1 3 times
Move 2 to 3 1 times
Move 3 to 4 1 times
Move 3 to 2 1 times
Move 2 to 4 1 times
Move 3 to 5 1 times
Sample Game Input II:
Level 5
Sample Input II:
6
gbbb
ybry
yggy
rrrg
Sample Output II:
Move 1 to 5 3 times
Move 2 to 6 1 times
Move 3 to 6 1 times
Move 1 to 3 1 times
Move 2 to 1 1 times
Move 2 to 5 1 times
Move 2 to 6 1 times
Move 3 to 2 3 times
Move 3 to 6 1 times
Move 4 to 2 1 times
Move 1 to 4 1 times
Refer to the below C++ implementation with the comments for the reference:
C++
// C++ program for the above approach#include <bits/stdc++.h>using namespace std;using Grid = vector<string>; Grid configureGrid(string stacks[], int numberOfStacks){     Grid grid;    for (int i = 0; i < numberOfStacks; i++)        grid.push_back(stacks[i]);     return grid;} // Function to find the maxint getStackHeight(Grid grid){    int max = 0;    for (auto stack : grid)        if (max < stack.size())            max = stack.size();    return max;} // Convert vector of strings to// canonicalRepresentation of stringsstring canonicalStringConversion(Grid grid){    string finalString;    sort(grid.begin(), grid.end());    for (auto stack : grid) {        finalString += (stack + ";");    }    return finalString;} // Function to check if it is solved// or notbool isSolved(Grid grid, int stackHeight){     for (auto stack : grid) {        if (!stack.size())            continue;        else if (stack.size() < stackHeight)            return false;        else if (std::count(stack.begin(),                            stack.end(),                            stack[0])                 != stackHeight)            return false;    }    return true;} // Check if the move is validbool isValidMove(string sourceStack,                 string destinationStack,                 int height){     // Can't move from an empty stack    // or to a FULL STACK    if (sourceStack.size() == 0        || destinationStack.size() == height)        return false;     int colorFreqs        = std::count(sourceStack.begin(),                     sourceStack.end(),                     sourceStack[0]);     // If the source stack is same colored,    // don't touch it    if (colorFreqs == height)        return false;     if (destinationStack.size() == 0) {         // If source stack has only        // same colored balls,        // don't touch it        if (colorFreqs == sourceStack.size())            return false;        return true;    }    return (        sourceStack[sourceStack.size() - 1]        == destinationStack[destinationStack.size() - 1]);} // Function to solve the puzzlebool solvePuzzle(Grid grid, int stackHeight,                 unordered_set<string>& visited,                 vector<vector<int> >& answerMod){    if (stackHeight == -1) {        stackHeight = getStackHeight(grid);    }    visited.insert(        canonicalStringConversion(grid));     for (int i = 0; i < grid.size(); i++) {         // Iterate over all the stacks        string sourceStack = grid[i];        for (int j = 0; j < grid.size(); j++) {            if (i == j)                continue;            string destinationStack = grid[j];            if (isValidMove(sourceStack,                            destinationStack,                            stackHeight)) {                 // Creating a new Grid                // with the valid move                Grid newGrid(grid);                 // Adding the ball                newGrid[j].push_back(newGrid[i].back());                 // Adding the ball                newGrid[i].pop_back();                if (isSolved(newGrid, stackHeight)) {                    answerMod.push_back(                        vector<int>{ i, j, 1 });                    return true;                }                if (visited.find(                        canonicalStringConversion(newGrid))                    == visited.end()) {                    bool solveForTheRest                        = solvePuzzle(newGrid, stackHeight,                                      visited, answerMod);                    if (solveForTheRest) {                        vector<int> lastMove                            = answerMod[answerMod.size()                                        - 1];                         // Optimisation - Concatenating                        // consecutive moves of the same                        // ball                        if (lastMove[0] == i                            && lastMove[1] == j)                            answerMod[answerMod.size() - 1]                                     [2]++;                        else                            answerMod.push_back(                                vector<int>{ i, j, 1 });                        return true;                    }                }            }        }    }    return false;} // Checks whether the grid is valid or notbool checkGrid(Grid grid){     int numberOfStacks = grid.size();    int stackHeight = getStackHeight(grid);    int numBallsExpected        = ((numberOfStacks - 2) * stackHeight);    // Cause 2 empty stacks    int numBalls = 0;     for (auto i : grid)        numBalls += i.size();    if (numBalls != numBallsExpected) {        cout << "Grid has incorrect # of balls"             << endl;        return false;    }    map<char, int> ballColorFrequency;    for (auto stack : grid)        for (auto ball : stack)            if (ballColorFrequency.find(ball)                != ballColorFrequency.end())                ballColorFrequency[ball] += 1;            else                ballColorFrequency[ball] = 1;    for (auto ballColor : ballColorFrequency) {        if (ballColor.second != getStackHeight(grid)) {            cout << "Color " << ballColor.first                 << " is not " << getStackHeight(grid)                 << endl;            return false;        }    }    return true;} // Driver Codeint main(void){     // Including 2 empty stacks    int numberOfStacks = 6;    std::string stacks[]        = { "gbbb", "ybry", "yggy", "rrrg", "", "" };     Grid grid = configureGrid(        stacks, numberOfStacks);    if (!checkGrid(grid)) {        cout << "Invalid Grid" << endl;        return 1;    }    if (isSolved(grid, getStackHeight(grid))) {        cout << "Problem is already solved"             << endl;        return 0;    }    unordered_set<string> visited;    vector<vector<int> > answerMod;     // Solve the puzzle instance    solvePuzzle(grid, getStackHeight(grid),                visited,                answerMod);     // Since the values of Answers are appended    // When the problem was completely    // solved and backwards from there    reverse(answerMod.begin(), answerMod.end());     for (auto v : answerMod) {        cout << "Move " << v[0] + 1             << " to " << v[1] + 1             << " " << v[2] << " times"             << endl;    }    return 0;}
  
Move 1 to 5 3 times
Move 2 to 6 1 times
Move 3 to 6 1 times
Move 1 to 3 1 times
Move 2 to 1 1 times
Move 2 to 5 1 times
Move 2 to 6 1 times
Move 3 to 2 3 times
Move 3 to 6 1 times
Move 4 to 2 1 times
Move 1 to 4 1 times
 
akshaysingh98088
Blogathon-2021
Puzzles
Backtracking
Blogathon
Dynamic Programming
Hash
Puzzles
Hash
Dynamic Programming
Backtracking
Puzzles
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Comments
Old Comments
Tug of War
Difference between Backtracking and Branch-N-Bound technique
Perfect Sum Problem
N-Queen Problem | Local Search using Hill climbing with random neighbour
Find shortest safe route in a path with landmines
How to Call or Consume External API in Spring Boot?
Re-rendering Components in ReactJS
SQL Query to Insert Multiple Rows
Python NOT EQUAL operator
Difference Between Local Storage, Session Storage And Cookies