How can I idiomatically write a doubly nested if/else in Ruby? - ruby

Suppose I have some code like:
def set_reminder(cond_one: false, cond_two: false)
if cond_two
if cond_one
outcome_a
else
outcome_b
end
else
if cond_one
outcome_c
else
outcome_d
end
end
end
How can I more elegantly write a function like this, which has 4 potential results (one for each combination of possible cond_one and cond_two values)?
I'm not satisfied with this version, using an if/else statement with another if/else in both branches. In the actual code, the outcomes are already complex expressions, so writing something like return outcome_a if cond_one && cond_two (for all 4 outcomes) would be unwieldy.

Ruby has a very powerful case expression that can be used for this sort of thing. Consider
def set_reminder(cond_one: false, cond_two: false)
case [cond_one, cond_two]
when [true, true] then outcome_a
when [true, false] then outcome_b
when [false, true] then outcome_c
when [false, false] then outcome_d
end
end
As pointed out in the comments, though, consider having your arguments convey more than just "pair of Booleans". See Boolean blindness for a good discussion on this.

You could go with something like below, flattening your nested ifs into a series of guard statements.
def set_reminder(cond_one: false, cond_two: false)
return outcome_a if cond_two && cond_one
return outcome_b if cond_two
return outcome_c if cond_one
outcome_d
end
This is neater and allows for further refactoring.
I can't recommend enough Sandi Metz et al's book on refactoring, 99 Bottles of OOP. There's a Ruby version. The entire book walks you through this kind of refactoring. From multiple ifs to extracted classes.

You could set it up in your constructor as a hash:
#choice = {[true, true] => :a, [true, false] => :c,
[false, true] => :b, [false, false] => :d}
and then your method would be:
def set_reminder(cond_one: false, cond_two: false)
#choice[[cond_one, cond_two]]
end
An alternative which avoids indexing by compound objects is to create a hash of hashes:
#h_of_h = {true => {true => :a, false => :c},
false => {true => :b, false => :d}}
def set_reminder_h2(cond_one: false, cond_two: false)
#h_of_h[cond_one][cond_two]
end
I did the following benchmark to compare the various proposed approaches:
require 'benchmark/ips'
combos = [[true, true], [true, false], [false, true], [false, false]]
def set_reminder_case(cond_one: false, cond_two: false)
case [cond_one, cond_two]
when [true, true] then :a
when [true, false] then :b
when [false, true] then :c
when [false, false] then :d
end
end
def set_reminder_guard(cond_one: false, cond_two: false)
return :a if cond_two && cond_one
return :b if cond_two
return :c if cond_one
:d
end
#choice = {[true, true] => :a, [true, false] => :c,
[false, true] => :b, [false, false] => :d}
def set_reminder_hash(cond_one: false, cond_two: false)
#choice[[cond_one, cond_two]]
end
#h_of_h = {true => {true => :a, false => :c},
false => {true => :b, false => :d}}
def set_reminder_h2(cond_one: false, cond_two: false)
#h_of_h[cond_one][cond_two]
end
N = 1_000
SEED_VALUE = 123_456_987
# The choicess of true/false combos are being randomized, but since the
# seed is reset they are identical for the two functions being tested.
Benchmark.ips do |b|
srand(SEED_VALUE)
b.report('case') do
N.times do
v1, v2 = combos[rand(4)]
set_reminder_case(cond_one: v1, cond_two: v2)
end
end
srand(SEED_VALUE)
b.report('hash') do
N.times do
v1, v2 = combos[rand(4)]
set_reminder_hash(cond_one: v1, cond_two: v2)
end
end
srand(SEED_VALUE)
b.report('guard') do
N.times do
v1, v2 = combos[rand(4)]
set_reminder_guard(cond_one: v1, cond_two: v2)
end
end
srand(SEED_VALUE)
b.report('hsh_of_hsh') do
N.times do
v1, v2 = combos[rand(4)]
set_reminder_h2(cond_one: v1, cond_two: v2)
end
end
b.compare!
end
The results generated using Ruby 3.2.0 on an M1 MacBook Pro are:
% ruby case_v_hash.rb
Warming up --------------------------------------
case 197.000 i/100ms
hash 239.000 i/100ms
guard 596.000 i/100ms
hsh_of_hsh 562.000 i/100ms
Calculating -------------------------------------
case 1.977k (± 1.2%) i/s - 10.047k in 5.083713s
hash 2.408k (± 0.5%) i/s - 12.189k in 5.062504s
guard 5.952k (± 0.6%) i/s - 29.800k in 5.006765s
hsh_of_hsh 5.637k (± 1.3%) i/s - 28.662k in 5.085419s
Comparison:
guard: 5952.2 i/s
hsh_of_hsh: 5637.2 i/s - 1.06x (± 0.00) slower
hash: 2407.8 i/s - 2.47x (± 0.00) slower
case: 1976.6 i/s - 3.01x (± 0.00) slower
With --yjit:
% ruby --yjit case_v_hash.rb
Warming up --------------------------------------
case 243.000 i/100ms
hash 290.000 i/100ms
guard 1.075k i/100ms
hsh_of_hsh 952.000 i/100ms
Calculating -------------------------------------
case 2.419k (± 0.7%) i/s - 12.150k in 5.022058s
hash 2.921k (± 0.8%) i/s - 14.790k in 5.062949s
guard 10.715k (± 1.4%) i/s - 53.750k in 5.017634s
hsh_of_hsh 9.430k (± 0.7%) i/s - 47.600k in 5.048054s
Comparison:
guard: 10714.6 i/s
hsh_of_hsh: 9429.9 i/s - 1.14x (± 0.00) slower
hash: 2921.4 i/s - 3.67x (± 0.00) slower
case: 2419.4 i/s - 4.43x (± 0.00) slower
It sure looks to me like user3574603's guard statement approach is the winner, with the hash of hashes a reasonably close second. Both dominate the hash lookup and case statement approaches.

