How to pass params as variables inside methods like "dig" - ruby

I have a hash like:
my_hash = {"one"=>{"two"=>{"three"=>"four"}}}
I'd like to do:
my_hash.dig("one", "two")
=> {"three"=>"four"}
It's ridiculous to hardcode the params every time and it's obvious to use a variable like:
my_var = "one", "two"
Unfortunately, output is not great at all:
my_hash.dig(my_var)
=> nil
Why is this not working and how do I do it right?

To use array elements as individual parameters you'll have to use the splat operator (*).
my_hash = {"one"=>{"two"=>{"three"=>"four"}}}
my_var = "one", "two" # same as: my_var = ["one", "two"]
my_hash.dig(*my_var)
#=> {"three"=>"four"}
# The above could be read as:
my_hash.dig(*my_var)
my_hash.dig("one", "two")
# While your version can be read as:
my_hash.dig(my_var)
my_hash.dig(["one", "two"])
The reason your version outputs nil is because objects (like arrays) can be used as hash keys. Your version is looking for the key ["one", "two"], which is not present in my_hash. Thus returning the default value nil.

Related

Understanding map in Ruby

I'm pretty new to Ruby and am trying to understand an example of the map method that I came across:
{:a => "foo", :b => "bar"}.map{|a, b| "#{a}=#{b}"}.join('&')
which returns:
=> "a=foo&b=bar"
I don't understand how the
b=bar
is returned. The string interpolation is what is confusing me as it seems it would return something like:
=> "a=foo&bbar"
> {:a => "foo", :b => "bar"}.map{|key, value| "#{key}=#{value}"}
#=> ["a=foo", "b=bar"]
map method will fetch each element of hash as key and value pair
"#{key}=#{value}" is a String Interpolation which adds = between your key and value
Using this syntax everything between the opening #{ and closing } bits
is evaluated as Ruby code, and the result of this evaluation will be
embedded into the string surrounding it.
Array#join will returns a string created by converting each element of the array to a string, separated by the given separator.
so here in your case:
> ["a=foo", "b=bar"].join('&')
#=> "a=foo&b=bar"
In Rails you can convert hash to query params using Hash#to_query method, which will return the same result.
> {:a => "foo", :b => "bar"}.to_query
#=> "a=foo&b=bar"
The symbol key :a and the local variable a have nothing in common. The names are only coincidentally the same. Consider this code instead:
{
var1: "value1",
var2: "value2"
}.map do |key, value|
"#{key}=#{value}"
end.join('&')
# => "var1=value1&var2=value2"
Here the variables are different. What map does, like each, is iterate over each key-value pair in the Hash. That means you can do things like this, too, to simplify:
{
var1: "value1",
var2: "value2"
}.map do |pair|
pair.join('=')
end.join('&')
# => "var1=value1&var2=value2"
Normally when iterating over a Hash you should use names like k,v or key,value to be clear on what you're operating on.
If you're ever confused what's going on internally in an iteration loop, you can debug like this:
{
var1: "value1",
var2: "value2"
}.map do |pair|
puts pair.inspect
pair.join('=')
end.join('&')
That gives you this output:
[:var1, "value1"]
[:var2, "value2"]
That technique helps a lot. There's even the short-hand notation for this:
p pair
There are 2 method calls occurring here, the map and the join. One way to make it clearer and easier to understand is to separate the two methods and alter the keywords used in the map method. So instead of
{:a => "foo", :b => "bar"}.map{|a, b| "#{a}=#{b}"}.join('&')
Lets have
{:a => "foo", :b => "bar"}.map{|key, value| "#{key}=#{value}"}
This returns an array. #=> ["a=foo", "b=bar"]
Now:
["a=foo", "b=bar"].join('&')
produces a sting
#=> "a=foo&b=bar"
Map is iterating over the two key/value pairs and creating a string with the '=' between them and returns it in an array. It would iterate over all the key/value pairs in the harsh. Our example just has 2.
Join attaches the two elements of the array together with the '&' symbol between them and returns it as string. It would attach all elements of the array no matter its size.
What helped me to learn map and join is to open up the irb or pry and create a few hashes and arrays and play around with them. I highly recommend using unique names for your values that explain what is going on.
I hope this helps you.

