Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

_.union doesn't work with arrays of objects #2311

Open
wilhen01 opened this issue Oct 2, 2015 · 32 comments
Open

_.union doesn't work with arrays of objects #2311

wilhen01 opened this issue Oct 2, 2015 · 32 comments

Comments

@wilhen01
Copy link

wilhen01 commented Oct 2, 2015

_.union will always produce duplicates when passed arrays of objects.

e.g. _.union( [ { a:1 } ], [ { a:1 } ]) will return [ { a:1 }, { a:1 } ]

Perversely, underscore's own isEqual function will tell you that the objects in question are equal. Maybe we could have a flag/option which dictates the equality comparison to use, or the option to pass in a comparator?

@michaelficarra
Copy link
Collaborator

I'm surprised it doesn't already accept the comparison function. 👍

@wilhen01
Copy link
Author

wilhen01 commented Oct 2, 2015

It's worth pointing out that this applies to all the other array computation functions like difference, intersection, unique, etc.

Would be nice if the full suite of array functions could be updated to allow use of a different equality comparator for objects.

@ghost
Copy link

ghost commented Oct 12, 2015

Wouldn't it be easier if we would have an option that compares equality based on boolean parameter value that could be passed to _.union() function? If it is true then it would automatically compare all objects in that array.

E.g. _.union([1, 2, 3, 10, [{a:1}, {a:1}]], true), would output [1,2,3,10, {a:1}]

@michaelficarra
Copy link
Collaborator

@amiral84 No. That is unrelated. If you want that behaviour, compose union with flatten.

@ghost
Copy link

ghost commented Oct 12, 2015

@michaelficarra Then did I miss the point of this topic? :D

@michaelficarra
Copy link
Collaborator

@amiral84 It appears so. The feature request is explained fully and succinctly in the first comment.

@dperrymorrow
Copy link

the underlying problem seems to be in _.uniq as _.union is merely a wrapper function for unique and flatten.

_.union = restArgs(function(arrays) {
  return _.uniq(flatten(arrays, true, true));
});

@jdalton
Copy link
Contributor

jdalton commented Nov 25, 2015

This thread inspired me to add _.intersectionWith, _.differenceWith, _.unionWith, and _.uniqWith to handle comparison customization in my own code.

var array = [ { 'a': 1, 'b': 2 }, { 'a': 1, 'b': 3 }, { 'a': 1, 'b': 2 } ];

_.uniqWith(array, _.isEqual);
// => [{ 'a': 1, 'b': 2 }, { 'a': 1, 'b': 3 }]

@dperrymorrow
Copy link

or something along the lines of _.isCollection to determine if you are dealing with a collection. If dealing with a collection, then the comparisons should use _.isEqual instead of === which does no good in the case of a collection.

@jdalton
Copy link
Contributor

jdalton commented Nov 25, 2015

@dperrymorrow

should use _.isEqual instead of === which does no good in the case of a collection.

Dynamic switching sounds like a bad idea. JS uses === or SameValueZero comparisons for many things. If there's a need to go outside those comparisons something like _.uniqWith would do.

@wilhen01
Copy link
Author

Thanks for this @jdalton, the _opWith functions you mention are absolutely perfect for what I'm trying to achieve. Any idea when they'll be available via release?

@dperrymorrow
Copy link

@jdalton good point on the comparisons, but wouldn't you usually use a key for unique on a collection rather than forcing Underscore to detect the entire difference between the objects?

Wouldn't the following solve @wilhen01 's request (albeit more verbose than desired)

_.chain([{ a: 1 }]).union( [{a: 1}]).unique('a').value();
//=> [{a: 1}]

@jdalton
Copy link
Contributor

jdalton commented Nov 30, 2015

Wouldn't the following solve @wilhen01 's request (albeit more verbose than desired)

_.uniq already supports that.

@dperrymorrow
Copy link

right, thats my point, the above code works currently as posted.
could't you just call uniq / unique with a key on the result of the union?

@jdalton
Copy link
Contributor

jdalton commented Nov 30, 2015

@dperrymorrow Think just a tiny bit outside that example and add another property.

@dperrymorrow
Copy link

