Enumerable#uniq?

Hello !

I wrote an ActiveSupport patch to add a new feature.

This is a tiny tested patch that checks for Enumerable content uniqueness.

It returns true if the collection has no duplicated content and can be called with a block too, much like any?, so people.uniq? { |p| p.age > 26 } returns true if only 1 person is over 26.

Unit test and doc are included.

Please test this useful tiny patch.

https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/6588-add-enumerableuniq

Github fork / branch is available at https://github.com/Bounga/rails/commits/enumerable_uniq

Have a nice day.

The name #uniq? here is not a good representation of the method's
functionality here. You're checking for uniqueness of a single member
of the collection, but in use the context would be the entire
collection. @people.uniq? being true should mean @people itself is, in
some way, unique. You'd need to come up with a better way of saying
@people.has_only_one_of?; it may be tough to come up with an
intuitive, succinct name for that, and using something long doesn't
make too much sense when @people.select{}.size==1 is already pretty
accessible.

From documentation:

enum.one? [{|obj| block }] → true or false

Passes each element of the collection to the given block. The method
returns true if the block returns true exactly once. If the block is
not given, one? will return true only if exactly one of the collection
members is true.

Robert Pankowecki

The function of the block here seems a little unclear; at first reading I assumed it was similar to the block passed to sort_by, which specifies *what* to sort by. As implemented in the patch, though, it's just selecting a subset of the array.

This implementation might be more useful (in the block_given? path):

uniq_by(&block).size == size

The result would be that the above example would still work, but you could also say things like:

@some_models.uniq? { |x| x.created_at.month }

to see if an array of models were all created on different months.

That said, I'm not clear on what this would actually be useful for outside of toy examples...

--Matt Jones

How would you handle simple cases like: [ 1, 1, 2 ].uniq? { |x| x > 1 } # => true ?

Yes but how to have @post.uniq? { |x| x.created_at.month } ?

Hi Chris,

I want to be able to do things like:

[ 1, 2 ].uniq?
[ {:lang => "Ruby"}, {:lang => "ObjC"} ].uniq?
[ 1, 1, 2 ].uniq? { |x| x > 1 }
[ nc,dhh ].uniq? { |p| p.name }

uniq? seems to be a good name to me. Enumerable#uniq is a good name and Enumerable#uniq? is just the same except we are asking, not really removing duplicates.

Is it possible ? I think you sometimes want to select records using
the given proc and sometimes map them. Your current implementation
uses "select":

class Person < Struct.new(:name, :age)
end
nc = Person.new(:nc, 1)
dhh1 = Person.new(:dhh, 2)
dhh2 = Person.new(:dhh, 3)

[ nc,dhh1 ].uniq? { |p| p.name } => true
[ nc, dhh1, dhh2 ].uniq? { |p| p.name } => true

All records are selected because p.name is evaluated to true, the
their uniquness is checked which is still true despite the fact the
their all have the same name...

[ 1, 1, 2].uniq? { |x| x > 1 } => true
[ 1, 1, 2, 2].uniq? { |x| x > 1 } => false

Works as you want.

If we change the implementation then first example will go ok but the
second one not.

We would have to have two methods. Am I right ?

uniq_by?(&:name) sounds good for me.

Robert Pankowecki

[ 1, 1, 2 ].uniq? { |x| x > 1 } # select
Could mean: Is it true that all objects greater than 1 are unique ?

[ nc,dhh ].uniq_by? { |p| p.name } # map

Could mean: Is it true that all objects have unique names ?

But I am not in favor of adding those methods. Just trying to
understand your scenarios.

Robert Pankowecki

Yes it's what I was trying to do. The only is to add 2 methods. Method names you chose sound good to me.

Why don't you want these methods to be added?

Filtering duplicate values should be done in the database layer IMO.

Why don't you want these methods to be added?

a) Never needed them
b) Rails already extends a lot of things and I see no reason to extend
more and more even if we like it. And I like those methods :slight_smile:
c) Because you can extremely easily create a gem with those methods
which then can be easily used by others. Post info on reddit/ruby,
send link it to people creating podcasts about ruby and thousands of
people will know about it and be able to choose if they like it and
want to use it.

Also: Are you sure that such method is not already in facets library ?
Maybe that's a good place to add it ?

Robert Pankowecki

The idea here is not to work on DB objects only but on any kind of object.

Why don't you want these methods to be added?

a) Never needed them
b) Rails already extends a lot of things and I see no reason to extend
more and more even if we like it. And I like those methods :slight_smile:

That's true. We can't include everything in AS

c) Because you can extremely easily create a gem with those methods
which then can be easily used by others. Post info on reddit/ruby,
send link it to people creating podcasts about ruby and thousands of
people will know about it and be able to choose if they like it and
want to use it.

Good idea. It's a nice way to share this.

Also: Are you sure that such method is not already in facets library ?

Wasn't aware of this gem! Thank you Robert.

There's no such methods in Facets.

Maybe that's a good place to add it ?

Yes, sounds like a good place for these methods. Gonna send a patch.

Thanks a lot for your advices Robert.