How would I remove multiple nested values from a hash - ruby

I've got a really confusing question that I can't seem to figure out.
Say I have this hash
hash = {
"lock_version"=>1,
"exhibition_quality"=>false,
"within"=>["FID6", "S5"],
"representation_file"=>{
"lock_version"=>0,
"created_by"=>"admin",
"within"=>["FID6", "S5"]
}
}
How can I delete from "within"=>["FID6", "S5"] a value with the pattern FID<Number> (in this example FID6)?
I've thought about it a bit and used the .delete, .reject! the within but I realised this was deleting the whole key value pair which is not what I want. Thanks a lot for any help that can put me on the right path.

You could use a method or proc/lambda to achieve the result. This solutions splits up the logic in two parts. Removing the actual FID<Number> string from the array and recursively calling the former on the correct key.
remove_fid = ->(array) { array.grep_v(/\AFID\d+\z/) }
remove_nested_fid = lambda do |hash|
hash.merge(
hash.slice('within').transform_values(&remove_fid),
hash.slice('representation_file').transform_values(&remove_nested_fid)
)
end
pp hash.then(&remove_nested_fid) # or remove_nested_fid.call(hash)
# {"lock_version"=>1,
# "exhibition_quality"=>false,
# "within"=>["S5"],
# "representation_file"=>
# {"lock_version"=>0, "created_by"=>"admin", "within"=>["S5"]}}
grep_v removes all strings from the array that do not match the given regex.
slice creates a new hash only containing the given keys. If a key is missing it will not be present in the resulting hash.
transform_values transforms the values of a hash into a new value (similar to map for Array), returning a hash.
merge creates a new hash, merging the hashes together.
This solution does not mutate the original hash structure.

You're going to need to recurse over the Hash and, in cases where the value is a Hash, process it using the same function, repeatedly. This can be problematic in Ruby -- which doesn't handle recursion very well -- if the depth of the tree is too deep, but it's the most natural way to express this type of issue.
def filter_fids(h)
h.each_pair do |k, v|
if v.is_a? Hash
filter_fids v
elsif k == "within"
v.reject! { |x| x.start_with? "FID" }
end
end
end
This will mutate the original data structure in place.

Related

Convert array of hashes to single hash with values as keys

Given a source array of hashes:
[{:country=>'england', :cost=>12.34}, {:country=>'scotland', :cost=>56.78}]
Is there a neat Ruby one-liner for converting it to a single hash, where the values for the :country key in the original hash (guaranteed to be unique) become keys in the new hash?
{:england=>12.34, :scotland=>56.78}
This should do what you want
countries.each_with_object({}) { |country, h| h[country[:country].to_sym] = country[:cost] }
=> {:england=>12.34, :scotland=>56.78}
You can do that using Enumerable#inject:
countries.inject({}) { |hsh, element| hsh.merge!(element[:country].to_sym => element[:cost]) }
=> {:england=>12.34, :scotland=>56.78}
We initialise the accumulator as {}, and then we iterate over each of the elements of the initial array and add the new formatted element to the accumulator.
One point to add is that using hsh.merge or hsh.merge! would have the same effect for the output, given that inject will set the accumulator hsh as the return value from the block. However, using merge! is better when it comes to memory usage, given that merge will always generate a new Hash, whereas merge! will apply the merge over the same existing Hash.
One more possible solution is:
countries.map(&:values).to_h
=> {"england"=>12.34, "scotland"=>56.78}

Find key of value within array in hash

