Rubymonk Ascent lesson: "Ripping the Guts" - ruby

From the Rubymonk ascent tutorial "Ripping the Guts", what's the difference between this code: (mine)
# compute([[4, 8], [15, 16]])
def compute(ary)
return nil unless ary
ary.map do |a, b|
a + b unless b.nil?
a if b.nil?
end
end
and the solution given:
def compute(ary)
return nil unless ary
ary.map { |(a, b)| !b.nil? ? a + b : a }
end
My code doesn't pass the tests, but the solution does. They look like they do the same thing though. What am I doing wrong?

If b.nil? is false, then the following line:
a if b.nil?
Evaluates to nil. Since this is the last statement in the block, this is the value that is returned from your block. It doesn't see the nil and return the previous line's value instead, or completely "skip over" the line since the if wasn't true, it just returns nil.
If you were interested in doing something instead of returning a value, then I think your code would do the same thing as the given solution. The statements a and a + b aren't evaluated unless they're the result you're interested in (which would be important if they were method calls doing something external).

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

ruby enumerables: is there a detect for results of block evaluation?

I'm looking for something similar to #detect in enumerables, but not quite. This is what enumerable does:
[1, 2, 3].detect {|i| i > 1 } #=> 2
it returns the first instance of the array which matches the condition. Now, my purpose is to return the value of the block. Concern is not exactly the conditions, but for instance, the first which is not nil. Something like this:
[var1, var2, var3].wanted_detect {|var| another_function(var) }
in which the function would return the first result of another_function call which isn't nil.
Mapping the values of applying the method on the variables and then using detect is not an option. This one would ideally have to work in lazy enumerators, for which the early mapping of all possible values is a no-go
[var1, var2, var3].lazy.map { |var| another_function(var) }.reject(&:nil?).first
If you don't have access to Enumerable#lazy, it is easy enough to implement what you want:
module Enumerable
def wanted_detect
self.each do |obj|
val = yield obj
return val if val
end
end
end
Demo:
[1, 2, 3, 4].wanted_detect { |x| x*x if x > 2 }
# => 9
EDIT: Sorry, I missed the last paragraph till falsetru pointed it out.
Thanks for the comments, falsetru.

find first element in array for which block returns true and return the block's return value

