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"}}
Related
I have a hash that looks something like:
{ "3g3dsd3" => {"price"=>0.12, "avg"=>81, "top"=>true}, "1sf3af" => {"price"=>0.14, "avg"=>121, "top"=>false}...}
I wanna reorder it so that items with "top"=>true would be on top but besides that the items would keep the previous order, meaning items with the same top value wont change the order between when.
I could not find evidence in the original doc that sort_by keeps the order of the attributes that are not sorted.
How can I do that?
You can make sort_by stable by incorporating with_index.
Instead of:
collection.sort_by { |...| ... }
You write:
collection.sort_by.with_index { |(...), i| [..., i] }
Applied to your problem:
hash.sort_by { |_k, v| v['top'] ? 0 : 1 } # unstable
hash.sort_by.with_index { |(_k, v), i| [v['top'] ? 0 : 1, i] } # stable
sort_by specifically states:
The result is not guaranteed to be stable. When two keys are equal, the order of the corresponding elements is unpredictable.
Meaning your question is well founded.
In this scenario you could use partition to split the collection up into two. Then paste them back together in the way you desire.
top, non_top = hash.partition { |key, hash| hash['top'] }
result = (top + non_top).to_h
partition keeps the order of the of the original array.
If you are a fan of one liners, the following does the same in this scenario.
result = hash.partition { |key, hash| hash['top'] }.flatten(1).to_h
Ruby sort / sort_by are not stable. You can however use the index in the original input as a tiebreaker: Is sort in Ruby stable?
Here is a way to reorder the hash's key-value pairs without converting the hash to one or more arrays and then converting those arrays, after modification, back to a hash.
Suppose
h = { "2rh4abc" => {"price"=>0.18, "avg"=>130, "top"=>false },
"3g3dsd3" => {"price"=>0.12, "avg"=>81, "top"=>true },
"1sf3af" => {"price"=>0.14, "avg"=>121, "top"=>false } }
then
top_key = h.find { |_,v| v["top"] == true }.first
#=> "3g3dsd3"
{ top_key=>h[top_key] }.merge(h.reject { |k,_| k == top_key })
#=> {"3g3dsd3"=>{"price"=>0.12, "avg"=>81, "top"=>true},
# "2rh4abc"=>{"price"=>0.18, "avg"=>130, "top"=>false},
# "1sf3af"=>{"price"=>0.14, "avg"=>121, "top"=>false}}
If h can be mutated (modified in place) this can be simplified to:
{ top_key=>h.delete(top_key) }.merge(h)
#=> {"3g3dsd3"=>{"price"=>0.12, "avg"=>81, "top"=>true},
# "2rh4abc"=>{"price"=>0.18, "avg"=>130, "top"=>false},
# "1sf3af"=>{"price"=>0.14, "avg"=>121, "top"=>false}}
Given the hash
person = {
"cats"=> 2,
"dogs"=> 1
}
I wish to construct the array
["cats", "cats", "dogs"]
"cats" appears twice because person["cats"] #=> 2. For the same reason "dogs" appears once. If the hash had a third key-value pair "pigs"=>3, I would want to return the array
["cats", "cats", "dogs", "pigs", "pigs", "pigs"]
I tried the following code.
arr = person.to_a
i = 0
new_arr = []
while i < arr.length
el = arr[i][0]
final = [new_arr << el]
print final.flatten
i += 1
end
This displays
["cats"]["cats", "dogs"] => nil
but does not seem to return a value.
new_arr
#=> ["cats", "dogs"]
As you see, I am not getting the answer I wanted and do not understand why print displays what I show above.
I would like to know what is wrong with my code and what would be a better way of doing this.
flat_map method will flatten multiple arrays into one
Array operator * creates array with multiple values
result = person.flat_map {|key, value| [key] * value}
# => ["cats", "cats", "dogs"]
Ruby has a lot of nice methods to work with collections. I believe it is better to use them instead of while loop.
You can iterate through the hash using inject
method. The first parameter in the block is the resulting array, that accumulates the result of each iteration, the second is a key/value pair.
person.inject([]) do |array, (key, value)|
array + Array.new(value, key)
end
Or it can be rewritten as a one line.
person.inject([]) { |array, (key, value)| array + Array.new(value, key) }
I have written a huge code something like below
headers, *data_rows = #testCaseSheet
local = headers.zip(*data_rows)
local = local[1..-1].map {|dataRow| local[0].zip(dataRow).to_h}
testCaseHash = {}
local.each do |value|
testCaseHash[value["Locator"]] = value.tap {|hs| hs.delete("Locator")}
end
#testCaseSheet = []
p testCaseHash
[h["Test Name"], testCaseHash],
which output me this as below, now I need to merge this action with each test, I don't know how to do this.
hash= {"Action"=>{"css=#entityType"=>"Type", "id=idNumber"=>"TypeAndWait", "id=shortName"=>"TypeAndTab", "id=FirstName"=>"TypeTabAndWait", nil=>nil},
"Test1"=>{"css=#entityType"=>"Individual", "id=idNumber"=>"2323", "id=shortName"=>"M", "id=FirstName"=>"Abc", "id=lastName"=>"Gg"},
"Test2"=>{"css=#entityType"=>"Legal", "id=idNumber"=>"2323", "id=shortName"=>"Z", "id=FirstName"=>"Xyz", "id=lastName"=>"Gg"}}
Now I want to merge this action with the followings tests for an example,
hash= { "Test1"=>{"css=#entityType"=>["Individual","Type"], "id=idNumber"=>["2323","TypeAndWait"], "id=shortName"=>["M","TypeAndTab"], "id=FirstName"=>["Abc","TypeTabAndWait"]},
"Test2"=>{"css=#entityType"=>["Legal""Type"], "id=idNumber"=>["2323","TypeAndWait"], "id=shortName"=>["Z","TypeAndTab"], "id=FirstName"=>["Xyz","TypeTabAndWait"]}}
I don't know how to merge this way, Can anyone help me?
If I understand you want something like this
hash_1 = {a: "a1", b: "b1", c: "c1"}
hash_2 = {a: "a2", b: "b2", d: "d1"}
p hash_1.merge(hash_2) { |k, v1, v2| v1 = [v1, v2] }
# => {:a=>["a1", "a2"], :b=>["b1", "b2"], :c=>"c1", :d=>"d1"}
Which in your case can be:
test_1_value = my_hash['Test1'].merge(my_hash['Action']) { |k, v1, v2| v1 = [v1, v2] }
# => {"css=#entityType"=>["Individual", "Type"], "id=idNumber"=>["2323", "TypeAndWait"], "id=shortName"=>["M", "TypeAndTab"], "id=FirstName"=>["Abc", "TypeTabAndWait"], "id=\"lastName"=>"Gg", nil=>nil}
This is a general solution, you can manipulate furthermore removing the unwanted keys ad apply to fit your needs.
Edit - picking up comments
Remove unwanted keys and simplified merge block:
keys_to_remove = ["id=lastName", "whatever", nil]
test_1_value = my_hash['Test1'].merge(my_hash['Action']) { |k, *vs| vs }.delete_if{ |k, _| keys_to_remove.include? k }
# => {"css=#entityType"=>["Individual", "Type"], "id=idNumber"=>["2323", "TypeAndWait"], "id=shortName"=>["M", "TypeAndTab"], "id=FirstName"=>["Abc", "TypeTabAndWait"]}
I want to expand on iGians answer. Although the answer describes how the issue should be solved, it didn't use any iteration. You can iterate over the tests in the following way:
hash = {
"Action"=>{"css=#entityType"=>"Type", "id=idNumber"=>"TypeAndWait", "id=shortName"=>"TypeAndTab", "id=FirstName"=>"TypeTabAndWait", nil=>nil},
"Test1"=>{"css=#entityType"=>"Individual", "id=idNumber"=>"2323", "id=shortName"=>"M", "id=FirstName"=>"Abc", "id=lastName"=>"Gg"},
"Test2"=>{"css=#entityType"=>"Legal", "id=idNumber"=>"2323", "id=shortName"=>"Z", "id=FirstName"=>"Xyz", "id=lastName"=>"Gg"},
}
action = hash.delete 'Action'
tests = hash
tests.each_value do |test|
action_with_test_keys = action.select { |key, _value| test.key? key }
test.merge!(action_with_test_keys) { |_key, *values| values } # values = [old, new]
end
This assumes that 'Action' is the only non-test key in the hash and all other values should be merged with the 'Action' value. Keep in mind that this approach mutates the hash variable. If you don't want this you should simply #dup the hash beforehand or look for a non-mutating approach.
Optimizations:
If you use Ruby 2.5.0 or higher you can use #slice instead of #select.
action.select { |key, _value| test.key? key }
# is replaced with
action.slice(*test.keys)
If you are 100% sure that each test in tests contains the same keys and there is always at least one test present, you could move the action_with_test_keys assignment out of the #each_value block to save resources.
tests = hash # anchor point in the above solution
action_with_test_keys = action.slice(*tests.values.first.keys) # added
References:
Hash#delete to remove the 'Action' key from the hash variable.
Hash#each_value to iterate over each value of tests.
Hash#select to select only the action keys that are present on test.
Hash#key? to check if the given key is present.
Hash#merge! to merge action_with_test_keys and update the test variable.
Hash#slice replacement for Hash#select if you use Ruby 2.5.0 or higher.
Generally speaking, it might be a good idea to build up the desired data structure while dealing with the underlaying data objects. However, if you need to transform you hash afterwards, here is one way to do that:
hash = {
"Action"=>{"css=#entityType"=>"Type", "id=idNumber"=>"TypeAndWait", "id=shortName"=>"TypeAndTab", "id=FirstName"=>"TypeTabAndWait", nil=>nil},
"Test1"=>{"css=#entityType"=>"Individual", "id=idNumber"=>"2323", "id=shortName"=>"M", "id=FirstName"=>"Abc", "id=lastName"=>"Gg"},
"Test2"=>{"css=#entityType"=>"Legal", "id=idNumber"=>"2323", "id=shortName"=>"Z", "id=FirstName"=>"Xyz", "id=lastName"=>"Gg"}
}
action = hash['Action']
tests = hash.reject { |k, v| k == 'Action' }
mapping = tests.map do |name, test|
groups = (action.to_a + test.to_a).group_by(&:first)
no_keys = groups.map { |k, v| [k, v.each(&:shift).flatten] }
no_keys.reject! { |k, v| v.length == 1 }
[name, Hash[no_keys]]
end
Hash[mapping]
# => {"Test1"=>{"css=#entityType"=>["Type", "Individual"], "id=idNumber"=>["TypeAndWait", "2323"], "id=shortName"=>["TypeAndTab", "M"], "id=FirstName"=>["TypeTabAndWait", "Abc"]},
# "Test2"=>{"css=#entityType"=>["Type", "Legal"], "id=idNumber"=>["TypeAndWait", "2323"], "id=shortName"=>["TypeAndTab", "Z"], "id=FirstName"=>["TypeTabAndWait", "Xyz"]}}
I hope you find that useful.
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 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? }