I have a hash categories as following:
categories = {"horeca" => ["bar", "waiter", "kitchen"],
"retail" => ["eerste", "tweede"]}
I want to find they key if the value is included in the array of values.
Something like following
categories.key("bar")
which would return "horeca"
as of now I can only get "horeca" if I do
categories.key(["bar", "waiter", "kitchen"])
Try Enumberable#find:
categories.find { |key, values|
values.include?("bar")
}.first
As Máté mentioned, you can use find if you want to find the first matching element. Use select if you want all matching elements. To just get the keys you would do:
categories.select { |key, values| values.include?("bar") }.map(&:first)
See https://ruby-doc.org/core-2.2.3/Enumerable.html#method-i-select
Creating intermediate array and then calling first on it is all unnecessary, also if the hash is large and you want first matched value, following solution is better
categories.each{ |k,v| break k if v.include?('bar') }
#=> "horeca"
Md. Farhan Memon's solution is generally the preferable solution but it has one downside: If there's no match in the collection, it returns the collection itself – which probably isn't a desirable result. You can fix this with a simple adjustment that combines both detect/find and break:
categories.detect { |key, values| break key if values.include?('bar') }
This breaks and returns the value if it finds it and otherwise returns nil (which I assume to be the preferable behavior).
If your collection may also contain nil values and/or non-arrays, you can improve it further:
categories.detect { |key, values| break key if Array(values).include?('bar') }
The only downside of this general approach is that it's not particularly intuitive to newcomers: You have to know a bit more than just basic Ruby to understand what's going on without running the code first.

How to select keys from hash and strip parts of the keyname with ruby