Unexpected result with splat operator

I have a hash, whose values are an array of size 1:
hash = {:start => [1]}
I want to unpack the arrays as in:
hash.each_pair{ |key, value| hash[key] = value[0] } # => {:start=>1}
and I thought the *-operator as in the following would work, but it does not give the expected result:
hash.each_pair{ |key, value| hash[key] = *value } # => {:start=>[1]}
Why does *value return [1] and not 1?
Because the []= method applied to hash takes only one argument in addition to the key (which is put inside the [] part), and a splatted/expanded array, which is in general a sequence of values (which coincidentally happens to be a single element in this particular case) cannot be directly accepted as the argument as is splatted. So it is accepted by the argument of []= as an array after all.
In other words, an argument (of the []= method) must be an object, but splatted elements (such as :foo, :bar, :baz) are not an object. The only way to interpret them as an object is to put them back into an array (such as [:foo, :bar, :baz]).
Using the splat operator, you can do it like this:
hash.each_pair{|key, value| hash.[]= key, *value}
sawa and Ninigi already pointed out why the assignment doesn't work as expected. Here's my attempt.
Ruby's assignment features work regardless of whether you're assigning to a variable, a constant or by implicitly invoking an assignment method like Hash#[]= with the assignment operator. For the sake of simplicity, I'm using a variable in the following examples.
Using the splat operator in an assignment does unpack the array, i.e.
a = *[1, 2, 3]
is evaluated as:
a = 1, 2, 3
But Ruby also allows you to implicitly create arrays during assignment by listing multiple values. Therefore, the above is in turn equivalent to:
a = [1, 2, 3]
That's why *[1] results in [1] - it's unpacked, just to be converted back to an array.
Elements can be assigned separately using multiple assignment:
a, b = [1, 2, 3]
a #=> 1
b #=> 2
or just:
a, = [1, 2, 3]
a #=> 1
You could use this in your code (note the comma after hash[key]):
hash = {:start => [1]}
hash.each_pair { |key, values| hash[key], = values }
#=> {:start=>1}
But there's another and more elegant way: you can unpack the array by putting parentheses around the array argument:
hash = {:start => [1]}
hash.each_pair { |key, (value)| hash[key] = value }
#=> {:start=>1}
The parentheses will decompose the array, assigning the first array element to value.
Because Ruby is acting unexpectedly smart here.
True, the splash operator will "fold" and "unfold" an array, but the catch in your code is what you do with that fanned value.
Take this code into account:
array = ['a', 'b']
some_var = *array
array # => ['a', 'b']
As you can see the splat operator seemingly does nothing to your array, while this:
some_var, some_other_var = *array
some_var # => "a"
somet_other_var # => "b"
Will do what you'd expect it does.
It seems ruby just "figures" if you splat an array into a single variable, that you want the array, not the values.
EDIT: As sawa pointed out in the comments, hash[key] = is not identical to variable =. []= is an instance Method of Hash, with it's own C-Code under the hood, which COULD (in theory) lead to different behaviour in some instances. I don't know of any example, but that does not mean there is none.
But for the sake of simplicity, we can asume that the regular variable assignment behaves exactly identical to hash[key] =.

How to return two separate arrays of keys and values

