What does this `*` symbol in this ruby snippet mean? (splat?) [duplicate] - ruby

This question already has answers here:
Why is the splat used inside an array definition here?
(3 answers)
Closed 7 years ago.
I'm new to Ruby and trying to read through some code. I can't figure out what exactly the * symbol is telling the Ruby interpreter to do in this snippet:
[1]> items = [:one, :two, :three]
=> [:one, :two, :three]
[2]> x = Hash[*items.map { |item| [item, item.to_s+' !!'] }.flatten ]
=> {:one=>"one !!", :two=>"two !!", :three=>"three !!"}

Suppose I had some method in Ruby:
def a_method(a,b,c)
"#{a} #{b} #{c}"
end
and an array:
arr = [1,2,3]
I want to pass the 3 elements of the array into the method. This will produce an error:
a_method(arr) # wrong number of arguments
So what do I do? I could certainly do:
a_method(arr[0], arr[1], arr[2])
but there's an easier way, using the * "splat" operator:
a_method(*arr)
Basically, you'll achieve the same effect as above. Think of it like a "shortcut" that "converts" each array element into a method argument when being used when you call a method. This splat operator is a bit complicated to understand because it behaves differently when being used in different places (you have lots of useful articles on the topic).
In your example, basically, after the following expression is done:
items.map { |item| [item, item.to_s+' !!'] }.flatten
it produces:
[:one, "one !!", :two, "two !!", :three, "three !!"]
and this data is being passed to the Hash method using the "splat" operator, because Hash will not accept a single array as an argument:
arr = ['a', 'b', 'c', 'd']
p Hash{arr} #=> error, wrong number of arguments
p Hash[*arr] #=> {"a"=>"b", "c"=>"d"}

Without the *, you get
Hash[[:one, "one !!", :two, "two !!", :three, "three !!"]]
which won't work. The * "splats" the array into a sequence of arguments, giving you:
Hash[:one, "one !!", :two, "two !!", :three, "three !!"]

* in this context is the "splat" operator.
It's function is to turn an array into individual arguments to a function.
Given a function that accepts arguments...
def my_func(a, b, c)
end
You can invoke this function with three arguments by directly specifying arguments: my_func(1,2,3)
Or, if you have an array containing the arguments for the function, you can use the splat operator to "expand" the array to fill the arguments for the function:
args = [1,2,3]
my_func(*args) # identical to my_func(1,2,3)
In your particular case, there is a class method on Hash called [], which accepts a variable number of arguments. The code is using map to produce an array, and then passing each element of the array as an argument to Hash[].

Related

Need Ruby method to convert an array of strings into a Hash

I need Ruby method to convert an array of strings into a Hash where each key is a string and each value is the 1-indexed index of the string in the original array.
hashify(%w(a b c))
# should return
{'a' => 1, 'b' => 2, 'c' => 3}
Even though I think I'm helping someone do their homework, I can't resist taking a golf swing, because Ruby is awesome:
%w(a b c).each.with_index(1).to_h
Also, defining "hashify" is VERY un-Ruby-like. I'd suggest alternatives, but it's probably a homework assignment anyways and you don't seem to want to learn it.
def hashify(array)
array.each.with_index(1).to_h
end
hashify(%w(a b c))
#=> { "a" => 1, "b" => 2, "c" => 3 }
There are (clearly) multiple ways you could achieve your goal in Ruby.
If you consider the expression %w(a b c).map.with_index(1).to_h, you can see that it is a matter of stringing together a few methods provided to us by the Enumerable class.
By first sending the :map message to the array, we receive back an Enumerator, which provides us the handy :with_index method. As you can see in the docs, with_index accepts an offset in an argument, so offsetting your indices by 1 is as simple as passing 1 as your argument to :with_index.
Finally, we call :to_h on the enumerator to receive the desired hash.
# fore!
def hashify(array)
array.map.with_index(1).to_h
end
> hashify %w(a b c)
=> {"a"=>1, "b"=>2, "c"=>3}
Try this, this will add a method to_hash_one_indexed to the Array class.
class Array
def to_hash_one_indexed
map.with_index(1).to_h
end
end
Then to call it:
%w(a b c).to_hash_one_indexed
#=> {"a"=>1, "b"=>2, "c"=>3}

Does Rubyist call Argument Lists and Array both Array?

