Возвращает все возможные комбинации чисел в массиве, сумма которых меньше или равна n

var a = [1,3,6,10,-1];
function combinations(array, n) {
}

combinations(a, 9) // should return...
[[1], [3], [6], [-1], [1,3], [1,6], [1,-1], [3,6], [3,-1], [6, -1], [10, -1], [1,3,-1], [3,6,-1], [1,6,-1], [1,3,6,-1]]

может быть, я пропустил некоторые правильные ответы, но вы поняли идею. Действительно умираю, чтобы знать, как решить эту проблему!

9 ответов


я бы сказал, что проблема здесь в том, чтобы взять набор мощности массива и отфильтровать его только до элементов, сумма которых больше определенного числа.

набор мощности набора-это набор всех подмножеств этого набора. (Скажите это пять раз быстро, и вы станете математиком)

например, набор мощности [1] и [[], [1]] и набор мощности [1, 2] и [[], [1], [2], [1, 2]].

сначала я бы определил и [1, 2] являются подмножествами [1, 2].

        set = set.slice(); // copy the array
        set.push(lastElement); // add the element
        powerset.push(set);

вот и все. На данный момент переменная powerset и [[], [2], [1], [1, 2]]. Верните его!

    }

    return powerset;
};

грубая сила O (N*2N), где N = a.length < 31.

Это использует индекс i как битовое поле для filter элементы a на каждой итерации в подсписок.

var a = [1,3,6,10,-1];

function combinations(array, n) {
    var lists = [], M = 1<<array.length;
    for( var i = 1 ; i < M ; ++i ) {
        var sublist = array.filter(function(c,k){return i>>k & 1});
        if( sublist.reduce(function(p,c){return p+c},0) <= n )
            lists.push(sublist);
    }
    return lists;
}

console.log(JSON.stringify(combinations(a,9)));

[[1],[3],[1,3],[6],[1,6],[3,6],[-1],[1,-1],[3,-1],[1,3,-1],[6,-1],[1,6,-1],[3,6,-1],[1,3,6,-1],[10,-1]]


