Comparing ruby hashes [duplicate] - ruby

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How do I compare two hashes?
I have two ruby hashes (which are essentially models) and am trying to find the differences between them, one is an old instance of an object where the other has new values assigned to some attributes. I'm trying to determine which keys have changed, but there doesn't seem to be anything built into the Hash for this. I can think of a few brute forceish solutions, but was wondering if there is perhaps an elegant solution out there.
Ideally I need to be able to take two hashs like so:
element1 = {:name => "Original", :description => "The original one!"}
element2 = {:name => "Original", :description => "The new one!"}
And be able to compare/diff them and get something back like this:
{:description => "The new one!"}
Right now all I can really think of is iterating through the keys in one hash and comparing the value at that key to the corresponding key in the second hash, but that seems too brute forced.
Any ideas? Thanks a lot!

here is a slightly modified version from colin's.
class Hash
def diff(other)
(self.keys + other.keys).uniq.inject({}) do |memo, key|
unless self[key] == other[key]
if self[key].kind_of?(Hash) && other[key].kind_of?(Hash)
memo[key] = self[key].diff(other[key])
else
memo[key] = [self[key], other[key]]
end
end
memo
end
end
end
It recurses into the hashes for more efficient left and right
{a: {c: 1, b: 2}, b: 2}.diff({a: {c: 2, b: 2}})
returns
{:a=>{:c=>[1, 2]}, :b=>[2, nil]}
instead of
{:a=>[{:c=>1, :b=>2}, {:c=>2, :b=>2}], :b=>[2, nil]}
Great idea colin
here is how to apply the diff to the original hashes
def apply_diff!(changes, direction = :right)
path = [[self, changes]]
pos, local_changes = path.pop
while local_changes
local_changes.each_pair {|key, change|
if change.kind_of?(Array)
pos[key] = (direction == :right) ? change[1] : change[0]
else
path.push([pos[key], change])
end
}
pos, local_changes = path.pop
end
self
end
def apply_diff(changes, direction = :right)
cloned = self.clone
path = [[cloned, changes]]
pos, local_changes = path.pop
while local_changes
local_changes.each_pair {|key, change|
if change.kind_of?(Array)
pos[key] = (direction == :right) ? change[1] : change[0]
else
pos[key] = pos[key].clone
path.push([pos[key], change])
end
}
pos, local_changes = path.pop
end
cloned
end
so to make the left look like the right you run
{a: {c: 1, b: 2}, b: 2}.apply_diff({:a=>{:c=>[1, 2]}, :b=>[2, nil]})
to get
{a: {c: 2, b: 2}, b: nil}
to get exact we would have to go a little farther and record a difference between between nil and no key
and it would also be nice to shorten long arrays by just providing adds and removes

Edit:
I keep coming back to this code to use it in projects I'm in. Here's the latest which is useful for deeply nested structures and based on Pete's code above. I usually drop it in config/initializers/core_ext.rb (in a Rails project):
class Hash
def deep_diff(other)
(self.keys + other.keys).uniq.inject({}) do |memo, key|
left = self[key]
right = other[key]
next memo if left == right
if left.respond_to?(:deep_diff) && right.respond_to?(:deep_diff)
memo[key] = left.deep_diff(right)
else
memo[key] = [left, right]
end
memo
end
end
end
class Array
def deep_diff(array)
largest = [self.count, array.count].max
memo = {}
0.upto(largest - 1) do |index|
left = self[index]
right = array[index]
next if left == right
if left.respond_to?(:deep_diff) && right.respond_to?(:deep_diff)
memo[index] = left.deep_diff(right)
else
memo[index] = [left, right]
end
end
memo
end
end
Here's a small demo:
> {a: [{b: "c", d: "e"}, {b: "c", f: "g"}]}.deep_diff({a: [{b: "c", d: "e"}, {b: "d", f: "g"}]})
=> {:a=>{1=>{:b=>["c", "d"]}}}
Older response:
I have found Rails' Hash diff method to not actually tell me what was on the left side and right side (which is far more useful). There was a plugin call "Riff", that has since disappeared, which would let you diff two ActiveRecord objects. Essentially:
class Hash
def diff(other)
self.keys.inject({}) do |memo, key|
unless self[key] == other[key]
memo[key] = [self[key], other[key]]
end
memo
end
end
end

If all you care about is what's unique in element2, you can just do:
element2.to_a - element1.to_a

Related

Trying to cull a set but the set keeps disappearing

