8

I want to merge arrays a little bit different way. I have 2 or more arrays like:

var array1 = ["apple", "banana"];

var array2 = ["apple", "apple", "orange"];

I want the output:

var array3 = ["apple", "apple", "banana", "orange"];

So if any given array has a variable in it more than once, merge algorithm should keep all of them from that array.

I saw some code that prevents duplication but it gives outputs like this:

var array3 = ["apple", "banana", "orange"];

for more example:

var arr1 = [1,2,3,4];

var arr2 = [1,1,2,4,5,5,5];

var arr3 = [1,3,3,5,5];

I want the output:

var array4 = [1,1,2,3,3,4,5,5,5];

How can I do this?

Community
  • 1
  • 1
u.zzz
  • 83
  • 4
  • 1
    `console.log(array1.concat(array2).sort())`, is that what you mean? – Xotic750 Jan 04 '15 at 22:00
  • 2
    @Xotic750: No. That would preserve every instance of an element, not just the greatest number of an element in any array. Look at his desired outputs carefully, especially in the last example. He wants two 1's, in the output, not four. – Robert Harvey Jan 04 '15 at 22:02
  • @RobertHarvey Aha, I see. (I think) – Xotic750 Jan 04 '15 at 22:05
  • @u.zzz What have you tried so far? – Xotic750 Jan 04 '15 at 22:13
  • @Xotic750 I am a beginner. I tried algorithms in [this](http://stackoverflow.com/questions/1584370/how-to-merge-two-arrays-in-javascript-and-de-duplicate-items) post but they are not working as I want. – u.zzz Jan 04 '15 at 22:17
  • Even as a beginner, you should try some code of your own to accomplish the task and ask specific question when you become stuck. – Xotic750 Jan 05 '15 at 02:27

5 Answers5

2

Here's one way to do it by counting the occurrences of each item in each array:

var arr1 = [1,2,3,4];
var arr2 = [1,1,2,4,5,5,5];
var arr3 = [1,3,3,5,5];

function joinCommon(/* list of arrays */) {
    var arr, arrayCounts, masterList = {}, item, output;
    // for each array passed in
    for (var i = 0; i < arguments.length; i++) {
        arr = arguments[i];
        arrayCounts = {};
        // iterate each array
        for (var j = 0; j < arr.length; j++) {
            item = arr[j];
            if (!arrayCounts[item]) {
                arrayCounts[item] = 1;
            } else {
                ++arrayCounts[item];
            }
            // now keep master list and master counts
            if (!masterList[item]) {
                masterList[item] = {cnt: 1, val: item};
            } else {
                masterList[item].cnt = Math.max(masterList[item].cnt, arrayCounts[item]);
            }
        }
    }
    // now output result
    output = [];
    for (var i in masterList) {
        for (var j = 0; j < masterList[i].cnt; j++) {
            output.push(masterList[i].val);
        }
    }
    return output;    
}

var results = joinCommon(arr1, arr2, arr3);

Working demo: http://jsfiddle.net/jfriend00/dtn6zw4m/

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • `["1","1","2","3","3","4","5","5","5"] !== [1,1,2,3,3,4,5,5,5]` – Xotic750 Jan 05 '15 at 02:20
  • 1
    @Xotic750 - was already working on fixing that. Worked fine for string values, but numbers were converted to strings because all property names are strings. Fixed now by keeping track of the actual value separately from the property name. – jfriend00 Jan 05 '15 at 02:34
  • Thought it was worth pointing out, and had every faith that you would fix it. ;) – Xotic750 Jan 05 '15 at 02:38
  • And just for interest a [jsPerf](http://jsperf.com/27770734) of ECMA3 vs ECMA5 solutions. – Xotic750 Jan 05 '15 at 03:00
1

I like to use ramda (http://ramdajs.com/docs/index.html) for this stuff

var arr1 = [1,2,3,4];

var arr2 = [1,1,2,4,5,5,5];

var arr3 = [1,3,3,5,5];

var allArrays = [arr1, arr2, arr3];

var allValues = R.compose(R.uniq, R.flatten)(allArrays);

var getItemCounts = R.countBy(function(item) {
   return item;
});

var itemCounts = R.map(function(arr) {
   return getItemCounts(arr);
})(allArrays);

var combined = [];
R.forEach(function(item) {
   var countsForItem = R.pluck(item, itemCounts);
   var maxCount = R.max(countsForItem);
   combined.push.apply(combined, R.repeatN(item, maxCount));
})(allValues);

console.log(combined.sort());

JSFiddle: http://jsfiddle.net/pcr0q1xa/3/

jtmarmon
  • 5,727
  • 7
  • 28
  • 45
1

Here is a solution using ECMA5.

Javascript

function indexOf(items, value) {
    return items.map(function (subitem) {
        return subitem.value;
    }).indexOf(value);
}

function countItems(previous, item) {
    var atIndex = indexOf(previous, item);

    if (atIndex !== -1) {
        previous[atIndex].count += 1;
    } else {
        previous.push({
            value: item,
            count: 1
        });
    }

    return previous;
}

function mergeCounts(item) {
    var atIndex = indexOf(this, item.value);

    if (atIndex === -1) {
        this.push(item);
    } else if (this[atIndex].count < item.count) {
        this[atIndex] = item;
    }
}

function expandCounts(previous, item) {
    var iter;

    for (iter = 0; iter < item.count; iter += 1) {
        previous.push(item.value);
    }

    return previous;
}

function mergeArg(items, arg) {
    arg.reduce(countItems, []).forEach(mergeCounts, items);

    return items;
}

function mergeMaxItems() {
    return [].reduce.call(arguments, mergeArg, []).reduce(expandCounts, []);
}

var arr1 = [1, 2, 3, 4],
    arr2 = [1, 1, 2, 4, 5, 5, 5],
    arr3 = [1, 3, 3, 5, 5];

document.body.appendChild(document.createTextNode(mergeMaxItems(arr1, arr2, arr3)));
Xotic750
  • 22,914
  • 8
  • 57
  • 79
1

Ramda is your friend.

function merge () {
  return R.chain(R.apply(R.repeat), R.toPairs(R.reduce(
    R.mergeWith(R.max),
    {},
    R.map(R.countBy(R.identity), arguments)
  )))
}

var array1 = ["apple", "banana"];

var array2 = ["apple", "apple", "orange"];

console.log(JSON.stringify(merge(array1, array2)))

var arr1 = [1,2,3,4];

var arr2 = [1,1,2,4,5,5,5];

var arr3 = [1,3,3,5,5];

console.log(JSON.stringify(merge(arr1, arr2, arr3)))
<script src="http://cdnjs.cloudflare.com/ajax/libs/ramda/0.22.1/ramda.min.js"></script>
David Braun
  • 5,573
  • 3
  • 36
  • 42
-1

Untested and not JS, but I think this is what you are looking for. I have just tested it manually, it worked for your test cases.

while items in lista or items in listb
    compare a.head, b.head
    if a.head is smaller or b.is_empty then 
         append a.head to output
         a.drophead
    else if b.head is smaller or a.is_empty then
         append b.head to output
         b.drophead
    else
         append b.head to output
         b.drophead
         a.drophead
ctrl-alt-delor
  • 7,506
  • 5
  • 40
  • 52