Stop duplicates from being added to an array of Ruby objects - ruby-on-rails

how can I eliminate duplicate elements from an array of ruby objects using an attribute of the object to match identical objects.
with an array of basic types I can use a set..
eg.
array_list = [1, 3, 4 5, 6, 6]
array_list.to_set
=> [1, 2, 3, 4, 5, 6]
can I adapt this technique to work with object attributes?
thanks

I think you are putting the cart before the horse. You are asking yourself: "How can I get uniq to remove objects which aren't equal?" But what you should be asking yourself, is: "Why aren't those two objects equal, despite the fact that I consider them to be?"
In other words: it seems you are trying to work around the fact that your objects have broken equality semantics, when what you really should do is simply fixing those broken equality semantics.
Here's an example for a Product, where two products are considered equal if they have the same type number:
class Product
def initialize(type_number)
self.type_number = type_number
end
def ==(other)
type_number == other.type_number
end
def eql?(other)
other.is_a?(self.class) && type_number.eql?(other.type_number)
end
def hash
type_number.hash
end
protected
attr_reader :type_number
private
attr_writer :type_number
end
require 'test/unit'
class TestHashEquality < Test::Unit::TestCase
def test_that_products_with_equal_type_numbers_are_considered_equal
assert_equal 2, [Product.new(1), Product.new(2), Product.new(1)].uniq.size
end
end

If you can write it into your objects to use eql? then you can use uniq.

what about uniq
a = [ "a", "a", "b", "b", "c" ]
a.uniq #=> ["a", "b", "c"]
you can use it on object as well!

Should you be using an Array, or should you be using a Set instead? If order isn't important, then the latter will make it more efficient to check for duplicates.

thanks for your responses.. uniq works once I added the following to my object model
def ==(other)
other.class == self.class &&
other.id == self.id
end
alias :eql? :==

Related

Ruby's bitwise shift operator "<<" confusion in conjuction with array

I am studying Ruby and can't figure this out. I have an exercise where I have to add a method into a Library class which contains games. Each game is an instance of a class Game. So the solution is the following:
class Library
attr_accessor :games
def initialize(games)
self.games = games
end
def has_game?(search_game)
for game in games
return true if game == search_game
end
false
end
def add_game(game)
#games << game
end
end
I can't understand how << works in this case. Is this a bitwise left shift? Does Library class just assumes that games is an array, I believe I can pass anything to the Library class when I am initialising, single game or an array of games?
When you have:
#games << game
<< is actually a method. If it is a method, you ask, why isn't it written in the normal way:
#games.<<(game)
? You could, in fact, write it that way and it would work fine. Many Ruby methods have names that are symbols. A few others are +, -, **, &, || and %. Ruby knows you'd prefer writing 2+3 instead of 2.+(3), so she let's you do the former (and then quietly converts it to the latter). This accommodation is often referred to as "syntactic sugar".
<< is one of #games' methods (and game is <<'s argument), because #games is the receiver of the method << (technically :<<). Historically, it's called the "receiver" because with OOP you "send" the method to the receiver.
To say that << is a method of #games means that << is an instance method of #games's class. Thus, we have:
#games.methods.include?(:<<) #=> true
#games.class.instance_methods.include?(:<<) #=> true
I expect #games is an instance of the class Array. Array's instance methods are listed here. For example:
#games = [1,2,3]
#games.class #=> [1,2,3].class => Array
#games << 4 #=> [1,2,3,4]
#games.<<(5) #=> [1,2,3,4,5]
On the other hand, suppose #games were an instance of Fixnum. For example:
#games = 7
#games.class #=> 7.class => Fixnum
which in binary looks like this:
#games.to_s(2) #=> "111"
Then:
#games << 2 #=> 28
28.to_s(2) #=> "11100"
because << is an instance method of the class Fixnum.
As a third example, suppose #games were a hash:
#games = { :a => 1 }
#games.class #=> { :a => 1 }.class => Hash
Then:
#games << { :b => 2 }
#=> NoMethodError: undefined method `<<' for {:a=>1}:Hash
The problem is clear from the error message. The class Hash has no instance method <<.
One last thing: consider the method Object#send. Recall that, at the outset, I said that methods are sent to receivers. Instead of writing:
#games = [1,2,3]
#games << 4 #=> [1,2,3,4]
you could write:
#games.send(:<<, 4) #=> [1, 2, 3, 4]
This is how you should think of methods and receivers. Because send is an instance method of the class Object, and all objects inherit Object's instance methods, we see that send is "sending" a method (:<<, which can alternatively be expressed as a string, "<<") and its arguments (here just 4) to the receiver.
There are times, incidentally, when you must use send to invoke a method. For one, send works with private and protected methods. For another, you can use send when you want to invoke a method dynamically, where the name of the method is the value of a variable.
In sum, to understand what method does in:
receiver.method(*args)
look for the doc for the instance method method in receiver's class. If you Google "ruby array", for example, the first hit will likely be the docs for the class Array.
It's not a bitwise left shift, it's "shovel" operator. You, probably, know that in Ruby operators are implemented as methods, i.e. when you write
1 + 1
what is actually going on:
1.+(1)
The same is true for "shovel" (<<) operator, it's just a method on Array class, that appends its arguments to the end of the array and returns the array itself, so it can be chained:
>> arr = []
=> []
>> arr << 1
=> [1]
>> arr
=> [1]
>> arr << 2 << 3
=> [1, 2, 3]
It looks like #games is an array. The shift operator for an array adds an element to the end of the array, similar to array#push.
ary << obj → ary
Append—Pushes the given object on to the end of this array. This expression returns the array itself, so several appends may be chained together.

