0

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;
    }, []);
};
2
  • Iterating entire array on adding every new element? You really should use a hash (object) for this check instead. Commented Jan 9, 2016 at 14:41
  • @OlegV.Volkov I don't know how to do that, if you could give me a reference? Commented Jan 9, 2016 at 14:43

3 Answers 3

2

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;
  }, []);
}
Sign up to request clarification or add additional context in comments.

2 Comments

should note not supported in numerous browsers and requires polyfill if using client side
@AmitJoki: Not at all, go ahead. :)
2

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

Comments

2

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>');

2 Comments

Is this supposed to be more effective?
@FiatPax, yes, because no lookup with indexOf, and because of the hash property and the single loop for the result.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.