I would say that you cannot improve on what you have now. Your method has the following characteristics:
it is easy to comprehend by anyone reading your code (including yourself when you return to it in future);
testing is straightforward;
the variables cond_two and cond_one are each examined just once; and
it easily accommodates future changes in logic.
The number of lines of code in your method could be reduced but I advise against doing that if it would adversely affect readability or ease of testing.
The following are more or less equivalent ways of writing your method. I would say that choosing among these is purely a stylistic choice.
Use ternaries for the inner conditionals
def set_reminder(cond_one: false, cond_two: false)
if cond_two
cond_one ? outcome_a : outcome_b
else
cond_one ? outcome_c : outcome_d
end
end
Use case statements
def set_reminder(cond_one: false, cond_two: false)
case cond_two
case cond_one
when true then outcome_a
else outcome_b
end
else
case cond_one
when true then outcome_c
else outcome_d
end
end
end
Replace inner conditionals with method calls
def set_reminder(cond_one: false, cond_two: false)
if cond_two
set_reminder_cond_two_true(cond_one)
else
set_reminder_cond_two_false(cond_one)
end
end
def set_reminder_cond_two_true(cond_one)
cond_one ? outcome_a : outcome_b
end
def set_reminder_cond_two_false(cond_one)
cond_one ? outcome_c : outcome_d
end

Related

Passing not as a variable

