【问题标题】:Find all possible subset combos in an array?查找数组中所有可能的子集组合?
【发布时间】:2011-08-10 18:06:58
【问题描述】:

我需要获取一个数组的所有可能子集,其中至少有 2 项和未知的最大值。谁能帮帮我?

假设我有以下数组:

[1, 2, 3]

我如何得到这个?

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

【问题讨论】:

  • 所以基本上你想要电源组,减去那些

标签: javascript arrays subset


【解决方案1】:

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

console.log(combinations([1, 2, 3]).filter(a => a.length >= 2))

你们能解释一下吗?

【讨论】:

  • 答案不是用来提问的。如果你想问问题,你可以添加 cmets。
【解决方案2】:

这可能会或可能不会很好地进行基准测试,但这是另一种方法,而且非常简洁:

const combinations = arr => arr.reduce((acc, item) => {
  return acc.concat(acc.map(x => [...x, item]));
}, [[]]);


console.log(combinations([1, 2, 3]).filter(a => a.length > 1));

【讨论】:

    【解决方案3】:

    组合,简称:

    function combinations(array) {
      return new Array(1 << array.length).fill().map(
        (e1, i) => array.filter((e2, j) => i & 1 << j));
    }
    
    console.log(combinations([1, 2, 3]).filter(a => a.length >= 2))

    【讨论】:

    • 很好,但它是一个非常“简洁”的算法,它确实值得比你放在那里的更多的词:) 不需要太多,只是给它一些上下文的小总结。跨度>
    • fill() 需要至少一个值...这不起作用
    【解决方案4】:

    有史以来第一次提交答案!希望它可以帮助某人。我将此用于涉及返回数组数组的类似递归解决方案,我发现 flatMap() 方法非常有用。

    var arr = [1,2,3,4,5];
    
    function getAllCombos(arr){
       if(arr[0] === undefined) return [arr]
       return getAllCombos(arr.slice(1)).flatMap(el => [el.concat(arr[0]), el])
    }
    console.log(JSON.stringify(getAllCombos(arr)));

    如果您希望您的 console.log() 输出以 [1, 2, 3, 4 , 5] 而不是 [5, 4, 3, 2, 1] 开头,您可以添加 sort() 方法到像这样的组合:

    var arr = [1,2,3,4,5];
    
    function getAllCombos(arr){
       if(arr[0] === undefined) return [arr]
       return getAllCombos(arr.slice(1)).flatMap(el => [el.concat(arr[0]).sort(), el])
    }
    console.log(JSON.stringify(getAllCombos(arr)));

    【讨论】:

      【解决方案5】:

      这个算法需要递归......这就是我会做的事情

      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 的数组作为参数,我们一直使用参数数组的 tail 调用相同的 getSubArrays 函数。所以[1,2,3,4,5] 的尾部是[2,3,4,5]
      • 一旦我们有一个单项数组作为参数,例如[5],我们会将[[5]] 返回到之前的getSubArrays 函数调用。
      • 那么在前面的getSubArrays 函数中,arr[4,5] 并且subarr 被分配给[[5]]
      • 现在我们将[[5]].concat([[5]].map(e =&gt; e.concat(4), [[4]]) 返回到之前的getSubArrays 函数调用中,这实际上是[[5], [5,4], [4]]
      • 那么在前面的getSubArrays 函数中,arr[3,4,5],而subarr 被分配给[[5], [5,4], [4]]
      • 等等……

      【讨论】:

      • 运行 sn-p 后,我可以告诉我这得到了我正在寻找的答案,我喜欢它使用递归的事实。但我很难理解。 @Redu 你能否解释一下你是如何使用这种方法的?
      • @user3295436 谢谢.. 答案中添加了相同算法的更现代版本和解释。
      【解决方案6】:

      这是一种使用 ECMAScript 2015 generator function 查找所有组合的方法:

      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));
      }

      【讨论】:

      • 我正在使用您的方法,但是在非常大的输入上尝试时该功能会超时。这是因为我们首先生成所有可能的组合,然后生成每个组合。我们如何修改此功能以使其更高效?以下是挑战功能和说明的链接:repl.it/E2QW/1
      • @PiotrBerebecki 看起来你的 for 循环周围需要一个 else
      • 谢谢,我会试着测试一下,让你知道结果。
      • 我仍然试图围绕这个答案,但我真的很喜欢代码。
      【解决方案7】:

      通过这个question 的小调整,我希望我的解决方案更有效,因为它使用位运算符来生成所有子集。

      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);
      

      【讨论】:

      • 如果只想要一种特定大小的子集怎么办?一种选择是将if(result.length &gt;= size) 更改为if(result.length === size)。但这似乎效率低下,因为它仍在生成所有 2^n 子集,然后丢弃大小错误的子集。可以进一步调整您的代码以仅关注一种尺寸吗?
      • 由于反向 while 循环,结果是向后的。如果使用前向循环,它将具有正确的顺序
      • 这是一个很好的解决方案,但一旦您的数组达到任何超过 16 个字符的唯一分类,就会崩溃。
      【解决方案8】:

      在窃取this 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]]
      

      【讨论】:

      • 当我合并 30 个数字时,应用程序会崩溃
      • 这似乎缺少空集。
      【解决方案9】:

      使用二进制数

      // 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);
      

      【讨论】:

        【解决方案10】:

        如果元素顺序很重要:

        // 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 个项目,则有近 100 万个组合。
        • 如果您的数组有 12 个项目,那么就有超过 10 亿个组合。
        • 如果您的数组有 15 个项目,那么就有超过 3 万亿种组合。
        • 如果您的数组有 18 个项目,则超过 17 万亿 组合。
        • 如果您的数组有 20 个项目,则超过 6 个 十亿组合。
        • 如果您的数组有 21 个项目,则有超过 138 种六亿组合。
        • 如果您的数组有 22 个项目,则有 超过 3 亿种组合。

        【讨论】:

        • 问题要求组合,而不是排列
        • 据我了解,排列是(唯一?)派生所有可能组合的方法。
        • 我认为这根本不正确。创建一个大小相同的不同数组,用布尔值填充。遍历该数组的所有可能性(每个布尔值都经过真或假),并且对于每种可能性,将每个“真”的对应值添加到集合中。这将在 2^n 中创建所有组合,但不会创建所有排列。您也没有描述从排列集中提取组合的过程,只是一种创建大量排列的方法。
        • 是的,你是对的,我认为元素顺序也起了作用。 (认为​​好像人们想要锁定组合而不是唯一值集。)排列考虑了元素顺序,而组合只寻求唯一集。因此,如果对排列返回的集合进行排序(单独),则会有许多相同的结果。所以现在我的理解更好了。
        【解决方案11】:

        我稍微修改了接受的解决方案,以在 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>
        

        【讨论】:

        • 我得到了你的家人:)
        • 感谢您解决不公平的情况 fam ;)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-20
        • 1970-01-01
        • 2013-12-01
        相关资源
        最近更新 更多