Ruby condition to insert unique elements into an array
I know that if you have array
and specify it as array.uniq
, it will return without any duplicates.
However, in this case it is an array of objects (what does the ruby โโactually say?). I want every call to go to an array @calls
, unless call.from
it is the same as the call_formatted object already present in the array.
How can I conditionally place these objects in the array if no other objects in the array matter call.from
?
calls_raw.each do |call|
call_formatted = {
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
@calls << call_formatted
end
source to share
Use #map
to build your array for you and call #uniq
on it ...
calls_raw.map do |call|
{
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
end.uniq{|call| call[:from]}
The above approach will first create an array of calls that is larger than might be required and the final #uniq call will make the list unique.
Or, to avoid adding all the duplicates in the array, you can construct it with Hash
as such:
calls_raw.each_with_object do |call, h|
h[call.from] ||= {
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
end.values
The approach Hash
will use the first occurrence of the call. Since it is installed with ||=
. To use the last occurrence of call.from, use direct assignment with =
.
It was also suggested to use Set
instead of Array
.
To take this approach, you will have to implement #eql?
it #hash
in the class in which we populate the set.
class CallRaw
attr_accessor :from
def initialize(from)
self.from = from
end
def eql?(o)
# Base equality on 'from'
o.from == self.from
end
def hash
# Use the hash of 'from' for our hash
self.from.hash
end
end
require 'set'
s = Set.new
=> <Set: {}>
s << CallRaw.new("Chewbaca")
=> <Set: {<CallRaw:0x00000002211888 @from="Chewbaca">}>
# We expect now, that adding another will not grow our set any larger
s << CallRaw.new("Chewbaca")
=> <Set: {<CallRaw:0x00000002211888 @from="Chewbaca">}>
# Great, it not getting any bigger
s << CallRaw.new("Chewbaca")
s << CallRaw.new("Chewbaca")
=> <Set: {#<CallRaw:0x00000002211888 @from="Chewbaca">}>
Awesome - the kit works !!!
Now it is interesting to note that by implementing #eql?
and #hash
, we can now use it Array#uniq
without having to step through the block.
a = Array.new
a << CallRaw.new("Chewbaca")
=> [<CallRaw:0x000000021e2128 @from="Chewbaca">]
a << CallRaw.new("Chewbaca")
=> [<CallRaw:0x000000021e2128 @from="Chewbaca">, <CallRaw:0x000000021c2bc0 @from="Chewbaca">]
a.uniq
=> [<CallRaw:0x000000021e2128 @from="Chewbaca">]
Now I'm just wondering if there is an icon that StackOverflow calls for too much coffee before heading to the question?
source to share
Unless for any reason, it should be an array, I would store the data in Hash
, entered with a value from
.
It is then easy and quick to find the entry using the value from
. You can insert a new value only if there is no value already with the same key, or you can insert a new value and replace it with an old record.
Example:
calls = Hash.new
def add(call)
if not calls[call.from]
calls[call.from] = call
end
end
source to share