I have two hashes like this:
hash1 = Hash.new
hash1["part1"] = "test1"
hash1["part2"] = "test2"
hash1["part3"] = "test3"
hash2 = Hash.new
hash2["part1"] = "test1"
hash2["part2"] = "test2"
hash2["part3"] = "test4"
Expected output: part3
Basically, I want to iterate both of the hashes and print out "part3" because the value for "part3" is different in the hash. I can guarantee that the keys for both hashes will be the same, the values might be different. I want to print out the keys when their values are different?
I have tried iterating both hashes at once and comparing values but does not seem to give the right solution.
The cool thing about Ruby is that it is so high level that it is often basically English:
Print keys from the first hash if the values in the two hashes are different:
hash1.keys.each { |key| puts key if hash1[key] != hash2[key] }
Select the first hash keys that have different values in the two hashes and print each of them:
hash1.keys.select { |key| hash1[key] != hash2[key] }.each { |key| puts key }
Edit: I'll leave this should it be of interest, but #ndn's solution is certainly better.
p hash1.merge(hash2) { |_,v1,v2| v1==v2 }.reject { |_,v| v }.keys
# ["part3"]
hash1["part1"] = "test99"
p hash1.merge(hash2) { |_,v1,v2| v1==v2 }.reject { |_,v| v }.keys
# ["part1", "part3"]
This uses the form of Hash#merge that employs a block (here { |_,v1,v2| v1==v2 }) to determine the values of keys that are present in both hashes being merged. See the doc for an explanation of the three block variables, _, v1 and v2. The first block variable equals the common key. I've used the local variable _ for that, as is customary when the variable is not used in the block calculation.
The steps (for the original hash1):
g = hash1.merge(hash2) { |_,v1,v2| v1==v2 }
#=> {"part1"=>true, "part2"=>true, "part3"=>false}
h = g.reject { |_,v| v }
#=> {"part3"=>false}
h.keys
#=> ["part3"]
The obvious way is that of ndn, here a solution without blocks by converting to arrays, joining them and subtracting the elements that are the same, followed by converting back to hash and asking for the keys.
Next time it would be better to include what you tried so far.
((hash1.to_a + hash2.to_a) - (hash1.to_a & hash2.to_a)).to_h.keys
# ["part3"]
Related
I have a hash:
h = { ["alpha"]=>[{ "bit"=>"100", "colour"=>"red"},
{ "id"=>"100", "colour"=>"red"},
{ "value"=>"65", "colour"=>"red"}],
["beta"] =>[{ "id"=>"070", "colour"=>"black"},
{"value"=>"338", "colour"=>"black"}]
}
I want to add hashes in values to itself and delete duplicated pairs from every item to get
h = { ["alpha"]=>[{"bit"=>"100", "id"=>"100", "value"=>"65", "colour"=>"red"}],
["beta"] =>[{"id"=>"070", "value"=>"338", "colour"=>"black"}]
}
I tried so far converting hashes to array and remove duplicates but how to convert array to hash again? And I also think it is not effective and elegant solution. Any other ideas?
for Ruby >= 2.4.0 - method #transform_values with methods #inject and #merge
h.transform_values { |v| v.inject(:merge) }
For lower versions:
h.map { |k, v| [k, v.inject(:merge)] }.to_h
Longer version, step by step, can look like that:
h.inject({}) { |result, (key, value)|
result[key] = value.inject({}){ |value_result, value_elem|
value_result.merge! value_elem; value_result
}; result
}
It basically transforms array of hashes (in each value) into single hash, containing unique values. But be aware - if you will have hash like that:
h = {
"alpha"=>[{"bit"=>"100", "colour"=>"red"}, {"id"=>"100", "colour"=>"blue"}]
}
with two different values for key "colour", the output will look like that:
{"alpha"=>{"bit"=>"100", "colour"=>"blue", "id"=>"100"}}
This is how #merge method works.
This uses the form of Hash#merge to determine the values of keys that are present in both hashes being merged, which here is all keys.
h.merge(h) { |_,arr| arr.reduce(&:merge) }
#=> {["alpha"]=>{"bit"=>"100", "colour"=>"red", "id"=>"100", "value"=>"65"},
# ["beta"] =>{"id"=>"070", "colour"=>"black", "value"=>"338"}}
I have an array of hashes (#1) that looks like this:
data = [{"username"=>"Luck", "mail"=>"root#localhost.net", "active"=>0}]
that I am trying to compare with following array of hashes (#2):
test = [{"username"=>"Luck", "mail"=>"root#localhost.net", "active"=>"0"}]
where #1 I obtained from database by mysql2 (what actually is in the database)
and #2 from my cucumber scenario (what I minimally expect ot be there).
By definition #2 must be a subset of #1 so I follow with this code:
data = data.to_set
test = test.to_set
assert test.subset?(data)
The problem is in data array the value of active is NOT a string. In case of data it is Fixnum, and in case of test, it is String.
I need a solution that will work even for more than one hash in the array. (As the database can return more than one row of results) That is why I convert to sets and use subset?
From other questions I got:
data.each do |obj|
obj.map do |k, v|
{k => v.to_s}
end
end
However it does not work for me. Any ideas?
Assumptions you can make:
All the keys in data will always be Strings.
All the keys in test will always be Strings. And always be the identical to data.
All the values in test will always be Strings.
Here are a couple of approaches that should do it, assuming I understand the question correctly.
#1: convert the hash values to strings
def stringify_hash_values(h)
h.each_with_object({}) { |(k,v),h| h[k] = v.to_s }
end
def sorta_subset?(data,test)
(test.map { |h| stringify_hash_values(data) } -
data.map { |h| stringify_hash_values(data) }).empty?
end
data = [{"username"=>"Luck", "mail"=>"root#localhost.net", "active"=>0}]
test = [{"username"=>"Luck", "mail"=>"root#localhost.net", "active"=>"0"}]
sorta_subset?(data,test) #=> true
#2 see if keys are the same and values converted to strings are equal
require 'set'
def hashes_sorta_equal?(h,g)
hk = h.keys
(hk.to_set == g.keys.to_set) &&
(h.values_at(*hk).map(&:to_s) == g.values_at(*hk).map(&:to_s))
end
def sorta_subset?(data,test)
test.all? { |h| data.any? { |g| hashes_sorta_equal?(g,h) } }
end
sorta_subset?(data,test) #=> true
Don't ask me why it works, but I found A solution:
data.map! do |obj|
obj.each do |k, v|
obj[k] = "#{v}"
end
end
I think it has something to do with what functions on arrays and hashes change the object itself and not create a changed copy of the object.
Problem:
I need to extract certain keys and count them in a hash, as a sample consider:
data = [{"name"=>"name1", "priority"=>"1", "owner"=>"test3"},
{"name"=>"name1", "priority"=>"1", "owner"=>"test4"},
{"name"=>"name2", "priority"=>"1", "owner"=>"test5"},
{"name"=>"name2", "priority"=>"2", "owner"=>"test5"},
{"name"=>"nae954me2", "priority"=>"2", "owner"=>"test5"}]
I want to count the number of records per each [id (extracted from name) and priority] so that at the end I will have something like:
#{{"priority"=>"1", "id"=>"name1"}=>2, {"priority"=>"1", "id"=>"name2"}=>1, {"priority"=>"2", "id"=>"name2"}=>1}
I'm doing the following but I have a feeling that I'm overcomplicating it:
#!/usr/bin/env ruby
data = [{"name"=>"name1", "priority"=>"1", "owner"=>"test3"},
{"name"=>"name1", "priority"=>"1", "owner"=>"test4"},
{"name"=>"name2", "priority"=>"1", "owner"=>"test5"},
{"name"=>"name2", "priority"=>"2", "owner"=>"test5"},
{"name"=>"nae954me2", "priority"=>"2", "owner"=>"test5"}]
# (1) trash some keys, just because I don't need them
data.each do |d|
d.delete 'owner'
# in the real data I have about 4 or 5 that I'm trashing
d['id'] = d['name'].scan(/[a-z][a-z][a-z][a-z][0-9]/)[0] # only valid ids
d.delete 'name'
end
puts data
#output:
#{"priority"=>"1", "id"=>"name1"}
#{"priority"=>"1", "id"=>"name1"}
#{"priority"=>"1", "id"=>"name2"}
#{"priority"=>"2", "id"=>"name2"}
#{"priority"=>"2", "id"=>nil}
# (2) reject invalid keys
data = data.reject { |d| d['id'].nil? }
puts data
#output:
#{"priority"=>"1", "id"=>"name1"}
#{"priority"=>"1", "id"=>"name1"}
#{"priority"=>"1", "id"=>"name2"}
#{"priority"=>"2", "id"=>"name2"}
# (3) count
counts = Hash.new(0)
data.each do |d|
counts[d] += 1
end
puts counts
#{{"priority"=>"1", "id"=>"name1"}=>2, {"priority"=>"1", "id"=>"name2"}=>1, {"priority"=>"2", "id"=>"name2"}=>1}
any suggestions on improving my method of counting?
There are many ways to do this. (You may have noticed that I've done a lot of editing of my answer, explaining in some detail how a method works, only to realize there's a better way to do it, so out comes the machete.) Here are two solutions. The first was inspired by the approach you took, but I've tried to package it to be more Ruby-like. I'm not sure what constitutes a valid "name", so I've put that determination in a separate method that can be easily changed.
Code
def name_valid?(name)
name[0..3] == "name"
end
data.each_with_object(Hash.new(0)) {|h,g|
(g[{"id"=>h["name"],"priority"=>h["priority"]}]+=1) if name_valid?(h["name"])}
#=> {{"id"=>"name1", "priority"=>"1"}=>2,
# {"id"=>"name2", "priority"=>"1"}=>1,
# {"id"=>"name2", "priority"=>"2"}=>1}
Explanation
Enumerable#each_with_object creates an initially-empty hash with default value zero that is represented by the block variable g. g is built by adding hash elements created from the the elements of data:
g[{"id"=>h["name"],"priority"=>h["priority"]}]+=1
If the hash g has the key
{"id"=>h["name"],"priority"=>h["priority"]}
the value associated with the key is incremented by one. If h does not have this key,
g[{"id"=>h["name"],"priority"=>h["priority"]}]
is set equal to zero before
g[{"id"=>h["name"],"priority"=>h["priority"]}]+=1
is invoked, so the value becomes 1.
Alternative Method
Code
data.each_with_object({}) do |h,g|
hash = { { "id"=>h["name"], "priority"=>h["priority"] } => 1 }
g.update(hash) { |k, vg, _| vg + 1 } if name_valid?(h["name"])
end
#=> {{"id"=>"name1", "priority"=>"1"}=>2,
# {"id"=>"name2", "priority"=>"1"}=>1,
# {"id"=>"name2", "priority"=>"2"}=>1}
Explanation
Here, I've used Hash#update (aka Hash#merge!) to merge each element of data (a hash) into the initially-empty hash h (provided the value of "name" is valid). update's block
{ |k, vg, _| vg + 1 }
is invoked if and only if the merged hash (g) and the merging hash (hash) have the same key, k, in which case the block returns the value of the key. Note the third block variable is the value for the key k for the hash hash. As we do not use that value, I've replaced it with the placeholder _.
Depending on what you mean by "something like" this might do the trick:
data.group_by { |h| [h["name"], h["priority"]] }.map { |k, v| { k => v.size } }
=> [{["name1", "1"]=>2}, {["name2", "1"]=>1}, {["name2", "2"]=>1}, {["nae954me2", "2"]=>1}]
So I have two arrays of hashes:
a = [{"b"=>123,"c"=>456}, {"b"=>456,"c"=>555}]
b = [{"c"=>456,"d"=>789}, {"b"=>222,"c"=>444}]
How would I concatenate them with the condition that the value of the key c is equivalent in both a and b? Meaning I want to be able to concatenate with the condition of a['c'] == b['c']
This is the result I want to get:
final_array = [{"b"=>123,"c"=>456,"d"=>789}, {"b"=>456,"c"=>555}, {"b"=>222,"c"=>444}]
a = [{"b"=>123,"c"=>456}, {"b"=>456,"c"=>555}]
b = [{"c"=>456,"d"=>789}, {"b"=>222,"c"=>444}]
p a.zip(b).map{|h1,h2| h1["c"] == h2["c"] ? h1.merge(h2) : [h1 ,h2]}.flatten
# => [{"b"=>123, "c"=>456, "d"=>789}, {"b"=>456, "c"=>555}, {"b"=>222, "c"=>444}]
a = [{"b"=>123,"c"=>456}, {"b"=>456,"c"=>555}]
b = [{"c"=>456,"d"=>789}, {"b"=>222,"c"=>444}]
def merge_hashes_with_equal_values(array_of_hashes, key)
array_of_hashes.sort { |a,b| a[key] <=> b[key] }.
chunk { |h| h[key] }.
each_with_object([]) { |h, result| result << h.last.inject(&:merge) }
end
p merge_hashes_with_equal_values(a + b, 'c')
# => [{"b"=>222, "c"=>444}, {"c"=>456, "d"=>789, "b"=>123}, {"b"=>456, "c"=>555}]
Concatenate the arrays first, and pass it to the method with the hash key to combine on. Sorting that array then places the hashes to merge next to each other in another array, which makes merging a bit easier to program for. Here I chose #chunk to handle detection of continuous runs of hashes with equal keys to merge, and #each_with_object to compile the final array.
Since this method takes one array to work on, the length of the starting arrays does not need to be equal, and the ordering of those arrays does not matter. A downside is that the keys to operate on must contain a sortable value (no nils, for example).
Here is yet another approach to the problem, this one using a hash to build the result:
def merge_hashes_with_equal_values(array_of_hashes, key)
result = Hash.new { |h,k| h[k] = {} }
remainder = []
array_of_hashes.each_with_object(result) do |h, answer|
if h.has_key?(key)
answer[h.fetch(key)].merge!(h)
else
remainder << h
end
end.values + remainder
end
Enumerable#flat_map and Hash#update are the perfect methods for this purpose :
a = [{"b"=>123,"c"=>456}, {"b"=>456,"c"=>555}]
b = [{"c"=>456,"d"=>789}, {"b"=>222,"c"=>444}]
p a.zip(b).flat_map{|k,v| next k.update(v) if k["c"] == v["c"];[k,v]}
# >> [{"b"=>123, "c"=>456, "d"=>789}, {"b"=>456, "c"=>555}, {"b"=>222, "c"=>444}]
I was wondering if there is a more elegant way of writing the following lines:
section_event_hash = []
sections.each do |s|
section_event_hash << { s => s.find_all_events }
end
I want to create a hash whose keys are the elements of sections, and the values are arrays of elements returned by the find_all_events method.
If you want section_event_hash to really be a Hash rather than an Array, then you could use each_with_object:
section_event_hash = sections.each_with_object({}) { |s, h| h[s] = s.find_all_events }
You could use map to build an array of arrays and then feed that to Hash[]:
section_event_hash = Hash[sections.map { |s| [s, s.find_all_events] }]
The code you posted isn't quite doing what you said you want. Let's take a closer look at it by testing like so:
sections = ["ab", "12"]
section_event_hash = []
sections.each do |s|
section_event_hash << { s => s.split("") }
end
puts section_event_hash.inspect
Gives:
[{"ab"=>["a", "b"]}, {"12"=>["1", "2"]}]
So you've actually created an array of hashes, where each hash contains one key-value pair.
The following code produces one hash with multiple elements. Notice how an empty hash is created with {} instead of []. Curly braces are the symbol for a hash, while the square brackets refer to a particular key.
section_event_hash = {}
sections.each do |s|
section_event_hash[s] = s.split("")
end
puts section_event_hash.inspect
=> {"ab"=>["a", "b"], "12"=>["1", "2"]}
As for a "more elegant" way of doing it, well that depends on your definition. As the other answers here demonstrate, there is usually more than one way to do something in ruby. seph's produces the same data structure as your original code, while mu's produces the hash you describe. Personally, I'd just aim for code that is easy to read, understand, and maintain.
array_of_section_event_hashes = sections.map do |s|
{s => s.find_all_events}
end