问题 如何在JavaScript中合并排序的数组


我有三个排序的数组,如下所示

[{name:"a"}, {name:"b"}, {name:"m"}, {name:"x"}]
[{name:"a"}, {name:"e"}, {name:"i"}, {name:"o"}]
[{name:"g"}, {name:"h"}, {name:"m"}, {name:"n"}]

这些数组基于Array中每个对象的name属性进行排序。这是我从Java转换为合并两个排序数组的方法

function mergeSorted(a, b) {
  var answer = new Array(a.length + b.length), i = 0, j = 0, k = 0;
  while (i < a.length && j < b.length) {
    if (a[i].name < b[j].name) {
        answer[k] = a[i];
        i++;
    }else {
        answer[k] = b[j];
        j++;
    }
    k++;
  }
  while (i < a.length) {
    answer[k] = a[i];
    i++;
    k++;
  }
  while (j < b.length) {
    answer[k] = b[j];
    j++;
    k++;
  }
  return answer;
}

这是两个阵列的工作小提琴 http://jsfiddle.net/euRn5/用n个数组实现相同目标的最佳方法是什么,我脑海中的想法是一个接一个,将它与之前合并到最后一个项目合并,如n + = i stuff。这是最好的方法吗?


3984
2017-09-09 04:48


起源

您是否正在尝试更快地执行某些操作而不仅仅是合并阵列和求助? - jfriend00
@jfriend00服务器将在个别排序数组中向我发送数据,我认为如果我能实现合并会更快。 - Exception
这些阵列有多大? - Joe Frambach
@JoeFrambach每个10K项目,单个对象中有5-6个属性 - Exception
您的代码在此基准测试中胜出了我的: jsperf.com/merge-and-sort-large-sorted-arrays - Joe Frambach


答案:


更新:

看得很清楚 current_year 现在这将是:

const mergeAll = (...arrays) => arrays.reduce(mergeSorted);

原版的:

如果您感觉功能正常,这是使用reduce的理想场所。

var mergeAll = function(){
    return Array.prototype.slice.call(arguments).reduce(mergeSorted);
};

例:

var a = [{name:"a"}, {name:"b"}, {name:"m"}, {name:"x"}];
var b = [{name:"a"}, {name:"e"}, {name:"i"}, {name:"o"}];
var c = [{name:"g"}, {name:"h"}, {name:"m"}, {name:"n"}];

console.log(mergeAll(a,b,c).map(function(x){return x.name;}));

的jsfiddle: http://jsfiddle.net/FeT6m/


4
2017-09-09 05:42



非常好 mergeAll 功能!我喜欢! - Joe Frambach
这是一个可怕的解决方案。你在做 ñ 合并。这比从单一合并更糟糕的表现 ñ 源。我很困惑为什么这将是公认的答案。


我相信标准和最理解的代码..

function mergeArray(arr1, arr2) {
 var new_array = [];
 var i = 0,
     j = 0,
     index = 0;

 while (new_array.length != (arr1.length + arr2.length) - 1) {
     if (arr1[i] < arr2[j]) {
         new_array.push(arr1[i]);
         i++;
     } else {
         new_array.push(arr2[j]);
         j++;
     }
 }
 return new_array;

}

功能调用:

var merged_array = mergeArray([1,6,9,95], [2,7,10,11,14,18]);

3
2018-05-14 11:21





更快,仅合并1次传递,具有更大的灵活性(keepDuplicates,自定义比较器):

