Why can I execute a = *ary.flatten but not simply *ary.flatten? - ruby

ary = = [[4, 8], [15, 16], [23, 42]]
In irb, why can I execute
a = *ary.flatten # => [4,8,15,16,23,42]
but not simply
*ary.flatten
which gives me an error:
SyntaxError: (irb):97: syntax error, unexpected '\n', expecting '='
from /usr/bin/irb:12:in `<main>'
I could also execute
a,b,c,d,e,f = *ary.flatten
without a problem, and the returned value after I press enter is
=> [4, 8, 15, 16, 23, 42]
with a,b,c,d,e,f being now of class Fixnum.
So, what does
*ary.flatten
return after all? Seems like it should return the individual elements of ary (what exactly is this object it returns?), that I can assign to something: either a (which somehow automatically becomes an Array) or separate individual variables a,b,c,d,e,f each of which is now a Fixnum.
Also,
a = *ary.flatten.class # => [Array]
b = ary.flatten.class # => Array
What is the difference between [Array] and Array? (perhaps I should make this a separate question but I can only post every 90min and I want to know now!)

The splat transforms an array into a list of objects as if you had written them out explicitly using commas. So, obviously, it can only be used where such a list of objects can be used.
a = 4, 8, 15, 16, 23, 42
is legal, it's a parallel assignment.
4, 8, 15, 16, 23, 42
is not legal, it's a SyntaxError.
So, what does
*ary.flatten
return after all? Seems like it should return the individual elements of ary (what exactly is this object it returns?),
It doesn't return an object. It is a syntactic construct that allows you to take an array and use it as if you had written out the elements by hand one-by-one.
a = *ary.flatten.class # => [Array]
b = ary.flatten.class # => Array
What is the difference between [Array] and Array?
Array is a class, [Array] is an array with a single element, which is the class Array.

Related

array_object.uniq.each chain Ruby

In the code below I get the expected result:
(the point of the learning exercise I'm working on is to write code to modify the original array rather than returning a new array)
def filter_out!(array, &prc)
array.uniq.each { |el| array.delete(el) if prc.call(el) } #my block of code
end
arr_2 = [11, 17, 13, 15 ]
filter_out!(arr_2) { |x| x.odd? }
p arr_2 # []
However, if I remove the .uniq and only utilize array.each the output changes to [17, 15].
I believe the difference is that when only using array.each the index is being cycled through and when deleting 11 (because its odd) at the zero index, it looks at the next index, 1, but 17 is no longer at that index (13 is now) so the element is skipped for testing against the block. Same for 15 which is why it and 17 remain.
Is my assumption correct? If so, how does the underlying functionality of .uniq bypass this? I would assume that chaining .uniq in before .each would simply return the same 'incorrect answer' of [17, 15] since all values are already unique and .each would once again be performed on [11, 17, 13, 15 ] .
Is my assumption correct?
Yes.
How does the underlying functionality of .uniq bypass this?
Because calling this method returns a NEW OBJECT, so you're no longer mutating the object that's being iterated over.
# Your object_ids will be different!
arr_2.object_id
#=> 70302248117520
arr_2.uniq.object_id
#=> 70302210605760

Reassign entire array to the same reference

I've searched extensively but sadly couldn't find a solution to this surely often-asked question.
In Perl I can reassign an entire array within a function and have my changes reflected outside the function:
#!/usr/bin/perl -w
use v5.20;
use Data::Dumper;
sub foo {
my ($ref) = #_;
#$ref = (3, 4, 5);
}
my $ref = [1, 2];
foo($ref);
say Dumper $ref; # prints [3, 4, 5]
Now I'm trying to learn Ruby and have written a function where I'd like to change an array items in-place by filtering out elements matching a condition and returning the removed items:
def filterItems(items)
removed, items = items.partition { ... }
After running the function, items returns to its state before calling the function. How should I approach this please?
I'd like to change an array items in-place by filtering out elements matching a condition and returning the removed items [...] How should I approach this please?
You could replace the array content within your method:
def filter_items(items)
removed, kept = items.partition { |i| i.odd? }
items.replace(kept)
removed
end
ary = [1, 2, 3, 4, 5]
filter_items(ary)
#=> [1, 3, 5]
ary
#=> [2, 4]
I would search for pass by value/reference in ruby. Here is one I found first https://mixandgo.com/learn/is-ruby-pass-by-reference-or-pass-by-value.
You pass reference value of items to the function, not the reference to items. Variable items is defined out of method scope and always refers to same value, unless you reassign it in the variable scope.
Also filterItems is not ruby style, see https://rubystyle.guide/
TL;DR
To access or modify an outer variable within a block, declare the variable outside the block. To access a variable outside of a method, store it in an instance or class variable. There's a lot more to it than that, but this covers the use case in your original post.
Explanation and Examples
In Ruby, you have scope gates and closures. In particular, methods and blocks represent scope gates, but there are certainly ways (both routine and meta) for accessing variables outside of your local scope.
In a class, this is usually handled by instance variables. So, as a simple example of String#parition (because it's easier to explain than Enumerable#partition on an Array):
def filter items, separator
head, sep, tail = items.partition separator
#items = tail
end
filter "foobarbaz", "bar"
#=> "baz"
#items
#=> "baz"
Inside a class or within irb, this will modify whatever's passed and then assign it to the instance variable outside the method.
Partitioning Arrays Instead of Strings
If you really don't want to pass things as arguments, or if #items should be an Array, then you can certainly do that too. However, Arrays behave differently, so I'm not sure what you really expect Array#partition (which is inherited from Enumerable) to yield. This works, using Enumerable#slice_after:
class Filter
def initialize
#items = []
end
def filter_array items, separator
#items = [3,4,5].slice_after { |i| i == separator }.to_a.pop
end
end
f = Filter.new
f.filter_array [3, 4, 5], 4
#=> [5]
Look into the Array class for any method which mutates the object, for example all the method with a bang or methods that insert elements.
Here is an Array#push:
ary = [1,2,3,4,5]
def foo(ary)
ary.push *[6, 7]
end
foo(ary)
ary
#=> [1, 2, 3, 4, 5, 6, 7]
Here is an Array#insert:
ary = [1,2,3,4,5]
def baz(ary)
ary.insert(2, 10, 20)
end
baz(ary)
ary
#=> [1, 2, 10, 20, 3, 4, 5]
Here is an example with a bang Array#reject!:
ary = [1,2,3,4,5]
def zoo(ary)
ary.reject!(&:even?)
end
zoo(ary)
ary
#=> [1, 3, 5]
Another with a bang Array#map!:
ary = [1,2,3,4,5]
def bar(ary)
ary.map! { |e| e**2 }
end
bar(ary)
ary
#=> [1, 4, 9, 16, 25]

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

Meaning of * in argument position

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.

Resources