2

In the game idle heroes, demon bells can be level 1,2,3,4. A level 4 is made of 4 level 1, a level 3 is made of 3 level 1 and so on.

I want to find all arrangements of db given a fixed number. I made this recursive algorithm in javascript:

Closer with a more simplified approach:

function findDB(numDB, arr) {
  console.log("findDB"+numDB);
  if (numDB == 1) {
    console.log("HERE2");
    return 1;
  } else {
    for (let i = 1; i < numDB; i++) {
      console.log("FOR"+i);
      console.log("COND" +(numDB + (numDB-i)));
      if((numDB + (numDB-i)) > numDB+1)
        continue;
      arr= arr.concat([numDB,[i,findDB(numDB - i, arr)]]);
    }
    return arr;
  }
}
var final = []
var y = findDB(3, final);
console.log(JSON.stringify(y));

Output:

findDB(2) CORRECT!

findDB2
FOR1
COND3
findDB1
HERE2
[2,[1,1]]

FindDB(3) is missing 1,1,1,

findDB3
FOR1
COND5
FOR2
COND4
findDB1
HERE2
[3,[2,1]]

here is intended output for input 1 through 6 (algo needs to scale for any number input)

    /1/ (1)
    /2/ (2),
        (1,1)
    /3/ (3),
        (2,1),
        (1,1,1)
    /4/ (4),
        (3,1),
        (2,2),(2,1,1),
        (1,1,1,1)
    /5/ (4,1),
        (3,2),(3,1,1),
        (2,2,1),(2,1,1,1),
        (1,1,1,1,1)
    /6/ (4,2),(4,1,1),
        (3,3),(3,2,1),(3,1,1,1),
        (2,2,2),(2,2,1,1),(2,1,1,1,1)
        (1,1,1,1,1,1)
  • 1
    Your algorithm is pretty much hardcoded. Try to make it more abstract - have a single base case, and a single recursive case (use a loop!). Ensure that you *always* return an array of arrays of numbers. – Bergi Apr 27 '22 at 03:29
  • Is an input of more than 5 valid? If so, what would be the expected output for an input of (say) 6? – Nick Apr 27 '22 at 03:43
  • @Nick yes I want this to scale for any number (realistically only going to 30). Expected output for 6 is added to question – WinWin WinWin Apr 27 '22 at 03:50
  • Is `(4)` a valid response for an input of `4`? – Nick Apr 27 '22 at 04:02
  • yes @Nick I added 1 through 6 to question – WinWin WinWin Apr 27 '22 at 04:09
  • Am I the only one who do not understand what the function is doing? Why `findDB(1)` returns `1 `while `findDB(2)` returns `[2, [1, 1]]` - a 2d array. Why it is not just `[1,1]` since the question suggest level 2 is made of 2* level 1 – Mr. Brickowski Apr 27 '22 at 04:14
  • @Mr.Brickowski yes, the algorithm is producing unnecessary nesting. The goal is to produce un-nested output as described at bottom of question. I'm playing around with concat and reduce but have not quite figured it out yet. – WinWin WinWin Apr 27 '22 at 04:16
  • @WinWinWinWin Thanks for the update! "*it produces incorrect output*" - don't `return` from within the loop, return after the loop. Also follow my advice on *always returning an array of arrays*. Not `1` as a number. – Bergi Apr 27 '22 at 05:39
  • updated question with a more abstracted approach, still incorrect, would appreciate any advice. – WinWin WinWin Apr 27 '22 at 05:40
  • @Bergi did so, but its still way off, any ideas? Question updated. – WinWin WinWin Apr 27 '22 at 05:47
  • @Bergi i made base case return [1] but it messes up the nesting. Now algo starting to work but missing 1,1,1 for input findDB(3) – WinWin WinWin Apr 27 '22 at 06:00

2 Answers2

2

This is called the partitions of a number, and is a well-known problem. I'm sure computer scientists have more efficient algorithms than this, but a naive recursive version might look like this:

