Meaning of * in argument position - ruby

Assume arr is an array [[1,3],[2,5],[3,8]]. I was wondering what * means. What does it mean?
hash = Hash[*arr.flatten] # => {1=>3, 2=>5, 3=>8}
I tried the following
arr.flatten # => [1, 3, 2, 5, 3, 8]
Hash[arr.flatten] # => {}
Hash[*a.flatten] # => {1=>3, 2=>5, 3=>8}

Here is the explanation
When you do array.flatten it will give you one flatten array where all inner array slatted. Now You are putting this flatten array inside the Hash::[] method. But Hash::[] supports the below constructs :
Hash[ key, value, ... ] → new_hash
# or
Hash[ [ [key, value], ... ] ] → new_hash
Now array.flatten gives you [1, 3, 2, 5, 3, 8]. Now you are putting this array inside Hash[] like Hash[[1, 3, 2, 5, 3, 8]]. Now compare with the above 2 structures. Does either of them match ? NO. So you need to splat the inner array again, thus it need (splatted operator)* to splat the inner array.
Now you did Hash[*[1, 3, 2, 5, 3, 8]], which gives Hash[1, 3, 2, 5, 3, 8]. Now again check from the above 2 constructs. Does it match with either of the 2 ? This time YES, first one. So you got the desired hash {1=>3, 2=>5, 3=>8}.
BTW, you don't need to splat, as the second construt exactly matched when you put array inside Hash::[] directly.
array = [[1,3],[2,5],[3,8]]
Hash[array] # => {1=>3, 2=>5, 3=>8}
The above worked, because Hash[array] means Hash[[[1,3],[2,5],[3,8]]], which exactly the second structure as documentation suggested.
Read some examples to see how splatting work in Ruby.
There is another construct :-
Hash[ object ] → new_hash
This I think is also important to tell you why you got {}. Hash[[1, 3, 2, 5, 3, 8]] same as the last type of construct as per the doc. The doc is saying -
The second and third form take a single argument which is either an array of key-value pairs or an object convertible to a hash.
So. [1,3,2,5,3,8] it is an Array object not convertible to Hash. Currently it is giving you an empty hash, if an object as per the third construct. But it will throw error in future version of release. See Below warnings.
[1] pry(main)> Hash[[1,2]]
(pry):1: warning: wrong element type Fixnum at 0 (expected array)
(pry):1: warning: ignoring wrong elements is deprecated, remove them explicitly
(pry):1: warning: this causes ArgumentError in the next release
(pry):1: warning: wrong element type Fixnum at 1 (expected array)
(pry):1: warning: ignoring wrong elements is deprecated, remove them explicitly
(pry):1: warning: this causes ArgumentError in the next release
=> {}
My Ruby version is :
ruby 2.0.0p451 (2014-02-24 revision 45167) [i686-linux]

When you pass an argument to the Hash class you can put it in parentheses...
Hash(arr.flatten)
or without...
Hash arr.flatten
In either case, Hash takes the argument and if it's an array with an even number of elements, it will create a hash where the odd elements are the keys and the even elements are the values.
Hash can also take square brackets to preform a similar operation
Hash[1, 2]
=> {1=>2}
BUT, when you do this...
Hash[arr.flatten]
You are passing the array WITHIN the array so you're getting unexpected results.
When you do this...
Hash[*arr.flatten]
You are saying take the array arr.flatten and pass it, not as an array, but as individual separate arguments to the method.
So where Hash[arr.flatten] is actually Hash[[1, 3, 2, 5, 3, 8]] (an array containing one element which is an array) instead Hash[*arr.flatten] is actually Hash[1, 3, 2, 5, 3, 8](an array containing six elements)
Interestingly enough, the [] method can take an array containing arrays of key, value pairs so..
Hash[arr]
Works fine! You don't have to flatten or splat anything.

Related

We have 'index' and 'rindex', we have 'find' but no 'rfind'

