4

I wrote a kind of N-Queens algorithm, handling only the vertical and horizontal threats detection. Thus, it's rather a N-Towers solutions finder.

To do this, I use recursion. It's a well-known algorithm. For each square of the chessboard, I place a tower. For each placed tower, I try to place another tower (this is the recursive call). If there is not any remaining tower to place, it means the program has found a solution and the recursive level has to return. If all the chessboard has been crossed with remaining tower(s) to place, it means the program didn't find a solution and the recursive level has to return.

My recursive function has two parameters : the number of towers which have to be placed and the chessboard (an array of array of string ; the string equals "T" to indicate a tower has been placed in this chessboard's square and "-" to indicate the square is empty).

The problem

My algorithm seems to find all the solutions and displays them as chessboards, using the "-" (and, if it worked well, "T") notation. This notation is explained above.

However, even if the number of solutions seems to be correct, the displayed solutions/chessboards contain only "-".

I think I don't pass my array of array (ie. : the chessboard) correctly in my recursive call.

Illustration of this problem

For 2 towers and a chessboard of 2*2 squares, two solutions are found and it's normal. But there are only "-" and no "T" appears... That's the problem. Indeed :

--

--

Code : focus on my recursive function

    /**
     * RECURSIVE FUNCTION. If there are still towers to place, this function tries to place them. If not, it means a
     * solution has been found : it's stored in an array (external to this function).
     * If this function can't place a tower, nothing happens.
     * Else, it places it and makes the recursive call.
     * Each recursion level does this for each next (to the placed tower) chessboard's squares.
     * @param number_of_left_towers how many remaining towers to place there are (if 0, it means a solution has been
     * found)
     * @param array_array_chessboard the chessboard
     * @returns {Number} the return is not important
     */
    function placeTower(number_of_left_towers, array_array_chessboard) {
        if (number_of_left_towers == 0) {
            return solutions.push(array_array_chessboard);
        }

        for (var current_x = 0; current_x < number_of_lines; current_x++) {
            for (var current_y = 0; current_y < number_of_columns; current_y++) {
                if (array_array_chessboard[current_x][current_y] == "-" && canBePlaced(array_array_chessboard, current_x, current_y)) {
                    array_array_chessboard[current_x][current_y] = "T";
                    placeTower(number_of_left_towers - 1, array_array_chessboard);
                    array_array_chessboard[current_x][current_y] = "-";
                }
            }
        }
    }

Code : JSFiddle with all the source

https://jsfiddle.net/btcj6uzp/

You can also find the same code below :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Recursive algorithm of the N-Towers</title>
</head>
<body>

<script type="text/javascript">
    /**
     * Finds all the solutions to the N-Towers algorithm.
     *
     * @param number_of_towers number of towers to try to place in the chessboard
     * @param number_of_lines chessboard's ones
     * @param number_of_columns chessboard's ones
     * @returns {nTowersSolutions} array containing all the solutions
     */
    function nTowersSolutions(number_of_towers, number_of_lines, number_of_columns) {
        /*
        NB
        "T" = "Tower" = presence of a tower in this square of the chessboard
        "-" = "nothing" = no tower in this square of the chessboard
        (used for both solutions displaying and finding)
         */

        var solutions = [];
        var array_array_chessboard = []; // Represents the chessboard
        for(var i = 0; i < number_of_lines; i++) {
            array_array_chessboard[i] =  new Array(number_of_columns);
            for(var j = 0; j < number_of_columns; j++) {
                array_array_chessboard[i][j] = "-"; // We initialize the chessboard with "-"
            }
        }

        /**
         * Uses HTML to display the found solutions, in the Web page
         */
        this.displaySolutions = function() {
            var body = document.body;
            solutions.forEach((array_array_chessboard) => {
                array_array_chessboard.forEach(function(array_chessboard) {
                    array_chessboard.forEach((square) => {
                        body.innerHTML += square; // New cell
                    });
                    body.innerHTML += "<br />"; // New line
                });
                body.innerHTML += "<br /><br />"; // New solution
            });
        };

        /**
         * RECURSIVE FUNCTION. If there are still towers to place, this function tries to place them. If not, it means a
         * solution has been found : it's stored in an array (external to this function).
         * If this function can't place a tower, nothing happens.
         * Else, it places it and makes the recursive call.
         * Each recursion level does this for each next (to the placed tower) chessboard's squares.
         * @param number_of_left_towers how many remaining towers to place there are (if 0, it means a solution has been
         * found)
         * @param array_array_chessboard the chessboard
         * @returns {Number} the return is not important
         */
        function placeTower(number_of_left_towers, array_array_chessboard) {
            if (number_of_left_towers == 0) {
                return solutions.push(array_array_chessboard);
            }

            for (var current_x = 0; current_x < number_of_lines; current_x++) {
                for (var current_y = 0; current_y < number_of_columns; current_y++) {
                    if (array_array_chessboard[current_x][current_y] == "-" && canBePlaced(array_array_chessboard, current_x, current_y)) {
                        array_array_chessboard[current_x][current_y] = "T";
                        placeTower(number_of_left_towers - 1, array_array_chessboard);
                        array_array_chessboard[current_x][current_y] = "-";
                    }
                }
            }
        }

        /**
         * Can this tower be placed ?
         * @param array_array_chessboard
         * @param new_x
         * @param new_y
         * @returns {boolean}
         */
        function canBePlaced(array_array_chessboard, new_x, new_y) {
            for(var i = 0; i < array_array_chessboard.length; i++) {
                for(var z = 0; z < array_array_chessboard[i].length; z++) {
                    if(array_array_chessboard[i][z] == "T"
                            && (
                                    new_x == z || new_y == i // Horizontal and vertical checks
                            )
                    ) {
                        return false;
                    }
                }
            }
            return true;
        }

        placeTower(number_of_towers, array_array_chessboard);
        return this;
    }

    // <!-- CHANGE THESE PARAMETERS' VALUE TO TEST -->
    nTowersSolutions(2, 2, 2).displaySolutions();
</script>

</body>
</html>
JarsOfJam-Scheduler
  • 2,809
  • 3
  • 31
  • 70
  • your fiddle isn't working for me, the arrow function expression is throwing a syntax error – narvoxx Oct 28 '16 at 12:21
  • Lambda notation is part of ES6, and is supported, for example, by Chrome v.54 : I use the latter and my fiddle's execution is done without any problem – JarsOfJam-Scheduler Oct 28 '16 at 12:32

2 Answers2

2

Your problem is most likely that there is only one (two dimentional) array, which is global, so in the end your solutions are all pointing to the same array which will be the last state it had before our recursive function completely returned.

array_array_chessboard[current_x][current_y] = "T";
placeTower(number_of_left_towers - 1, array_array_chessboard);
array_array_chessboard[current_x][current_y] = "-";

If I understand the above correctly, you are (looping over all positions, ish)
1) assigning T to a location
2) solving all boards with T in that location
3) assigning "-" to the previous location

