合并数组并保持排序

时间:2018-12-11 09:21:08

标签: javascript arrays topological-sort

注意

已根据@Kaddath的良好建议对问题进行了编辑,以突出显示以下事实:排序不必是字母顺序的,而是取决于数组中项目的位置。


我有一个数组数组,其中每个数组都基于给定的顺序,但是它们可以有所不同。

例如,基本顺序为X -> D -> H -> B,这是我的数组数组:

const arrays = [
  ['X', 'D', 'H', 'B'],
  ['X', 'D', 'K', 'Z', 'H', 'B', 'A'],
  ['X', 'M', 'D', 'H', 'B'],
  ['X', 'H', 'T'],
  ['X', 'D', 'H', 'B']
]

我想将所有数组合并为一个数组,并删除重复项,但要保持顺序。在我的示例中,结果为['X', 'M', 'D', 'K', 'Z', 'H', 'T', 'B', 'A']

在该示例中,我们可以看到M在第三个数组内的XD之间,并且它被放置在XD之间最终输出。

我知道可能会发生冲突,但这是以下规则:

  • 每个项目都应出现在最终输出中。
  • 如果某项在多个数组中的不同位置出现,则第一个外观是右侧的外观(跳过其他外观)。

到目前为止,我所做的是通过使用

将所有这些数组合并为一个数组
const merged = [].concat.apply([], arrays);