When applied to an Array, index (invoked with a block) returns the index of the first element satisfying the condition, and rindex returns the index of the last one. Similarly, we have find to return the element itself. However, there is no corresponding rfind that would return the last element of an array satisfying a condition.
Does Ruby already have a method that accomplishes this?
Before monkey-patching the Array class, I want to make sure.
You can do it like this:
enum = [1, 2, 3, 4].reverse_each
# => #<Enumerator: [1, 2, 3, 4]:reverse_each>
enum.find(&:odd?)
# => 3
Notice that, unlike using Array#reverse, it does not create a temporal array that is thrown out immediately.
Doing it at once:
[1, 2, 3, 4].reverse_each.find(&:odd?)
# => 3
In other words, we have reverse_each.find instead of rfind; not a big deal.

Understanding flattening an array in Ruby

I'm a confused with what .each_with_object does to an extent.
For example:
("a".."c").each_with_object("") {|i,str| str << i} # => "abc"
Also:
(1..3).each_with_object(0) {|i,sum| sum += i} #=> 0
(since integers are immutable).
After reading the example in the Ruby documentation, I'm
confused as to what the parameter inside object() actually does.
Regarding the flattify code below: I was confused with the usage of *; and why is the else statement just element? What is element intended to do?
def flattify(array)
array.each_with_object([]) do |element, flattened|
flattened.push *(element.is_a?(Array) ? flattify(element) : element)
end
end
confused with what #each_with_object does
You may have a better time understanding #each_with_object if you look at #inject first. #each_with_object is similar to #inject. Examples from http://blog.krishnaswamy.in/blog/2012/02/04/ruby-inject-vs-each-with-object/, included below:
#using inject
[[:tom,25],[:jerry,15]].inject({}) do |result, name_and_age|
name, age = name_and_age
result[name] = age
result
end
=> {:tom=>25, :jerry=>15}
#using each_with_object
[[:tom,25],[:jerry,15]].each_with_object({}) do |name_and_age, result|
name, age = name_and_age
result[name] = age
end
=> {:tom=>25, :jerry=>15}
See this Gist for example tests: https://gist.github.com/cupakromer/3371003
In depth article: http://engineering-blog.alphasights.com/tap-inject-and-each_with_object/
UPDATE
would #inject as opposed to #each_with_object work in this flattening code?
Yes, see below. I've illustratively refactored your flattening code to use #inject. Additionally, I removed the dependency on the "splat" operator (http://ruby-doc.org/core-2.3.1/doc/syntax/calling_methods_rdoc.html#label-Array+to+Arguments+Conversion)
# Flattens nested array; uses `Enumerable#inject`
# #see http://ruby-doc.org/core-2.3.1/Enumerable.html#method-i-inject
# #param arg [Array] contains objects of any type including any amount of nested arrays.
# #raise [StandardError] if arg is not Array class
# #return [Array] flat array comprised of elements from arg.
# #example
# flattify([nil, [1, [:two, [3.0], {4=>5}], "6"]]) #=> [nil, 1, :two, 3.0, {4=>5}, "6"]
def flattify(arg)
raise "arg is not Array" unless arg.is_a?(Array)
# variable ret_var used here to illustrate method's return in verbose fasion
# supplied [] used as initial value for flattened_array
ret_var = arg.inject([]) do |flattened_array, element|
# check if element class is Array
if element.is_a?(Array)
# Array#concat because flattify returns Array
# same as: a = a + b
# same as: a += b
flattened_array.concat(
# recursively call flattify with element as arg
# element is an Array
flattify(element)
)
else
# Array#push because element is not an Array
# same as: a << b
flattened_array.push(element)
end
# used in next iteration as value for first arg above in: "|flattened_array, element|"
# OR returned on last iteration, becoming value of ret_var above
flattened_array
end
# explicit return for illustrative purposes
return ret_var
end
UPDATE 2
may [I] ask why the splat operator is used here? I am still a bit
confused on that. It seems the code is [looping] each time and pushing
it in the flattened array, whats the point of the *?
flattened.push *(element.is_a?(Array) ? flattify(element) : element)
The above block is a "ternary operation" (see: https://en.wikipedia.org/wiki/Ternary_operation), which is explained here: https://stackoverflow.com/a/4252945/1076207 like so:
if_this_is_a_true_value ? then_the_result_is_this : else_it_is_this
Compare the flattify examples with each other:
# each_with_object
flattened.push *(flattify(element))
# inject
flattened_array.concat(flattify(element))
Here the * splat operator (see: https://stackoverflow.com/search?q=%5Bruby%5D+splat) is doing the same thing as Array#concat. However, the splat allows flattened.push to accept either of the two possible types the ternary operation returns: 1) an Array; or 2) whatever element is. For illustration, notice how the splat operator prevents nesting:
# each_with_object with splat
flattened = [1,2,3]
flattened.push *([4,5,6]) # => [1, 2, 3, 4, 5, 6]
flattened.push *(7) # => [1, 2, 3, 4, 5, 6, 7]
# each_with_object without splat
flattened = [1,2,3]
flattened.push ([4,5,6]) # => [1, 2, 3, [4, 5, 6]]
flattened.push (7) # => [1, 2, 3, [4, 5, 6], 7]
Conversely, Array#concat will only accept an array. If the same ternary operation was used and returned an element, it would cause an error:
# inject
flattened_array = [1,2,3]
flattened_array.concat([4,5,6]) # => [1, 2, 3, 4, 5, 6]
flattened_array.concat(7) # => TypeError: no implicit conversion of Fixnum into Array
In summary, both versions of flattify achieve the same result. However, #each_with_object uses #push, a ternary operation and a splat operator; while #inject uses an if/else statement, #concat and #push.
UPDATE 3
When we did each with object([]), the last parameter became an
array.
Yes. It becomes an array and continues to be that same array throughout the iterations until it's passed back.
So with inject the first one becomes an array?
Yes. The first one becomes the passed in array, but only for the first iteration, then it's replaced by the result of the code block for each subsequent iteration.
how does our code know if element is defined as an int and
flattened_Array is an array?
element.is_a?(Array) # => true or false
When element is Array class this method returns true, and if not returns false. false means that it's anything but an array including int.
For more info, see: http://ruby-doc.org/core-2.3.1/Object.html#method-i-is_a-3F
The parameter you pass to object() acts as accumulator for intermediate values between iterations. On entry to each iteration it is passed as flattened argument.
* is a splat operator. It converts an array to a list of arguments being passed to the push method.
Here element will take value of each array element consequently.
All this piece of code does is just recursively flat all nested arrays inside initial array.
But Ruby has built in flatten method which does the same thing.
For example
ar = [1, 2, [3, 4, [5, 6]]]
ar.flatten
#=> [1, 2, 3, 4, 5, 6]
Just to compare with your flattify
flattify ar
#=> [1, 2, 3, 4, 5, 6]
# flattened.push *(element.is_a?(Array) ? flattify(element) : element)
# flattened is the array ...object([])
# element.is_a?(Array) ...is the element in this iteration an array?
# if true flattify(element) again... meaning recursively apply method again
# if false push element onto the object([]) aka flattened
# the () around the ternary allow the recursion to complete
# the * operator can then pass the elements "passing the array condition"
# cont'd... onto flattened.push(4, 5, 6) as list of args instead of an array
# array object with range of string elements
("a".."c").each_with_object([]) do |element, the_object|
p the_object.class # returns Array
p element.class # returns String
end
# hash object with range of fixnum elements
(1..3).each_with_object({}) do |element, the_object|
p the_object.class # returns Hash
p element.class # returns Fixnum
end

getting differences between values in an array

I want to write an Array method in ruby that takes the successive values in the array and returns their differences as a new array (unshifting a '0' in at the beginning).
So feeding the array [4,7,11,16] into the method returns a new array [4,3,4,5].
1) does such a method already exist?
If not, then I think I know how to write it. However,
2) does a method already exist which allows me to test the input array and make sure it only consists of integers and/or floats?
Again, if not, I think I know how to write one.
p [4,7,11,16].unshift(0).each_cons(2).map{|a,b| b-a} # => [4, 3, 4, 5]
Keep it simple:
arr = [4,7,11,16]
last = 0
arr.map { |e| new=e-last; last=e; new }
#=> [4, 3, 4, 5]
Another way:
a = [arr.first]
enum = arr.each
loop do
a << -enum.next + enum.peek
end
a
#=> [4, 3, 4, 5]
Enumerator#peek raises a StopIteration exception when enum is at its last element. Kernel#loop handles the exception by breaking from the loop.
Regarding the first method, I am not aware of any such method in the Ruby Array class.
Regarding the second one, you can do it as explained in this answer:
your_array.all? {|i| i.is_a? Numeric }

