Найти все возможные комбинации подмножеств в массиве? - PullRequest
30 голосов
/ 22 апреля 2011

Мне нужно получить все возможные подмножества массива с минимумом 2 элемента и неизвестным максимумом. Кто-нибудь, кто может мне немного помочь?

Скажи, что у меня есть ...

[1,2,3]

... как мне это получить?

[
    [1,2]
    , [1,3]
    , [2,3]
    , [1,2,3]
]

Ответы [ 8 ]

56 голосов
/ 22 апреля 2011

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

var combine = function(a, min) {
    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 = min; i < a.length; i++) {
        fn(i, a, [], all);
    }
    all.push(a);
    return all;
}

Для использования укажите массив и минимальную желаемую длину подмножества,

var subsets = combine([1, 2, 3], 2);

Выход есть,

[[1, 2], [1, 3], [2, 3], [1, 2, 3]]
12 голосов
/ 14 августа 2013

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

var sets = (function(input, size) {
    var results = [], result, mask, i, total = Math.pow(2, input.length);
    for (mask = size; mask < total; mask++) {
        result = [];
        i = input.length - 1;

        do {
            if ((mask & (1 << i)) !== 0) {
                result.push(input[i]);
            }
        } while (i--);

        if (result.length >= size) {
            results.push(result);
        }
    }

    return results; 
})(['a','b','c','d','e','f'], 2);
console.log(sets);
8 голосов
/ 23 августа 2016

Вот способ найти все комбинации, используя ECMAScript 2015 Функция генератора :

function* generateCombinations(arr) {
  function* doGenerateCombinations(offset, combo) {
    yield combo;
    for (let i = offset; i < arr.length; i++) {
      yield* doGenerateCombinations(i + 1, combo.concat(arr[i]));
    }
  }
  yield* doGenerateCombinations(0, []);
}

for (let combo of generateCombinations([1, 2, 3, 4, 5])) {
  console.log(JSON.stringify(combo));
}

Чтобы ограничить минимальный размер, как указано в вопросе, просто убедитесь, что длина комбинации перед ее получением:

function* generateCombinations(arr, minSize) {
  function* doGenerateCombinations(offset, combo) {
    if (combo.length >= minSize) {
      yield combo;
    }
    for (let i = offset; i < arr.length; i++) {
      yield* doGenerateCombinations(i + 1, combo.concat(arr[i]));
    }
  }
  yield* doGenerateCombinations(0, []);
}

for (let combo of generateCombinations([1, 2, 3, 4, 5], 2)) {
  console.log(JSON.stringify(combo));
}

Ограничение в точке yield позволяет удобочитаемым способом адаптировать эту функцию к другим распространенным случаям использования, например, выбрать все комбинации точного размера:

function* generateCombinations(arr, size) {
  function* doGenerateCombinations(offset, combo) {
    if (combo.length == size) {
      yield combo;
    } else {
      for (let i = offset; i < arr.length; i++) {
        yield* doGenerateCombinations(i + 1, combo.concat(arr[i]));
      }
    }
  }
  yield* doGenerateCombinations(0, []);
}

for (let combo of generateCombinations([1, 2, 3, 4, 5], 2)) {
  console.log(JSON.stringify(combo));
}
6 голосов
/ 23 августа 2016

Этот алгоритм кричит о рекурсии ... вот как бы я это сделал

var arr = [1,2,3,4,5];
function getSubArrays(arr){
  if (arr.length === 1) return [arr];
  else {
  	subarr = getSubArrays(arr.slice(1));
  	return subarr.concat(subarr.map(e => e.concat(arr[0])), [[arr[0]]]);
  }
}
console.log(JSON.stringify(getSubArrays(arr)));

Еще одна модная версия вышеуказанного алгоритма;

var arr = [1,2,3,4,5],
    sas = ([n,...ns],sa) => !ns.length ? [[n]]
                                       : (sa = sas(ns),
                                          sa.concat(sa.map(e => e.concat(n)),[[n]]));