I'm trying to program the AI for a Mastermind game in ruby using Donal Knuth's 5 guess algorithm. The game consists of a codemaker, who uses 8 different colored pegs to create a set of 4, and a codebreaker, who guesses at the code and receives feedback (a red square for a peg which is both the right color and in the right spot, and a white square for a peg which is the right color but in the wrong spot).
I've created a set for all possible codes. My goal is to compare feedback from the guess to feedback from all codes in the set, then delete the ones that don't match. It seems to delete the entire set though.
class ComputerPlayer < Player
def initialize(game)
super(game)
#all_possible_codes = create_codes
#turn = 1
end
def get_code
Array.new(4){rand(1..6)}
end
def get_guess
puts #all_possible_codes.length
if #turn == 0
#turn += 1
cull_set([1, 1, 2, 2])
#all_possible_codes.delete("1122")
return [1, 1, 2, 2]
else
random_sample = #all_possible_codes.to_a.sample.split('').map{|str| str.to_i}
#all_possible_codes.delete(random_sample.join(''))
cull_set(random_sample)
random_sample
end
end
def cull_set(guess)
feedback = #game.feedback_on_guess(guess)
puts feedback
#all_possible_codes.delete_if { |str| #game.feedback_on_guess(str.split.map{|num| num.to_i}) != feedback }
end
def create_codes
set = Set.new
(1..8).each do |i|
(1..8).each do |j|
(1..8).each do |k|
(1..8).each do |l|
set << [i, j, k, l].join('')
end
end
end
end
set
end
end
#this is the feedback_on_guess method used by the above class
def feedback_on_guess(code_guess)
code_duplicate = #code
feedback = []
code_duplicate.map.with_index do |entry, i|
if entry == code_guess[i]
feedback.push('r')
code_guess[i] = -1
-2
else
entry
end
end.each do |entry|
found_index = code_guess.find_index(entry)
if found_index
feedback.push('g')
code_guess[found_index] = -1
end
end
puts feedback
feedback
end
Try
copy = something.dup
because after just
copy = something
copy and something are pointing to the same object. You can confirm this by checking the object_id of the object referenced by the variable. If it is the same, then it is the same object.
When you dup an object, you will cretae a copy. Depending on what you want to dup you might need to implement/override the logic to create a copy. For built in Classes like String, Hash and so on it will work out of the box.
Be aware that nested constructs (eq. Hash containing other Hashes) are not duplicated.
h1 = {"a" => {"b" => 2}}
h2 = h1.dup
puts h1.object_id # 70199597610060
puts h2.object_id # 70199597627020
puts h1["a"].object_id # 70199597610080
puts h2["a"].object_id # 70199597610080

How to find count matching characters at the same indes and at an unmatching index