Is there a way to split an array of objects in Rails by two different delimiters?

I would like to do something like this:
#residenciais, #comerciais = TipoImovel.all.split { |t| t.residencial? }
The problem is that #comerciais is always empty because it never returns the object, since the condition is false.
Is there a better way of doing this?
You're looking for the standard method Enumerable#partition, rather than the Rails split add-on.
#residenciais, #comerciais = TipoImovel.all.partition { |t| t.residencial? }
Which can also be written like this, since the condition is a single method call:
#residenciais, #comerciais = TipoImovel.all.partition(&:residencial?)
Some more explanation:
The Rails Array#split method is used to separate an array into ordered groups delimited by elements which return true for a given block. It's a generalization of the standard String method. For example:
[1,2,3,4,5,6].split(&:odd?) #=> [[], [2], [4], [6]]
Any odd number is a delimiter, so it returns the portions of the array between the odd numbers, in order.
Whereas this is closer to what you're doing:
odds, evens = [1,2,3,4,5,6].partition(&:odd?) #=> [[1, 3, 5], [2, 4, 6]]
If the partition condition is not simply Boolean, or if you want to key off the values regardless, then you can use Enumerable#group_by, which returns a Hash of Arrays instead of a pair:
[1,2,3,4,5,6].group_by(&:odd?) #=> {true=>[1, 3, 5], false=>[2, 4, 6]}
You can use group_by:
#residenciais, #comerciais = TipoImovel.all.group_by { |t| t.residencial }.values

