Why is my block only executed once? - ruby

Why is it that:
array = (1..20).to_a
array.index.each_slice(5) do |slice|
puts slice.inspect
end
returns:
[1, 2, 3, 4, 5]
[6, 7, 8, 9, 10]
[11, 12, 13, 14, 15]
[16, 17, 18, 19, 20]
while:
other_array = []
array = (1..20).to_a
array.index.each_slice(5) do |slice|
puts slice.inspect
other_array.push(1)
end
returns only:
[1, 2, 3, 4, 5]
How does other_array.push(1) breaks the execution of the block? An obvious conclusion would be that I cannot access variables that are not in the scope of the block, but why is that?

I found the solution in the array documentation. I wondered why you used the index function for the array when it seems like you just want to iterate over the array. For this you can use array.each_slice without invoking index.
Index says the following: http://ruby-doc.org/core-2.2.0/Array.html#method-i-index
Returns the index of the first object in ary such that the object is == to obj.
If a block is given instead of an argument, returns the index of the first object for which the block returns true. Returns nil if no match is found
So your code evaluates the block and checks if the result is true.
In the first example you do only a puts which returns nil. Nil is false.
The second example returns an object of an array containing a single 1.
In ruby every condition is true, if it is not false or nil.
You can see this here:
if nil
puts "foo"
end
=> nil
other_array = [1]
if other_array
puts "foo"
end
=> "foo"
So the block in your second example returns something not-false so it will not run again, because it found a "valid" result.
For the return, you maybe should know that ruby returns the last expression in any scope, if no other return is given. So it returns other_array.
If you don't want to reformat your code you could to the following:
other_array = []
array = (1..20).to_a
array.index.each_slice(5) do |slice|
puts slice.inspect
other_array.push(1)
nil
end
This will force the block to return nil and the iteration will work.

It seems tricky at the first glance, but if you check the docs for
Array#index
you'll see that this method return Enum if no block given.
Then you call #each_slice(n) on that Enum object with a block:
in the first case:
do |slice|
puts slice.inspect
end
it returns nil each time to #index method. You'll get the same result if you call array.index.each_slice(5) { nil } or
in the second one:
do |slice|
puts slice.inspect
other_array.push(1)
end
value of this block getting evaluated inside block and returns [1] to the #index method, so it returns the first slice of the array. You'll get the same result if you call array.index.each_slice(5) { any_non_falsy_object }

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]

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

Is code academy wrong here?

CodeAcademy's Error: Oops, try again. It looks like your method doesn't default to alphabetizing an array when it doesn't receive a second parameter.
def alphabetize(arr, rev=false)
if rev == true
arr.sort! { |item1, item2| item2 <=> item1 }
else
arr.sort!
end
puts arr
end
alphabetize(["a", "b", "c", "d", "e"])
EDIT:
Here are the objectives of this section (sorry for not including originally):
1) Add an if/else statement inside your method. If rev is true, it
should sort the array reverse-alphabetically; if rev is false (which
happens if the user passes false or doesn't pass an argument for rev
at all), it should sort the array in alphabetical order.
2) Outside your method, puts the sorted array. (This is so you can see
all the fine work your method did.)
3) Call your method on an array of your choice to see it in action!
2) Outside your method, puts the sorted array.
Not inside, like you did. puts arr returns nil, codeacademy wants array as return value. Outside you should puts return value from this method outside:
def alphabetize(arr, rev=false)
if rev == true
arr.sort! { |item1, item2| item2 <=> item1 }
else
arr.sort!
end
arr
end
puts alphabetize(["a", "b", "c", "d", "e"])
ps. as Wayne Conrad noted in the comment, sort! modify your array.
arr1=[2,3,1,5,22]
# => [2, 3, 1, 5, 22]
alphabetize arr1
# => [1, 2, 3, 5, 22]
arr1
# => [1, 2, 3, 5, 22] # you didn't want this array changed, right?
You should use normal sort that doesn't change array if you don't want array to change.
! also know as bang suggest dangerous method, in this case it modify the array.
Try adding the array as return value to your function.
You do not need to include an if/else statement.
I ran this code and Codecademy accepted it.
You are suppose to return the ordered array and then puts the result in console after method is called.
def alphabetize(arr, rev=false)
arr.sort!
return arr
end
puts numbers
numbers = [2,1,9,3]
alphabetize(numbers)

Why does Array#each return an array with the same elements?

I'm learning the details of how each works in ruby, and I tried out the following line of code:
p [1,2,3,4,5].each { |element| el }
And the result is an array of
[1,2,3,4,5]
But I don't think I fully understand why. Why is the return value of each the same array? Doesn't each just provide a method for iterating? Or is it just common practice for the each method to return the original value?
Array#each returns the [array] object it was invoked upon: the result of the block is discarded. Thus if there are no icky side-effects to the original array then nothing will have changed.
Perhaps you mean to use map?
p [1,2,3,4,5].map { |i| i*i }
If you want, for some reason, to suppress the output (for example debugging in console) here is how you can achive that
[1,2,3,4,5].each do |nr|
puts nr.inspect
end;nil
Array#each
The block form of Array#each returns the original Array object. You generally use #each when you want to do something with each element of an array inside the block. For example:
[1, 2, 3, 4, 5].each { |element| puts element }
This will print out each element, but returns the original array. You can verify this with:
array = [1, 2, 3, 4, 5]
array.each { |element| element }.object_id === array.object_id
=> true
Array#map
If you want to return a new array, you want to use Array#map or one of its synonyms. The block form of #map returns a different Array object. For example:
array.object_id
=> 25659920
array.map { |element| element }.object_id
=> 20546920
array.map { |element| element }.object_id === array.object_id
=> false
You will generally want to use #map when you want to operate on a modified version of the original array, while leaving the original unchanged.
All methods return something. Even if it's just a nil object, it returns something.
It may as well return the original object rather than return nil.

Resources