13

I have three sorted arrays like below

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

Those arrays are sorted based on name property of each object in Array. Here is the method I converted from Java to merge two sorted arrays

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

Here is the working fiddle with two arrays http://jsfiddle.net/euRn5/. What is the best approach to achieve the same with n number of Arrays, the thought I have in my mind currently is take one by one, merge it with previously merged till the last item, like n += i stuff. Is this a best approach?

Exception
  • 7,173
  • 18
  • 78
  • 128
  • Are you trying to do something faster than just merging the arrays and resorting? – jfriend00 Sep 09 '13 at 04:52
  • @jfriend00 Server will send data to me in individual sorted arrays, I am thinking it would be faster if I can achieve the merging. – Exception Sep 09 '13 at 04:56
  • How big are these arrays? – Joe Frambach Sep 09 '13 at 04:59
  • @JoeFrambach 10K items in each with 5-6 of properties in a single object – Exception Sep 09 '13 at 05:01
  • 1
    Your code won in this benchmark against mine: http://jsperf.com/merge-and-sort-large-sorted-arrays – Joe Frambach Sep 09 '13 at 05:15
  • Your question just says you want the "best approach". What is "best" is entirely ambiguous. If you want fastest approach regardless of complexity, then you *need* to say that to clarify what you mean. In most cases, where performance is not critical, "best" is more often simpler and more maintainable and less likely to have bugs (e.g. built off proven code). – jfriend00 Sep 09 '13 at 05:59
  • @jfriend00 To clarify, my first intension was, how to merge n number of sorted Arrays and second is whether is efficient approach in all scenarios. My second is clarified and first to be. – Exception Sep 10 '13 at 04:21
  • @Exception - I would suggest you put that clarification in the question itself with the Edit button. – jfriend00 Sep 10 '13 at 04:28

5 Answers5

4

Update:

Seeing as it is current_year this would now be:

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

Original:

If you're feeling functional this is a perfect place to use reduce.

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

example:

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/

david
  • 16,020
  • 4
  • 40
  • 57
  • 1
    Very nice `mergeAll` function! I like it! – Joe Frambach Sep 09 '13 at 15:40
  • 2
    This is a horrible solution. You are doing *n* merges. That wil have far worse performance than doing a single merge from *n* sources. I'm mystified why this would be the accepted answer. –  Jan 15 '17 at 05:36
4

The standard and most understanding code I believe..

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

Function call:

var merged_array = mergeArray([1,6,9,95], [2,7,10,11,14,18]);
Buggy
  • 2,914
  • 1
  • 14
  • 35
eugsun
  • 10,726
  • 6
  • 50
  • 69
3

Faster, merges in only 1 pass, with more flexibility (keepDuplicates, custom comparator):

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

Merging in 1 pass eliminates the creation of intermediate arrays which takes time and memory. Also, the number of comparisons is nicely reduced by keeping a sorted list of the next element from each array (see the next array). And when array sizes are known, they are pre-allocated to prevent dynamic re-allocations (though that will depend on your javascript engine).

For your case, I would call it like this:

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

Note: If you have a large number of arrays you may benefit from using a binary search instead of the linear search in insertNextIndex(). Or from using a Binary Heap for next.

jlgrall
  • 1,412
  • 11
  • 13
2

Edited to reflect that Exception's original solution, extended by calling it like mergeSorted(mergeSorted(a,b),c) is faster than my solution here.


Javascript's builtin sort is [not] fast enough that you can just concatenate all the arrays together and sort the entire thing in one go. Javascript is not good for re-implementing things that should be done lower level.

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"}]
Joe Frambach
  • 25,568
  • 9
  • 65
  • 95
2

The native implementations are not always the fastest (as you may have noticed) and have, historically, been somewhat sluggish, due to extensive error checking. That being said, there may be performance enhancements in the future, due to more robust integration with the hardware or routines specifically built to optimize certain tasks. If you write your own code, your application won't be able to take advantage of these boosts in performance once they're implemented. It's up to you to decide where the advantages lie and what the risks are.

At any rate, I've written a prettier version of your optimized code for funsies:

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;
}
THEtheChad
  • 2,232
  • 1
  • 14
  • 20