похоже на ответ Мэтта,но использует массив.фильтр () и массив.уменьшить (), чтобы упаковать удар. Переменная,mask увеличивается от 1 до 32-1 в этом примере (потому что длина массива равна 5 и count = 1

значение включается в перестановку, если маска сдвинута вправо на индекс значения нечетно. Думайте двоично здесь, потому что либо предполагается, что значение находится в перестановке, либо нет (0 или 1), и поскольку маска будет проходить через все возможные числа, все возможные перестановки покрываются непосредственно в числе, выраженном в двоичном виде:

    : 4,3,2,1,0
  • маска: 0 0 0 0 1 (грейферный индекс 0, [1])
  • маска: 0 0 0 1 0 (захватить индексом 1, [3])
  • маска: 0 0 0 1 1 (захватить индексами 0 и 1, [1,3])
  • маска: 1 1 0 0 0 (индекс захватить 3 и 4, [10,-1])

var a = [1,3,6,10,-1];
function combinations(array, n) {
  var mask, len = array.length, count = 1 << len, permutations = [];
  var indexVisible = function(v, i) { return ((mask >> i) & 1) == 1 }
  var sum = function(a, b) { return a + b }
  for (mask = 1; mask < count; ++mask) {
    permutations.push(array.filter(indexVisible))
  }
  return permutations.filter(function(p) { return p.reduce(sum) <= n })
}
console.log(JSON.stringify(combinations(a, 9)));

функции indexVisible() используется для фильтрации исходного массива и возврата перестановки, соответствующей маске.

функции sum() используется для уменьшения каждой перестановки сумме ее значений, и если эта сумма меньше или равна n затем он включается в конечный результат и возвращается из combinations()


вот перестановки: [[1],[3],[1,3],[6],[1,6],[3,6],[1,3,6],[10],[1,10],[3,10],[1,3,10],[6,10],[1,6,10],[3,6,10],[1,3,6,10],[-1],[1,-1],[3,-1],[1,3,-1],[6,-1],[1,6,-1],[3,6,-1],[1,3,6,-1],[10,-1],[1,10,-1],[3,10,-1],[1,3,10,-1],[6,10,-1],[1,6,10,-1],[3,6,10,-1],[1,3,6,10,-1]]

здесь результаты: [[1],[3],[1,3],[6],[1,6],[3,6],[-1],[1,-1],[3,-1],[1,3,-1],[6,-1],[1,6,-1],[3,6,-1],[1,3,6,-1],[10,-1]]

вы можете увидеть, как все это работает и играть с различными комбинациями в этой JSFiddle.


редактировать: предоставление кредита, где это необходимо.. позаимствовал основную часть этой логики из ответ

var combinations = function(a,m) {
  var gc = function(a) {
    var fn = function(n, src, got, all) {
      if (n == 0) {
        if (got.length > 0) {
          all[all.length] = got;
        }
        return;
      }
      for (var j = 0; j < src.length; j++) {
        fn(n - 1, src.slice(j + 1), got.concat([src[j]]), all);
      }
      return;
    }
    var all = [];
    for (var i = 0; i < a.length; i++) {
      fn(i, a, [], all);
    }
    all.push(a);
    return all;
  }
  var c = gc(a);
  return c.filter(function(e) {
    var n = e.length;
    var sum = 0;
    while(n--)
      sum += parseFloat(e[n]) || 0;
    return sum<=m;
  },m);
}
var a = [1,3,6,10,-1];
combinations(a,9);

выход

[[1], [3], [6], [-1], [1, 3], [1, 6], [1, -1], [3, 6], [3, -1], [6, -1], [10, -1], [1, 3, -1], [1, 6, -1], [3, 6, -1], [1, 3, 6, -1]]

похоже, что очень весело не играть, вот что у меня есть.

в JavaScript

function kCombs(set, k) {
    var setLength = set.length,
        combs = [],
        i = 0,
        tailLength,
        head,
        tail,
        j,
        t,
        u;

    if (k > 0 && k <= setLength) {
        if (k === setLength) {
            combs.push(set);
        } else if (k === 1) {
            while (i < setLength) {
                combs.push([set[i]]);
                i += 1;
            }
        } else {
            u = k - 1;
            setLength = setLength - k + 1;
            while (i < setLength) {
                t = i + 1;
                head = set.slice(i, t);
                tail = kCombs(set.slice(t), u);
                j = 0;
                tailLength = tail.length;
                while (j < tailLength) {
                    combs.push(head.concat(tail[j]));
                    j += 1;
                }

                i = t;
            }
        }
    }

    return combs;
}

function combinations(array, n) {
    var arrayLength = array.length,
        combs = [],
        combsLength,
        results = [],
        temp = 0,
        current,
        currentLength,
        i,
        j,
        k = 1;

    while (k <= arrayLength) {
        i = 0;
        current = kCombs(array, k);
        currentLength = current.length;
        while (i < currentLength) {
            combs.push(current[i]);
            i += 1;
        }

        k += 1;
    }

    i = 0;
    combsLength = combs.length;
    while (i < combsLength) {
        j = 0;
        current = combs[i];
        currentLength = current.length;
        while (j < currentLength) {
            temp += current[j];
            j += 1;
        }

        if (temp <= n) {
            results.push(current);
        }

        temp = 0;
        i += 1;
    }

    return results;
}

var a = [1, 3, 6, 10, -1];

console.log(JSON.stringify(combinations(a, 9)));

выход

[[1],[3],[6],[-1],[1,3],[1,6],[1,-1],[3,6],[3,-1],[6,-1],[10,-1],[1,3,-1],[1,6,-1],[3,6,-1],[1,3,6,-1]] 

On jsFiddle

и a см. Этот тест jsperf из всех этих, хотя решения @jcarpenter дают двусмысленность.

в современном браузере вы можете выжать больше из этого решения, используя for intead while как они оптимизированы для for. И назначить по индексу, а не push также даст вам прирост производительности.

было бы неплохо расширить тесты производительности, чтобы включить еще несколько наборов тестов, возможно, если мне станет скучно.


краткость здесь очень загадочно. Как насчет некоторых описательных функций?

подход использует двоичный код для создания карт всех возможных комбинаций. Затем карта используется для извлечения элементов из массива. Выщипанные предметы суммируются, вот и все.

результат combinations([1, 3, 6, 10, -1], 9) произвел: [[-1],[10,-1],[6],[6,-1],[3],[3,-1],[3,6],[3,6,-1],[1],[1,-1],[1,6],[1,6,-1],[1,3],[1,3,-1],[1,3,6,-1]].

вот Скрипка.

/**
* Get an array of all the possible combinations
* of x items.  Combinations are represented as binary.
* @param {Number} x - example 2
* @return {String[]} - example ['00', '01', '10', '11']
*/
function getCombinationsOfXItems(x) {

  var allOn = '',
    numCombos = 0,
    i = 0,
    combos = [];

  // find upper limit
  while (allOn.length < x) {
    allOn += 1;
  }

  // number of possible combinations
  numCombos = parseInt(allOn, 2) + 1;

  // generate the combos
  while(i < numCombos) {
    combos.push(pad(toBase2(i++), allOn.length));
  }

  return combos;
}

/**
* Pad a string with leading zeros.
* @param {String} x - example '100'
* @param {Number} length - example 6
* @return {String} - example '000100'
*/
function pad(x, length) {
  while (x.length < length) {
    x = 0 + x;
  }

  return x;
}

/**
* Get a number as a binary string.
* @param {Number} x - example 3
* @return {String} - example '11'
*/
function toBase2(x) {
  return x.toString(2);
}

/**
* Given an array and a map of its items as a binary string,
* return the items identified by 1.
* @param {Array} arr - example [1,2,3]
* @param {String} binary - example '101'
* @return {Array} - example [1,3]
*/
function pluckFromArrayByBinary(arr, binary) {
  var  plucked = [],
    i = 0,
    max = binary.length;

  for (; i < max; i++) {
    if (binary[i] === '1') {
      plucked.push(arr[i]);
    } 
  }

  return plucked;
}

/**
* Given an array, return a multi-dimensional
* array of all the combinations of its items.
* @param {Array} - example [1, 2];
* @return {Array[]} - [ [1], [1, 2], [2] ]
*/
function getCombosOfArrayItems(arr) {
  var comboMaps = getCombinationsOfXItems(arr.length),
    combos = [];

  // remove the "all off" combo (ex: '00000')
  comboMaps.shift();

  for (var i = 0; i < comboMaps.length; i++) {
    combos.push(pluckFromArrayByBinary(arr, comboMaps[i]));
  }

  return combos;
}

/**
* Return all possible combinations of numbers in an
* array whose sum is less than or equal to n
* @param {Number[]} arr
* @param {Number} x
* return {Number[]} - stringified for readability
*/
function combinations(arr, x) {
  var combos = getCombosOfArrayItems(arr),
    i = 0,
    max = combos.length,
    combo;

  for (; i < max; i++) {
    if (sumArray(combos[i]) > x) {
      combos.splice(i, 1);
      i--;
      max--;
    }
  }

  return JSON.stringify(combos);
}

/**
* Return the sum of an array of numbers.
* @param {Number[]} arr
* @return {Number}
*/
function sumArray(arr) {
  var sum = 0,
    i = 0,
    max = arr.length;

  for (; i < max; i++) {
    sum += arr[i];
  }

  return sum;
}

console.log(combinations([1, 3, 6, 10, -1], 9));

@jcarpenter решение было настолько хорошим, что мне просто пришлось переработать его для тех, кто любит ECMA5. Это будет не так быстро, как грубую силу for, у современных методов не было времени для такой высокой оптимизации (и они делают совсем немного больше работы). Но результаты производительности показывают, насколько хорошо powerSet алгоритм (и это многоразовая функция). Я также отфильтровал двусмысленность, которая замедляет вещи слегка.

в JavaScript

function powerSet(arr) {
    var lastElement,
        val;

    if (!arr.length) {
        val = [[]];
    } else {
        lastElement = arr.pop();
        val = powerSet(arr).reduce(function (previous, element) {
            previous.push(element);
            element = element.slice();
            element.push(lastElement);
            previous.push(element);

            return previous;
        }, []);
    }

    return val;
}

function combinations(array, n) {
    return powerSet(array).filter(function (set) {
        return set.length && set.reduce(function (previous, element) {
            return previous + element;
        }, 0) <= n;
    });
}

var a = [1, 3, 6, 10, -1];

console.log(JSON.stringify(combinations(a, 9)));

выход

[[-1],[10,-1],[6],[6,-1],[3],[3,-1],[3,6],[3,6,-1],[1],[1,-1],[1,6],[1,6,-1],[1,3],[1,3,-1],[1,3,6,-1]] 

On jsFiddle

и добавил см. Этот тест jsperf


следующий код даст вам все суб-массивы, суммирующие до 9 или менее..

function getSubArrays(arr,n){
  var len = arr.length,
     subs = Array(Math.pow(2,len)).fill();
  return subs.map((_,i) => { var j = -1,
                                 k = i,
                               res = [];
                             while (++j < len ) {
                               k & 1 && res.push(arr[j]);
                               k = k >> 1;
                             }
                             return res;
                           }).slice(1)
                             .filter(a => a.reduce((p,c) => p+c) <= n);
}

var arr = [1,3,6,10,-1],
 result = getSubArrays(arr,9);
console.log(JSON.stringify(result));

попробуйте это:

var a = [1,3,6,10,-1];
function combinations(array, n) {
    var arrayCopy = [],
        results = [];

    // duplicate the array
    for (var i in array)
        arrayCopy[i] = array[i];

    for (var i in array)
        for (var j in arrayCopy)
            if ((array[i] + arrayCopy[j]) <= n)
                results.push([array[i], arrayCopy[j]]);

    return results;
}

console.log(combinations(a, 9));

эта запись:

[1, 1], [1, 3], [1, 6], [1, -1], 
[3, 1], [3, 3], [3, 6], [3, -1], 
[6, 1], [6, 3], [6, -1], 
[10, -1], 
[-1, 1], [-1, 3], [-1, 6], [-1, 10], [-1, -1]