I have built a version of mastermind that checks a user's input and provides feedback based on how close the user's guess was to the winning sequence. If you're not familiar with the game, you get feedback indicating how many of your characters were guessed correctly at the same index and how many characters guessed are in the sequence, but at the wrong index. If there are duplicates in the guess, then you would not count the extra values unless they correspond to the same number of duplicates in the secret code.
Example: If the sequence is ["G","G","G","Y"] and the user guesses ["G", "Y","G","G"] then you'd want to return 2 for items at the same index and 2 for items at different indexes that are included in the secret sequence.
Another example: If the sequence is ["X","R","Y","T"] and the user guesses ["T","T","Y","Y"] then you'd return 1 for items at the same index 1 for the character guessed that is in the sequence but at the wrong index.
Anyway, to me this is not a simple problem to solve. Here's the code I used to get it to work, but it's not elegant. There must be a better way. I was hoping someone can tell me what I'm missing here?? New to Ruby...
def index_checker(input_array, sequence_array)
count = 0
leftover_input = []
leftover_sequence = []
input.each_with_index do |char, idx|
if char == sequence[idx]
count += 1
else
leftover_input << char
leftover_sequence << sequence[idx]
end
end
diff_index_checker(leftover_input, leftover_sequence, count)
end
def diff_index_checker(input, sequence, count)
count2 = 0
already_counted = []
input.each do |char|
if sequence.include?(char) && !already_counted.include?(char)
count2 += 1
already_counted << char
end
end
[count, count2]
end
Here's a clean Ruby solution, written in idiomatic Ruby object-oriented style:
class Mastermind
def initialize(input_array, sequence_array)
#input_array = input_array
#sequence_array = sequence_array
end
def matches
[index_matches, other_matches]
end
def results
[index_matches.size, other_matches.size]
end
private
attr_reader :input_array, :sequence_array
def index_matches
input_array.select.with_index { |e, i| e == sequence_array[i] }
end
def other_matches
non_exact_input & non_exact_sequence
end
def non_exact_input
array_difference(input_array, index_matches)
end
def non_exact_sequence
array_difference(sequence_array, index_matches)
end
# This method is based on https://stackoverflow.com/a/3852809/5961578
def array_difference(array_1, array_2)
counts = array_2.inject(Hash.new(0)) { |h, v| h[v] += 1; h }
array_1.reject { |e| counts[e] -= 1 unless counts[e].zero? }
end
end
You would use this class as follows:
>> input_array = ["G","G","G","Y"]
>> sequence_array = ["G", "Y","G","G"]
>> guess = Mastermind.new(input_array, sequence_array)
>> guess.results
#> [2, 2]
>> guess.matches
#> [["G", "G"], ["G", "Y"]]
Here's how it works. First everything goes into a class called Mastermind. We create a constructor for the class (which in Ruby is a method called initialize) and we have it accept two arguments: input array (the user guess), and sequence array (the answer).
We set each of these arguments to an instance variable, which is indicated by its beginning with #. Then we use attr_reader to create getter methods for #input_array and #sequence_array, which allows us to get the values by calling input_array and sequence_array from any instance method within the class.
We then define two public methods: matches (which returns an array of exact matches and an array of other matches (the ones that match but at the wrong index), and results (which returns a count of each of these two arrays).
Now, within the private portion of our class, we can define the guts of the logic. Each method has a specific job, and each is named to (hopefully) help a reader understand what it is doing.
index_matches returns a subset of the input_array whose elements match the sequence_array exactly.
other_matches returns a subset of the input_array whose elements do not match the sequence_array exactly, but do match at the wrong index.
other_matches relies on non_exact_input and non_exact_sequence, each of which is computed using the array_difference method, which I copied from another SO answer. (There is no convenient Ruby method that allows us to subtract one array from another without deleting duplicates).
Code
def matches(hidden, guess)
indices_wo_match = hidden.each_index.reject { |i| hidden[i] == guess[i] }
hidden_counts = counting_hash(hidden.values_at *indices_wo_match)
guess_counts = counting_hash(guess.values_at *indices_wo_match)
[hidden.size - indices_wo_match.size, guess_counts.reduce(0) { |tot, (k, cnt)|
tot + [hidden_counts[k], cnt].min }]
end
def counting_hash(arr)
arr.each_with_object(Hash.new(0)) { |s, h| h[s] += 1 }
end
Examples
matches ["G","G","G","Y"], ["G", "Y","G","G"]
#=> [2, 2]
matches ["X","R","Y","T"] , ["T","T","Y","Y"]
#=> [1, 1]
Explanation
The steps are as follows.
hidden = ["G","G","G","Y"]
guess = ["G", "Y","G","G"]
Save the indices i for which hidden[i] != guess[i].
indices_wo_match = hidden.each_index.reject { |i| hidden[i] == guess[i] }
#=> [1, 3]
Note that the number of indices for which the values are equal is as follows.
hidden.size - indices_wo_match.size
#=> 2
Now compute the numbers of remaining elements of guess that pair with one of the remaining values of hidden by having the same value. Begin by counting the numbers of instances of each unique element of hidden and then do the same for guess.
hidden_counts = counting_hash(hidden.values_at *indices_wo_match)
#=> {"G"=>1, "Y"=>1}
guess_counts = counting_hash(guess.values_at *indices_wo_match)
#=> {"Y"=>1, "G"=>1}
To understand how counting_hash works, see Hash::new, especially the explanation of the effect of providing a default value as an argument of new. In brief, if a hash is defined h = Hash.new(3), then if h does not have a key k, h[k] returns the default value, here 3 (the hash is not changed).
Now compute the numbers of matches of elements of guess that were not equal to the value of hidden at the same index and which pair with an element of hidden that have the same value.
val_matches = guess_counts.reduce(0) do |tot, (k, cnt)|
tot + [hidden_counts[k], cnt].min
end
#=> 2
Lastly, return the values of interest.
[hidden.size - indices_wo_match.size, val_matches]
#=> [2, 2]
In the code presented above I have substituted out the variable val_matches.
With Ruby 2.4+ one can use Enumerable#sum to replace
guess_counts.reduce(0) { |tot, (k, cnt)| tot + [hidden_counts[k], cnt].min }
with
guess_counts.sum { |k, cnt| [hidden_counts[k], cnt].min }
def judge(secret, guess)
full = secret.zip(guess).count { |s, g| s == g }
semi = secret.uniq.sum { |s| [secret.count(s), guess.count(s)].min } - full
[full, semi]
end
Demo:
> judge(["G","G","G","Y"], ["G","Y","G","G"])
=> [2, 2]
> judge(["X","R","Y","T"], ["T","T","Y","Y"])
=> [1, 1]
A shorter alternative, though I find it less clear:
full = secret.zip(guess).count(&:uniq!)
I prefer my other answer for its simplicity, but this one would be faster if someone wanted to use this for arrays larger than Mastermind's.
def judge(secret, guess)
full = secret.zip(guess).count { |s, g| s == g }
pool = secret.group_by(&:itself)
[full, guess.count { |g| pool[g]&.pop } - full]
end
Demo:
> judge(["G","G","G","Y"], ["G","Y","G","G"])
=> [2, 2]
> judge(["X","R","Y","T"], ["T","T","Y","Y"])
=> [1, 1]

Counting the number of times a value is repeated in a Hash

I am pulling a hash from mashable.com, and I need to count instances of author names (author is the key, and the value is the author name). mashable's api
{
new: [
{
other_keys: 'other_values'...
author: 'Author's Name'
}
]
I want to iterate over the hash and pull out the author's name, and then count the amount of times it is repeated in the entire list from the mashable api.
Here is what I have; it turns the hash into an array, iterates over it, adding the count to each author name as the key, and then adds the number of repeats as the value.
This would be great, but I can't get it back into my original hash from mashable to add all of the other hash items I want to display.
all_authors = []
all_stories.each do |story|
authors = story['author']
all_authors << authors
end
counts = Hash.new(0)
all_authors.each do |name|
counts[name] += 1
end
counts.each do |key, val|
puts "#{key}: " "#{val}"
end
That does what it is supposed to, but I try to put it back into the original hash from mashable:
all_stories.each do |com|
plorf = com['comments_count'].to_i
if plorf < 1
all_stories.each do |story|
puts "Title:\n"
puts story['title']
puts "URL:\n"
puts story['short_url']
puts "Total Shares:\n"
puts story['shares']['total']
end
end
end
When I drop the code back in to that iteration, all it does is iterate of the initial has, and after each entry, I get a list of all authors and the number of stories they have written, instead of listing each author connected to the other information about each story and the number of stories they have written.
Any help is greatly appreciated.
Here's a simplified version:
h = { a: 1, b: 2, c: 1, d: 1 }
h.count { |_, v| v == 1 } #=> 3
h.values.count(1) #=> 3
Alternatively you can also group by key and then count:
h.group_by(&:last).map { |v, a| [v, a.count] }.to_h #=> {1=>3, 2=>1}
This groups the hash by its values, the counts the times elements in the array of key/value pairs. Here's a more explicit version:
grouped = h.group_by(&:last) #=> {1=>[[:a, 1], [:c, 1], [:d, 1]], 2=>[[:b, 2]]}
grouped.map { |v, a| [v, a.count] #=> [[1, 3], [2, 1]]
Then the final to_h turns the array of 2 element arrays into a hash.
#Michael Kohl it was a good answer, I think I was asking the question wrong. I wound up doing this:
author = story['author']
puts "Number of stories by #{story['author']}: #{author_count['author']}"
inside my "all_stories" loop...
yeah I am pretty sure I was trying to "re-inject" the values to the original hash, and that was way wrong...
Thanks so much for your help though

Why does this use of the Hash#each method work only when I remove the splat operator from the parameter?

I'm going through a problem on Ruby Monk, https://rubymonk.com/learning/books/1-ruby-primer/problems/155-restaurant#solution4804
Their solution is great; I like it and it's more compact than mine. Problem is for mine, I just don't understand why it only works when I remove the splat operator from the cost parameter orders. Even if I shouldn't be doing it this way, I'm struggling to figure out what's up. I know sometimes it's unnecessary to understand everything, and it's best to just move on.. but curious.
Here is mine:
class Restaurant
def initialize(menu)
#menu = menu
end
def cost(*orders)
total_cost = 0
orders.each do |item, number|
total_cost += #menu[item] * number
end
end
menu = {:rice => 3, :noodles => 2}
orders = {:rice => 1, :noodles => 1}
eat = Restaurant.new(menu)
puts eat.cost(orders)
Edit:
To include their suggested solution below
class Restaurant
def initialize(menu)
#menu = menu
end
def cost(*orders)
orders.inject(0) do |total_cost, order|
total_cost + order.keys.inject(0) {|cost, key| cost + #menu[key]*order[key] }
end
end
end
Edit:
To clear up and answer my own question in the comment
I tried these experiments and it shows inject "removing" the array brackets that splat "put on". Perhaps not the most proper way to think about it? It does help clear up my confusion.
order = { :rice => 1, :noodles => 1 }
menu = { :rice => 3, :noodles => 2 }
[order].inject(0) do |bla, blu|
p bla #=> 0
p blu #=> {:rice=>1, :noodles=>1}
p blu.keys #=> [:rice, :noodles]
end
When you write:
def cost(*orders)
end
then all the parameters passed to the cost method will be put into a single array named orders. These two are thus equivalent:
def cost(*orders)
p orders.class #=> Array
p orders #=> [1,2,3]
end
cost(1,2,3)
def cost(orders)
p orders.class #=> Array
p orders #=> [1,2,3]
end
cost( [1,2,3] ) # note the array literal brackets
In your case, when you remove the "splat" you are saying "set orders to reference whatever was passed in directly". In this case you're passing it a Hash, and when you iterate a hash you get key/value pairs for each entry. This is just what you want.
When you do have the splat, though, you're getting this:
def cost(*orders)
p orders.class #=> Array
p orders #=> [{:rice=>1, :noodles=>1}]
end
orders = {:rice=>1, :noodles=>1}
cost(orders)
So you're wrapping your hash in an array, and then iterating over the elements of the array. Thus, the first value passed to the block is the entire hash, and there is no second parameter.
def cost(*orders)
p orders.class #=> Array
p orders #=> [{:rice=>1, :noodles=>1}]
orders.each do |item,number|
p item #=> {:rice=>1, :noodles=>1}
p number #=> nil
end
end
orders = {:rice=>1, :noodles=>1}
cost(orders)
At this point you can't multiply anything by nil and so your code breaks.

'Hash#keys' in helper

I have this method in helper:
def get_hash_keys(hash)
hash.delete_if{|k, v| v.class != Hash}
return hash.keys
end
When I call get_hash_keys from another method in the same helper, it returns a blank array :
def datas_size
sum = 0
new_hash = {title: "This is a test", datas: {firstname: "foo", lastname: "bar"}}
new_hash.each do |k, v|
sum += v.size if get_hash_keys(new_hash).includes? k
end
return sum
end
I tested to change return hash.keys with fixed array, and I get it. Only keys function seems not to work. I also double checked my array in params.
Is there some specifications I ignore working inside helpers ?
Edit for #DRSE :
I start with hash that contains others hashes. I need to know size of each children hash. The point is when I call this function (get_hash_keys) from the views, it fails (return blank array), but from console it works (return keys).
More investigation this morning drive me to conclude may be this is a wrong usage of delete_if. My ununderstood solution is to replace :
def get_hash_keys(hash)
hash.delete_if{|k, v| v.class != Hash}
return hash.keys
end
with
def get_hash_keys(hash)
tmp_hash = hash.clone
tmp_hash.delete_if{|k, v| v.class != Hash}
return tmp_hash.keys
end
Nothing is wrong with hash.keys:
def get_new_keys(hash)
hash.delete_if{|k,v| k == :bar}
return hash.keys
end
get_new_keys({qux: :bar, bar: :baz}) # => [:qux]
This, on the other hand, is busted.
def display_keys(hash)
return "foo " + get_new_keys(hash)
end
Are you expecting an array to be returned? If so, that's not the way to go about it at all. You could do something like:
def display_keys(hash)
return [:foo].concat(get_new_keys(hash))
end
In which case, it'd do something like:
def display_keys(hash)
return [:foo].concat(get_new_keys(hash))
end
display_keys({qux: :bar, bar: :baz}) # => [:foo, :qux]
The .keys method returns and Array of keys in the Hash.
Your question is impossible to answer for two reasons:
You have not included the condition in your delete_if block. My
guess is that you are deleting all keys in the hash, so .keys is
returning [].
You have not described the output you are expecting.
On a side note, your method display_keys does not work in ruby 2.3. You cannot concatenate an Array and a String like that. It will throw a TypeError: no implicit conversion of Array into String
But taking my best guess at it the following code works just fine:
def get_new_keys(hash)
hash.delete_if{ |k,v| v % 2 == 0 }
return hash.keys
end
def display_keys(hash)
return ["foo"] + get_new_keys(hash)
end
hash = {
one: 1,
two: 2,
three: 3,
four: 4,
five: 5,
six: 6,
seven: 7,
eight: 8,
nine: 9,
ten: 10
}
Outputs:
pry(main)> display_keys( hash )
=> ["foo", :one, :three, :five, :seven, :nine]

Resources