Ruby (Monkey Patching Array)

Back for more help on my coursework at Bloc. Decided to bring you guys in on a problem I'm having with Monkey Patching the Array Class. This assignment had 8 specs to be met and I'm stuck now with one left and I'm not sure what to do.
I'm only going to give you the RSpecs and written requirements for the part that I'm having trouble w/ since everything else seems to be passing. Also I'll include the starter scaffolding that they gave me to begin with so there's no confusion or useless additions to the code.
Here are the written requirements for the Array Class Monkey Patch:
Write a new new_map method that is called on an instance of the Array class. It should use the array it's called on as an implicit (self) argument, but otherwise behave identically. (INCOMPLETE)
Write a new_select! method that behaves like the select, but mutates the array on which it's called. It can use Ruby's built-in collection select method. (COMPLETE)
Here are the RSpecs that need to be met regarding the Array class:
Note: "returns an array with updated values" Is the only Spec not passing.
describe Array do
describe '#new_map' do
it "returns an array with updated values" do
array = [1,2,3,4]
expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} )
expect( array.new_map{ |e| e + 2 } ).to eq( [3, 4, 5, 6] )
end
it "does not call #map" do
array = [1,2,3,4]
array.stub(:map) { '' }
expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} )
end
it "does not change the original array" do
array = [1,2,3,4]
expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} )
expect( array ).to eq([1,2,3,4])
end
end
describe '#new_select!' do
it "selects according to the block instructions" do
expect( [1,2,3,4].new_select!{ |e| e > 2 } ).to eq( [3,4] )
expect( [1,2,3,4].new_select!{ |e| e < 2 } ).to eq( [1] )
end
it "mutates the original collection" do
array = [1,2,3,4]
array.new_select!(&:even?)
expect(array).to eq([2,4])
end
end
end
Here is the scaffolding they started me with:
class Array
def new_map
end
def new_select!(&block)
end
end
Lastly, here is my code:
class Array
def new_map
new_array = []
self.each do |num|
new_array << num.to_s
end
new_array
end
def new_select!(&block)
self.select!(&block)
end
end
Ruby Array Class:
map { |item| block } → new_ary
A block is like a method, and you specify a block after a method call, for example:
[1, 2, 3].map() {|x| x*2} #<---block
^
|
method call(usually written without the trailing parentheses)
The block is implicitly sent to the method, and inside the method you can call the block with yield.
yield -> calls the block specified after a method call. In ruby, yield is equivalent to yield(), which is conceptually equivalent to calling the block like this: block().
yield(x) -> calls the block specified after a method call sending it the argument x, which is conceptually equivalent to calling the block like this: block(x).
So, here is how you can implement new_map():
class Array
def new_map
result = []
each do |item|
result << yield(item)
end
result
end
end
arr = [1, 2, 3].new_map {|x| x*2}
p arr
--output:--
[2, 4, 6]
This comment is a bit advanced, but you don't actually have to write self.each() to call the each() method inside new_map(). All methods are called by some object, i.e. the object to the left of the dot, which is called the receiver. For instance, when you write:
self.each {....}
self is the receiver of the method call each().
If you do not specify a receiver and just write:
each {....}
...then for the receiver ruby uses whatever object is assigned to the self variable at that moment. Inside new_map() above, ruby will assign the Array that calls the new_map() method to self, so each() will step through the items in that Array.
You have to be a little bit careful with the self variable because ruby constantly changes the value of the self variable without telling you. So, you have to know what ruby has assigned to the self variable at any particular point in your code--which comes with experience. Although, if you ever want to know what object ruby has assigned to self at some particular point in your code, you can simply write:
puts self
Experienced rubyists will crow and cluck their tongues if they see you write self.each {...} inside new_map(), but in my opinion code clarity trumps code trickiness, and because it makes more sense to beginners to write self there, go ahead and do it. When you get a little more experience and want to show off, then you can eliminate explicit receivers when they aren't required. It's sort of the same situation with explicit returns:
def some_method
...
return result
end
and implicit returns:
def some_method
...
result
end
Note that you could write new_map() like this:
class Array
def new_map(&my_block) #capture the block in a variable
result = []
each do |item|
result << my_block.call(item) #call the block
end
result
end
end
arr = [1, 2, 3].new_map {|x| x*2}
p arr
--output:--
[2, 4, 6]
Compare that to the example that uses yield(). When you use yield(), it's as if ruby creates a parameter variable named yield for you in order to capture the block. However, with yield you use a different syntax to call the block, namely (), or if their are no arguments for the block, you can eliminate the parentheses--just like you can when you call a method. On the other hand, when you create your own parameter variable to capture a block, e.g. def new_map(&my_block), you have to use a different syntax to call the block:
my_block.call(arg1, ...)
or:
myblock[arg1, ...]
Note that #2 is just like the syntax for calling a method--except that you substitute [] in place of ().
Once again, experienced rubyists will use yield to call the block instead of capturing the block in a parameter variable. However, there are situations where you will need to capture the block in a parameter variable, e.g. if you want to pass the block to yet another method.
Looking at the spec here:
it "returns an array with updated values" do
array = [1,2,3,4]
expect( array.new_map(&:to_s) ).to eq( %w{1 2 3 4} )
expect( array.new_map{ |e| e + 2 } ).to eq( [3, 4, 5, 6] )
end
It looks like they just want you to re-write Array.map so that it will work with any given block. In your implementation you are telling the method to work in a very specific way, namely to call .to_s on all of the array elements. But you don't want it to always stringify the array elements. You want it to do to each element whatever block is provided when the method is called. Try this:
class Array
def new_map
new_array = []
each do |num|
new_array << yield(num)
end
new_array
end
end
Notice in my example that the method definition doesn't specify any particular operation to be performed on each element of self. It simply loops over each element of the array, yields the element (num) to whatever block was passed when .new_map was called, and shovels the result into the new_array variable.
With that implementation of .new_map and given array = [1,2,3,4], you can call either array.new_map(&:to_s) (the block is where the arbitrary operation to be performed on each element of the array is specified) and get ["1","2","3","4"] or you can call array.new_map { |e| e + 2 } and get [3,4,5,6].
I would like to say a few words about new_select!.
select vs select!
You have:
class Array
def new_select!(&block)
self.select!(&block)
end
end
which you could instead write:
class Array
def new_select!
self.select! { |e| yield(e) }
end
end
This uses the method Array#select!. You said Array#select could be used, but made no mention of select!. If you cannot use select!, you must do something like this:
class Array
def new_select!(&block)
replace(select(&block))
end
end
Let's try it:
a = [1,2,3,4,5]
a.new_select! { |n| n.odd? }
#=> [1, 3, 5]
a #=> [1, 3, 5]
Explicit vs implicit receivers
Notice that I have written this without any explicit receivers for the methods Array#replace and Array#select. When there is no explicit receiver, Ruby assumes that it is self, which is the a. Therefore, Ruby evaluates replace(select(&block)) as though it were written with explicit receivers:
self.replace(self.select(&block))
It's up to you to decide if you want to include self.. Some Rubiests do, some don't. You'll notice that self. is not included in Ruby built-in methods implemented in Ruby. One other thing: self. is required in some situations to avoid ambiguity. For example, if taco= is the setter for an instance variable #taco, you must write self.taco = 7 to tell Ruby you are referring to the setter method. If you write taco = 7, Ruby will assume you want to create a local variable taco and set its value to 7.
Two forms of select!
Is your method new_select! a direct replacement for select!? That is, are the two methods functionally equivalent? If you look at the docs for Array#select!, you will see it has two forms, the one you have mimicked, and another that you have not implemented.
If select! is not given a block, it returns an enumerator. Now why would you want to do that? Suppose you wish to write:
a = [1,2,3,4,5]
a.select!.with_index { |n,i| i < 2 }
#=> [1, 2]
Has select! been given a block? No, so it must return an enumerator, which becomes the receiver of the method Enumerator#with_index.
Let's try it:
enum0 = a.select!
#=> #<Enumerator: [1, 2, 3, 4, 5]:select!>
Now:
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [1, 2, 3, 4, 5]:select!>:with_index>
You can think of enum1 as a "compound enumerator". Look carefully at the description (above) of the object Ruby returns when you define enum1.
We can see the elements of the enumerator by converting it to an array:
enum1.to_a
# => [[1, 0], [2, 1], [3, 2], [4, 3], [5, 4]]
Each of these five elements is passed to the block and assigned to the block variables by Enumerator#each (which calls Array#each).
Enough of that, but the point is that having methods return enumerators when no block is given is what allows us to chain methods.
You do not have a test that ensures new_select! returns an enumerator when no block is given, so maybe that's not expected, but why not give it a go?
class Array
def new_select!(&block)
if block_given?
replace(select(&block))
else
to_enum(:new_select!)
end
end
end
Try it:
a = [1,2,3,4,5]
a.new_select! { |n| n.odd? }
#=> [1, 3, 5]
a #=> [1, 3, 5]
a = [1,2,3,4,5]
enum2 = a.new_select!
#=> #<Enumerator: [1, 2, 3, 4, 5]:new_select!>
enum2.each { |n| n.odd? }
#=> [1, 3, 5]
a #=> [1, 3, 5]
a = [1,2,3,4,5]
enum2 = a.new_select!
#=> #<Enumerator: [1, 2, 3, 4, 5]:new_select!>
enum2.with_index.each { |n,i| i>2 }
#=> [4, 5]
a #=> [4, 5]

