Mo Martin
Mo Martin

Reputation: 207

Ruby merge arrays without duplicates then use higher value out of duplicate array

i'm working on a version control system in which chef cookbook version numbers are specified in multiple different environments.

I've been able to merge the two environment files together using this format --

source      = JSON.parse(File.read("A.json"))
destination = JSON.parse(File.read("B.json"))
source      = destination.merge(source)

The values of each file are in this format --

'A'

{
  "a": "v2.0.18",
  "b": "v5.0.2",
  "c": "v17.0.0",
  "d": "v9.0.0",
}

'B'

{
  "a": "v1.0.18",
  "b": "v4.0.0",
  "c": "v20.0.0",
  "d": "v7.0.0"
}

Currently does --

{
  "a": "v2.0.18",
  "b": "v5.0.2",
  "c": "v17.0.0",
  "d": "v9.0.0",
}

What i'd like it to do --

{
  "a": "v2.0.18",
  "b": "v5.0.2",
  "c": "v20.0.0", #keeps higher value
  "d": "v9.0.0",
}

Any help would be greatly appreciated. Thanks

Upvotes: 0

Views: 131

Answers (4)

Andrey Deineko
Andrey Deineko

Reputation: 52357

Hash#merge is what you're looking for:

a.merge(b) do |key, old_val, new_val|
  Gem::Version.new(old_val[1..-1]) > Gem::Version.new(new_val[1..-1]) ? old_val : new_val
end
#=> {:a=>"v2.0.18", :b=>"v5.0.2", :c=>"v20.0.0", :d=>"v9.0.0"}

As @Stefan suggested, the above could be improved by incorporating the approach @Eric Duminil used in his answer:

a.merge(b) { |key, *values| values.max_by { |v| Gem::Version.new(v[1..-1]) } }

Upvotes: 5

Eric Duminil
Eric Duminil

Reputation: 54223

You can use Hash#merge, but you also need to define how the comparison is made between two strings.

major_minor converts "v2.0.18" to [2,0,18], which can be compared to other version arrays to find the maximum.

source = {
  "a": "v2.0.18",
  "b": "v5.0.2",
  "c": "v17.0.0",
  "d": "v9.0.0",
}

destination = {
  "a": "v1.0.18",
  "b": "v4.0.0",
  "c": "v20.0.0",
  "d": "v7.0.0"
}

def major_minor(version)
  version.scan(/\d+/).map(&:to_i)
end

p source.merge(destination){|key, old, new| [old, new].max_by{|v| major_minor(v) } }
#=> {:a=>"v2.0.18", :b=>"v5.0.2", :c=>"v20.0.0", :d=>"v9.0.0"}

Upvotes: 5

Darth Kotik
Darth Kotik

Reputation: 2351

merge takes additional argument block to figure out which key to keep in case both hashes has same key

a = {
      "a": "v2.0.18",
      "b": "v5.0.2",
      "c": "v17.0.0",
      "d": "v9.0.0",
    }
b = {
      "a": "v1.0.18",
      "b": "v4.0.0",
      "c": "v20.0.0",
      "d": "v7.0.0"
  }
c = a.merge(b) {|k, v1, v2| [v1, v2].max}
 => {:a=>"v2.0.18", :b=>"v5.0.2", :c=>"v20.0.0", :d=>"v9.0.0"} 

Upvotes: -1

Bustikiller
Bustikiller

Reputation: 2498

You can use merge passing a block to choose which value should be chosen when the key is duplicated.

Upvotes: 2

Related Questions