What does Ruby block return to? - ruby

Good day. I've tried writing this code in ruby
x = [1, 2, 3, 4, 5]
x.each do |a|
a + 1
end
When I type this in irb, I don't understand why does it return
=> [1, 2, 3, 4, 5]
I thought it would return
=> [2, 3, 4, 5, 6] # because of a + 1

each yields the array's elements to the given block (one after another) without modifying the array. At the end, it returns the array, as mentioned in the docs:
[...] passes each successive array element to the block; returns self
You are probably looking for map, which works similar to each but instead of returning self, it ...
[...] returns a new Array whose elements are the return values from the block
Example:
x = [1, 2, 3, 4, 5]
x.map { |a| a + 1 }
#=> [2, 3, 4, 5, 6]
Note that it returns a new array without actually modifying x. There's also map! (with !) which does modify the receiver.

Related

Ruby inject daisy chaining?

I'm not sure what sugar syntax this is, but let me just show you the problem.
def factors num
(1..num).select {|n| num % n == 0}
end
def mutual_factors(*nums)
nums
.map { |n| factors(n) }
.inject(:&)
end
p mutual_factors(50, 30) # [1, 2, 5, 10]
p mutual_factors(50, 30, 45, 105) # [1, 5]
p mutual_factors(8, 4) # [1, 2, 4]
p mutual_factors(8, 4, 10) # [1, 2]
p mutual_factors(12, 24) # [1, 2, 3, 4, 6, 12]
p mutual_factors(12, 24, 64) # [1, 2, 4]
p mutual_factors(22, 44) # [1, 2, 11, 22]
p mutual_factors(22, 44, 11) # [1, 11]
p mutual_factors(7) # [1, 7]
p mutual_factors(7, 9) # [1]
with this being the portion in questioning:
nums
.map { |n| factors(n) }
.inject(:&)
okay, so this is my mental trace: first, map uses the helper method to get the factors, and outputs the factors into another array, and then that array gets injected?
I think the
.inject(:&)
is what is throwing me off. I ran a quick google on it, but I haven't used inject for many things other than summing arrays, and basic stuff like that. I've also done things like
test = "hello".split("").map(&:upcase)
p test.join
but .inject(:&)? I know & is a proc, but I've only used them in arguments. I don't know the fundamentals under the hood. Please, take my current level into mind when trying to explain this to me =), I know how the basic inject works, and the splat operator also.
Partial quote form the documentation of Enumerable#inject.
inject(symbol) → object
[...]
Returns an object formed from operands via either:
A method named by symbol.
[...]
With method-name argument symbol, combines operands using the method:
# Sum, without initial_operand.
(1..4).inject(:+) # => 10
That means in the context of inject the (:&) is not a proc but simply the symbol :& that tells inject what operation to perform to combine the elements in the array.
Let's look at this example:
mutual_factors(8, 4, 10)
#=> [1, 2]
and let's look what happens at each step:
nums
.map { |n| factors(n) } #=> [[1, 2, 4, 8], [1, 2, 4], [1, 2, 5, 10]]
.inject(:&) #=> [1, 2, 4, 8] & [1, 2, 4] & [1, 2, 5, 10]
And Array#& is a method that returns a new array containing each element found in both arrays (duplicates are omitted).

Why a new call of a method with exclamation mark affects all previous calls of that method?

I'm sorry if this is a duplicate - I couldn't find anything similar in the existing posts.
I understand the difference between methods like shuffle and shuffle!. However, I am confused why calling the method more than once would result in changing the variables of all objects that previously referred to it? I'd expect once we apply a method, that the variable gets a value and we're done with it. Not that it continues to refer to the method call and the argument passed and that it would get re-evaluated later on.
I thought it's best to demonstrate with an example:
irb(main):001:1* def shuffle(arr)
irb(main):002:1* arr.shuffle!
irb(main):003:0> end
=> :shuffle
irb(main):004:0> arr = [1,2,3,4]
=> [1, 2, 3, 4]
irb(main):005:0> one = shuffle(arr)
=> [4, 2, 3, 1]
irb(main):006:0> two = shuffle(arr)
=> [1, 2, 4, 3]
irb(main):007:0> one
=> [1, 2, 4, 3]
So, here I'd expect one to stay [4, 2, 3, 1]. However, with each new call, all previous ones would get equated to the latest result of the method call. I realise it should have something to do with calling it with the same argument arr, but still doesn't quite make sense.
Array#shuffle! shuffles the array in-place and returns its receiver:
ary = [1, 2, 3, 4]
ary.equal?(ary.shuffle!) #=> true
Assigning the result from shuffle! to another variable doesn't change this. It merely results in two variables referring to the same array:
a = [1, 2, 3, 4]
b = a.shuffle!
a #=> [2, 4, 1, 3]
b #=> [2, 4, 1, 3]
a.equal?(b) #=> true
You probably want a new array. That's what Array#shuffle (without !) is for:
a = [1, 2, 3, 4]
b = a.shuffle
a #=> [1, 2, 3, 4]
b #=> [2, 4, 1, 3]
Even if shuffle returns the element in the original order, you'll get another array instance:
a = [1, 2, 3, 4]
b = a.shuffle until b == a
a #=> [1, 2, 3, 4]
b #=> [1, 2, 3, 4]
a.equal?(b) #=> false