How to test records order in Rails?

I find very verbose and tedious to test if records coming from the database are correctly ordered.
I'm thinking using the array '==' method to compare two searches arrays. The array's elements and order must be the same so it seems a good fit. The issue is that if elements are missing the test will fail even though they are strictly ordered properly.
I wonder if there is a better way...
Rails 4
app/models/person.rb
default_scope { order(name: :asc) }
test/models/person.rb
test "people should be ordered by name" do
xavier = Person.create(name: 'xavier')
albert = Person.create(name: 'albert')
all = Person.all
assert_operator all.index(albert), :<, all.index(xavier)
end
Rails 3
app/models/person.rb
default_scope order('name ASC')
test/unit/person_test.rb
test "people should be ordered by name" do
xavier = Person.create name: 'xavier'
albert = Person.create name: 'albert'
assert Person.all.index(albert) < Person.all.index(xavier)
end
I haven't come across a built-in way to do this nicely but here's a way to check if an array of objects is sorted by a member:
class MyObject
attr_reader :a
def initialize(value)
#a = value
end
end
a = MyObject.new(2)
b = MyObject.new(3)
c = MyObject.new(4)
myobjects = [a, b, c]
class Array
def sorted_by?(method)
self.each_cons(2) do |a|
return false if a[0].send(method) > a[1].send(method)
end
true
end
end
p myobjects.sorted_by?(:a) #=> true
Then you can use it using something like:
test "people should be ordered by name by default" do
people = Person.all
assert people.sorted_by?(:age)
end
I came across what I was looking for when I asked this question. Using the each_cons method, it makes the test very neat:
assert Person.all.each_cons(2).all?{|i,j| i.name >= j.name}
I think having your record selection sorted will give you a more proper ordered result set, and in fact its always good to order your results
By that way I think you will not need the array == method
HTH
sameera

