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_"
]
}
]