(参见https://stackoverflow.com/a/10865042/3520621)。

然后通过使用https://stackoverflow.com/a/1584377/3520621中的以下代码片段来获取唯一值:

Array.prototype.unique = function() {
    var a = this.concat();
    for(var i=0; i<a.length; ++i) {
        for(var j=i+1; j<a.length; ++j) {
            if(a[i] === a[j])
                a.splice(j--, 1);
        }
    }

    return a;
}; 
const finalArray = merged.unique();

但是我的结果是这样的

[
  "X",
  "D",
  "H",
  "B",
  "K",
  "Z",
  "A",
  "M",
  "T"
]

欢迎任何帮助!

谢谢。

10 个答案:

答案 0 :(得分:3)

平整,删除重复项并进行排序可能更简单:

const arrays = [
  ['A', 'B', 'C', 'D'],
  ['A', 'B', 'B-bis', 'B-ter', 'C', 'D', 'D-bis'],
  ['A', 'A-bis', 'B', 'C', 'D'],
  ['A', 'C', 'E'],
  ['A', 'B', 'C', 'D'],
];
console.log(
  arrays
    .flat()
    .filter((u, i, all) => all.indexOf(u) === i)
    .sort((a, b) => a.localeCompare(b)),
);

根据穆罕默德·乌斯曼(Mohammad Usman)现在已删除的帖子,事件更简单:

const arrays = [
  ['A', 'B', 'C', 'D'],
  ['A', 'B', 'B-bis', 'B-ter', 'C', 'D', 'D-bis'],
  ['A', 'A-bis', 'B', 'C', 'D'],
  ['A', 'C', 'E'],
  ['A', 'B', 'C', 'D'],
];
console.log(
  [...new Set([].concat(...arrays))].sort((a, b) =>
    a.localeCompare(b),
  ),
);

答案 1 :(得分:1)

您可以将.concat()Set一起使用,以得到唯一值的结果数组:

const data = [
  ['A', 'B', 'C', 'D'],
  ['A', 'B', 'B-bis', 'B-ter', 'C', 'D', 'D-bis'],
  ['A', 'A-bis', 'B', 'C', 'D'],
  ['A', 'C', 'E'],
  ['A', 'B', 'C', 'D']
];

const result = [...new Set([].concat(...data))].sort((a, b) => a.localeCompare(b));

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

答案 2 :(得分:1)

使用array#concat创建单个数组,然后使用Set从该数组中获取唯一值,然后对该数组进行排序。

const arrays = [ ['A', 'B', 'C', 'D'], ['A', 'B', 'B-bis', 'B-ter', 'C', 'D', 'D-bis'], ['A', 'A-bis', 'B', 'C', 'D'], ['A', 'C', 'E'], ['A', 'B', 'C', 'D'] ],
      result = [...new Set([].concat(...arrays))].sort();
console.log(result);

答案 3 :(得分:1)

好玩的问题要解决;我想我只能部分成功。

  • 我忽略了B -> A -> TT -> B -> A的“未指定”示例
  • 效率很低

仍然发布,因为我认为这可能会帮助您正确处理。这是我的方法:

步骤1:创建原始索引

我们正在创建一个对象,该对象针对嵌套数组中的每个唯一元素跟踪其成功或之前:

{
  "X": { prev: Set({}), next: Set({ "D", "H", "B", "K", "Z", "A", "M", "T" })
  "M": { prev: Set({ "X" }), next: Set({ "D", "H", "B" })
  // etc.
}

我将其命名为“天真”,因为这些集合仅包含一层深度的信息。

  

即:它们仅报告同一数组中元素之间的关系。他们看不到MK之前,因为他们从来不在同一数组中。

步骤2:以递归方式加入索引

在这里我忽略了所有可能涉及到的big-O问题。我递归合并索引:next的{​​{1}}是M的{​​{1}}的联接。递归直到找到没有next的元素,即D, H, Bnext

步骤3:创建一个尊重排序索引的排序器:

T

此函数创建一个排序方法,该方法使用生成的索引查找任意两个元素之间的排序顺序。

将它们组合在一起:

A

建议

我认为,通过使用链表/树状结构并遍历直到其元素插入正确索引处的元素,该问题的优雅解决方案可能能够跳过const indexSorter = idx => (a, b) => idx[a].next.has(b) || idx[b].prev.has(a) ? -1 : idx[a].prev.has(b) || idx[b].next.has(a) ? 1 : 0 ; 的所有合并找到(function() { const naiveSortIndex = xss => xss .map(xs => // [ prev, cur, next ] xs.map((x, i, xs) => [ xs.slice(0, i), x, xs.slice(i + 1) ]) ) // flatten .reduce((xs, ys) => xs.concat(ys), []) // add to index .reduce( (idx, [prev, cur, next]) => { if (!idx[cur]) idx[cur] = { prev: new Set(), next: new Set() }; prev.forEach(p => { idx[cur].prev.add(p); }); next.forEach(n => { idx[cur].next.add(n); }); return idx; }, {} ); const expensiveSortIndex = xss => { const naive = naiveSortIndex(xss); return Object .keys(naive) .reduce( (idx, k) => Object.assign(idx, { [k]: { prev: mergeDir("prev", naive, k), next: mergeDir("next", naive, k) } }), {} ) } const mergeDir = (dir, idx, k, s = new Set()) => idx[k][dir].size === 0 ? s : Array.from(idx[k][dir]) .reduce( (s, k2) => mergeDir(dir, idx, k2, s), new Set([...s, ...idx[k][dir]]) ); // Generate a recursive sort method based on an index of { key: { prev, next } } const indexSorter = idx => (a, b) => idx[a].next.has(b) || idx[b].prev.has(a) ? -1 : idx[a].prev.has(b) || idx[b].next.has(a) ? 1 : 0; const uniques = xs => Array.from(new Set(xs)); // App: const arrays = [ ['X', 'D', 'H', 'B'], ['X', 'D', 'K', 'Z', 'H', 'B', 'A'], ['X', 'M', 'D', 'H', 'B'], ['X', 'H', 'T'], ['X', 'D', 'H', 'B'] ]; const sortIndex = expensiveSortIndex(arrays); const sorter = indexSorter(sortIndex); console.log(JSON.stringify( uniques(arrays.flat()).sort(sorter) )) }()) / Set

答案 4 :(得分:1)

const arrays = [
  ['X', 'D', 'H', 'B'],
  ['X', 'D', 'K', 'Z', 'H', 'B', 'A'],
  ['X', 'M', 'D', 'H', 'B'],
  ['X', 'H', 'T'],
  ['X', 'D', 'H', 'B']
];
const result = [];
arrays.forEach(array => {
  array.forEach((item, idx) => {
    // check if the item has already been added, if not, try to add
    if(!~result.indexOf(item)) {
      // if item is not first item, find position of his left sibling in result array
      if(idx) {
        const result_idx = result.indexOf(array[idx - 1]);
        // add item after left sibling position
        result.splice(result_idx + 1, 0, item);
        return;
      }
      result.push(item);
    }
  });
});
console.log('expected result', ['X', 'M', 'D', 'K', 'Z', 'H', 'T', 'B', 'A'].join(','));
console.log(' current result',result.join(','));

答案 5 :(得分:0)

为此使用BST。将所有元素添加到bst中,然后按顺序遍历。

function BST(){
  this.key = null;
  this.value = null;
  this.left = null;
  this.right = null;

  this.add = function(key}{
   const val = key;

   key = someOrderFunction(key.replace(/\s/,''));
   if(this.key == null) {
      this.key = key;
      this.val = val;

   } else if(key < this.key) {
      if(this.left){
        this.left.add(val);
      } else {
        this.left = new BST();
        this.left.key = key;
        this.left.val = val;
      }
   } else if(key > this.key) {

      if(this.right){
        this.right.add(val);
      } else {
        this.right= new BST();
        this.right.key = key;
        this.right.val = val;
      }
   }

   this.inOrder = function(){
      const leftNodeOrder = this.left ? this.left.inOrder() : [],
            rightNodeOrder = this.right? this.right.inOrder() : [];
      return leftNodeOrder.concat(this.val).concat(this.rightNodeOrder);

   }

}

// MergeArrays uses a BST to insert all elements of all arrays
// and then fetches them sorted in order
function mergeArrays(arrays) {
    const bst = new BST();
    arrays.forEach(array => array.forEach( e => bst.add(e)));
    return bst.inOrder();
}

答案 6 :(得分:0)

我只是将数组展平,将它们作为键映射到对象(从而删除双精度),然后对最终结果进行排序

const arrays = [
  ['A', 'B', 'C', 'D'],
  ['A', 'B', 'B-bis', 'B-ter', 'C', 'D', 'D-bis'],
  ['A', 'A-bis', 'B', 'C', 'D'],
  ['A', 'C', 'E'],
  ['A', 'B', 'C', 'D']
];

const final = Object.keys( arrays.flat().reduce( (aggregate, entry) => {
  aggregate[entry] = '';
  return aggregate;
}, {} ) ).sort( (x1, x2) => x1.localeCompare(x2) );

console.log( final );

答案 7 :(得分:0)

  1. 合并[].concat.apply([], arrays)
  2. 找到uniq [...new Set(merged)]
  3. .sort()进行排序

const arrays = [
  ['A', 'B', 'C', 'D'],
  ['A', 'B', 'B-bis', 'B-ter', 'C', 'D', 'D-bis'],
  ['A', 'A-bis', 'B', 'C', 'D'],
  ['A', 'C', 'E'],
  ['A', 'B', 'C', 'D']
];


let merged = [].concat.apply([], arrays);  // merge array

let sort = [...new Set(merged)].sort(); // find uniq then sort

console.log(sort);

答案 8 :(得分:0)

对于您的代码,合并后,您需要删除重复项。这样您将获得唯一的数组。

使用array.sort对数组进行排序。

我希望这能解决问题。

const arrays = [
  ['A', 'B', 'C', 'D'],
  ['A', 'B', 'B-bis', 'B-ter', 'C', 'D', 'D-bis'],
  ['A', 'A-bis', 'B', 'C', 'D'],
  ['A', 'C', 'E'],
  ['A', 'B', 'C', 'D']
]

const merged = [].concat.apply([], arrays);

const unique = Array.from(new Set(merged))


const sorted = unique.sort()

console.log("sorted Array", sorted)

// Single Line
      const result = [...new Set([].concat(...arrays))].sort();
      
 console.log("sorted Array single line", result)

答案 9 :(得分:0)

每个数组实际上都是一组规则,它们告诉元素之间的相对顺序是什么。最终列表应在遵守所有规则定义的相对顺序的同时返回所有元素。

一些解决方案已经解决了最初的请求,有些甚至没有解决该问题(所有建议都使用某种方式遗漏了问题的要点)。但是,没有人提出通用解决方案。

问题

如果我们查看OP中提出的问题,这就是规则如何定义元素之间的相对位置:

   M    K -> Z    T
  ^ \  ^      \  ^
 /   v/        v/
X -> D ------> H -> B -> A

因此,很容易看到我们的数组以X开头。下一个元素可以是D和M。但是,D要求M已经在数组中。这就是为什么我们将M设为下一个元素,然后设为D的原因。接下来,D指向K和H。但是,由于H之前还没有收集到其他的前任元素,而K却没有(实际上它有D,但已经在列表中了),我们将K和Z放在一起,然后再放H。

H指向T和B。实际上,我们首先输入哪个都无所谓。因此,最后三个元素可以按以下三个顺序中的任何一个排列:

  • T,B,A
  • B,A,T
  • B,T,A

让我们也考虑一些更复杂的情况。规则如下:

['10', '11', '12', '1', '2'],
['11', '12', '13', '2'],
['9', '13'],
['9', '10'],

如果使用这些规则绘制图形,则会得到以下结果:

   --------------> 13 ----
  /                ^      \
 /                /        v
9 -> 10 -> 11 -> 12 > 1 -> 2

此案的具体内容是什么?两件事:

  • 仅在最后一条规则中,我们“发现”数字9是数组的开头
  • 从12到2有两条非直接路径(一个在数字1上,第二个在数字13上)。

解决方案

我的想法是从每个元素创建一个节点。然后使用该节点来跟踪所有直接后继者和直接前任者。之后,我们将找到所有没有前任的元素,然后从那里开始“收集”结果。如果我们到达具有多个前任节点的节点,但是其中一些未收集,我们将在此处停止递归。有可能某些继任者已经通过其他途径获得了。我们将跳过该继任者。

function mergeAndMaintainRelativeOrder(arrays/*: string[][]*/)/*: string[]*/ {
    /*
    interface NodeElement {
        value: string;
        predecessor: Set<NodeElement>;
        successor: Set<NodeElement>;
        collected: boolean;
    }
    */
    const elements/*: { [key: string]: NodeElement }*/ = {};
    // For every element in all rules create NodeElement that will
    // be used to keep track of immediate predecessors and successors
    arrays.flat().forEach(
        (value) =>
            (elements[value] = {
                value,
                predecessor: new Set/*<NodeElement>*/(),
                successor: new Set/*<NodeElement>*/(),
                // Used when we form final array of results to indicate
                // that this node has already be collected in final array
                collected: false,
            }),
    );

    arrays.forEach((list) => {
        for (let i = 0; i < list.length - 1; i += 1) {
            const node = elements[list[i]];
            const nextNode = elements[list[i + 1]];

            node.successor.add(nextNode);
            nextNode.predecessor.add(node);
        }
    });

    function addElementsInArray(head/*: NodeElement*/, array/*: string[]*/) {
        let areAllPredecessorsCollected = true;
        head.predecessor.forEach((element) => {
            if (!element.collected) {
                areAllPredecessorsCollected = false;
            }
        });
        if (!areAllPredecessorsCollected) {
            return;
        }
        array.push(head.value);
        head.collected = true;
        head.successor.forEach((element) => {
            if (!element.collected) {
                addElementsInArray(element, array);
            }
        });
    }

    const results/*: string[]*/ = [];
    Object.values(elements)
        .filter((element) => element.predecessor.size === 0)
        .forEach((head) => {
            addElementsInArray(head, results);
        });
    return results;
}

console.log(mergeAndMaintainRelativeOrder([
    ['X', 'D', 'H', 'B'],
    ['X', 'D', 'K', 'Z', 'H', 'B', 'A'],
    ['X', 'M', 'D', 'H', 'B'],
    ['X', 'H', 'T'],
    ['X', 'D', 'H', 'B'],
]));


console.log(mergeAndMaintainRelativeOrder([
    ['10', '11', '12', '1', '2'],
    ['11', '12', '13', '2'],
    ['9', '13'],
    ['9', '10'],
]));

大O

如果我们说n是规则数,而m是每个规则中的元素数,则此算法的复杂度为 O(n * m)。这考虑到了Set implementation for the JS is near O(1)