Clean way to find ActiveRecord objects by id in the order specified

I want to obtain an array of ActiveRecord objects given an array of ids.
I assumed that
Object.find([5,2,3])
Would return an array with object 5, object 2, then object 3 in that order, but instead I get an array ordered as object 2, object 3 and then object 5.
The ActiveRecord Base find method API mentions that you shouldn't expect it in the order provided (other documentation doesn't give this warning).
One potential solution was given in Find by array of ids in the same order?, but the order option doesn't seem to be valid for SQLite.
I can write some ruby code to sort the objects myself (either somewhat simple and poorly scaling or better scaling and more complex), but is there A Better Way?
It's not that MySQL and other DBs sort things on their own, it's that they don't sort them. When you call Model.find([5, 2, 3]), the SQL generated is something like:
SELECT * FROM models WHERE models.id IN (5, 2, 3)
This doesn't specify an order, just the set of records you want returned. It turns out that generally MySQL will return the database rows in 'id' order, but there's no guarantee of this.
The only way to get the database to return records in a guaranteed order is to add an order clause. If your records will always be returned in a particular order, then you can add a sort column to the db and do Model.find([5, 2, 3], :order => 'sort_column'). If this isn't the case, you'll have to do the sorting in code:
ids = [5, 2, 3]
records = Model.find(ids)
sorted_records = ids.collect {|id| records.detect {|x| x.id == id}}
Based on my previous comment to Jeroen van Dijk you can do this more efficiently and in two lines using each_with_object
result_hash = Model.find(ids).each_with_object({}) {|result,result_hash| result_hash[result.id] = result }
ids.map {|id| result_hash[id]}
For reference here is the benchmark i used
ids = [5,3,1,4,11,13,10]
results = Model.find(ids)
Benchmark.measure do
100000.times do
result_hash = results.each_with_object({}) {|result,result_hash| result_hash[result.id] = result }
ids.map {|id| result_hash[id]}
end
end.real
#=> 4.45757484436035 seconds
Now the other one
ids = [5,3,1,4,11,13,10]
results = Model.find(ids)
Benchmark.measure do
100000.times do
ids.collect {|id| results.detect {|result| result.id == id}}
end
end.real
# => 6.10875988006592
Update
You can do this in most using order and case statements, here is a class method you could use.
def self.order_by_ids(ids)
order_by = ["case"]
ids.each_with_index.map do |id, index|
order_by << "WHEN id='#{id}' THEN #{index}"
end
order_by << "end"
order(order_by.join(" "))
end
# User.where(:id => [3,2,1]).order_by_ids([3,2,1]).map(&:id)
# #=> [3,2,1]
Apparently mySQL and other DB management system sort things on their own. I think that you can bypass that doing :
ids = [5,2,3]
#things = Object.find( ids, :order => "field(id,#{ids.join(',')})" )
A portable solution would be to use an SQL CASE statement in your ORDER BY. You can use pretty much any expression in an ORDER BY and a CASE can be used as an inlined lookup table. For example, the SQL you're after would look like this:
select ...
order by
case id
when 5 then 0
when 2 then 1
when 3 then 2
end
That's pretty easy to generate with a bit of Ruby:
ids = [5, 2, 3]
order = 'case id ' + (0 .. ids.length).map { |i| "when #{ids[i]} then #{i}" }.join(' ') + ' end'
The above assumes that you're working with numbers or some other safe values in ids; if that's not the case then you'd want to use connection.quote or one of the ActiveRecord SQL sanitizer methods to properly quote your ids.
Then use the order string as your ordering condition:
Object.find(ids, :order => order)
or in the modern world:
Object.where(:id => ids).order(order)
This is a bit verbose but it should work the same with any SQL database and it isn't that difficult to hide the ugliness.
As I answered here, I just released a gem (order_as_specified) that allows you to do native SQL ordering like this:
Object.where(id: [5, 2, 3]).order_as_specified(id: [5, 2, 3])
Just tested and it works in SQLite.
Justin Weiss wrote a blog article about this problem just two days ago.
It seems to be a good approach to tell the database about the preferred order and load all records sorted in that order directly from the database. Example from his blog article:
# in config/initializers/find_by_ordered_ids.rb
module FindByOrderedIdsActiveRecordExtension
extend ActiveSupport::Concern
module ClassMethods
def find_ordered(ids)
order_clause = "CASE id "
ids.each_with_index do |id, index|
order_clause << "WHEN #{id} THEN #{index} "
end
order_clause << "ELSE #{ids.length} END"
where(id: ids).order(order_clause)
end
end
end
ActiveRecord::Base.include(FindByOrderedIdsActiveRecordExtension)
That allows you to write:
Object.find_ordered([2, 1, 3]) # => [2, 1, 3]
Here's a performant (hash-lookup, not O(n) array search as in detect!) one-liner, as a method:
def find_ordered(model, ids)
model.find(ids).map{|o| [o.id, o]}.to_h.values_at(*ids)
end
# We get:
ids = [3, 3, 2, 1, 3]
Model.find(ids).map(:id) == [1, 2, 3]
find_ordered(Model, ids).map(:id) == ids
Another (probably more efficient) way to do it in Ruby:
ids = [5, 2, 3]
records_by_id = Model.find(ids).inject({}) do |result, record|
result[record.id] = record
result
end
sorted_records = ids.map {|id| records_by_id[id] }
Here's the simplest thing I could come up with:
ids = [200, 107, 247, 189]
results = ModelObject.find(ids).group_by(&:id)
sorted_results = ids.map {|id| results[id].first }
#things = [5,2,3].map{|id| Object.find(id)}
This is probably the easiest way, assuming you don't have too many objects to find, since it requires a trip to the database for each id.

