Home > Software design >  How to iterate over an array of hashes in Ruby to compare each member with the following members in
How to iterate over an array of hashes in Ruby to compare each member with the following members in

Time:05-29

So, I have an array of hashes, let's use this as an example

{:series=>"GOT", :rating=>"Good", :type=>"Fantasy"}
{:series=>"BB", :rating=>"Great", :type=>"Crime"}
{:series=>"E", :rating=>"Poor", :type=>"Drama"}

I'm trying to loop over this array so that I can compare each member with all following members.

E.g. Hash 1 compares with Hash 2 and Hash 3, Hash 2 compares with Hash 3

The actual comparison function I already have written:

output = (data[X].keys & data[Y].keys).select { |k| data[X][k] == data[Y][k] }

X would be the current array and Y is the next element we are comparing to.

EDIT

Here's what I've got so far

for i in 0..data.length
  for j in i..data.length
    # puts data[j   1]
    output = (data[j].keys & data[j 1].keys).select { |k| data[j][k] == data[j 1][k] }
    puts data[j]
    puts data[j 1]
    puts output
  end
  puts "*****"
end

My desired output is to print the hash and the other hash we are comparing with, as well as what keys they share a value for.

For example this array:

{:series=>"GOT", :rating=>"Great", :type=>"Fantasy"}
{:series=>"BB", :rating=>"Great", :type=>"Crime"}

Should print this:

{:series=>"GOT", :rating=>"Great", :type=>"Fantasy"}
{:series=>"BB", :rating=>"Great", :type=>"Crime"}
rating

If the key is nil, it should not compare. I think that's why I'm also getting this error when running the above code:

Traceback (most recent call last):
        4: from match_users.rb:18:in `<main>'
        3: from match_users.rb:18:in `each'
        2: from match_users.rb:19:in `block in <main>'
        1: from match_users.rb:19:in `each'
match_users.rb:21:in `block (2 levels) in <main>': undefined method `keys' for nil:NilClass (NoMethodError)

CodePudding user response:

Note that you're not using "i" inside your loop, which looks like a bug. Also, your index "j 1" is going off the end of the array, resulting in accessing the nil element. Actually, even "j" goes off the end of the array. Arrays are accessed from 0...length-1, whereas "0..data.length" will access the element at index data.length. I'm guessing you mean something more like:

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

CodePudding user response:

Create an Iterable Collection

First of all, the data you posted isn't a valid Ruby array of hashes; it's just a sequential list of Hash objects, so you can't iterate over it. You need to wrap your Hash objects into something iterable first, such as an Array. Here's an example:

titles = 
  [{:series=>"GOT", :rating=>"Good", :type=>"Fantasy"},                                                                                                                                  
   {:series=>"BB", :rating=>"Great", :type=>"Crime"},
   {:series=>"E", :rating=>"Poor", :type=>"Drama"}]

Relative Comparisons of Consecutive Elements

Now that you have an iterable collection, you can use Enumerable#each_cons (which is already mixed into Array in Ruby's core) in order to iterate over each sequential pair of Hash objects. For demonstration purposes, I've chosen to store the relative comparisons as part of an Array within each title. For example, using the Array of Hash objects stored in titles as above:

STAR_MAPPINGS = {'great' => 5, 'good' => 4, 'fair' => 2,
                 'poor' => 1, 'unwatchable' => 0}.freeze

COMPARISON_MAP = {
  -1 => 'is worse than',
   0  => 'the same as',
   1  => 'is better than'
}.freeze

def compare_ratings_for title_1, title_2
  fmt = '_%s_ %s _%s_'
  series_1, series_2 = title_1[:series], title_2[:series]
  ratings_1, ratings_2 = 
    STAR_MAPPINGS[title_1[:rating].downcase],
    STAR_MAPPINGS[title_2[:rating].downcase]
  comparison_str = COMPARISON_MAP[ratings_1 <=> ratings_2]
  format fmt, series_1, comparison_str, series_2
end

titles.each_cons(2).each do |h1, h2|
  # Array#| return an ordered, deduplicated union of keys
  matching_keys = (h1.keys | h2.keys).flatten.uniq
  next if matching_keys.none?

  # perform whatever comparisons you want here; this example
  # compares ratings by assigning stars to each rating
  h1[:comparisons] =
    h1.fetch(:comparisons, []) << compare_ratings_for(h1, h2)
  h2[:comparisons] =
    h2.fetch(:comparisons, []) << compare_ratings_for(h2, h1)
end

titles

The titles variable now holds and returns the following data:

[{:series=>"GOT", :rating=>"Good", :type=>"Fantasy", :comparisons=>["_GOT_ is worse than _BB_"]},
 {:series=>"BB", :rating=>"Great", :type=>"Crime", :comparisons=>["_BB_ is better than _GOT_", "_BB_ is better than _E_"]},
 {:series=>"E", :rating=>"Poor", :type=>"Drama", :comparisons=>["_E_ is worse than _BB_"]}]

Here's the same data again, but this time titles was pretty-printed with amazing_print for improved readability:

[
  {                                                                                     
         :series => "GOT",                                                              
         :rating => "Good",                                                             
           :type => "Fantasy",                                                          
    :comparisons => [                                                                   
      "_GOT_ is worse than _BB_"                                                        
    ]                                                                                   
  },                                                                                    
  {                                                                                     
         :series => "BB",                                                               
         :rating => "Great",                                                            
           :type => "Crime",
    :comparisons => [
      "_BB_ is better than _GOT_",
      "_BB_ is better than _E_"
    ]
  },
  {
         :series => "E",
         :rating => "Poor",
           :type => "Drama",
    :comparisons => [
      "_E_ is worse than _BB_"
    ]
  }
]

See Also

  • Related