JS - 合并至少共享一个公共值的数组

时间:2016-09-17 23:30:18

标签: javascript arrays

如果我有以下数组数组:

var myArr = [[0, 1, 2], [1, 2, 6], [9, 10], [10, 11], [11, 12], [13]];

如何合并至少共享一个公共值的数组以产生以下输出?

var myMergedArr = [[0, 1, 2, 6], [9, 10, 11, 12], [13]];

谢谢!

注意:它们并不总是很好地排序,并且在订购所有内容时,共享值可能并不总是开始/结束值。为清楚起见,我订购了上述值。

3 个答案:

答案 0 :(得分:3)

可以使用空数组(merged)作为起始值来减少数组。对于myArray中的每个数组,existing被定义为subArray merged的{​​{1}}数组,以便每个subArrayarray的交集不是空的。

如果找不到这样的数组,existing将保持未定义,并且新数组(包含在另一个数组中)被定义为existing并被推送到merged

如果找到多个匹配项(existing.slice(1)不为空),则需要将它们合并在一起:existing[0]充当所有其他子数组(existing[1..])获取的容器合并(没有重复)。然后需要在merged中找到这些进一步的匹配并将其删除,因为它们已经合并。这可以保证多个数组合并在一起,即使它们之前没有合并,也可以合并。

然后,array的每个项目都会被推送到existing[0](如果尚未包含)。最后,返回merged。然后,reduce的下一次迭代可以再次merged作为第一个参数,并继续myArr中的下一个数组。

这是ES6的代码。如果需要,您可以将其转换并将其填充到ES5。

var myArr = [
    [0, 1, 2],
    [1, 2, 6],
    [9, 10],
    [10, 11],
    [11, 12],
    [13]
  ],
  myMergedArr = myArr.reduce((merged, array) => {
    let existing = merged.filter((subArray) => subArray.filter((subItem) => array.includes(subItem)).length);

    if (!existing.length) {
      existing = [
        []
      ];
      merged.push(existing[0]);
    }
    else {
      existing.slice(1).forEach((furtherArray) => {
        furtherArray.forEach((item) => {
          if (!existing[0].includes(item)) {
            existing[0].push(item);
          }
        });
        merged.splice(merged.findIndex((subArray) => furtherArray == subArray), 1);
      });
    }

    array.forEach((item) => {
      if (!existing[0].includes(item)) {
        existing[0].push(item);
      }
    });

    return merged;
  }, []);

console.log(myMergedArr);

第二个代码段是相同的代码,但更改了数组。这是为了证明这个脚本可以工作,即使子数组不是有序的:第一个[0, 1, 2]是独立的,然后[3, 4, 5]也是独立的 - 两者仍然是分开的。只有稍后[2, 3]会导致所有先前的数组合并为一个。

var myArr = [
    [0, 1, 2],
    [3, 4, 5],
    [2, 3],
    [7, 9],
    [9, 10],
    [13]
  ],
  myMergedArr = myArr.reduce((merged, array) => {
    let existing = merged.filter((subArray) => subArray.filter((subItem) => array.includes(subItem)).length);

    if (!existing.length) {
      existing = [
        []
      ];
      merged.push(existing[0]);
    }
    else {
      existing.slice(1).forEach((furtherArray) => {
        furtherArray.forEach((item) => {
          if (!existing[0].includes(item)) {
            existing[0].push(item);
          }
        });
        merged.splice(merged.findIndex((subArray) => furtherArray == subArray), 1);
      });
    }

    array.forEach((item) => {
      if (!existing[0].includes(item)) {
        existing[0].push(item);
      }
    });

    return merged;
  }, []);

console.log(myMergedArr);

答案 1 :(得分:1)

disjoint-set data structure似乎非常适合您的情况:



function merge(arrays) {
  var ds = new DisjointSet();
  arrays.forEach(function(array) {
    array.reduce(function(prevSet, currentValue) {
      var currentSet = ds.getSet(currentValue);
      if(prevSet) prevSet.mergeWith(currentSet);
      return currentSet;
    }, false);
  });
  return ds.partitions();
}
var myArr = [[0, 1, 2], [1, 2, 6], [9, 10], [10, 11], [11, 12], [13]];
console.log(JSON.stringify(merge(myArr)));

