【问题标题】:Generate subsets of length n生成长度为 n 的子集
【发布时间】:2016-06-18 02:37:20
【问题描述】:

给定一个Set,生成所有长度为n的子集。

  • 示例输入:

    set = new Set(['a', 'b', 'c']), length = 2
    
  • 样本输出:

    Set {'a', 'b'}, Set {'a', 'c'}, Set {'b', 'c'}
    

如何有效地做到这一点,不将Set 转换为Array

我已经有了一个很好的数组作为输入的解决方案:

function* subsets(array, length, start = 0) {
  if (start >= array.length || length < 1) {
    yield new Set();
  } else {
    while (start <= array.length - length) {
      let first = array[start];
      for (subset of subsets(array, length - 1, start + 1)) {
        subset.add(first);
        yield subset;
      }
      ++start;
    }
  }
}

let array = ['a', 'b', 'c', 'd'];

for (subset of subsets(array, 2)) {
  console.log(...subset);
}

但是,如果不将集合转换为数组,我就无法为集合提供有效的实现——我想避免这种情况,例如纯粹使用迭代器。

【问题讨论】:

  • 谁投票结束这个问题,因为它太宽泛了:我找不到任何解决方案。如果至少有一个,我会很高兴。如果您认为它太宽泛,您介意至少提供一个答案吗?
  • 如果您关心的是效率,使用数组版本不是更好吗?这是一个更有效的答案(对象与数组)stackoverflow.com/questions/8423493/…
  • 看看这个:link
  • @IsmailRBOUH "实现了计算 JS 数组中元素组合的函数。" 嗯...
  • @Oriol 这正是我被卡住的部分:克隆或反转迭代器是不可能的。但是 - 通过利用 Set.values() 保持插入顺序这一事实,可能存在解决方案,从开头删除并插入到结尾可能允许通过简单地增加迭代器来“回溯”。我还没有探索过,如果它比数组转换慢得多,我也不会感到惊讶。我希望有另一种方法。但是您的评论强烈表明没有。谢谢!

标签: javascript set subset


【解决方案1】:

我实现了您使用有序多子集删除和重新插入值的想法:

function orderedMultiSubsets(set, n) {
  if(!Number.isInteger(n) || n < 0) return function*(){}();
  var subset = new Array(n),
      iterator = set.values();
  return (function* backtrack(index) {
    if(index === n) {
      yield subset.slice();
    } else {
      for(var i=0; i<set.size; ++i) {
        subset[index] = iterator.next().value; /* Get first item */
        set.delete(subset[index]); /* Remove it */
        set.add(subset[index]); /* Insert it at the end */
        yield* backtrack(index+1);
      }
    }
  })(0);
}
for(var subset of orderedMultiSubsets(new Set([1,2,3]), 2)) {
  console.log(subset.join());
}

然后我想我设法为无序子集的情况尽早修剪:

function subsets(set, n) {
  if(!Number.isInteger(n) || n < 0 || n > set.size) return function*(){}();
  var subset = new Array(n),
      iterator = set.values();
  return (function* backtrack(index, remaining) {
    if(index === n) {
      yield subset.slice();
    } else {
      for(var i=0; i<set.size; ++i) {
        subset[index] = iterator.next().value; /* Get first item */
        set.delete(subset[index]); /* Remove it */
        set.add(subset[index]); /* Insert it at the end */
        if(i <= remaining) {
          yield* backtrack(index+1, remaining-i);
        }
      }
    }
  })(0, set.size-n);
}
for(var subset of subsets(new Set([1,2,3,4]), 2)) {
  console.log(subset.join());
}

我仍然使用数组作为子集,但它的大小只有n。因此,如果集合的大小比n 大得多,这种方法可能会比将集合复制到数组中使用更少的内存,我想这是你问题的重点。但请注意,删除和插入可能比数组查找更昂贵。

奖励:最后,集合的顺序与调用函数之前相同。

【讨论】:

  • 顺便说一句:你有性能比较吗?您的最后一次编辑表明您这样做了:)
  • @le_m 我刚刚使用了var t = performance.now(); for(var subset of subsets(new Set([1,2,3,4,5,6,7,8,9,10,11,12,13,14]), 7)) { if(subset.length !== 7) throw Error('wrong'); } performance.now() - t;。它从 4 秒提高到 40 毫秒。
  • 如何更改有序函数,使其仅使用集合 1 中的每个项目(例如,不允许使用“2,2”)?
猜你喜欢
  • 1970-01-01
  • 2015-11-27
  • 1970-01-01
  • 1970-01-01
  • 2018-04-17
  • 1970-01-01
  • 1970-01-01
  • 2015-06-24
  • 1970-01-01
相关资源
最近更新 更多