Ruby remove nil values from array with .reject

I have an array:
scores = [1, 2, 3, "", 4]
And I want to remove all blank values. But when I run this:
puts scores.reject(&:empty?)
I get an error:
undefined method `empty' for 1:Fixnum
How can I remove values that are not integers from my array in a one step process? I am using Ruby 1.9.3.
To reject only nil would be:
array.compact
If you want to remove blank values, you should use blank?: (requires Rails / ActiveSupport)
scores.reject(&:blank?)
#=> [1, 2, 3, 4]
"", " ", false, nil, [], and {} are blank.
It is as simple as:
scores.grep(Integer)
Note that if you plan to map the values, you can do that in a block after:
scores.grep(Integer){|x| x+1 }
Bonus if you want to do the same thing, but your numbers are strings:
scores.grep(/\d+/){|x|x.to_i}
Try this :
scores.select{|e| e.is_a? Integer}
# => [1, 2, 3, 4]
If you really need reject nil only, so it can be done like this:
scores.reject(&:nil?)
scores = [1, 2, 3, "", 4, nil]
scores.reject{|s| s.to_s == ''}
# => [1, 2, 3, 4]
This Worked for me
scores.reject!{|x| x.to_s.empty?}
scores.select{|score| score.is_a? Fixnum}
or, as Fixnum inherits from Integer, you can also go for
scores.select{|score| score.is_a? Integer)
...if that seems more descriptive.
Array and Enumerable tend to offer many ways of doing the same thing.
&:empty? will work for hashes, arrays, and strings, but not numbers. The method you use in reject must be valid for all items in a list. &:blank? will work fine for this reason.

Resources