1

If I have an array like:

[
    {
        id: 1,
        title: 'foo'
    },
    {
        id: 2,
        title: 'bar'
    },
    {
        id: 3,
        title: 'bat'
    },
    {
        id: 4,
        title: 'bantz'
    },
    {
        id: 2,
        title: 'bar'
    },
    {
        id: 3,
        title: 'bat'
    }
]

And I want to return an array that contains any objects that appear only once. So for this example, the desired output would be:

[
    {
        id: 1,
        title: 'foo'
    },
    {
        id: 4,
        title: 'bantz'
    }
]

I have tried a few different approaches that I have found to solve this using reduce() and indexOf(), like this solution, but they do not work with objects for some reason.

Any assistance would be greatly appreciated.

5
  • All of those objects are distinct; from what you posted, none of them are really duplicates. You probably have your own criteria for what makes two objects the same; is it the "id" being equal? Both the "id" and "title"? Commented Jun 21, 2017 at 20:54
  • They would be objects with all the same keys and values. I don't understand your comment. How is { id: 2, title: bar } not a duplicate of { id: 2, title: bar }? They have the exact same keys and values. EDIT: I just noticed what I listed as desired outcome is wrong. I will edit it now. Commented Jun 21, 2017 at 20:57
  • @MattGween Pointy is explaining why the solutions using indexOf fail. {} != {} because, despite having the same keys and values as each other, they are two separate objects. Commented Jun 21, 2017 at 21:02
  • 1
    They would be unique objects because you are creating them directly. Each object you create is unique, even if the data is inside. What you want is to use your own version of equality and filter on that. Commented Jun 21, 2017 at 21:04
  • Possible duplicate of Unique values in an array Commented Jun 21, 2017 at 21:26

3 Answers 3

3

You could use a Map to avoid having to look through the array again and again, which would lead to inefficient O(n²) time-complexity. This is O(n):

function getUniquesOnly(data) {
    return Array.from(
        data.reduce( (acc, o) => acc.set(o.id, acc.has(o.id) ? 0 : o), new Map),
        (([k,v]) => v)
    ).filter( x => x );
}

var data = [
    {
        id: 1,
        title: 'foo'
    },
    {
        id: 2,
        title: 'bar'
    },
    {
        id: 3,
        title: 'bat'
    },
    {
        id: 4,
        title: 'bantz'
    },
    {
        id: 2,
        title: 'bar'
    },
    {
        id: 3,
        title: 'bat'
    }
];

console.log(getUniquesOnly(data));

Sign up to request clarification or add additional context in comments.

Comments

3

Do something like this:

const data = [
    {
        id: 1,
        title: 'foo'
    },
    {
        id: 2,
        title: 'bar'
    },
    {
        id: 3,
        title: 'bat'
    },
    {
        id: 4,
        title: 'bantz'
    },
    {
        id: 2,
        title: 'bar'
    },
    {
        id: 3,
        title: 'bat'
    }
];

const isEqual = (a, b) => a.id === b.id;
const unique = (arr) => arr.reduce((result, a, index) =>
  result.concat(arr.some(b => a !== b && isEqual(a, b)) ? [] : a)
, []);

console.log(unique(data));

In this case, we loop through each element to reduce(), and before we add it, we see if another version of it exists in the array before adding it. We have to make sure that we are also not being equal without ourselves (otherwise we'd get an empty array).

isEqual() is a separate function to make it easy to customize what "equal" means.

As written, each element in data is unique, they're all separate objects. data[0] === data[4] is false, even though they have the same data. You must compare the data inside to determine if they're duplicates or not. As Paulpro mentioned earlier, {} === {} is also false, because they're two different objects, even though their values are the same.

console.log({} === {});
console.log({ a: 1 } === { a: 1 });

In the example version of isEqual(), I considered them equal if they had the same id.


Answer to previous version of the question

Do something like this:

const data = [
    {
        id: 1,
        title: 'foo'
    },
    {
        id: 2,
        title: 'bar'
    },
    {
        id: 3,
        title: 'bat'
    },
    {
        id: 4,
        title: 'bantz'
    },
    {
        id: 2,
        title: 'bar'
    },
    {
        id: 3,
        title: 'bat'
    }
];

const isEqual = (a, b) => a.id === b.id;
const unique = (arr) => arr.reduce((result, a) =>
  result.concat(result.some(b => isEqual(a, b)) ? [] : a)
, []);

console.log(unique(data));

I split isEqual() to it's own function so you could easily define what "equal" means. As someone pointed out, technically all of those are unique, even if the data is different. In my example, I defined equal ids to mean equal.

I then use reduce to go through each and build an object. Before I add it to the array (via concat()), I loop through all of them with some() and go until either I find one that is equal (which I wouldn't include) or none are equal and I add it.

2 Comments

Original question has been significantly altered
@mhodges Indeed. I've tweaked my answer to match the new requirements.
0

A straightforward implementation would look something like this:

  • Create an empty set (in this case an array) to contain unique values by whatever metric you define (I.E. deep comparison or comparing by a unique value like the "id")
  • Loop over the list of values
  • Whenever you find a value that is not contained within the set of unique values, add it

That is essentially how the solution you posted works, except that all of the values in your array are -- in JavaScript's eyes -- unique. Because of this you need to define your own way to compare values.

The .reduce method can be used like so:

function areEqual(a, b) { /* define how you want the objects compared here */ }

function contains(a, lst) {
    return lst.reduce(function (acc, x) {
        return acc || areEqual(a, x);
    }, false);
}

function getUnique(lst) {
    return lst.reduce(function (acc, x) {
        if(!contains(x, acc))
        {
            acc.push(x);
        }

        return acc;
    }, []);
}

You may want to look at how JavaScript object comparison works. For deep comparison specifically (which it sounds like you want) I would look at existing answers.

Comments

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.