0

Assume I have the following 3 arrays which are sorted in ascending order by exercisedateandtime:

var listA = [
  {name: "Mike", exercisedateandtime: 1299233593000, exercise: "Jumping Jacks"}, 
  {name: "Mike", exercisedateandtime: 1299237012000, exercise: "Running"}
];

var listB = [
  {name: "Charles", exercisedateandtime: 1299233712000, exercise: "Swimming"},
  {name: "Charles", exercisedateandtime: 1299233831000, exercise: "Swimming"},
  {name: "Charles", exercisedateandtime: 1299240620000, exercise: "Swimming"}
];

var listC = [
{name: "William", exercisedateandtime: 1299237320000, exercise: "Fishing"},
{name: "William", exercisedateandtime: 1299237611000, exercise: "Motor Boating"},
{name: "William", exercisedateandtime: 1299305420000, exercise: "Cycling"}
];

What is the most efficient way to merge these lists such that I get a resulting array listD is also sorted in ascending order by exercisedateandtime? Is it possible to create a function that accepts any number of arrays? (Note: Though the sample provided is small, the ideal method should be handle large list(s) sizes.)

var listD = [
{name: "Mike", exercisedateandtime: 1299233593000, exercise: "Jumping Jacks"},
{name: "Charles", exercisedateandtime: 1299233712000, exercise: "Swimming"},
{name: "Charles", exercisedateandtime: 1299233831000, exercise: "Swimming"},
{name: "Mike", exercisedateandtime: 1299237012000, exercise: "Running"},
{name: "William", exercisedateandtime: 1299237320000, exercise: "Fishing"},
{name: "William", exercisedateandtime: 1299237611000, exercise: "Motor Boating"},
{name: "Charles", exercisedateandtime: 1299240620000, exercise: "Swimming"},
{name: "William", exercisedateandtime: 1299305420000, exercise: "Cycling"}
];
Setsuna
  • 2,081
  • 7
  • 32
  • 50
  • Yes, assume the lists are ordered by date in ascending order. – Setsuna Jun 27 '13 at 04:13
  • Look here http://stackoverflow.com/questions/5958169/how-to-merge-two-sorted-arrays-into-a-sorted-array – Piotr Stapp Jun 27 '13 at 04:13
  • Can we rely on the single list to be sorted correctly already? How many lists with how many items are there in average? – Bergi Jun 27 '13 at 04:15
  • You can rely on each "input" list (like listA, listB, listC... and any others that would be used as input to be sorted correctly by exercisedateandtime already). – Setsuna Jun 27 '13 at 04:16
  • 2
    In `listC` "Fishing" should come before "Motor Boating". Edited. – Aadit M Shah Jun 27 '13 at 04:48
  • @Setsuna Please accept an answer. Preferably aaronman's answer or mine. We've put a great deal of effort into explaining and writing code for you. – Aadit M Shah Jul 12 '13 at 06:34

4 Answers4

