crusarovid
crusarovid

Reputation: 541

counting duplicate arrays within an array in javascript

I have an array of arrays as follows:

    [[3, 4], [1, 2], [3, 4]]

I wish to create a new array of arrays that has no duplicates, and has a count of the number of occurrences of each element in the first array:

    [[3,4,2], [1,2,1]]

here is what I have so far:

var alreadyAdded = 0; 
dataset.forEach(function(data) {
 From = data[0];
 To = data[1];

 index = 0;
 newDataSet.forEach(function(newdata) {
  newFrom = newData[0];
  newTo = newData[1];

  // check if the point we are looking for is already added to the new array
  if ((From == newFrom) && (To == newTo)) {

   // if it is, increment the count for that pair
   var count = newData[2];
   var newCount = count + 1;
   newDataSet[index] = [newFrom, newTo, newCount];
   test = "reached here";
   alreadyAdded = 1;
  }
  index++;
 });

 // the pair was not already added to the new dataset, add it
 if (alreadyAdded == 0) {
  newDataSet.push([From, To, 1]);
 }

 // reset alreadyAdded variable
 alreadyAdded = 0;
});

I am very new to Javascript, can someone help explain to me what I'm doing wrong? I'm sure there is a more concise way of doing this, however I wasn't able to find an example in javascript that dealt with duplicate array of arrays.

Upvotes: 1

Views: 343

Answers (4)

Ben Stephens
Ben Stephens

Reputation: 3371

const x = [[3, 4], [1, 2], [3, 4]];

const with_duplicate_count = [
  ...x
    .map(JSON.stringify)
    .reduce( (acc, v) => acc.set(v, (acc.get(v) || 0) + 1), new Map() )
    .entries()
].map(([k, v]) => JSON.parse(k).concat(v));

console.log(with_duplicate_count);

Upvotes: 0

Colin
Colin

Reputation: 882

Depending on how large the dataset is that you're iterating over I'd be cautious of looping over it so many times. You can avoid having to do that by creating an 'index' for each element in the original dataset and then using it to reference the elements in your grouping. This is the approach that I took when I solved the problem. You can see it here on jsfiddle. I used Array.prototype.reduce to create an object literal which contained the grouping of elements from the original dataset. Then I iterated over it's keys to create the final grouping.

var dataSet = [[3,4], [1,2], [3,4]],
    grouping = [],
    counts,
    keys,
    current;

counts = dataSet.reduce(function(acc, elem) {
    var key = elem[0] + ':' + elem[1];
    if (!acc.hasOwnProperty(key)) {
        acc[key] = {elem: elem, count: 0}
    }
    acc[key].count += 1;
    return acc;
}, {});

keys = Object.keys(counts);
for (var i = 0, l = keys.length; i < l; i++) {
    current = counts[keys[i]];
    current.elem.push(current.count);
    grouping.push(current.elem);
}

console.log(grouping);

Upvotes: 1

Xotic750
Xotic750

Reputation: 23472

Assuming order of sub array items matters, assuming that your sub arrays could be of variable length and could contain items other than numbers, here is a fairly generic way to approach the problem. Requires ECMA5 compatibility as it stands, but would not be hard to make it work on ECMA3.

Javascript

// Create shortcuts for prototype methods
var toClass = Object.prototype.toString.call.bind(Object.prototype.toString),
    aSlice = Array.prototype.slice.call.bind(Array.prototype.slice);

// A generic deepEqual defined by commonjs
// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
function deepEqual(a, b) {
    if (a === b) {
        return true;
    }

    if (toClass(a) === '[object Date]' && toClass(b) === '[object Date]') {
        return a.getTime() === b.getTime();
    }

    if (toClass(a) === '[object RegExp]' && toClass(b) === '[object RegExp]') {
        return a.toString() === b.toString();
    }

    if (a && typeof a !== 'object' && b && typeof b !== 'object') {
        return a == b;
    }

    if (a.prototype !== b.prototype) {
        return false;
    }

    if (toClass(a) === '[object Arguments]') {
        if (toClass(b) !== '[object Arguments]') {
            return false;
        }

        return deepEqual(aSlice(a), aSlice(b));
    }

    var ka,
        kb,
        length,
        index,
        it;

    try {
        ka = Object.keys(a);
        kb = Object.keys(b);
    } catch (eDE) {
        return false;
    }

    length = ka.length;
    if (length !== kb.length) {
        if (Array.isArray(a) && Array.isArray(b)) {
            if (a.length !== b.length) {
                return false;
            }
        } else {
            return false;
        }
    } else {
        ka.sort();
        kb.sort();
        for (index = 0; index < length; index += 1) {
            if (ka[index] !== kb[index]) {
                return false;
            }
        }
    }

    for (index = 0; index < length; index += 1) {
        it = ka[index];
        if (!deepEqual(a[it], b[it])) {
            return false;
        }
    }

    return true;
};

// Recursive function for counting arrays as specified
// a must be an array of arrays
// dupsArray is used to keep count when recursing
function countDups(a, dupsArray) {
    dupsArray = Array.isArray(dupsArray) ? dupsArray : [];

    var copy,
        current,
        count;

    if (a.length) {
        copy = a.slice();
        current = copy.pop();
        count = 1;
        copy = copy.filter(function (item) {
            var isEqual = deepEqual(current, item);

            if (isEqual) {
                count += 1;
            }

            return !isEqual;
        });

        current.push(count);
        dupsArray.push(current);
        if (copy.length) {
            countDups(copy, dupsArray);
        }
    }

    return dupsArray;
}

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

console.log(JSON.stringify(countDups(x)));

Output

[[3,4,2],[1,2,1]] 

on jsFiddle

Upvotes: 1

supertopi
supertopi

Reputation: 3488

After fixing a typo I tried your solution in the debugger; it works!

Fixed the inner forEach-loop variable name to match case. Also some var-keywords added.

  var alreadyAdded = 0;
  dataset.forEach(function (data) {
    var From = data[0];
    var To = data[1];

    var index = 0;
    newDataSet.forEach(function (newData) {
        var newFrom = newData[0];
        var newTo = newData[1];

        // check if the point we are looking for is already added to the new array
        if ((From == newFrom) && (To == newTo)) {

            // if it is, increment the count for that pair
            var count = newData[2];
            var newCount = count + 1;
            newDataSet[index] = [newFrom, newTo, newCount];
            test = "reached here";
            alreadyAdded = 1;
        }
        index++;
    });

    // the pair was not already added to the new dataset, add it
    if (alreadyAdded == 0) {
        newDataSet.push([From, To, 1]);
    }

    // reset alreadyAdded variable
    alreadyAdded = 0;
});

Upvotes: 0

Related Questions