so in the end you have an array full of "-", and all solutions point to this same array

Try replacing

return solutions.push(array_array_chessboard);

by

return solutions.push(JSON.decode(JSON.encode(array_array_chessboard)));

The above will make a deep copy of your solution, and while it might not be the utmost efficient way to make a deep copy it is a simple one. If your algorithm needs to be really fast you might want to look in a faster way to clone your solution.

Though I can't guarantee that this will work since I can't run your fiddle

(also for readability I suggest you write your return like so:)

solutions.push(JSON.parse(JSON.stringify(array_array_chessboard)));
return;

EDIT: why to use JSON.parse+stringify over Array::from:

if you simply do

solutions.push(Array.from(array_array_chessboard));

The second dimention will still reference the same arrays, and that is where your string data is stored after all.

to demonstrate (note that you need to polyfill the Array.from in IE, or simply try on a different browser):

var arr1 = ["a"];
var arr2 = ["b"];
var metaArr = [arr1, arr2];
console.log(metaArr[0][0], metaArr[1][0]); // "a b"

var metaArrClone = Array.from(metaArr);
var metaArrClone[0][0] = "c";
console.log(metaArrClone[0][0]); // "c"
console.log(metaArr[0][0]); // "c"

var metaArrClone2 = JSON.parse(JSON.stringify(metaArr));
console.log(metaArrClone2[0][0]); // "c"
metaArrClone2[0][0] = "d";
console.log(metaArrClone2[0][0]); // "d"
console.log(metaArr[0][0]); // "c"
narvoxx
  • 817
  • 6
  • 13
  • So you think that I should pass a `new Array` in this `solutions.push` ? The functions `Array::from`, `Array::slice`, and others do this ; however it doesn't solve my problem. `JSON::decode` and `JSON::encode`, both used, would do the same. – JarsOfJam-Scheduler Oct 28 '16 at 13:52
  • there is a big difference in using `Array::from` and `Array::slice` in that they will only 'deep' copy the first dimention which is completely useless in your case. It's not entirely right to elborate in the comments, I will edit my answer – narvoxx Oct 28 '16 at 15:08
  • also note that my initial use of JSON.encode/decode were not working in IE so I changed it to parse/stringify – narvoxx Oct 28 '16 at 15:29
  • Ah thank you, now the T appear. However, the displayed solutions are the same. I don't understand how it can be possible with your code ? – JarsOfJam-Scheduler Oct 28 '16 at 15:45
  • Moreover, when I add : `console.log(array_array_chessboard)` just before the `solution.push(JSON.parse(...`, it displays 2 arrays each containing 2 arrays, which contain only "-". No T appears. But if I do : `console.log(JSON.parse(...`, T appear (but the 2 solutions are the same...). It's really weird. As always, I used a 2*2 chessboard and 2 towers. – JarsOfJam-Scheduler Oct 28 '16 at 18:35
  • The reason `console.log(array_array_chessboard)` seems to be broken is because chrome doesn't log the contents of the actually arrays, it logs an 'inspector object' with a reference to the array, which at the end of the loop (when you access it) contains only `-` – narvoxx Oct 29 '16 at 16:10
  • I think the rest of the problem might be in your algorithm – narvoxx Oct 31 '16 at 07:18