I'm simply curious about how the terms are used, so I have a question.
First, let me quote where the terms are used.
quote from Active Record document:
Active Record Query Interface — Ruby on Rails Guides
2 Conditions
Conditions can either be specified as a string, array, or hash.
2.2 Array Conditions
Now what if that number could vary, say as an argument from somewhere? The find
would then take the form:
Client.where("orders_count = ?", params[:orders])
I was confused
Client.where("orders_count = ?", params[:orders])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I was confused the document. Does the document refer to ^^^ part as Array?. I think ruby Array is [ ].
I found other site call this Argument Lists.
Programming Ruby: The Pragmatic Programmer's Guide
In Ruby It does what is called?
def f(a, b)
end
f(1, 2)
^^^^^^
Array or List?
Array and List?
How do you distinguish Array and List in Ruby?
Client.where("orders_count = ?", params[:orders]) ... this is Array?
No, this is not an array. These are two arguments.
How do you distinguish Array and List in Ruby?
Argument list refers to a method's arguments, it's not a class.
You can provide an argument list when defining a method:
def foo(a, b)
p a: a, b: b
end
foo is the method name and a, b is the argument list.
When calling a method, the passed arguments may also be called argument list:
foo 1, 2 # prints {:a=>1, :b=>2}
1, 2 is the argument list.
Converting between array and argument list
You can convert an array into a argument list by using *:
foo *[1, 2] # prints {:a=>1, :b=>2}
You can also convert an argument list to an array by prefixing an argument with * in the method definition:
def bar(*args)
p args
end
This allows the method to take a variable number of arguments:
bar 1, 2 # prints [1, 2]
In Ruby an argument list can be handled as both - an array or single arguments - depending on the assignment:
a, b, c = 1, 2, 3
a #=> 1
b #=> 2
c #=> 3
But:
array = 1, 2, 3
array #=> [1, 2, 3]

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.

What are the differences between "each", "foreach", "collect" and "map"? [duplicate]

This question already has answers here:
what's different between each and collect method in Ruby [duplicate]
(7 answers)
Closed 8 years ago.
It seems like there are a lot of ways to loop over an Enumerable in Ruby. Are there any differences between each, foreach, or in collect, map or other similar methods?
Or is there a reason I shouldn't use certain methods in certain situations?
collect/map are equivalent. They differ from each in that each only executes the block for each element, whereas collect/map return an array with the results of calling the block for each element. Another way to put it might be, each is expected to do something with each element, whereas map is expected to transform each element (map it onto something else).
You could use collect or map anywhere each is used, and your code will still work. But it will probably be slightly less efficient because it collects the results in an array, unless your Ruby implementation realizes it doesn't have to bother creating an array because it's never used.
Another reason to use each instead of map or collect is to help out anyone reading your code. If I see each then I can be like okay, we're about to use each element of the data to do something. If I see map then I'm expecting to see new data being created, based on a remapping of the old data.
With regards to map vs. collect I would say it's a matter of preference, but you should pick one and stick with it for consistency.
Using pry and Rubinus (it's easier to read ruby code) take a look for yourself
$ pry
[1] pry(main)> cd Enumerable
[2] pry(Enumerable):1> show-method collect
From: .../rbx-head/kernel/common/enumerable18.rb # line 4:
Owner: Enumerable
Visibility: public
Number of lines: 9
def collect
if block_given?
ary = []
each { |o| ary << yield(o) }
ary
else
to_a
end
end
[3] pry(Enumerable):1> show-method map
From: .../rbx-head/kernel/common/enumerable18.rb # line 4:
Owner: Enumerable
Visibility: public
Number of lines: 9
def collect
if block_given?
ary = []
each { |o| ary << yield(o) }
ary
else
to_a
end
end
[4] pry(Enumerable):1>
So nope not for these two.
map and collect iterate over a collection. For each object it executes your block then collects the result. The each methods do not collect the results
Array#collect (and Array#map) return a new array based on the code passed in the block. Array#each performs an operation (defined by the block) on each element of the array.
I would use collect like this:
array = [1, 2, 3]
array2 = array.collect {|val| val + 1}
array.inspect # => "[1, 2, 3]"
array2.inspect # => "[2, 3, 4]"
And each like this:
array = [1, 2, 3]
array.each {|val| puts val + 1 }
# >> 2
# >> 3
# >> 4
array.inspect # => "[1, 2, 3]"

Resources