Array conversion to hash - ruby

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.

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.

Who can explain this?

I am having trouble understanding this comment.
Array({:a => "a", :b => "b"}) #=> [[:a, "a"], [:b, "b"]]
Could you explain how it works in detail?
{:a => "a", :b => "b"} creates a Hash.
Passing that to Array will create an array of arrays. Each array element of the outer array will be another array containing the key and the value of one item of the hash.
The Array methods transforms your hash into an array.
Therefore, for each entry of the hash, ruby will create an array with two elements : the key and the value of the entry in the hash.
You have two entries in your array :
:a => "a" which becomes [:a, "a"]
:b => "b" which becomes [:b, "b"]
It's actually a method provided by Kernel module.
Firstly it tries to call to_ary(return self for array), then to_a on argument.
You'll get the same result by using corresponding methods to_ary and to_a.

Manipulating a byte array

I have a simple byte array
["\x01\x01\x04\x00"]
I'm not sure how I can alter just the second value in the string (I know the array only has one item), whilst still keeping the object a byte array.
Something along these lines:
["\x01#{ARGV[0]}\x04\x00"]
I think the secret is that you have a nested array:
irb(main):002:0> x = ["\x01\x02\x01\x01"]
=> ["\001\002\001\001"]
You can index it:
irb(main):003:0> x[0][1]
=> 2
You can assign into it:
irb(main):004:0> x[0][1] = "\x05"
=> "\005"
And it looks like what you want:
irb(main):005:0> x
=> ["\001\005\001\001"]
use each_byte string method:
$ irb --simple-prompt
>> str = "\x01\x01\x04\x00"
=> "\001\001\004\000"
>> str.each_byte {|byte| puts byte}
1
1
4
0
=> "\001\001\004\000"
>>
It might be less confusing to get rid of the array wrapper.
a = ["\x01\x01\x04\x00"]
a = a[0]
a[1] = ...
You can always put the string back inside an array:
a = [a]
Also, technically, it's not a "byte array", it's a single-element Array, with a String object. (And for that matter, strictly speaking, Ruby doesn't really have Array of Type; all Ruby arrays are something like Array of Object elsewhere.)

Resources