How to use a recursive array

I have array, named a and define it with [1, 2, 3].
Next, I pushed it to itself:
a = [1, 2, 3]
a << a
and the result I get is:
#=> [1, 2, 3, [...]]
When I want to get the last element of array using a.last I get:
a.last
#=> [1, 2, 3, [...]]
#even
a.last.last.last
#=> [1, 2, 3, [...]]
What is going on, when we would push array to itself?
Yes, I understand that this should create a recursive array, but what can we do with it?
In Ruby variables, array elements etc. are object references. So when you do a = [1, 2, 3], there will be an array somewhere in memory and the a variable is a reference to that memory. Now when you do a << a, a[4] will also be a reference to that object. So in effect a now contains a reference to itself.
a = [1, 2, 3]
a << a.dup
a.last
=> [1, 2, 3]
a.last.last
=> 3
Maybe this is what you wanted. This just insert an array [1, 2, 3] as the last item of the a array. In the way you did you put a reference at the end of the a array and this becomes recursive.

How to drop the end of an array in Ruby

Array#drop removes the first n elements of an array. What is a good way to remove the last m elements of an array? Alternately, what is a good way to keep the middle elements of an array (greater than n, less than m)?
This is exactly what Array#pop is for:
x = [1,2,3]
x.pop(2) # => [2,3]
x # => [1]
You can also use Array#slice method, e.g.:
[1,2,3,4,5,6].slice(1..4) # => [2, 3, 4, 5]
or
a = [1,2,3,4,5,6]
a.take 3 # => [1, 2, 3]
a.first 3 # => [1, 2, 3]
a.first a.size - 1 # to get rid of the last one
The most direct opposite of drop (drop the first n elements) would be take, which keeps the first n elements (there's also take_while which is analogous to drop_while).
Slice allows you to return a subset of the array either by specifying a range or an offset and a length. Array#[] behaves the same when passed a range as an argument or when passed 2 numbers
this will get rid of last n elements:
a = [1,2,3,4,5,6]
n = 4
p a[0, (a.size-n)]
#=> [1, 2]
n = 2
p a[0, (a.size-n)]
#=> [1, 2, 3, 4]
regard "middle" elements:
min, max = 2, 5
p a.select {|v| (min..max).include? v }
#=> [2, 3, 4, 5]
I wanted the return value to be the array without the dropped elements. I found a couple solutions here to be okay:
count = 2
[1, 2, 3, 4, 5].slice 0..-(count + 1) # => [1, 2, 3]
[1, 2, 3, 4, 5].tap { |a| a.pop count } # => [1, 2, 3]
But I found another solution to be more readable if the order of the array isn't important (in my case I was deleting files):
count = 2
[1, 2, 3, 4, 5].reverse.drop count # => [3, 2, 1]
You could tack another .reverse on there if you need to preserve order but I think I prefer the tap solution at that point.
You can achieve the same as Array#pop in a non destructive way, and without needing to know the lenght of the array:
a = [1, 2, 3, 4, 5, 6]
b = a[0..-2]
# => [1, 2, 3, 4, 5]
n = 3 # if we want drop the last n elements
c = a[0..-(n+1)]
# => [1, 2, 3]
Array#delete_at() is the simplest way to delete the last element of an array, as so
arr = [1,2,3,4,5,6]
arr.delete_at(-1)
p arr # => [1,2,3,4,5]
For deleting a segment, or segments, of an array use methods in the other answers.
You can also add some methods
class Array
# Using slice
def cut(n)
slice(0..-n-1)
end
# Using pop
def cut2(n)
dup.tap{|x| x.pop(n)}
end
# Using take
def cut3(n)
length - n >=0 ? take(length - n) : []
end
end
[1,2,3,4,5].cut(2)
=> [1, 2, 3]

ruby method chaining

var.split('/').delete_at(0)
upon inspection returns
""
no matter what the string, however....
var.split('/')
var.delete_at(0)
gives me no trouble. this is probably a stupid question, but are there some sort of restrictions/limitations regarding method chaining like this?
thanks,
brandon
The delete_at method deletes the element but returns the deleted element not the new array.
If you want to always return the object, you can use the tap method (available since Ruby 1.8.7).
a = [1, 2, 3, 4, 5, 6, 7]
a.delete_at(0) # => 1
a # => [2, 3, 4, 5, 6, 7]
a = [1, 2, 3, 4, 5, 6, 7]
a.tap { |a| a.delete_at(0) } # => returns [2, 3, 4, 5, 6, 7]
a # => [2, 3, 4, 5, 6, 7]
literally the first thing I tried to do was:
var.split('/').delete_at(0)
which upon inspection returned
""
no matter what the string
Are you sure? Try the string 'a/b':
irb(main):001:0> var = 'a/b'
=> "a/b"
irb(main):003:0> var.split('/').delete_at(0)
=> "a"
Note that the return value is the element deleted, not the array. The array which you created by performing the split was not stored anywhere and now you have no reference to it. You probably want to do this instead:
a = var.split('/')
a.delete_at(0)
If you always need to delete the first element, you can use other methods that return the object itself, such as slice!, for example:
s = 'foo/bar/baz'
#=> "foo/bar/baz"
s.split('/').slice!(1..-1)
#=> ["bar", "baz"]

Resources