Array.each do vs Array do - what is the difference? - ruby

I am at a loss to explain the result of evaluating the second code block (not using .each).
Array.new(3).each do |i|; p i; end
# nil
# nil
# nil
#=> [nil, nil, nil]
Array.new(3) do |i|; p i; end
# 0
# 1
# 2
#=> [0, 1, 2] # <-- ???
I understand that:
Array.new(#) invokes an Array with the corresponding # of nil values, and
The call to the .each method with do |i| iterates over (enumerates?) each index value (=nil),
p prints that value, and
the code block ends.
I am confused as to how removing the .each method call results in the Array.new indexes getting assigned values.
Of note, an error is generated with the same do block after an explicitly declared array of nil values (if I am describing it correctly?)
[nil, nil, nil].each do |i|; p i; end
# nil
# nil
# nil
#=> [nil, nil, nil]
[nil, nil, nil] do |i|; p i; end
# SyntaxError: (irb):4: syntax error, unexpected keyword_do_block, expecting end-of-input
# [nil, nil, nil] do |i|; p i; end
# ^
# from C:/Ruby22/bin/irb:11:in `<main>'
I am assuming the do code block start paired with Array.new is making some kind of a difference. Any explanation of what is going on here would be very helpful. Thank you.

Array.new(3).each { ... } creates a new array with the size 3 and calls each on that array.
Whereas Array.new(3) { ... } creates an array of the size 3 and sends the block as a second argument to the new method. When you call new with a block then the return value of the block is used to initialize the array.
See the docs about Array#new

Documentation says:
new(size=0, default=nil)
new(array)
new(size) {|index| block }
In the last form, an array of the given size is created. Each element in this array is created by passing the element’s index to the given block and storing the return value.
You get your elements assigned by pure chance (p returns value it printed, making it return value of the block, making it value of the corresponding array element). Had you used puts (which always returns nil), you'd get nil elements in the array and printed 0, 1, 2 to the standard output, which would add to the confusion, I imagine :)

Related

Give the code below, I was told that if a hash is called and pass through as argument, I can't return Nil. Can anyone explain to me how come?

def key_for_min_value(name_hash)
name_hash.max_by {|k, v| 0-v}[0]
end
This was my code to fulfill the test suite for finding the lowest value of a hash (this was for one of my lessons online).
I know there are much easier ways to do this but I had some restrictions, as you can see below:
**A Few Restrictions:
We want you to build this on your own. Some of the following methods are helpful but off limits for this exercise. (We'll cover a few below in more depth in subsequent lessons).
I could not use keys, values, min, sort, min_by to make it pass.
This code returned the key with the lowest value (a hash of key ==> integers) but here was the requirement I could not figure out.
If the method is called and passed an argument of an empty hash, it should return nil.
Only first month programming, so this may be obvious but is there a way to return nil for an empty hash, and keep my existing code intact?
Thanks for your help
To a beginner programmer I would recommend to print all intermediate results of expressions, or work in IRB.
def key_for_min_value(name_hash)
puts
puts "in key_for_min_value with parameter #{name_hash}"
# puts "about to return nil" if name_hash.empty?
# return nil if name_hash.empty?
name_hash.max_by { | item | puts "item=#{item}" }
max = name_hash.max_by do | k, v |
puts "k=#{k} v=#{v} 0 - v = #{0 - v}"
0 - v
end
puts "max=#{max.inspect}, class of value returned by max_by : #{max.class}"
result = name_hash.max_by {|k, v| 0-v}[0]
puts "result=#{result.inspect}"
result
end
key_for_min_value({a: 1, b: 2, c: 3})
key_for_min_value({})
Execution :
$ ruby -w t.rb
in key_for_min_value with parameter {:a=>1, :b=>2, :c=>3}
item=[:a, 1]
item=[:b, 2]
item=[:c, 3]
k=a v=1 0 - v = -1
k=b v=2 0 - v = -2
k=c v=3 0 - v = -3
max=[:a, 1], class of value returned by max_by : Array
result=:a
in key_for_min_value with parameter {}
max=nil, class of value returned by max_by : NilClass
t.rb:15:in `key_for_min_value': undefined method `[]' for nil:NilClass (NoMethodError)
from t.rb:21:in `<main>'
The documentation of enum.max_by says :
Returns the item corresponding to the largest value returned by the
block.
But if the enum is empty, it returns nil, from which you fetch element [0], which causes the error because there is no such method in the NilClass.
If you add return nil if name_hash.empty? at the beginning of the method, you prevent it to happen (with two uncommented lines) :
$ ruby -w t.rb
in key_for_min_value with parameter {:a=>1, :b=>2, :c=>3}
...
in key_for_min_value with parameter {}
about to return nil
There a lot of different possibilities to do what you want. The most obvious one is to literally translate the sentence: "return nil if the hash is empty" into Ruby:
def key_for_min_value(name_hash)
return nil if name_hash.empty?
name_hash.max_by {|k, v| 0-v}[0]
end
Another possibility would be to use the safe navigation operator:
def key_for_min_value(name_hash)
name_hash.max_by {|k, v| 0-v}&.[](0)
end
Yet another way would be to ensure that the value you are trying to index into is never nil:
def key_for_min_value(name_hash)
(name_hash.max_by {|k, v| 0-v} || [])[0]
end
# or
def key_for_min_value(name_hash)
Array(name_hash.max_by {|k, v| 0-v})[0]
end

Non destructively append object to an array with Ruby

So I need to create an instance method for Array that takes two arguments, the size of an array and an optional object that will be appended to an array.
If the the size argument is less than or equal to the Array.length or the size argument is equal to 0, then just return the array. If the optional argument is left blank, then it inputs nil.
Example output:
array = [1,2,3]
array.class_meth(0) => [1,2,3]
array.class_meth(2) => [1,2,3]
array.class_meth(5) => [1,2,3,nil,nil]
array.class_meth(5, "string") => [1,2,3,"string","string"]
Here is my code that I've been working on:
class Array
def class_meth(a ,b=nil)
self_copy = self
diff = a - self_copy.length
if diff <= 0
self_copy
elsif diff > 0
a.times {self_copy.push b}
end
self_copy
end
def class_meth!(a ,b=nil)
# self_copy = self
diff = a - self.length
if diff <= 0
self
elsif diff > 0
a.times {self.push b}
end
self
end
end
I've been able to create the destructive method, class_meth!, but can't seem to figure out a way to make it non-destructive.
Here's (IMHO) a cleaner solution:
class Array
def class_meth(a, b = nil)
clone.fill(b, size, a - size)
end
def class_meth!(a, b = nil)
fill(b, size, a - size)
end
end
I think it should meet all your needs. To avoid code duplication, you can make either method call the other one (but not both simulaneously, of course):
def class_meth(a, b = nil)
clone.class_meth!(a, b)
end
or:
def class_meth!(a, b = nil)
replace(class_meth(a, b))
end
As you problem has been diagnosed, I will just offer a suggestion for how you might do it. I assume you want to pass two and optionally three, not one and optionally two, parameters to the method.
Code
class Array
def self.class_meth(n, arr, str=nil)
arr + (str ? ([str] : [nil]) * [n-arr.size,0].max)
end
end
Examples
Array.class_meth(0, [1,2,3])
#=> [1,2,3]
Array.class_meth(2, [1,2,3])
#=> [1,2,3]
Array.class_meth(5, [1,2,3])
#=> [1,2,3,nil,nil]
Array.class_meth(5, [1,2,3], "string")
#=> [1,2,3,"string","string"]
Array.class_meth(5, ["dog","cat","pig"])
#=> [1,2,3,"string","string"]
Array.class_meth(5, ["dog","cat","pig"], "string")
#=> [1,2,3,"string","string"]
Array.class_meth(5, ["dog","cat","pig"])
#=> ["dog", "cat", "pig", nil, nil]
Array.class_meth(5, ["dog","cat","pig"], "string")
#=> ["dog", "cat", "pig", "string", "string"]
Before withdrawing his answer, #PatriceGahide suggested using Array#fill. That would be an improvement here; i.e., replace the operative line with:
arr.fill(str ? str : nil, arr.size, [n-arr.size,0].max)
self_copy = self does not make a new object - assignment in Ruby never "copies" or creates a new object implicitly.
Thus the non-destructive case works on the same object (the instance the method was invoked upon) as in the destructive case, with a different variable bound to the same object - that is self.equal? self_copy is true.
The simplest solution is to merely use #clone, keeping in mind it is a shallow clone operation:
def class_meth(a ,b=nil)
self_copy = self.clone # NOW we have a new object ..
# .. so we can modify the duplicate object (self_copy)
# down here without affecting the original (self) object.
end
If #clone cannot be used other solutions involve create a new array or obtain an array #slice (returns a new array) or even append (returning a new array) with #+; however, unlike #clone, these generally lock-into returning an Array and not any sub-type as may be derived.
After the above change is made it should also be apparent that it can written as so:
def class_meth(a ,b=nil)
clone.class_meth!(a, b) # create a NEW object; modify it; return it
# (assumes class_meth! returns the object)
end
A more appropriate implementation of #class_meth!, or #class_meth using one of the other forms to avoid modification of the current instance, is left as an exercise.
FWIW: Those are instance methods, which is appropriate, and not "class meth[ods]"; don't be confused by the ill-naming.

Passing splat on nil as argument

All values for b below let me call a method with the *args syntax.
def some_method(a)
puts a
end
b = 1
some_method(*b) # => 1
b = false
some_method(*b) # => false
b = "whatever"
some_method(*b) # => "whatever"
With nil, I expected to get nil, not argument error:
b = nil
some_method(*b) # => ArgumentError: wrong number of arguments (0 for 1)
What is happening here?
The splat operator * first applies to_a to the object if it is not an array and to_a is defined on it. For numerals, falseclass, and strings, to_a is not defined, and they remain themselves. For nilclass, to_a is defined and returns an empty array. When they are splatted, the numerals, falseclass, and strings remain themselves, but the empty array becomes absence of anything. Also see an answer to this question.

Adding array together with nil as a possibility

I am creating a method that will take an array of numbers and add them together. I don't want to use inject because I haven't learned it yet. I prefer to start with the basics. I want to use either each or while.
I've been re-writing this code and testing it against rspec, and I keep running into a problem because the first test consists of the array being empty with nil. I tried doing an if else statement to set nil to 0 if the array is empty?, but that didn't seem to work. Here is what I've got right now.
def sum(x)
total = 0
sum.each { |x| total += x}
total
end
The rspec is testing an empty array [] as well as others that have multiple integers. Thoughts?
You're not enumerating the array passed in to the method, you're enumerating the variable sum. You want x.each { |x| total += x}, although using x within the {} is a little odd in this case because you've used the name for your method parameter.
You can use compact! to remove the nils from your array.
def sum(x)
total = 0
x.compact! #lose the nils
x.each { |i| total += i}
total
end
Edit:
If the x being passed to your sum() method is nil, you can check for that with nil?.
The do something like
if x.nil?
0 #assuming you want to return 0
else
#rest of your function
You want to return nil if the array passed in is empty?
You are getting confused with your identifiers. You are trying to iterate over sum, which is the name of the method, and you are using x as both the method parameter and the iteration block parameter.
I suggest you use something more descriptive, like arr for the method parameter and v for the block parameter (holding the value of each value from the array).
Finally, you need to initialise the total to nil so that the correct value is returned if the array is empty. Unfortunately you can't do arithmetic on nil, so in the code below I have added a line to set total to zero if it isn't already set.
This will do what you ask.
def sum(arr)
total = nil
arr.each do |v|
total = 0 unless total
total += v
end
total
end
p sum [1,2,3]
p sum []
output
6
nil
You could create a new instance method for the Array class:
class Array
def sum
total = 0.0
self.each {|x| total += x if ['Fixnum', 'Float'].include?(x.class.name)}
total%1==0 ? total.to_i : total
end
end
Then you would use it like so:
puts [].sum # => 0
puts [1, 2, 3].sum # => 6
puts [2, nil, "text", 4.5].sum # => 6.5

How do I write a Ruby method to handle zero, one, or many inputs?

I've got a Ruby method like the following:
# Retrieve all fruits from basket that are of the specified kind.
def fruits_of_kind(kind)
basket.select { |f| f.fruit_type == kind.to_s }
end
Right now, you can call this like:
fruits_of_kind(:apple) # => all apples in basket
fruits_of_kind('banana') # => all bananas in basket
and so on.
How do I change the method so that it will correctly handle iterable inputs as well as no inputs and nil inputs? For example, I'd like to be able to support:
fruits_of_kind(nil) # => nil
fruits_of_kind(:apple, :banana) # => all apples and bananas in basket
fruits_of_kind([:apple, 'banana']) # => likewise
Is this possible to do idiomatically? If so, what's the best way to write methods so that they can accept zero, one, or many inputs?
You need to use the Ruby splat operator, which wraps all remaining arguments into an Array and passes them in:
def foo (a, b, *c)
#do stuff
end
foo(1, 2) # a = 1, b = 2, c = []
foo(1, 2, 3, 4, 5) #a = 1, b = 2, c = [3, 4, 5]
In your case, something like this should work:
def fruits_of_kind(*kinds)
kinds.flatten!
basket.select do |fruit|
kinds.each do |kind|
break true if fruit.fruit_type == kind.to_s
end == true #if no match is found, each returns the whole array, so == true returns false
end
end
EDIT
I changed the code to flatten kinds so that you can send in a list. This code will handle entering no kinds at all, but if you want to expressly input nil, add the line kinds = [] if kinds.nil? at the beginning.
Use the VARARGS feature of Ruby.
# Retrieve all fruits from basket that are of the specified kind.
# notice the * prefix used for method parameter
def fruits_of_kind(*kind)
kind.each do |x|
puts x
end
end
fruits_of_kind(:apple, :orange)
fruits_of_kind()
fruits_of_kind(nil)
-sasuke
def fruits_of_kind(kind)
return nil if kind.nil?
result = []
([] << kind).flatten.each{|k| result << basket.select{|f| f.fruit_type == k.to_s }}
result
end
The 'splat' operator is probably the best way to go, but there are two things to watch out for: passing in nil or lists. To modify Pesto's solution for the input/output you'd like, you should do something like this:
def fruits_of_kind(*kinds)
return nil if kinds.compact.empty?
basket.select do |fruit|
kinds.flatten.each do |kind|
break true if fruit.fruit_type == kind.to_s
end == true #if no match is found, each returns the whole array, so == true returns false
end
end
If you pass in nil, the * converts it to [nil]. If you want to return nil instead of an empty list, you have to compact it (remove nulls) to [], then return nil if it's empty.
If you pass in a list, like [:apple, 'banana'], the * converts it to [[:apple, 'banana']]. It's a subtle difference, but it's a one-element list containing another list, so you need to flatten kinds before doing the "each" loop. Flattening will convert it to [:apple, 'banana'], like you expect, and give you the results you're looking for.
EDIT: Even better, thanks to Greg Campbell:
def fruits_of_kind(basket, kind)
return nil if kind.nil?
kind_list = ([] << kind).flatten.map{|kind| kind.to_s}
basket.select{|fruit| kind_list.include?(fruit) }
end
OR (using splat)
def fruits_of_kind(*kinds)
return nil if kinds.compact.empty?
kind_list = kinds.flatten.map{|kind| kind.to_s}
basket.select{|fruit| kind_list.include?(fruit.fruit_type) }
end
There's a nicely expressive use of splat as an argument to array creation that handles your last example:
def foo(may_or_may_not_be_enumerable_arg)
arrayified = [*may_or_may_not_be_enumerable_arg]
arrayified.each do |item|
puts item
end
end
obj = "one thing"
objs = ["multiple", "things", 1, 2, 3]
foo(obj)
# one thing
# => ["one thing"]
foo(objs)
# multiple
# things
# 1
# 2
# 3
# => ["multiple", "things", 1, 2, 3]

Resources