0

You do not need to keep the solutions outside your recursive function. Could be better if you keep the solutions inside your recursive function and than return all the solutions, so you do not need to worry about the state outside the function.

Of course if you have to use the finded solutions before that the recursive function returns (maybe you have a big cheesboard) your solution could be better. Or you could use a generator.

Could be also good keep this kind of logic separate from the ui so first focus on the solution and then try to draw the result in the browser or where you want, or do the opposite.

You can start from the code below, but please check if it actually finds all the solutions before use it.

'use strict'
/* Finds all the solutions to the N-Towers algorithm.
 *
 * @param number_of_towers number of towers to try to place in the chessboard
 * @param number_of_lines chessboard's ones
 * @param number_of_columns chessboard's ones
 * @returns {nTowersSolutions} array containing all the solutions
 * "Tower" = presence of a tower in this square of the chessboard
 * "Nothing" = no tower in this square of the chessboard
 * "Blocked" = the cell is blocked
 */

function nTowersSolutions(number_of_towers, number_of_lines, number_of_columns) {
  var chessboard = _initChessboard(number_of_lines, number_of_columns);
  var solutions = _findAllSolution(chessboard, number_of_towers);
  return solutions;
}

// nuber, * -> array
var _newArrFromLenAndElement = function(length, element) {
  return Array.apply(null, Array(length)).map(function(){ return element; }); 
};

// number, number -> cheesboard
var _initChessboard = function(number_of_lines, number_of_columns) {
  var oneColumnChessboard = _newArrFromLenAndElement(number_of_lines, []);
  var chessboard = oneColumnChessboard.map(function() {                                                                                                                                                     
    var line = _newArrFromLenAndElement(number_of_columns, 'Nothing');
    return line;
  }); 
  return chessboard;
};

// chessboard, line_index, column_index -> chessboard
var _placeTower = function(chessboard, line_index, column_index) {
  var ff = chessboard.map(function(line, index) {
    if (index === line_index) {
      return line.map(function() { return 'Blocked'; }); 
    }   
    else {
      return line.map(function(x, index){
        if(index===column_index){return'Blocked';}
        else{return x;} 
      }); 
    }   
  }); 
  ff[line_index][column_index] = 'Tower';
    return ff; 
};

// array[][] -> array[]
var _flatten = function(arr) {
  return [].concat.apply([], arr);
};

// *, array -> boolean
var _isInArray = function(value, array) {
    return array.indexOf(value) > -1; 
};

// cheesboard, numberm number -> array
// this could be a bruteforce recursive solution at your problem ( actually you have to check if
// it is correct )
// we need _lines_done for don't look for solutions already finded (if you have tried all the
// pattern with a tower in the first line you don't want try to place a tower in the first line)
var _findAllSolution = function(chessboard, number_of_towers, _lines_done, _deep) {
  _lines_done = _lines_done || [];
  _deep = _deep || 0;

  if (number_of_towers === 0){
    return chessboard;
  }

  //for all the cells in the ceesboard try to place a tower
  var solutions = chessboard.map(function(line, line_index) {
    var solution = line.map(function(cell, column_index) {
      if (_isInArray(line_index, _lines_done)) {
        return 'alreadyVisitedLine';
      }
      else if (cell === 'Nothing') {
        var fulfilledChessboard = _placeTower(chessboard, line_index, column_index);
        if (line_index > 0 && _deep === 0 && !(_isInArray(line_index - 1, _lines_done))) {
          _lines_done.push(line_index - 1);
        }
        return _findAllSolution(fulfilledChessboard, number_of_towers -1, _lines_done, _deep + 1);
      }
      else {
        return 'DeadEnd!';
      }
    });
      return _flatten(solution);
  });

  var flatSolutions = _flatten(solutions);
  //you should .filter the solutions
  return _flatten(solutions);
};

var h = nTowersSolutions(2,2,2)
console.log(h)
Fi3
  • 429
  • 6
  • 17