3

I know that if you have an array and reference it as array.uniq it will return without any of the duplicates.

However in this case it is an array of objects (is that proper ruby speak?). I want each call to go into the @calls array unless the call.from is the same as a 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 have the same call.from value?

calls_raw.each do |call|       
        call_formatted = {
              :date => date,
              :time => time,
              :from => call.from,
              :duration => call.duration,
              :recording => recording,
        }
        @calls << call_formatted
end
captainrad
  • 3,760
  • 16
  • 41
  • 74
  • 1
    Wouldn't it simplify things if you used a set? See http://ruby-doc.org/stdlib-2.2.2/libdoc/set/rdoc/Set.html – panmari Jul 23 '15 at 21:33
  • @panmari, you don't mean a hash, with the keys being the values of `call.from`? If not, how would that work with a set? – Cary Swoveland Jul 23 '15 at 23:52

3 Answers3

5
array.uniq { |item| item[:from] }
mrodrigues
  • 1,052
  • 10
  • 14
1

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 build an array of calls larger than it may ultimately need to be, and the final call to #uniq will make the list unique.

Or, to avoid adding all the duplicates in the array, you could build it with a 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 Hash approach will use the first occurrence of call.from as it is being set with ||=. To use the last occurrence of call.from then use a straightforward assignment with =.

It's also been suggested to just use a Set instead of an Array.

To take that approach you're going to have to implement #eql? and #hash on the class we're populating the set with.

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's not getting any bigger
s << CallRaw.new("Chewbaca")
s << CallRaw.new("Chewbaca")
 => <Set: {#<CallRaw:0x00000002211888 @from="Chewbaca">}> 

Awesome - the Set works!!!

Now, it is interesting to note that having implemented #eql? and #hash, we can now use Array#uniq without having to pass in a 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 a badge that StackOverflow awards for having too much coffee before setting out to answer a question?

Darren Hicks
  • 4,946
  • 1
  • 32
  • 35
0

Unless there's some reason it has to be an array, I'd store the data in a Hash, keyed by the from value.

Then it's easy and fast to look up an entry by the from value. You can choose to insert a new value only if there's no value already with the same key, or insert the new value and let it replace the old entry with that key.

Example:

calls = Hash.new

def add(call)
  if not calls[call.from]
    calls[call.from] = call
  end
end
Jerry101
  • 12,157
  • 5
  • 44
  • 63