Home > Software design >  How to append ID key:value pairs when iterating over an array of hashes in Ruby?
How to append ID key:value pairs when iterating over an array of hashes in Ruby?

Time:05-29

I have an array of hashes, e.g.:

{"Breed"=>"Beagle", "Size"=>"Medium", "Colour"="Brown"}
{"Breed"=>"Pug", "Size"=>"Small", "Colour"=nil}
{"Breed"=>"Beagle", "Size"=>"Medium", "Colour"="Brown"}
{"Breed"=>"Beagle", "Size"=>"Medium", "Colour"=nil}

I want to give them all ID values based on a given search criteria. For example, searching by Size should return:

{"Breed"=>"Beagle", "Size"=>"Medium", "Colour"="Brown", "ID"="0"}
{"Breed"=>"Pug", "Size"=>"Small", "Colour"=nil, "ID"="1"}
{"Breed"=>"Beagle", "Size"=>"Medium", "Colour"="Brown", "ID"="0"}
{"Breed"=>"Poodle", "Size"=>"Medium", "Colour"=nil, "ID"="0"}

I have written the following code which checks each hash in the sequence with the following hashes.

for i in 0..data.length-2
  data[i].store("ID", i)

  for j in i 1..data.length-1
    output = (data[i].keys & data[j].keys).select { |k| data[i][k] == data[j][k] }

    if output.include? searchTerm
      puts "Match!"
      puts "---"
      data[j].store("ID", data[i]["ID"])
    else
      puts "No match :("
      puts "---"
    end
  end

  puts "---Finished checking row---"    
end

puts data

The issue is twofold:

A. Nil values count as a match, e.g when searching by Colour:

{"Breed"=>"Beagle", "Size"=>"Medium", "Colour"="Brown", "ID"="0"}
{"Breed"=>"Pug", "Size"=>"Small", "Colour"=nil, "ID"="1"}
{"Breed"=>"Beagle", "Size"=>"Medium", "Colour"="Brown", "ID"="0"}
{"Breed"=>"Poodle", "Size"=>"Medium", "Colour"=nil, "ID"="1"}

B. Matches seem to only work for the last pair found, e.g. when searching by Size:

{"Breed"=>"Beagle", "Size"=>"Medium", "Colour"="Brown", "ID"="0"}
{"Breed"=>"Pug", "Size"=>"Small", "Colour"=nil, "ID"="1"}
{"Breed"=>"Beagle", "Size"=>"Medium", "Colour"="Brown", "ID"="2"}
{"Breed"=>"Poodle", "Size"=>"Medium", "Colour"="White", "ID"="2"}

In summary, I want to ignore nil values so they don't count as matches and for all instances of the same key:value pair to have the same value for the ID key.

CodePudding user response:

Don't use for loops for array iteration. Ruby has plenty of nice methods for doing that.

Here is my solution:

hashes = [{"Breed" => "Beagle", "Size" => "Medium", "Colour" => "Brown"},
          {"Breed" => "Pug", "Size" => "Small", "Colour" => nil},
          {"Breed" => "Beagle", "Size" => "Medium", "Colour" => "Brown"},
          {"Breed" => "Beagle", "Size" => "Large", "Colour" => nil}]

def search_id(elements, search_key)
  # Get rid of elements with nil values.
  target_elements = elements.reject {|e| e[search_key].nil?}

  resul = []
  id = 0

  # Iterate through target_elements
  target_elements.each do |currentElement|

    # Check if exsits an element with the same value in the resul array
    match = resul.find {|previousElement| currentElement[search_key] == previousElement[search_key]}

    if match
      # Use previous id
      currentElement[:id] = match[:id]
    else
      # Assing a new id
      currentElement[:id] = id
      id  = 1
    end

    # Add element to result
    resul << currentElement
  end

  resul
end

puts search_id(hashes, 'Size')

CodePudding user response:

I think that it might be worth considering grabbing all of the unique values from the searched entries first and then assigning each of them an id inside a hash (with the available terms as the keys), so that you can simply loop though the hashes and grab the correct id from the pre-generated hash. e.g. something like:

hashes = [
    {"Breed"=>"Beagle", "Size"=>"Medium", "Colour"=>"Brown"},
    {"Breed"=>"Pug", "Size"=>"Small", "Colour"=>nil},
    {"Breed"=>"Beagle", "Size"=>"Medium", "Colour"=>"Brown"},
    {"Breed"=>"Beagle", "Size"=>"Medium", "Colour"=>nil}
]


def append_id(items, search_key)
    id_map = items.
        map { |hash| hash[search_key] }.
        uniq.
        reject { |value| value.nil? }.
        map.with_index { |value, index| [value, index] }.
        to_h
    
    items.each do |hash|
        value = hash[search_key]
        hash['ID'] = id_map[value] unless value.nil?
    end
    
    items
end

append_id(hashes, 'Size')
  • Related