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

      

+3


source to share


3 answers


array.uniq { |item| item[:from] }

      



+5


source


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?

+1


source


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

      

0


source







All Articles