I need to iterate over an array and apply a supplied block to each element, and return the first true value returned by the block, which implies that I need to stop immediately as soon as I get a true value.
below is my code. I am a ruby newbie, and I am not sure if this code is reinventing the wheel. Maybe there is a library method or methods that can do that already? or may be this code can be simplified?
RS = {
:x => %w(\d+ a\d+ bb\d+ ccc\d+).map{|x| /^#{x}$/},
:y => %w(\w+ 1\w+ 22\w+ 333\w+).map{|x| /^#{x}$/}
}.freeze
def find s, t
r = RS[s]
if r
r.each do |p|
m = p.match t
return m if m
end
nil
end
end
p find :x, 'bb12345'
If you want the result of the block you could do it this way. This will iterate over the whole array, but wont evaluate any matches after the first one.
def find(s,t)
RS[s].inject(nil) {|m, p| m || p.match(t)}
end
You can break out early doing something like this
RS[s].inject(nil) {|m, p| (m && (break m)) || p.match(t)}
This is duplicated with: Ruby - Array.find, but return the value the block
You want a lazy map:
[nil, 1, 2, 3].lazy.map{|i| i && i.to_s}.find{|i| i}
# => "1"
Hopefully still actual: here a solution using detect, i made it possible to verbose the output so you can see which expressions are evaluated before returning a hit.
def find_match symbol, string , verbose = false, match = nil
if verbose
RS.detect{|x,v|x==symbol;v.detect{|re|puts re;match=string.match(/#{re}/)}}
else
RS.detect{|x,v|x==symbol;v.detect{|re|match=string.match(/#{re}/)}}
end
match
end
p find_match :x, 'bb12345'
p find_match :x, 'ee12345' , true #verbose output
p find_match :x, '12345'
p find_match :y, '22abcd'
#<MatchData "bb12345">
(?-mix:^\d+$)
(?-mix:^a\d+$)
(?-mix:^bb\d+$)
(?-mix:^ccc\d+$)
(?-mix:^\w+$)
#<MatchData "ee12345">
#<MatchData "12345">
#<MatchData "22abcd">
If your regex patterns are simple, you can just apply the regex again at the end, maybe.
Something like:
def find(s,t)
r = RS[s] and r.find{|p| p.match(t)}.try(:match, t)
end
Although it makes one redundant call to match, it is easier to understand.
First, find the pattern you want, then use that pattern.

Shouldn't these two Ruby snippets work the same way?

In venturing into Ruby, I started toying with things like the way Ruby returns the last thing you've mentioned even if it was not after a return construct. However, why don't these two snippets work the same way? Shouldn't they?
module Enumerable
def palindrome?
reversed_self = self.reverse
self.each_with_index {|el,index|
unless self[index]==reversed_self[index]
return false ## <-----
end
}
true
end
end
all good so far: puts ['foo','bar','baz'].palindrome? prints 'false'
and
module Enumerable
def palindrome?
reversed_self = self.reverse
self.each_with_index {|el,index|
unless self[index]==reversed_self[index]
false ## <------
end
}
true
end
end
puts ['foo','bar','baz'].palindrome? prints 'true' for some reason
What's the science behind this?
Ruby will return the value of the last executed expression in a method. The false in the second version is not the last expression, there's nothing telling Ruby to stop executing at that point so it will chug along until the method ends.
return is a way to explicitly say to Ruby to stop executing and return a value.
Not quite! A return from inside a block is different from a return inside a lambda, as mentioned in my answer here. When you return from inside a block, you're returning from the entire method rather than just the block.
We can illustrate this as follows:
return :foo # => LocalJumpError: unexpected return
[1, 2, 3].map { return :foo } # => LocalJumpError: unexpected return
[1, 2, 3].map { :foo } # => [:foo, :foo, :foo]
Normally, this doesn't happen with lambdas:
l = lambda { return :foo }
l.call # => :foo
[1, 2, 3].map { l.call } # => [:foo, :foo, :foo]
But when we try to pass the lambda as a block to the method, the behavior changes back:
[1, 2, 3].map &l # => LocalJumpError: unexpected return
If no return statement is present, then return value of a function is the last value evaluated. In the second snipped the last value is always true.
First snippet returns early with false. Second does nothing with that false, it's discarded.

sorting a ruby array of objects by an attribute that could be nil

I have an array of objects that I need to sort by a position attribute that could be an integer or nil, and I need the objects that have the nil position to be at the end of the array. Now, I can force the position to return some value rather than nil so that the array.sort doesn't fail, but if I use 0 as this default, then it puts those objects at the front of the sort. What's the best way to to do this sort? should I just set the nil values to some ridiculously high number that is 'almost' always guaranteed to be at the end? or is there some other way i could cause the array.sort method to put the nil attribute objects at the end of the array? the code looks like this:
class Parent
def sorted_children
children.sort{|a, b| a.position <=> b.position}
end
end
class Child
def position
category ? category.position : #what should the else be??
end
end
now, if i make the 'else' something like 1000000000, then it's most likely gonna put them at the end of the array, but I don't like this solution as it's arbitrary
I would just tweak your sort to put nil items last. Try something like this.
foo = [nil, -3, 100, 4, 6, nil, 4, nil, 23]
foo.sort { |a,b| a && b ? a <=> b : a ? -1 : 1 }
=> [-3, 4, 4, 6, 23, 100, nil, nil, nil]
That says: if a and b are both non-nil sort them normally but if one of them is nil, return a status that sorts that one larger.
How about in Child defining <=> to be based on category.position if category exists, and sorting items without a category as always greater than those with a category?
class Child
# Not strictly necessary, but will define other comparisons based on <=>
include Comparable
def <=> other
return 0 if !category && !other.category
return 1 if !category
return -1 if !other.category
category.position <=> other.category.position
end
end
Then in Parent you can just call children.sort.
I handle these kinds of things like this:
children.sort_by {|child| [child.position ? 0 : 1,child.position || 0]}
To be fair, I'm not very familiar with Ruby, so take this as more of an algorithm idea rather than a code one... and rewrite the ?: operator as whatever Ruby has that's cleaner.
Can't you just check for nil in the comparison:
class Parent
def sorted_children
children.sort{|a,b|( a and b ) ? a <=> b : ( a ? -1 : 1 ) }
end
end
Edited to use Glenra's code, which implements the same thing as mine but in a smaller (and probably easier to read) amount of code.
The most simple solution for me is
def sorted_children(children)
children.sort_by { |child| child.position || -1}
end
I haven't done Ruby in a while, but you could split the null-checking from the sorting (and just allow Child#position to return null):
def sorted_children
children.reject{|c| c.position.nil?}.sort_by(&:position) +
children.select{|c| c.position.nil?}
end
Admittedly it's not the most efficient solution, but it doesn't have any magic numbers.
You can do this without overriding the spaceship operator by defining a new comparison method.
class Child
include Comparable
def compare_by_category(other)
return 0 if !category && !other.category
return 1 if !category
return -1 if !other.category
category.position <=> other.category.position
end
end
The sort method can take a block, so you can then sort using this new method:
children.sort {|a,b| a.compare_by_category(b) }

Resources