const partitions = (n, m = n) =>
  m > n
    ? partitions (n, n)
  : m == 1
    ? [Array (n) .fill (1)]
  : m < 1
    ? [[]]
  : [
      ... partitions (n - m, m) .map (p => [m, ...p]),
      ... partitions (n, m - 1)
    ];

[1, 2, 3, 4, 5, 6] .forEach ((n) => console .log (`${n}: ${JSON .stringify (partitions (n))}`))

And if you're worried about the default parameter (there sometimes are good reasons to worry), then you can just make this a helper function and wrap it in a public function like this:

const _partitions = (n, m) =>
  m > n
    ? _partitions (n, n)
  : m == 1
    ? [Array (n) .fill (1)]
  : m < 1
    ? [[]]
  : [
      ... _partitions (n - m, m) .map (p => [m, ...p]),
      ... _partitions (n, m - 1)
    ];

const partitions = (n) => _partitions (n, n);

[1, 2, 3, 4, 5, 6] .forEach ((n) => console .log (`${n}: ${JSON .stringify (partitions (n))}`))

in either case, n is the integer we're summing to, and m is the maximum integer we can use. If m is too large, we simply call again with an appropriate m. If it equals 1, then we can only have an array of n 1's. If m reaches zero, then we have only the empty partition. Finally, we have two recursive cases to combine: When we choose to use that maximum number, we recur with the remainder and that maximum, prepending the maximum to each result. And when we don't use the maximum, we recur with the same target value and a decremented maximum.

I feel as though this has too many cases, but I don't see immediately how to combine them.

The time is exponential, and will be in any case, because the result is exponential in the size of n. If we added memoization, we could really speed this up, but I leave that as an exercise.

Update

I was bothered by those extra cases, and found an Erlang answer that showed a simpler version. Converted to JS, it might look like this:

const countdown = (n) => n > 0 ? [n , ...countdown (n - 1)] : []

const _partitions = (n, m) =>
  n < 0
    ? []
  : n == 0
    ? [[]]
  : countdown (m) .flatMap (x => _partitions (n - x, x) .map (p => [x, ...p])) 

We have a quick helper, countdown to turn, say 5 into [5, 4, 3, 2, 1]. The main function has two base cases, an empty result if n is negative and a result containing only the empty partition if n is zero. Otherwise, we countdown the possibilities for the maximum value in a single partition, and recur on the partitions for the target less than this new maximum, adding the maximum value to the front of each.

This should have similar performance characteristics as the above, but it somewhat simpler.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
1

Here is a recursive function that produces the results you want. It attempts to break down the input (numDB) into parts up to the maximum number (maxDB, which defaults to 4). It does this by taking the numbers from numDB down to 1 and adding all the possible results from a recursive call to that number, noting that the value of maxDB has to change to be no more than the first number.

const findDB = function(numDB, maxDB = 4) {
  if (numDB == 0) return [ [] ];
  let result = [];
  let thisDB = Math.min(numDB, maxDB);
  for (let i = thisDB; i > 0; i--) {
    findDB(numDB - i, Math.min(i, thisDB)).forEach(n => {
      result.push([i].concat(n));
    });
  }
  return result;
}
;
[6, 5, 4, 3, 2, 1].forEach((i) => console.log(JSON.stringify(findDB(i))))
.as-console-wrapper {
  min-height: 100% !important;
}

I've written the above function in the style in your question, with the use of various ES6 Array methods it can be simplified:

const DBlist = (n) => [...Array(n).keys()].map(k => n - k)

const findDB = (numDB, maxDB = 4) => {
  if (numDB == 0) return [ [] ];
  const thisDB = Math.min(numDB, maxDB);
  return DBlist(thisDB).flatMap((i) => findDB(numDB - i, Math.min(i, thisDB)).map(a => [i, ...a]))
}

DBlist(6).forEach((n) => console.log(JSON.stringify(findDB(n))))
.as-console-wrapper {
  min-height: 100% !important;
}
Nick
  • 138,499
  • 22
  • 57
  • 95