I have a config hash in my ruby project and I want to pick certain keys with their value to have them as a separate hash.
Project.config.to_h.select{ |k,v| k[/db_/] }
=> {:db_name => value, .... }
This returns me nicely all the k,v that I need. But I also want to strip the db_ from the keynames so that it returns me
=> {:name => value, ....}
I tried something like
Project.config.to_h.select{ |k,v| k[/db_/] }.each_key { |k| k.to_s.gsub(/db_/) }
But it returns the same hash like the above example. Any idea or suggestions to get this as a smooth one or two liner?
inject to the rescue (assuming the db_ is in the beginning):
Project.config.to_h.inject({}) do | a, (k, v) |
k =~ /\Adb_/ ? a.update($' => v) : a
end
inject can be used to iterate through an enumerable data structure and build any kind of output in the course.
$' is a magic variable containing the portion of the string after a successful regexp match.
So let's break down your solution and see what's wrong with it:
Project.config.to_h.select{ |k,v| k[/db_/] }.each_key { |k| k.to_s.gsub(/db_/) }
So:
k.to_s.gsub(/db_/)
is creating a new object (and not mutating the keys like you would expect), in this case an enumerator, when you don't pass gsub a second argument it returns an enumerator, so let's assume you did:
k.to_s.gsub(/db_/, "")
This would be creating new objects and not mutating the keys in the hash(to_s would create a new string object, and gsub doesn't mutate, instead creates a new object, gsub! is the mutant version)
Now let's assume the keys were strings and you could even mutate them (with gsub!), ruby would not let you mutate them in place, else ruby hash would be broken, when a string is used as a key it is frozen, see http://ruby-doc.org/core-2.2.2/Hash.html#5B-5D-3D-method
key should not have its value changed while it is in use as a key (an unfrozen String passed as a key will be duplicated and frozen).
So with this knowledge how would I implement this:
Project.config.to_h.each_with_object({}) do |(k,v), hsh|
next unless k[/db_/] # skip every key that doesn't match the regex (if you want the keys to not have part of them stripped then you can use an if/else along with the next line)
hsh[k.to_s.sub(/db_/, "")] = v
end

Ruby Hash destructive vs. non-destructive method

Could not find a previous post that answers my question...I'm learning how to use destructive vs. non-destructive methods in Ruby. I found an answer to the exercise I'm working on (destructively adding a number to hash values), but I want to be clear on why some earlier solutions of mine did not work. Here's the answer that works:
def modify_a_hash(the_hash, number_to_add_to_each_value)
the_hash.each { |k, v| the_hash[k] = v + number_to_add_to_each_value}
end
These two solutions come back as non-destructive (since they all use "each" I cannot figure out why. To make something destructive is it the equals sign above that does the trick?):
def modify_a_hash(the_hash, number_to_add_to_each_value)
the_hash.each_value { |v| v + number_to_add_to_each_value}
end
def modify_a_hash(the_hash, number_to_add_to_each_value)
the_hash.each { |k, v| v + number_to_add_to_each_value}
end
The terms "destructive" and "non-destructive" are a bit misleading here. Better is to use the conventional "in-place modification" vs. "returns a copy" terminology.
Generally methods that modify in-place have ! at the end of their name to serve as a warning, like gsub! for String. Some methods that pre-date this convention do not have them, like push for Array.
The = performs an assignment within the loop. Your other examples don't actually do anything useful since each returns the original object being iterated over regardless of any results produced.
If you wanted to return a copy you'd do this:
def modify_a_hash(the_hash, number_to_add)
Hash[
the_hash.collect do |k, v|
[ k, v + number_to_add ]
end
]
end
That would return a copy. The inner operation collect transforms key-value pairs into new key-value pairs with the adjustment applied. No = is required since there's no assignment.
The outer method Hash[] transforms those key-value pairs into a proper Hash object. This is then returned and is independent of the original.
Generally a non-destructive or "return a copy" method needs to create a new, independent version of the thing it's manipulating for the purpose of storing the results. This applies to String, Array, Hash, or any other class or container you might be working with.
Maybe this slightly different example will be helpful.
We have a hash:
2.0.0-p481 :014 > hash
=> {1=>"ann", 2=>"mary", 3=>"silvia"}
Then we iterate over it and change all the letters to the uppercase:
2.0.0-p481 :015 > hash.each { |key, value| value.upcase! }
=> {1=>"ANN", 2=>"MARY", 3=>"SILVIA"}
The original hash has changed because we used upcase! method.
Compare to method without ! sign, that doesn't modify hash values:
2.0.0-p481 :017 > hash.each { |key, value| value.downcase }
=> {1=>"ANN", 2=>"MARY", 3=>"SILVIA"}

Hash.map method

One of the exercises in this tutorial is:
Exploit the fact that map always returns an array: write a method hash_keys that accepts a hash and maps over it to return all the keys in a linear Array.
The solution is:
def hash_keys(hash)
hash.map { |pair| pair.first }
end
However, I'm having trouble understanding why the above works. For example, I wrote a solution as follows that also works:
def hash_keys(hash)
# Initialize a new array
result = Array.new
# Cycle through each element of the hash and push each key on to our array
hash.map { |x,y| result.push(x) }
# Return the array
result
end
I can understand why my method works, but I don't understand their proposed solution. For example, they are not even creating an Array object. They are not returning anything. It seems they are just listing the first element in each key/value element array.
I think you misunderstood the point of map. It doesn't just iterate over the given collection (that's what each is for) - it creates an array where each element is the result of calling the block with the corresponding element of the original collection.
Your solution could (and should) just as well be written using each instead of map as you aren't really making use of what map does - you're only making use of the fact that it invokes its block once for each element in the given collection.
When map is applied to a hash, the hash is converted to an array. That is why explicit conversion into an array is not necessary. And map returns an array by replacing each item of the original array with the result of evaluating the block. Each time the block is evaluated, it will be given an array that is a pair of a key and its value. first applies to this pair and returns the key. map returns an array of these keys.
map turns an Enumerable object into an Array. It's what it does. The block describes, in terms of each element in the receiver, what the corresponding element in the resulting array should be.
So, a simpler example is map on an Array:
[1,2,3,4].map {|n| n*2}
# => [2,4,6,8]
That is - from [1,2,3,4], generate a new Array, where each element is twice the equivalent entry in [1,2,3,4].
Half of your answer is right in the question: "Exploit the fact that map always returns an array." You don't need to explicitly create an array because map does that for you.
As far as returning it, you already seem to know that the last line of a ruby method is its return value. In the tutorial's solution, since the hash creates an array at the last (and only line), the array is returned from the method.

Resources