0

I have 2 arrays of objects, I want to merge them such that the resulting array will contain all elements in the first array replacing any elements for which the second array has an object of the same id.

  finalArr=[]
  arr1.each do |e1|
    set2Contains=false
    arr2.each do |e2|
      if(e2.id==e1.id)
        set2Contains=true
      end
    end
    if(set2Contains)
      finalArr.push(e2)
    else
      finalArr.push(e1)
    end
  end

I'm new to ruby, but as it is the king of the one liners the above seems a little verbose. I was wondering if my code could be shortened / optimized in any way?

Thanks for any suggestions

4 Answers 4

5

You'll want to make your second array a hash on IDs, so you don't have to scan through it every time:

hash = Hash.new
arr2.collect{|x| hash[x.id] = x}

then you can go ahead and do:

finalArr = arr1.map{|x| hash.has_key?(x.id) ? hash[x.id] : x }

Note that there might be caveats to be wary of if your arrays can contain nil, which in this case I am assuming is not the case.

Sign up to request clarification or add additional context in comments.

7 Comments

Nice, I'm guessing this would be faster for large arrays, will .map preserve the order of the original array?
Ruby 1.9 has ordered hashes that can preserve order. 1.8.7 generally randomizes the order.
Map preserves the order, basically given an array of elements, it'll apply a block on all of these and return an array of the results. So for any i, if we call the block f, result[i] = f(input[i]) is what map does.
Right you are. As a note, you can simplify the first two lines to: hash = Hash[arr2.collect { |x| [ x.id, x ] }]
@tadman I know, but I find the writing I used easier to grasp for ruby newcomers.
|
1

Same logic, but refined code:

final_arr=[]
arr1.each do |e1|
  if arr2.any? { |e2| e1.id == e2.id }
    final_arr << e2
  else
    final_arr << e1
  end
end

More succint

final_arr=[]
arr1.each do |e1|
  final_arr << arr2.any? { |e2| e1.id == e2.id } ? e2 : e1
end

3 Comments

That's awesome, no if blocks at all! Cheers
Actually in the most succinct version doesn't look like valid ruby, e2 is out of scope
A ternary ? : is a type of if, though.
1

Since you mentioned one-liners, here's a functional one:

merged = Hash[ a1.map{|o| [o.id,o]} ].merge(Hash[ a2.map{|o| [o.id,o]} ]).values

This converts both arrays into hashes keyed by the id, merges them (values from a2 overwriting those in a1, and then extracts just the values.

If you're going to do a lot of set-like work with these objects, I suggest that you define eql? and hash methods on them to compare their id values, and then just use the built-in Ruby Set class:

require 'set'

Foo = Struct.new(:id,:name) do
  def eql?(o2)
    id==o2.id
  end
  def hash
    id.hash
  end
end

a1 = Set[ Foo.new(1,"Phrogz"), Foo.new(17,"Cat")   ]
a2 = Set[ Foo.new(42,"Arthur"), Foo.new(1,"Gavin") ]

all = a1 + a2
all.each{ |foo| puts foo }
#=> #<struct Foo id=1, name="Phrogz">
#=> #<struct Foo id=17, name="Cat">
#=> #<struct Foo id=42, name="Arthur">

Comments

1

In ruby 1.9 it's as simple as:

(a|b).uniq{|x| x[:id]}

put the array with the values you don't want replaced first.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.