Чтобы понять, что происходит, давайте пошагово шаг за шагом

  • До тех пор, пока мы не получим массив длины 1 в качестве аргумента, мы продолжаем вызывать одну и ту же функцию getSubArrays с tail массива аргументов. Итак, хвост [1,2,3,4,5] равен [2,3,4,5].
  • Как только у нас есть один массив элементов в качестве аргумента, такой как [5], мы возвращаем [[5]] к предыдущему вызову функции getSubArrays.
  • Тогда в предыдущей функции getSubArrays arr равно [4,5] и subarr присваивается [[5]].
  • Теперь мы возвращаем [[5]].concat([[5]].map(e => e.concat(4), [[4]]), что на самом деле [[5], [5,4], [4]] к предыдущему вызову функции getSubArrays.
  • Тогда в предыдущей функции getSubArrays arr равно [3,4,5] и subarr присваивается [[5], [5,4], [4]].
  • и так далее ...
5 голосов
/ 01 марта 2017

Комбинации, короткий:

function combinations(array) {
    return new Array(1 << array.length).fill().map(
        (e1,i) => array.filter((e2, j) => i & 1 << j));
}

И вызов как

combinations([1,2,3]).filter(a => a.length >= 2)
2 голосов
/ 05 ноября 2014

Использование двоичных чисел

// eg. [2,4,5] ==> {[],[2],[4],[5],[2,4],[4,5],[2,5], [2,4,5]}

var a = [2, 4, 5], res = [];
for (var i = 0; i < Math.pow(2, a.length); i++) {
    var bin = (i).toString(2), set = [];
    bin = new Array((a.length-bin.length)+1).join("0")+bin;
    console.log(bin);
    for (var j = 0; j < bin.length; j++) {
        if (bin[j] === "1") {
            set.push(a[j]);
        }
    }
    res.push(set);
}
console.table(res);
0 голосов
/ 04 февраля 2014

Если важен порядок элементов:

// same values, different order:

[1,2]
[2,1]

[1,3]
[3,1]

Тогда вы также можете рассмотреть вопрос о перестановке.

// ---------------------
// Permutation
// ---------------------
function permutate (src, minLen, maxLen){

    minLen = minLen-1 || 0;
    maxLen = maxLen || src.length+1;
    var Asource = src.slice(); // copy the original so we don't apply results to the original.

    var Aout = [];

    var minMax = function(arr){
        var len = arr.length;
        if(len > minLen && len <= maxLen){
            Aout.push(arr);
        }
    }

    var picker = function (arr, holder, collect) {
        if (holder.length) {
           collect.push(holder);
        }
        var len = arr.length;
        for (var i=0; i<len; i++) {
            var arrcopy = arr.slice();
            var elem = arrcopy.splice(i, 1);
            var result = holder.concat(elem);
            minMax(result);
            if (len) {
                picker(arrcopy, result, collect);
            } else {
                collect.push(result);
            }
        }   
    }

    picker(Asource, [], []);

    return Aout;

}

var combos = permutate(["a", "b", "c"], 2);


for(var i=0; i<combos.length; i++){
    var item = combos[i];
    console.log("combos[" + i + "]" + " = [" + item.toString() + "]");
}

ВНИМАНИЕ !!! - Ваша машина не может обрабатывать массивы с> 10 элементами.

  • Если в вашем массиве 9 элементов, комбинаций почти 1 миллион.
  • Если в вашем массиве 12 элементов, существует более 1 миллиарда комбинаций.
  • Если в вашем массиве 15 элементов, существует более 3 триллионов комбинаций.
  • Если в вашем массиве 18 элементов, то здесь более 17 квадриллионов комбинации.
  • Если в вашем массиве 20 элементов, их более 6 квинтиллионные комбинации.
  • Если в вашем массиве 21 элемент, 138 комбинаций секстиллионов.
  • Если в вашем массиве 22 элемента, есть более 3 миллиардов комбинаций.
0 голосов
/ 19 сентября 2012

Я немного изменил принятое решение, чтобы рассмотреть пустой набор, когда min равен 0 (пустой набор - это подмножество любого данного набора).

Вот полная примерная страница для копированиявставить, готов к запуску с некоторым выходом.

<html>

<head>

<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<title>All Subsets</title>

<script type="text/javascript">

// get all possible subsets of an array with a minimum of X (min) items and an unknown maximum
var FindAllSubsets = function(a, min) {
    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 = [];

    // empty set is a subset of the set (only when min number of elements can be 0)
    if(min == 0)
      all.push([-1]); // array with single element '-1' denotes empty set

    for (var i = min; i < a.length; i++) {
        fn(i, a, [], all);
    }

    all.push(a);
    return all;
}

function CreateInputList(){
  var inputArr = [];
  var inputArrSize = 4;
  var maxInputValue = 10;
  for(i=0; i < inputArrSize; i++){
    var elem = Math.floor(Math.random()*maxInputValue);
    // make sure to have unique elements in the array
    while(inputArr.contains(elem)){ // OR - while(inputArr.indexOf(elem) > -1){
      elem = Math.floor(Math.random()*maxInputValue);
    }
    inputArr.push(elem);
  }
  return inputArr;
}

Array.prototype.contains = function(obj) {
    var i = this.length;
    while (i--) {
        if (this[i] === obj) {
            return true;
        }
    }
    return false;
}

function ArrayPrinter(arr){
  var csv = 'input = [';
  var i = 0;
  for(i; i<arr.length - 1; i++){
    csv += arr[i] + ', ';
  }
  csv += arr[i];

  var divResult = document.getElementById('divResult');
  divResult.innerHTML += csv + ']<br />';
}

// assumes inner array with single element being '-1' an empty set
function ArrayOfArraysPrinter(arr){
  var csv = 'subsets = ';
  var i = 0;
  for(i; i<arr.length; i++){
    csv += '[';
    var j = 0;
    var inArr = arr[i];
    for(j; j<inArr.length - 1; j++){
      csv += inArr[j] + ', ';
    }
    // array with single element '-1' denotes empty set
    csv += inArr[j] == -1 ? '&lt;E&gt;' : inArr[j];
    csv += ']';
    if(i < arr.length - 1)
      csv += '&nbsp;&nbsp;';
  }

  csv += ' &nbsp; (&#35; of subsets =' + arr.length + ')';

  var divResult = document.getElementById('divResult');
  divResult.innerHTML += csv + '<br />';
}

function Main(){
  // clear output
  document.getElementById('divResult').innerHTML = '';

  // sample run (min = 0)
  document.getElementById('divResult').innerHTML += '<hr/>MIN = 0 (must include empty set)<br />';
  var list = CreateInputList();
  ArrayPrinter(list);
  var subsets = FindAllSubsets(list, 0);
  ArrayOfArraysPrinter(subsets);
  document.getElementById('divResult').innerHTML += '<hr />';

  // sample run (min = 1)
  document.getElementById('divResult').innerHTML += 'MIN = 1<br />'; 
  var list = CreateInputList();
  ArrayPrinter(list);
  var subsets = FindAllSubsets(list, 1);
  ArrayOfArraysPrinter(subsets);
  document.getElementById('divResult').innerHTML += '<hr />';

  // sample run (min = 2)
  document.getElementById('divResult').innerHTML += 'MIN = 2<br />'; 
  var list = CreateInputList();
  ArrayPrinter(list);
  var subsets = FindAllSubsets(list, 2);
  ArrayOfArraysPrinter(subsets);
  document.getElementById('divResult').innerHTML += '<hr />';

  // sample run (min = 3)
  document.getElementById('divResult').innerHTML += 'MIN = 3<br />'; 
  var list = CreateInputList();
  ArrayPrinter(list);
  var subsets = FindAllSubsets(list, 3);
  ArrayOfArraysPrinter(subsets);
  document.getElementById('divResult').innerHTML += '<hr />';

  // sample run (min = 4)
  document.getElementById('divResult').innerHTML += 'MIN = 4<br />'; 
  var list = CreateInputList();
  ArrayPrinter(list);
  var subsets = FindAllSubsets(list, 4);
  ArrayOfArraysPrinter(subsets);
  document.getElementById('divResult').innerHTML += '<hr />';
}

</script>

</head>

<body>
  <input type="button" value="All Subsets" onclick="Main()" />
  <br />
  <br />
  <div id="divResult"></div>
</body>

</html>
...