0

I have a problem where I need to iterate through many possibilities programatically.

Let me give you an example as it'll be far more understandable.

I got a vector (or array for simplicity) say [0.4, 0.2, 0.3, 0.1] where every value is between 0 and 1, with a step of 0.1 and the sum of them has to be 1.

I also have function to which I pass this array and it returns me a number.

The goal is to try all the possible combination of the values into the array and keep the one where the function gives me the lowest number possible.

I tried to find a simple way to iterate throught the possible solutions but couldn't arrive to something I could code easily.

  • please add an example of the wanted result - and what you have tried ... – Nina Scholz Aug 26 '17 at 19:46
  • 2
    All possible combinations, when the result is the lowest number, would be just the lowest number in the array, which is `Math.min.apply(null, array)`? – adeneo Aug 26 '17 at 19:47

2 Answers2

0

First, you'll need a function that calculate all possible permutations of your array.

Here it is (I took it from here):

function permutations(list) {
  if (list.length <= 1) {
    return list.slice();
  }

  var result = [];
  var rest;
  var current;
  var permutationsRest;
  var i, j;

  for (i = 0; i < list.length; i++) {
    rest = list.slice(); // make a copy of list
    current = rest.splice(i, 1);
    permutationsRest = permutations(rest);
    for (j = 0; j < permutationsRest.length; j++) {
      result.push(current.concat(permutationsRest[j]));
    }
  }

  return result;
}

Next, make your function that return a number for a given array. For the sake of example, I made mine:

function calculate(array) {
  var i;
  var score = 0;
  for (i = 0; i < array.length; i++) {
    score = array[i] * (i + 1);
  }
  return score;
}

Then apply this function over all permutations, while storing the best result.

var permutations = permutations([0.4, 0.2, 0.3, 0.1]);
var score;
var bestScore = Number.MAX_VALUE; // as you stated that lower the score is, better it is, so MAX_VALUE would be the worst score possible
var bestPermutation;
var i;

for (i = 0; i < permutations.length; i++) {
  score = calculate(permutations[i]);
  if (score < bestScore) { // lower is best
    bestScore = score;
    bestPermutation = permutations[i];
  }
}

console.log(bestScore);
console.log(bestPermutation);
// prints "[0.4, 0.2, 0.3, 0.1]"

I want the permutations to be: [0.0, 1.0] [0.1, 0.9] [0.2, 0.8] [0.3, 0.7] [0.4, 0.6] [0.5, 0.5] [0.6, 0.4] [0.7, 0.3] [0.8, 0.2] [0.9, 0.1] [1.0, 0.0] Note how in every permutation, the sum is 1.

The following function will create any possible permutation for given size, where sum would be equal to 1 and step to 0.1. I used ES6 to be more concise, tell me if you need old browser support.

Note that to avoid weird float values (like 0.7999999999999998), I use integers from 0 to 10, that I divide just before returning array of results.

function createVector(size) {
  return (function recursive(permutations) {
    if (permutations[0].length === size) {
      // end of recursive, so map 0-10 values to 0.0-1.0
      return permutations.filter(t => t.reduce((a, b) => a + b, 0) === 10)
                         .map(t => t.map(i => i / 10));
    }

    // calculate each permutation which sum <= 10
    const newPermutations = [];
    for (let i = 0; i <= 10; i++) {
      permutations.filter(permutation => permutation.reduce((a, b) => a + b, i) <= 10)
                  .forEach(permutation => newPermutations.push([i].concat(permutation)));
    }

    return recursive(newPermutations);
  }([[]])); // start with empty array []
}

const result = createVector(2);
console.log(result);
// [[0,1], [0.1,0.9], [0.2,0.8], [0.3,0.7], [0.4,0.6], [0.5,0.5], [0.6,0.4], [0.7,0.3], [0.8,0.2], [0.9,0.1], [1,0]]

I found some limitations when wanting to compute a vector of size > 4 (it just stack overflows)

To avoid stack overflow errors, I made an iterative version, so no function are involved. It is 2 to 3 times faster according to this benchmark, and since the code is a bit long, you'll find it in the following fiddle:
https://jsfiddle.net/7v5w7zpy/

Jonathan
  • 772
  • 4
  • 11
  • Cool ! Actually the permutations function in interesting. The only thing is that, if I have a starting vector of [0.4, 0.6], I want the permutations to be: `[0.0, 1.0] [0.1, 0.9] [0.2, 0.8] [0.3, 0.7] [0.4, 0.6] [0.5, 0.5] [0.6, 0.4] [0.7, 0.3] [0.8, 0.2] [0.9, 0.1] [1.0, 0.0]` Note how in every permutation, the sum is 1. – Adrien Aggery Aug 28 '17 at 07:50
  • The function createVector is exactly what I needed. Thank you ! – Adrien Aggery Aug 28 '17 at 09:43
  • After using your answer in my project I found some limitations when wanting to compute a vector of size > 4 (it just stack overflows) which is understandable with that complexity. Do you think using tail recursion, would solve this problem ? And if yes how ? – Adrien Aggery Sep 04 '17 at 17:08
  • Well I think this is already tail recursion. On my browser (Chrome), I can even do `createVector(15)` to get a `1961256`-length array. I'll try make the non-recursive way, it may help. – Jonathan Sep 05 '17 at 08:08
  • @AdrienAggery I've updated my answer with an iterative version. – Jonathan Sep 05 '17 at 09:16
  • oh yea sorry I omited the fact that i changed the 10 to a 100, so i can have 2 decimals number lol thats why the complexity got increased a lot and that I need to find a more efficient way – Adrien Aggery Sep 05 '17 at 16:59
0

Assuming, by combination you mean combinations with at least two elements and knowing that all elements are at least 0.1 step far way from each other (which means there are no dupes) You may find the minimum and the second minimum and add them.

function getMinSum(a){
  var min = Math.min(...a);
  return min + a.reduce((p,c) => p > min && c > min ? p < c ? p
                                                            : c
                                                    : p === min ? c
                                                                : p);
}

var arr = [0.4, 0.2, 0.3, 0.1];
console.log(getMinSum(arr));
Redu
  • 25,060
  • 6
  • 56
  • 76