I have two functions, each of which pulls items from an array while asking if a certain value is present or not. How could I create a method that can be flipped according to whether I'm asking if the value is present or not? The (!) below is what I'd like to pass as a variable.
ary.select{ |item| (!)item.my_value.nil? }
Is it possible to pass not to as a variable, somewhat like multiplying something by x where x could be 1 or -1?
I'll assume this is in general terms and you are wondering how to pass something that you can use in the implementation of your function to reverse it. Two strategies:
Ruby metaprogramming. You can use the BasicObject#! and Object#itself:
<
def values_from(array, post_mutator:)
array.select { |item| item.nil?.public_send(post_mutator) }
end
array = [1, nil, :b, nil, nil, 'foo']
values_from(array, post_mutator: :itself) # => [nil, nil, nil]
values_from(array, post_mutator: :!) # => [1, :b, "foo"]
Boolean algebra. Basically, you want to find an operation ., for which:
x . true = x
x . false = -x
All possibilities are:
true, true => true
false, false => true
false, true => false
true, false => false
Which obviously leaves the . to be equality. Therefore:
def values_from(array, switcher)
array.select { |item| item.nil? == switcher }
end
array = [1, nil, :b, nil, nil, 'foo']
values_from(array, true) # => [nil, nil, nil]
values_from(array, false) # => [1, :b, "foo"]
ndn's answer addresses how to do this with Ruby meta-programming, but I was hoping for something a little more elegant, so I ended up addressing this by adding to TrueClass and FalseClass (with a RoR initializer).
class TrueClass
def *(value)
!!value
end
end
class FalseClass
def *(value)
!value
end
end
So I can do the following, which feels a bit more natural.
def select_from_ary(present)
ary.select{ |item| present * item.my_value.nil? }
end
If you have a function called presence? that returns true when you checking the item is present and false when you're checking it's absent then:
ary.select{ |item| !!item.my_value == presence? }
Or as a method where the Boolean is passed in:
def get_value_presence(boolean presence?)
ary.select{ |item| !!item.my_value == presence? }
end
Splitting the truesand falses - that is what partition does:
array = [1, nil, :b, nil, nil, 'foo']
nils, non_nils = array.partition{|item| item.nil?} # or: array.partition(&:nil?)

Distinguish {k: :v} vs. [:k, :v] when iterating through Hash/Array

I need to implement the callback on #each. The receiver of each might be both Array and Hash. So, I have a code like:
receiver.each do |k, v|
case receiver
when Hash then puts "#{k} = #{v}"
when Array then puts "[#{k}, #{v}]"
end
end
The check for receiver is lame, though. Is there a way to receive/interprete a codeblock argument[s] to clearly distinguish the following cases:
{ a: 1, b: 2 }
versus
[[:a, 1], [:b, 2]]
I tried parenthesis, single argument, splatted argument. Everything just gets an Array of size 2. Am I doomed to stick with explicit type check?
The best you can do is to get the type check out of the loop:
def foo(receiver)
format = receiver.is_a?(Array) ? "[%s, %s]" : "%s = %s"
receiver.each do |k_v|
puts format % k_v
end
end
See String#%
If you want to tell, within the each block, whether the key/value pair came from an array or a hash, you have to resort to monkey patching. It can be done, but it's pretty awful:
class Hash
orig_each = instance_method(:each)
define_method(:each) do |&block|
orig_each.bind(self).call do |kv|
def kv.from_hash?
true
end
def kv.from_array?
false
end
block.call(kv)
end
end
end
class Array
orig_each = instance_method(:each)
define_method(:each) do |&block|
orig_each.bind(self).call do |kv|
def kv.from_hash?
false
end
def kv.from_array?
true
end
block.call(kv)
end
end
end
in use:
e = {a: 1, b: 2}
e.each do |kv|
p [kv.from_hash?, kv.from_array?, kv]
end
# => [true, false, [:a, 1]]
# => [true, false, [:b, 2]]
e = [[:a, 1], [:b, 2]]
e.each do |kv|
p [kv.from_hash?, kv.from_array?, kv]
end
# => [false, true, [:a, 1]]
# => [false, true, [:b, 2]]

Remove key/value pair from array of hashes

