Fiat Pax
Fiat Pax

Reputation: 417

Javascript - Reduce and Array

I am trying to find get unique records by their ID including the name with the code most below and if I replace the if statement with:

if (p.indexOf(c.ID) < 0) p.push(c.ID);

it will create an array with unique IDs, but I want my final array to also have person's name so I modified the if statement, but then p[1] is not initialized for the first time and the reduce function doesn't run as expected. How do I correctly change the code below to work for what I want?

var arrayUnique = function(a) {
    return a.reduce(function(p, c) {
        if (p[1].indexOf(c.ID) < 0) p.push([c.person, c.ID]);
        return p;
    }, []);
};

Upvotes: 0

Views: 1225

Answers (3)

Nina Scholz
Nina Scholz

Reputation: 386540

A solution with a temporary object and a hash function.

var array = [
        { ID: 1, person: 'John' },
        { ID: 2, person: 'Malcolm' },
        { ID: 3, person: 'Vera' },
        { ID: 1, person: 'John' },
        { ID: 2, person: 'Malcolm' }
    ],
    unique = array.reduce(function (r, a) {
        if (!(a.ID in r.hash)) {
            r.array.push(a);
            r.hash[a.ID] = true;
        }
        return r;
    }, { array: [], hash: [] }).array;
        
document.write('<pre>' + JSON.stringify(unique, 0, 4) + '</pre>');

Upvotes: 2

Guffa
Guffa

Reputation: 700192

The expression p[1] isn't an array of all the id:s in the array p, it's the second item in the array p.

You would use the findIndex method so that you can provide a function that compares the id:s in the items:

function arrayUnique(a) {
  return a.reduce(function(p, c) {
    if (p.findIndex(function(e){ return e[1] == c.ID; }) == -1) p.push([c.person, c.ID]);
    return p;
  }, []);
}

console.log(arrayUnique([
  { ID: 1, person: 'John' },
  { ID: 2, person: 'Malcolm' },
  { ID: 3, person: 'Vera' },
  { ID: 1, person: 'Ian' },
  { ID: 2, person: 'Jenny' }
]));

Note: The findIndex method is not supported in all browsers, so you would need a 'polyfill' to support them. You can find one in the findIndex documentation.

It can also be done using the filter method that has wider support, but that has a bit more overhead:

function arrayUnique(a) {
  return a.reduce(function(p, c) {
    if (p.filter(function(e){ return e[1] == c.ID; }).length == 0) p.push([c.person, c.ID]);
    return p;
  }, []);
}

Upvotes: 2

Amit Joki
Amit Joki

Reputation: 59232

You're messing up with data structures here. If you're pushing an array into an array, indexOf won't work as you expect. Instead, push objects.

var arrayUnique = function(a) {
    return a.reduce(function(p, c) {
        if (!p['i' + c.ID]) p['i' + c.ID] = {name: c.person, id: c.ID};
        return p;
    }, {});
};

What if(!p['i' + c.ID]) does is, it checks if there is already a property of that name. And the reason, behind joining it with string 'i' is to make it an identifier.

console.log(arrayUnique([
  { ID: 1, person: 'John' },
  { ID: 2, person: 'Malcolm' },
  { ID: 3, person: 'Vera' },
  { ID: 1, person: 'Ian' },
  { ID: 2, person: 'Jenny' }
]));

/*
   {  
      i1: {id: 1, name: 'John'},
      i2: {id: 2, name: 'Malcolm'},
      i3: {id: 3, name: 'Vera'}
   }
*/

Upvotes: 2

Related Questions