/*  mergeSortedArrays(arrays[, keepDuplicates[, comparator[, thisArg]]])
    Merges multiple sorted arrays into a new sorted array.
    Arguments:
        - arrays: array of sorted arrays to be merged
        - keepDuplicates (optional): (true/false) whether to keep duplicate values
            Default: false
        - comparator (optional): function used to compare values
            Default: sort numbers in ascending order
            Example comparator: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
        - thisArg (optional): comparator is bound to thisArg when invoked
    Returns: a new sorted array containing all the values from the arrays
*/
function mergeSortedArrays(arrays, keepDuplicates, comparator, thisArg) {
    // Coerce to boolean to speed up testings in some javascript engines:
    keepDuplicates = !!keepDuplicates;

    // By default, sort numbers in ascending order:
    if(!comparator) comparator = function(a, b) { return a - b; };

    var nb = arrays.length,     // Number of arrays to be merged
        iter = new Array(nb),   // Current position of iteration of each array
        next = [],              // Keep each array sorted by the value of their next element
        length = 0;             // The combined length of all arrays

    // Populate iter and next:
    for(var i = 0, arr; i < nb; i++) {
        arr = arrays[i];
        iter[i] = 0;
        if(arr.length > 0) {
            insertNextIndex(next, i, arr[0], comparator, thisArg);
        }
        length += arr.length;
    }

    // Insert index of array into next:
    function insertNextIndex(next, index, val, comparator, thisArg) {
        var i = next.length;
        while(i--) {    // Reverse loop...
            var j = next[i];
            if(comparator.call(thisArg, arrays[j][iter[j]], val) >= 0) {    // ...until we find a greater value
                break;
            }
        }
        next.splice(i + 1, 0, index);
    }


    var merged = keepDuplicates ? new Array(length) : [],
        k = 0,  // Iterate over merged
        min, val, lastVal;

    // First iteration to get a value for lastVal (for duplicate checks):
    if(!keepDuplicates && next.length > 0) {
        min = next.pop();
        arr = arrays[min];
        i = iter[min]++;
        val = arr[i];
        merged[k++] = val;
        lastVal = val;
        if(++i < arr.length) {  // If available, insert next value in next:
            insertNextIndex(next, min, arr[i], comparator, thisArg);
        }
    }

    // Merge multiple arrays:
    while(next.length > 1) {    // While there is still multiple arrays to be merged
        min = next.pop();
        arr = arrays[min];
        i = iter[min]++;
        val = arr[i];
        if(keepDuplicates || comparator.call(thisArg, lastVal, val) !== 0) {
            merged[k++] = val;
            lastVal = val;
        }
        if(++i < arr.length) {  // If available, insert next value in next:
            insertNextIndex(next, min, arr[i], comparator, thisArg);
        }
    }

    // When there remain only 1 array with unmerged values, use a faster loop:
    if(next.length > 0) {
        arr = arrays[next[0]];
        i = iter[next[0]];
        length = arr.length;

        while(i < length) { // To the end
            val = arr[i++];
            if(keepDuplicates || comparator.call(thisArg, lastVal, val) !== 0) {
                merged[k++] = val;
                lastVal = val;
            }
        }
    }

    return merged;
}

在1遍中合并消除了创建需要时间和内存的中间阵列。此外,通过保持每个数组中下一个元素的排序列表,可以很好地减少比较次数(参见 next 数组)。当数组大小已知时,它们会被预先分配以防止动态重新分配(尽管这取决于您的javascript引擎)。

对于你的情况,我会这样称呼它:

mergeSortedArrays(arrays, true, function(a, b) {
    return a.name < b.name ? -1 : 1;
});

注意:如果您有大量阵列,则可以使用a 二分搜索 而不是线性搜索 insertNextIndex()。或者从使用 二进制堆 对于 next


3
2018-05-14 03:31





编辑 反映这一点 Exception原来的解决方案,通过称之为扩展 mergeSorted(mergeSorted(a,b),c) 是 更快 比我的解决方案。


Javascript的内置排序不够快,你可以将所有数组连接在一起并一次性对整个事物进行排序。 Javascript是  有利于重新实施应该在较低级别完成的事情。

var a1 = [{name:"a"}, {name:"b"}, {name:"m"}, {name:"x"}]
var a2 = [{name:"a"}, {name:"e"}, {name:"i"}, {name:"o"}]
var a3 = [{name:"g"}, {name:"h"}, {name:"m"}, {name:"n"}]

a1.concat(a2,a3).sort(function(a,b){return (a.name>b.name)-(a.name<b.name)})
// [{name:"a"}, {name:"a"}, {name:"b"}, {name:"e"}, {name:"h"}, {name:"i"}, {name:"g"}, {name:"m"}, {name:"m"}, {name:"n"}, {name:"o"}, {name:"x"}]

2
2017-09-09 04:52



通常情况下,所有语言的高级实现很少超过内部的低级实现吗? - Mark Fox
一般是的。我同意。 - Joe Frambach
哇。获得例外代码: jsperf.com/merge-and-sort-large-sorted-arrays - Joe Frambach
这很酷。打败这门语言很有趣。不过,你的阅读情况要好得多。 - Mark Fox
我想知道这是连接慢,还是排序 - Joe Frambach


原生实现并不总是最快(正如您可能已经注意到的那样),并且由于广泛的错误检查,历史上有点迟缓。话虽如此,由于与专门为优化某些任务而专门构建的硬件或例程的更强大集成,未来可能会有性能增强。如果您编写自己的代码,那么一旦实现,您的应用程序将无法利用这些性能提升。由您来决定优势所在以及风险是什么。

无论如何,我已经为你的优化编写了一个更漂亮的优化代码版本:

function mergeSorted(a,b){
    var alen = a.length
      , blen = b.length
      , i, j, k = j = i = 0
      , answer = new Array(alen + blen)
    ;//var

    while(i < alen && j < blen)
                    answer[k++] = a[i].name < b[j].name ? a[i++] : b[j++];
    while(i < alen) answer[k++] = a[i++];
    while(j < blen) answer[k++] = b[j++];

    return answer;
}

2
2017-09-09 06:36