Suppose I have an array of hashes
a = [
{'id'=>'1','imageUrl'=>'abc','name'=>'x','age'=>'20'},
{'id'=>'2','imageUrl'=>'efg','name'=>'y','age'=>'30'},
{'id'=>'3','imageUrl'=>'hij','name'=>'z','age'=>'40'}
]
What can be the fastest way to remove the key 'name' and 'age' and their corresponding value from all hashes in the array?
Basically how can I remove multiple key/value pairs?
Try the following code:
a = [
{'id'=>'1','imageUrl'=>'abc','name'=>'x'},
{'id'=>'2','imageUrl'=>'efg','name'=>'y'},
{'id'=>'3','imageUrl'=>'hij','name'=>'z'}
]
a.each { |h| h.delete("name") }
p a # => [{"id"=>"1", "imageUrl"=>"abc"}, {"id"=>"2", "imageUrl"=>"efg"}, {"id"=>"3", "imageUrl"=>"hij"}]
Nothing like benchmarking:
Collected from the above answers: and using benchmark-ips
require 'benchmark/ips'
def a
[
{'id'=>'1','imageUrl'=>'abc','name'=>'x'},
{'id'=>'2','imageUrl'=>'efg','name'=>'y'},
{'id'=>'3','imageUrl'=>'hij','name'=>'z'}
]
end
Benchmark.ips do |x|
x.report("map w/ except!") do |times|
a.map {|o| o.except!('name') }
end
x.report("each w/ except!") do |times|
a.each {|o| o.except!('name') }
end
x.report("map w/ except") do |times|
a.map {|o| o.except('name') }
end
x.report("each w/ except") do |times|
a.each {|o| o.except('name') }
end
x.report("map w/ delete") do |times|
a.map { |h| h.delete("name") }
end
x.report("each w/ delete") do |times|
a.each { |h| h.delete("name") }
end
x.compare!
end
I got the following:
Calculating -------------------------------------
map w/ except! 8.438k i/100ms
each w/ except! 8.439k i/100ms
map w/ except 5.242k i/100ms
each w/ except 5.469k i/100ms
map w/ delete 9.840k i/100ms
each w/ delete 9.810k i/100ms
-------------------------------------------------
map w/ except! 1.311B (±25.3%) i/s - 2.994B
each w/ except! 1.360B (±25.2%) i/s - 3.048B
map w/ except 423.818M (±25.8%) i/s - 1.238B
each w/ except 458.859M (±25.7%) i/s - 1.315B
map w/ delete 1.955B (±24.0%) i/s - 3.982B
each w/ delete 2.025B (±23.5%) i/s - 4.033B
Comparison:
each w/ delete: 2024710811.4 i/s
map w/ delete: 1955349074.3 i/s - 1.04x slower
each w/ except!: 1360241861.3 i/s - 1.49x slower
map w/ except!: 1311373772.5 i/s - 1.54x slower
each w/ except: 458859254.7 i/s - 4.41x slower
map w/ except: 423818242.2 i/s - 4.78x slower
Using a.each { |h| h.delete("name") } is the fastest (as pointed in the comment).
For example use except! (or except) method:
a.map {|o| o.except!('name') }
Iterate the Array and delete it from each hash:
a = [
{'id'=>'1','imageUrl'=>'abc','name'=>'x'},
{'id'=>'2','imageUrl'=>'efg','name'=>'y'},
{'id'=>'3','imageUrl'=>'hij','name'=>'z'}
]
=> [{"id"=>"1", "imageUrl"=>"abc", "name"=>"x"}, {"id"=>"2", "imageUrl"=>"efg", "name"=>"y"}, {"id"=>"3", "imageUrl"=>"hij", "name"=>"z"}]
a.each do |h|
h.delete("name")
end
=> [{"id"=>"1", "imageUrl"=>"abc"}, {"id"=>"2", "imageUrl"=>"efg"}, {"id"=>"3", "imageUrl"=>"hij"}]

Changing every value in a hash in Ruby