ok, gotcha, sorry... Im not trying to be belligerent, just wanted to totally understand the problem. Id love to submit a pull request on the _.uniqWith function.

@jdalton
Copy link
Contributor

jdalton commented Nov 30, 2015

No worries, that would be rad.

dperrymorrow added a commit to dperrymorrow/underscore that referenced this issue Nov 30, 2015
this allows for using custom matcher function for comparison
defaults to _.isEqual jashkenas#2311
@jashkenas
Copy link
Owner

_.intersectionWith, _.differenceWith, _.unionWith, and _.uniqWith

Wouldn't it be a nicer API to just allow the comparison function to be optionally passed as the final argument, instead of minting four new functions?

@jdalton
Copy link
Contributor

jdalton commented Dec 1, 2015

@jashkenas

Wouldn't it be a nicer API to just allow the comparison function to be optionally passed as the final argument, instead of minting four new functions?

Yes, it could be done but there's complications because methods like _.uniq already support passing an iteratee and are heavily overloaded with support for binary/sorted search flags and context params too . This would mean introducing arity sniffing which feels too clever for this situation. This would also complicate future modularization efforts because it bundles lots of optional functionality into a single point when the implementations could be simplified and split into separate methods.

@jashkenas
Copy link
Owner

Right, a totally unfortunate design issue. But making new functions just to allow a comparator doesn't feel like the right solution either.

@dperrymorrow
Copy link

ok so additional comparison function parameters are the way to go here then?
If so I can update my pull request.

the only tricky part I forsee is making the parameter parsing a little more hairy as @jdalton mentioned above.

_.uniq = _.unique = function(array, isSorted, iteratee, context) {
  if (!_.isBoolean(isSorted)) {
    context = iteratee;
    iteratee = isSorted;
    isSorted = false;
  }
//...

Perhaps adding an additional check to see if isSorted _.isFunction then treat it as the comparator.

@jdalton
Copy link
Contributor

jdalton commented Dec 1, 2015

@jashkenas

But making new functions just to allow a comparator doesn't feel like the right solution either.

It may be the best option for a bad situation. I've gone on a kick of splitting out overloaded functionality recently and have been pretty happy with the result. Though it increases the API surface it allows for simpler implementations and grouping of similarly themed methods like maxBy, uniqBy, pickBy, or uniqWith, unionWith, zipWith, or sortedIndexBy, sortedIndexOf, sortedUniq. In the case of uniq though I still use a shared base function at the moment.

@dperrymorrow
Copy link

have updated this pull request #2368 thanks.

@megawac
Copy link
Collaborator

megawac commented Dec 1, 2015

I'm 👍 for uniqBy or uniqWith. I would be completely against overloading uniq further (as #2368 currently is proposed)

@michaelficarra
Copy link
Collaborator

👍 @megawac, uniqBy.

@jdalton
Copy link
Contributor

jdalton commented Dec 1, 2015

Fwiw lodash will be using uniqBy as the split out form of _.uniq(array, iteratee) and _.uniqWith as the form to allow comparator customization.

@megawac
Copy link
Collaborator

megawac commented Dec 2, 2015

Yeah, on second thought uniqWith is a better name

@dperrymorrow
Copy link

should I pull request on Lodash with the separate method then?
I thought the two projects merged, am i mistaken?

@jdalton
Copy link
Contributor

jdalton commented Dec 2, 2015

@dperrymorrow

should I pull request on Lodash with the separate method then

No need, they're already in lodash's edge master branch.

I thought the two projects merged, am i mistaken?

Not yet. Lodash v4 proofs out some of the ideas of the merge though.

@ghost
Copy link

ghost commented Jan 10, 2016

@jdalton Can you please elaborate a liitle more on implementation of _.uniqWith with other iteratee.

@jdalton
Copy link
Contributor

jdalton commented Feb 10, 2016

@pavnii
Sure. You can check out lodash/npm/_baseUniq.
If a comparator is passed it uses arrayIncludesWith helper to do the check instead of arrayIncludes (Underscore's contains).

@ghost
Copy link

ghost commented Feb 12, 2016

@jdalton That helps me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants