I need to create a hash which contains a number of parameters. If the value of the max_id parameter is nil though I want to remove the key from the hash:
params = { since_id: since_id, count: 50, max_id: max_id }
params.delete( :max_id ) unless max_id
The above code works fine but Ruby has so many nice hash and array operators that I wonder if there's an even cleaner way to write it (perhaps something using the splat operator).
Your solution looks good, although I'd do it the other way round:
params = { since_id: since_id, count: 50 }
params[:max_id] = max_id if max_id
You could also use reject:
params = params.reject {|key,value| value == nil }
A specific key
If you just want to check for :max_id then your solution:
params.delete( :max_id ) unless max_id
is the cleanest one. Notice that if max_id is false then the key-value pair will be deleted, therefore I suggest you to use the below version instead:
params.delete( :max_id ) if max_id.nil?
A generic key
The cleanest way I can think of, using hash methods, to delete a key-value pair if the value is nil, is by using Hash#reject!:
params.reject! { |k, v| v.nil? }
This will reject all key-value pairs that has nil as value in the params hash.
There are other alternatives. All of the followings lines are equivalent (except for their returned value):
params.reject! { |k, v| v.nil? }
params.select! { |k, v| not v.nil? }
params.delete_if { |k, v| not v.nil? }
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 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"]
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.
If I have a hash of book ratings like
books = {"Gravity's Rainbow"=>:splendid,
"House in Hyde Park"=>:splendid,
"The Week"=>:quite_good
}
and I want to count the number of occurrences of a particular value, or rating, how is this done?
I tried books.values[:splendid].length - but I suppose the error is brought about because it thinks I want to slice everything up to "splendid", which is of the wrong type, from "books.values".
How do I remove everything not ":splendid" from books.values? Should I be looking at list operations rather than hash? I'm totally new to Ruby, thinking as I type. I'm not sure if books.values has returned a list or some other type though?
books.values returns an array:
books.values
#=> [:splendid, :splendid, :quite_good]
So you can just use Array#count:
books.values.count(:splendid)
#=> 2
Or something like this to count all values:
Hash[books.group_by { |k, v| v }.map { |k, v| [k, v.count] }]
#=> {:splendid=>2, :quite_good=>1}
Using Enumerable's #count method:
books.values.count { |v| v == :splendid }
As #Stefan answered, you can also shorten it by just books.values.count(:splendid)
I have the following:
array_of_hashes = [{:a=>10, :b=>20}, {:a=>11, :b=>21}, {:a=>13, :b=>23}]
How would I go about finding if :a=>11 exists in array_of_hashes
array_of_hashes.include? does not seem to work
array_of_hashes.any? {|h| h[:a] == 11}
You did ask for a boolean result in the OQ, but if you really want the hash element itself do:
array_of_hashes.detect { |h| h[:a] == 11 }
If you want the result really fast you could group the original object and then get the result with a single hash lookup:
t = array_of_hashes.group_by { |x| x[:a] }
t[11]