I want to change every value in a hash so as to add '%' before and after the value so
{ :a=>'a' , :b=>'b' }
must be changed to
{ :a=>'%a%' , :b=>'%b%' }
What's the best way to do this?
In Ruby 2.1 and higher you can do
{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h
If you want the actual strings themselves to mutate in place (possibly and desirably affecting other references to the same string objects):
# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |_,str| str.gsub! /^|$/, '%' }
my_hash.each{ |_,str| str.replace "%#{str}%" }
If you want the hash to change in place, but you don't want to affect the strings (you want it to get new strings):
# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |key,str| my_hash[key] = "%#{str}%" }
my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }
If you want a new hash:
# Ruby 1.8.6+
new_hash = Hash[*my_hash.map{|k,str| [k,"%#{str}%"] }.flatten]
# Ruby 1.8.7+
new_hash = Hash[my_hash.map{|k,str| [k,"%#{str}%"] } ]
Ruby 2.4 introduced the method Hash#transform_values!, which you could use.
{ :a=>'a' , :b=>'b' }.transform_values! { |v| "%#{v}%" }
# => {:a=>"%a%", :b=>"%b%"}
The best way to modify a Hash's values in place is
hash.update(hash){ |_,v| "%#{v}%" }
Less code and clear intent. Also faster because no new objects are allocated beyond the values that must be changed.
A bit more readable one, map it to an array of single-element hashes and reduce that with merge
the_hash.map{ |key,value| {key => "%#{value}%"} }.reduce(:merge)
There is a new 'Rails way' method for this task :)
http://api.rubyonrails.org/classes/Hash.html#method-i-transform_values
One method that doesn't introduce side-effects to the original:
h = {:a => 'a', :b => 'b'}
h2 = Hash[h.map {|k,v| [k, '%' + v + '%']}]
Hash#map may also be an interesting read as it explains why the Hash.map doesn't return a Hash (which is why the resultant Array of [key,value] pairs is converted into a new Hash) and provides alternative approaches to the same general pattern.
Happy coding.
[Disclaimer: I am not sure if Hash.map semantics change in Ruby 2.x]
my_hash.each do |key, value|
my_hash[key] = "%#{value}%"
end
Hash.merge! is the cleanest solution
o = { a: 'a', b: 'b' }
o.merge!(o) { |key, value| "%#{ value }%" }
puts o.inspect
> { :a => "%a%", :b => "%b%" }
After testing it with RSpec like this:
describe Hash do
describe :map_values do
it 'should map the values' do
expect({:a => 2, :b => 3}.map_values { |x| x ** 2 }).to eq({:a => 4, :b => 9})
end
end
end
You could implement Hash#map_values as follows:
class Hash
def map_values
Hash[map { |k, v| [k, yield(v)] }]
end
end
The function then can be used like this:
{:a=>'a' , :b=>'b'}.map_values { |v| "%#{v}%" }
# {:a=>"%a%", :b=>"%b%"}
If you are curious which inplace variant is the fastest here it is:
Calculating -------------------------------------
inplace transform_values! 1.265k (± 0.7%) i/s - 6.426k in 5.080305s
inplace update 1.300k (± 2.7%) i/s - 6.579k in 5.065925s
inplace map reduce 281.367 (± 1.1%) i/s - 1.431k in 5.086477s
inplace merge! 1.305k (± 0.4%) i/s - 6.630k in 5.080751s
inplace each 1.073k (± 0.7%) i/s - 5.457k in 5.084044s
inplace inject 697.178 (± 0.9%) i/s - 3.519k in 5.047857s

Searching for range overlaps in Ruby hashes

Say you have the following Ruby hash,
hash = {:a => [[1, 100..300],
[2, 200..300]],
:b => [[1, 100..300],
[2, 301..400]]
}
and the following functions,
def overlaps?(range, range2)
range.include?(range2.begin) || range2.include?(range.begin)
end
def any_overlaps?(ranges)
# This calls to_proc on the symbol object; it's syntactically equivalent to
# ranges.sort_by {|r| r.begin}
ranges.sort_by(&:begin).each_cons(2).any? do |r1, r2|
overlaps?(r1, r2)
end
end
and it's your desire to, for each key in hash, test whether any range overlaps with any other. In hash above, I would expect hash[:a] to make me mad and hash[:b] to not.
How is this best implemented syntactically?
hash.each{|k, v| puts "#{k} #{any_overlaps?( v.map( &:last )) ? 'overlaps' : 'is ok'}."}
output:
a overlaps.
b is ok.
Here's another way to write any_overlaps:
def any_overlaps?(ranges)
(a = ranges.map { |r| [r.first, r.last] }.sort_by(&:first).flatten) != a.sort
end
any_overlaps? [(51..60),(11..20),(18..30),(0..10),(31..40)] # => true
any_overlaps? [(51..60),(11..20),(21..30),(0..10),(31..40)] # => false

Resources