Please explain how this piece of code can return two arrays.
def keysAndValues(data)
[data.keys, data.values]
end
keysAndValues method does return a single array (of two arrays inside of it), but its output can be interpreted as two arrays. Let me explain:
single_array = ["hello", "world"]
puts single_array # => ["hello", "world"]
first_element, second_element = single_array
puts first_element # => "hello"
puts second_element # => "world"
The reason this notation is possible is the implementation of Ruby's assignment (=) operator. Thus, calling a, b = keysAndValues(data) makes both variables a and b filled. But beware, although the outcome technically makes sense this might be unexpected in some situations:
first, second = 1 # first is 1, second is nil
There are also some other uses for multiple assignment, consider the following case:
a, b, *c = [1, 2, 3, 4] # note the asterisk symbol here
puts a # => 1
puts b # => 2
puts c # => [3,4]
Ruby supports parallel assignment. This is a simple example:
foo, bar = 1, 2
foo # => 1
bar # => 2
Your method is returning two values inside an array:
keys_and_values(
{
a: 1,
b: 2
}
).size # => 2
which are assigned via = to the two values on the left side of the equation. The values in the array are references to where the keys and values sub-arrays are located:
foo, bar = keys_and_values(
{
a: 1,
b: 2
}
)
foo.object_id # => 70159936091440
bar.object_id # => 70159936091420
Ruby isn't unique with supporting parallel assignment; It is used in other languages too. Any decent Ruby manual will talk about this. It's good to understand because the assignment to variables, or passing multiple parameters to methods is something you'll encounter repeatedly in Ruby. Do a search for more information.
Also, in Ruby we don't name methods using camelCase, such as "keysAndValues". Instead we use snake_case: keys_and_values.

Search hash values for substring and return key in Ruby

I have an array:
array = ["One is enough", "Two is a couple", "Where's the beef?", "One"]
I then have a hash:
hash = {"one" => "Defined",
"two" => "Test"
}
I need to loop over each object in array and see if that object contains a substring that is found in the hash keys. If it is found, it should return the hash value. Otherwise, it should return undefined. The end goal is to create an array that looks like this:
final_array = ["Defined,"Test","Undefined","Defined"]
How can I write that loop effectively?
Here is an approach :
array = ["One is enough", "Two is a couple", "Where's the beef?", "One"]
hash = {"one" => "Defined","two" => "Test"}
array.map{|e| hash.find(proc{["undefined"]}){|k,v| e.downcase.include? k}.last}
# => ["Defined", "Test", "undefined", "Defined"]
Explanation:
Enumerable#find will be good idea. As doc is saying - Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns its result when it is specified, or returns nil otherwise.
I used Array#map,and passing each element string,to the block. Now inside the block I called #find on hash. Now hash.find passing each key/value pair to the hash.find method block. Inside that block I am calling String#include? method on e,passing key as argument to the #include? method. If #include? test results true,then key/value of that iteration is returned,otherwise default argument proc{["undefined"]}.call is being performed.
Hope that helps!
hash = {"one" => "Defined", "two" => "Test"}
array = ["One is enough", "Two is a couple", "Where's the beef?", "One"]
hash.default = "Undefined"
keys = hash.keys
array.map {|a| hash[(a.split.map(&:downcase) & keys).first]}
if hash does not contain a key 'k', hash[k] => "Undefined"
a.split.map(&:downcase) changes, for example, a = "One is Enough" to "one is enough"
the intersection with keys equals ["one"], ["two"], [nil], ["one"], respectively
.first is to extract the hash key from the 1-element arrays
the hash is evaluated at the computed hash key, with hash[nil] => "Undefined" (default)

Array conversion to hash

I'm confused as to this behavior. Do I really need to split my array to make this work?
pry(main)> ary = ["foo", "bar"]
=> ["foo", "bar"]
pry(main)> Hash[ary]
=> {"f"=>"o", "b"=>"a"}
pry(main)> Hash["foo", "bar"]
=> {"foo"=>"bar"}
pry(main)> Hash[["foo", "bar"]]
=> {"f"=>"o", "b"=>"a"}
pry(main)> Hash[ary.split(",")]
=> {"foo"=>"bar"}
Tries 1 and 3 above are equivalent, passing a single one dimensional array to the constructor, which is not correct.
For this to work as you expect, you'd need to pass the parameters as separate arguments, or as a 2 dimensional array of pairs>
# Split the array into args (equivalent to example #2 above)
# equivalent to Hash[key1, val1, key2, val2]
Hash[*ary]
# or wrap the array in another array (an array of nested pairs)
# equivalent to Hash[[[key1,val1],[key2,val2]]]
Hash[[ary]]
The incorrect behavior you're seeing is presumably because the constructor expects an array of length-2 arrays, while you've passed an array of strings. It interprets arg[0] as the key for each pair, and arg[1] as the value, in this case f and o, b and a.

Resources