<script> /* DisjointSet library */
class MySet {
  constructor(owner) {
    this.rank = 0;
    this.parent = this;
    this.owner = owner;
  }
  representative() {
    var parent = this.parent;
    if(this === parent) return this;
    while(parent !== (parent = parent.parent));
    this.parent = parent; /* Path compression */
    return parent;
  }
  mergeWith(other) {
    var r1 = this.representative(),
        r2 = other.representative();
    if(r1 === r2) return;
    if(r1.owner !== r2.owner) throw new Error("Can't merge");
    if(r1.rank > r2.rank) { r2.parent = r1; return; }
    r1.parent = r2;
    if(r1.rank === r2.rank) ++r1.rank;
  }
}
class DisjointSet {
  constructor() {
    this.sets = new Map();
  }
  getSet(value) {
    var sets = this.sets;
    var set = sets.get(value);
    if(set) return set;
    set = new MySet(this);
    sets.set(value, set);
    return set;
  }
  partitions() {
    var parts = new Map();
    for(var [value,set] of this.sets) {
      var repre = set.representative();
      var arr = parts.get(repre);
      if(arr) arr.push(value);
      else parts.set(repre, [value]);
    }
    return [...parts.values()];
  }
}
</script>
&#13;
&#13;
&#13;

假设持续的地图操作,摊销时间成本应仅为O(n α(n)) ≈ O(n)

  

每次操作的摊还时间仅为O(α(n)),其中α(n) [...]   对于n的所有远程实用值,小于5。就这样   每次操作的摊销运行时间实际上是一个很小的常数。

注意我使用ES6映射能够将每个值与其集合相关联。如果所有值都是数字,则不需要这样,然后可以将它们存储为对象属性。但是在partitions中,您需要提取与集合关联的值,并且存储该数据将需要更多内存。

&#13;
&#13;
function merge(arrays) {
  var ds = new DisjointSet();
  arrays.forEach(function(array) {
    array.reduce(function(prevSet, currentValue) {
      var currentSet = ds.getSet(currentValue);
      if(prevSet) prevSet.mergeWith(currentSet);
      return currentSet;
    }, false);
  });
  return ds.partitions();
}
var myArr = [[0, 1, 2], [1, 2, 6], [9, 10], [10, 11], [11, 12], [13]];
console.log(JSON.stringify(merge(myArr)));
&#13;
<script> /* DisjointSet library */
function MySet(value, owner) {
  this.rank = 0;
  this.parent = this;
  this.value = value;
  this.owner = owner;
}
MySet.prototype.representative = function() {
  var parent = this.parent;
  if(this === parent) return this;
  while(parent !== (parent = parent.parent));
  this.parent = parent; /* Path compression */
  return parent;
};
MySet.prototype.mergeWith = function(other) {
  var r1 = this.representative(),
      r2 = other.representative();
  if(r1 === r2) return;
  if(r1.owner !== r2.owner) throw new Error("Can't merge");
  if(r1.rank > r2.rank) { r2.parent = r1; return; }
  r1.parent = r2;
  if(r1.rank === r2.rank) ++r1.rank;
};
function DisjointSet() {
  this.sets = Object.create(null);
}
DisjointSet.prototype.getSet = function(value) {
  var sets = this.sets;
  var set = sets[value];
  if(set) return set;
  set = new MySet(value, this);
  sets[value] = set;
  return set;
};
DisjointSet.prototype.partitions = function() {
  var parts = [];
  var assoc = Object.create(null);
  var sets = this.sets;
  Object.getOwnPropertyNames(sets).forEach(function(value) {
    var set = sets[value];
    var repreValue = set.representative().value;
    var arr = assoc[repreValue];
    if(arr) arr.push(set.value);
    else parts.push(assoc[repreValue] = [set.value]);
  });
  return parts;
};
</script>
&#13;
&#13;
&#13;

答案 2 :(得分:1)

我相信问题可以用一个相当简单的功能代码来解决,如下所示;

&#13;
&#13;
function merger(arr){
  return arr.map((e,i,a) => a.slice(i)
                             .reduce((p,c) => e.some(n => c.includes(n)) ? [...new Set([...p,...c])] : p,[]))
            .reduce((r,s) => { var merged = false;
                               r = r.map(a => a.some(n => s.includes(n)) ? (merged = true, [...new Set([...a,...s])]) : a);
                               !merged && r.push(s);
                               return r;
                             },[]);
}

var arr1 = [[0, 1, 2], [1, 2, 6], [9, 10], [10, 11], [11, 12], [13]],
    arr2 = [[0, 1], [2, 3], [1, 2]];


console.log(merger(arr1));
console.log(merger(arr2));
&#13;
&#13;
&#13;

关键部分的一点解释。在扩展运算符的帮助下,使用Set对象手动使用Array对象非常容易。所以下面这篇文章可能看起来有点令人困惑,但它确实很重要。

[...new Set([...a,...s])]

假设a是一个数组或集合,s是另一个数组或集合。然后[...a,...s]将两者合并成一个数组。 new Set([...a,...s])通过删除合并数组中的dupes来创建一个新集。 [...new Set([...a,...s])]将我们的集合转换为ab连接和删除的数组。酷..!

a.some(n => s.includes(n))

如果数组a和数组s至少有一个公共项目返回true其他false