【问题标题】:How to Merge sorted Arrays in JavaScript如何在 JavaScript 中合并已排序的数组
【发布时间】:2013-09-09 04:48:15
【问题描述】:

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

[{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 的东西。这是最好的方法吗?

【问题讨论】:

  • 您是否正在尝试做一些比合并数组和重新排序更快的事情?
  • @jfriend00 服务器会以单独的排序数组向我发送数据,我想如果我能实现合并会更快。
  • 这些数组有多大?
  • @JoeFrambach 10K 项,每个对象中包含 5-6 个属性
  • 你的代码在这个基准测试中胜过我的:jsperf.com/merge-and-sort-large-sorted-arrays

标签: javascript


【解决方案1】:

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

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

【讨论】:

  • 这是错误的,因为j 可能超出了arr2 的范围。测试用例失败mergeArray([2,3], [1]),返回[1,undefined]
  • 这个答案处理你的边缘情况@SE-stopfiringthegoodguys stackoverflow.com/a/18692680/630752
【解决方案2】:

更新:

现在看来是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/

【讨论】:

  • 非常好的mergeAll函数!我喜欢!
  • 这是一个可怕的解决方案。您正在执行 n 合并。这将比从 n 源进行单个合并的性能差得多。我很困惑为什么这会是公认的答案。
【解决方案3】:

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

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

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

【讨论】:

    【解决方案4】:

    更快,仅 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;
    });
    

    注意:如果您有大量数组,您可能会受益于使用binary search 而不是insertNextIndex() 中的线性搜索。或者使用Binary Heap 代替next

    【讨论】:

      【解决方案5】:

      编辑以反映 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"}]
      

      【讨论】:

      • 不是所有语言的高级实现都很少能击败内部的低级实现吗?
      • 一般是的。我同意。
      • 这很酷。打败语言很有趣。不过,你的读起来好多了。
      • 不知道是连接慢,还是排序慢
      • 这甚至不是问题的解决方案,因为它完全没有利用输入的预排序特性。
      【解决方案6】:

      我喜欢 thetheChad 的回答,但我更喜欢更具可读性的内容

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

      基本上mergeSorted 为一个新数组分配内存,该数组的大小与两个输入数组的总和相同。然后它按顺序用项目填充目的地。如果其中一个数组更长,我们 while 循环,直到所有项目都被复制。应该通过以下测试套件。

      // test
      console.log(mergeSorted([1,6,9,95], [2,7,10,11,14,18]));
      console.log(mergeSorted([2,3], [1]))
      console.log(mergeSorted([0,0], [0,0]))
      console.log(mergeSorted([1,3], [2]))
      console.log(mergeSorted([1,2], [3,4]))
      

      【讨论】:

        猜你喜欢
        • 2021-06-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-02-20
        • 1970-01-01
        • 1970-01-01
        • 2021-08-12
        • 1970-01-01
        相关资源
        最近更新 更多