Uniq by object attribute in Ruby

What's the most elegant way to select out objects in an array that are unique with respect to one or more attributes?
These objects are stored in ActiveRecord so using AR's methods would be fine too.
Use Array#uniq with a block:
#photos = #photos.uniq { |p| p.album_id }
Add the uniq_by method to Array in your project. It works by analogy with sort_by. So uniq_by is to uniq as sort_by is to sort. Usage:
uniq_array = my_array.uniq_by {|obj| obj.id}
The implementation:
class Array
def uniq_by(&blk)
transforms = []
self.select do |el|
should_keep = !transforms.include?(t=blk[el])
transforms << t
should_keep
end
end
end
Note that it returns a new array rather than modifying your current one in place. We haven't written a uniq_by! method but it should be easy enough if you wanted to.
EDIT: Tribalvibes points out that that implementation is O(n^2). Better would be something like (untested)...
class Array
def uniq_by(&blk)
transforms = {}
select do |el|
t = blk[el]
should_keep = !transforms[t]
transforms[t] = true
should_keep
end
end
end
Do it on the database level:
YourModel.find(:all, :group => "status")
You can use this trick to select unique by several attributes elements from array:
#photos = #photos.uniq { |p| [p.album_id, p.author_id] }
I had originally suggested using the select method on Array. To wit:
[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0}
gives us [2,4,6] back.
But if you want the first such object, use detect.
[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3} gives us 4.
I'm not sure what you're going for here, though.
I like jmah's use of a Hash to enforce uniqueness. Here's a couple more ways to skin that cat:
objs.inject({}) {|h,e| h[e.attr]=e; h}.values
That's a nice 1-liner, but I suspect this might be a little faster:
h = {}
objs.each {|e| h[e.attr]=e}
h.values
Use Array#uniq with a block:
objects.uniq {|obj| obj.attribute}
Or a more concise approach:
objects.uniq(&:attribute)
The most elegant way I have found is a spin-off using Array#uniq with a block
enumerable_collection.uniq(&:property)
…it reads better too!
If I understand your question correctly, I've tackled this problem using the quasi-hacky approach of comparing the Marshaled objects to determine if any attributes vary. The inject at the end of the following code would be an example:
class Foo
attr_accessor :foo, :bar, :baz
def initialize(foo,bar,baz)
#foo = foo
#bar = bar
#baz = baz
end
end
objs = [Foo.new(1,2,3),Foo.new(1,2,3),Foo.new(2,3,4)]
# find objects that are uniq with respect to attributes
objs.inject([]) do |uniqs,obj|
if uniqs.all? { |e| Marshal.dump(e) != Marshal.dump(obj) }
uniqs << obj
end
uniqs
end
You can use a hash, which contains only one value for each key:
Hash[*recs.map{|ar| [ar[attr],ar]}.flatten].values
Rails also has a #uniq_by method.
Reference: Parameterized Array#uniq (i.e., uniq_by)
I like jmah and Head's answers. But do they preserve array order? They might in later versions of ruby since there have been some hash insertion-order-preserving requirements written into the language specification, but here's a similar solution that I like to use that preserves order regardless.
h = Set.new
objs.select{|el| h.add?(el.attr)}
ActiveSupport implementation:
def uniq_by
hash, array = {}, []
each { |i| hash[yield(i)] ||= (array << i) }
array
end
Now if you can sort on the attribute values this can be done:
class A
attr_accessor :val
def initialize(v); self.val = v; end
end
objs = [1,2,6,3,7,7,8,2,8].map{|i| A.new(i)}
objs.sort_by{|a| a.val}.inject([]) do |uniqs, a|
uniqs << a if uniqs.empty? || a.val != uniqs.last.val
uniqs
end
That's for a 1-attribute unique, but the same thing can be done w/ lexicographical sort ...
If you are not married with arrays, we can also try eliminating duplicates through sets
set = Set.new
set << obj1
set << obj2
set.inspect
Note that in case of custom objects, we need to override eql? and hash methods

Resources