3
var listD = listA.concat(listB, listC).sort(function(a, b) {
    return a.exercisedateandtime - b.exercisedateandtime
});
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
Aguardientico
  • 7,641
  • 1
  • 33
  • 33
  • 5
    `[] + []` [doesn't result in an `Array`](http://jsconsole.com/?%5B%5D%20%2B%20%5B%5D) in [JavaScript](http://es5.github.io/#x11.6.1). Though, `[].concat([])` [will](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat). – Jonathan Lonowski Jun 27 '13 at 04:17
  • 1
    @aaronman The `.sort(...)` looks good. Just replace `(lista + listb + listc)` with `lista.concat(listb, listc)`. – Jonathan Lonowski Jun 27 '13 at 04:36
  • 1
    correct answer but it will change listB by merging c into it which is not desired, use a.concat(b).concat(c) .. also consider the optimization of the algorithm as the OP requires. – CME64 Jun 27 '13 at 05:27
  • 1
    This is clean and clear javascript and a good answer but not most efficient. – Jacob Jun 27 '13 at 06:26
  • This clearly not the most efficient way to do this that has been posted – aaronman Jun 27 '13 at 08:10
1

This seems like it is just a slight adaptation of a merge in a normal mergesort (runs in O(n)). Basically how that works is you keep looking at the beginning of each list and and taking the largest the smallest one an appending it to the new list it works exactly the same for three lists . So this is the code from Aadit's solution(same algo as mine), but everyone seems to be up-voting a solution that is significantly slower than this solution and I would like to counter act this so the OP doesn't end up using it in his code.

function merge(key) {
    var length = arguments.length, lists = [], newlist = [];
    for (var i = 1; i < length; i++) lists.push(arguments[i].slice());

    while (length = lists.length) {
        var min = Infinity;

        for (var i = 0; i < length; i++) {
            var list = lists[i];
            var value = list[0][key];

            if (value < min) {
                var index = i;
                min = value;
            }
        }

        var list = lists[index];
        newlist.push(list.shift());
        if (!list.length) lists.splice(index, 1);
    }

    return newlist;
}

Now you can merge the lists as follows:

var listD = merge("exercisedateandtime", listA, listB, listC);  

Basically how this works is is gets an list of lists and the key that it is supposed to sort by, in this case exercisedateandtime. Then it keeps finding the minimum front element of all the list until there are no lists left(the while loop is a little tricky since the = returns whatever is the value it is set to when lists is 0 it becomes false). This merge will work with any number of lists even if they are different sizes. This algorithm runs in O(n) assuming the list sizes are large enough to make the number of lists negligible.

EDIT: in an extremely unideal case where the number of lists is very large, which is not the case here as there are only three lists, and the size of each list is small it might be better to do a solution where the lists are merged like in a merge sort, 2 at a time. The code could be adapted to do this by checking the number of lists but it would be a lot more work to code. I'll also repeat one more time that the original question is for 3 lists so it will work fine for that.

Here is a link to the most ideal n-way merge I could find

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
aaronman
  • 18,343
  • 7
  • 63
  • 78
  • you are right, it is half the procedure of mergesort if they are already sorted within themselves .. – CME64 Jun 27 '13 at 04:17
  • yeah in fact if you think about it one could actually adapt merge sort to run with 3 partitions instead of two – aaronman Jun 27 '13 at 04:19
  • mergesort uses more than one partition by combining each pair. this can be done to 3 partitions too ( (1) (2 3) ) .. it is actually not halfway of the procedure, it is rather the last quarter of the procedure, well assuming all the 3 partitions have the same size ! otherwise re-partitioned to more arrays then processed normally as a mergesort. – CME64 Jun 27 '13 at 04:23
  • @CME64 are you arguing with me or agreeing? – aaronman Jun 27 '13 at 04:24
  • Does this handle more arrays of greater size and perform faster/acceptable performance relatively? Could you provide a sample? – Setsuna Jun 27 '13 at 04:41
  • @Setsuna Mine runs in O(n), everyone else's runs O(n*log(n)), I'll try to write an example but I will admit the code is more complex – aaronman Jun 27 '13 at 04:42
  • @Setsuna also as written Aguardientico's solution doesn't work – aaronman Jun 27 '13 at 04:43
  • @aaronman My solution runs in `O(n)` time. See it for yourself: http://stackoverflow.com/a/17334658/783743 – Aadit M Shah Jun 27 '13 at 04:51
  • 1
    @AaditMShah it's the same as mine your just less lazy than me and actually coded it – aaronman Jun 27 '13 at 04:53
  • merge sort is : O(n log n) the normal comparison function is O(n^2) – CME64 Jun 27 '13 at 05:13
  • the normal function runs as a selectionsort check this link guys : https://en.wikipedia.org/wiki/Sorting_algorithm – CME64 Jun 27 '13 at 05:15
  • 1
    @AaditMShah: No, it's `O(#lists * avListLen²)` for your solution. The n-way-merge described in the linked question with a priority queue would run in `O(#lists * avListLen * log(avListLen))`. And you cannot do better. – Bergi Jun 27 '13 at 05:17
  • @Bergi in the spirit of the question #lists is going to be very small, the only reason he wrote it as an n lists merge is because writing a solely three way merge is more trouble than it's worth – aaronman Jun 27 '13 at 05:27
  • In the spirit of my question, the ideal solution would be preferable for large list sizes to be merged together. – Setsuna Jul 06 '13 at 16:17
  • @Setsuna Yes and my solution and Aadit M Shah's are the only solutions that run in linear time and yet we both have the least upvotes. The first two solutions run in O(n log(n)) and both of ours run in O(n), not sure why the other two solutions are being upvoted – aaronman Jul 06 '13 at 16:20
  • @Setsuna also looking at your edit I don't think you understood my comment about the number of lists, my solution handles large lists fine, it starts to do slightly worse if the ***number*** of lists is large but still better than the other solutions – aaronman Jul 06 '13 at 16:48
  • @AaditMShah why bother editing it, the OP has committed to not accepting any answer, and the solution with the most votes is crap – aaronman Jul 12 '13 at 06:18
1

Yes, indeed. Try this:

function merge(key) {
    var length = arguments.length, lists = [], newlist = [];
    for (var i = 1; i < length; i++) lists.push(arguments[i].slice());

    while (length = lists.length) {
        var min = Infinity;

        for (var i = 0; i < length; i++) {
            var list = lists[i];
            var value = list[0][key];

            if (value < min) {
                var index = i;
                min = value;
            }
        }

        var list = lists[index];
        newlist.push(list.shift());
        if (!list.length) lists.splice(index, 1);
    }

    return newlist;
}

Now you can merge the lists as follows:

var listD = merge("exercisedateandtime", listA, listB, listC);

See the demo here: http://jsfiddle.net/b8WdW/2/

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • There +1 for the actually coding it – aaronman Jun 27 '13 at 04:54
  • Right now the other solution with 2 votes doesn't even work correctly – aaronman Jun 27 '13 at 04:55
  • @aaronman The first line of the code says that all the arguments after `key` are put into an array called `lists`. Hence calling `merge("exercisedateandtime", listA, listB, listC)` will result in `var lists = [listA, listB, listC]` inside the function. – Aadit M Shah Jun 27 '13 at 04:59
  • @aaronman No, `arguments` is a variable available freely inside every function. See for yourself: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments – Aadit M Shah Jun 27 '13 at 05:01
  • Nice I would code my own solution but it would end up looking the same as yours – aaronman Jun 27 '13 at 05:02
0

Fiddle

Basically I merged the arrays first before sorting them. So this should work even if you arrays are unsorted.

var listD = listB.concat(listA, listC);
console.log(listD);
listD.sort(function (a, b) {
return a.exercisedateandtime - b.exercisedateandtime
});
console.log(listD);
Akshat Jiwan Sharma
  • 15,430
  • 13
  • 50
  • 60
  • correct answer but it will change listA by merging c into it which is not desired, use .concat().concat() .. still not fast enough if arrays are huge – CME64 Jun 27 '13 at 04:33