Ruby case statements with multiple variables - ruby

Ruby has a fairly powerful case..when..else construct for when you need to match criteria against a single variable. What is the "canonical" way to match criteria against multiple variables without simply nesting case statements?
Wrapping multiple variables in an array (like [x, y]) and matching against it isn't equivalent, because Ruby won't apply the magical case === operator to the elements of the array; the operator is only applied to the array itself.
I'm going to go ahead and respond with a community-wiki answer with a (defeated) stab at this question.

You need to use an if..elsif..else, and ensure that the variables you want to match against appear on the right-hand side of the === operator (which is what case essentially does).
For example, if you want to match x and y against some criteria:
if (SomeType === x) && (1..10 === y)
some_value
elsif (:some_symbol === x) && (11..20 === y)
some_other_value
end

This is a simplistic way to add ===:
class Array
def ===(other)
return false if (other.size != self.size)
other_dup = other.dup
all? do |e|
e === other_dup.shift
end
end
end
[
['foo', 3],
%w[ foo bar ],
%w[ one ],
[]
].each do |ary|
ary_type = case ary
when [String, Fixnum] then "[String, Fixnum]"
when [String, String] then "[String, String]"
when [String] then "[String]"
else
"no match"
end
puts ary_type
end
# >> [String, Fixnum]
# >> [String, String]
# >> [String]
# >> no match

If this pattern is common enough in your code to warrant economical expression, you can do it yourself:
class BiPartite
attr_reader :x, :y
def self.[](x, y)
BiPartite.new(x, y)
end
def initialize(x, y)
#x, #y = x, y
end
def ===(other)
x === other.x && y === other.y
end
end
....
case BiPartite[x, y]
when BiPartite[SomeType, 1..10]
puts "some_value"
when BiPartite[:some_symbol, 11..20]
puts "some_other_value"
end

Since Ruby's when keyword supports a comma separated list of values, you could use the splat * operator. This is, of course, assuming that you're referring to a set of discrete values that are in or could become an array.
The splat operator converts a list of arguments into an array, as frequently seen in
def method_missing(method, *args, &block)
Less well known is the fact that it also performs the inverse operation - turning an array into a list of arguments.
So in this case, you could do something like
passing_grades = ['b','c']
case grade
when 'a'
puts 'great job!'
when *passing_grades
puts 'you passed'
else
puts 'you failed'
end

My not battled tested solution is to call .map(&:class) and then is the array in the when statements
def foo(a, b)
case [a,b].map(&:class)
when [Integer, Integer]
puts "Integer"
when [String, String]
puts "String"
else
puts "otherwise"
end
end

Related

How to find first winning combination in Tic-Tac-Toe board?

New at Ruby so excuse the poor code. I would like to iterate through the multidimensional array WIN_COMBINATIONS and check whether at least one array has all of its elements equal to 'X' or all equal to 'O'. If so, return the matched array. This is done through the won? function but it seems to only be returning the entire multidimensional array. Any assistance would be appreciated.
class TicTacToe
WIN_COMBINATIONS = [
[0,1,2], # top_row
[3,4,5], # middle_row
[6,7,8], # bottom_row
[0,3,6], # left_column
[1,4,7], # center_column
[2,5,8], # right_column
[0,4,8], # left_diagonal
[6,4,2] # right_diagonal
]
def initialize
#board = Array.new(9, " ")
end
def display_board
puts " #{#board[0]} | #{#board[1]} | #{#board[2]} "
puts "-----------"
puts " #{#board[3]} | #{#board[4]} | #{#board[5]} "
puts "-----------"
puts " #{#board[6]} | #{#board[7]} | #{#board[8]} "
end
def input_to_index(board_position)
user_input = board_position.to_i
user_input - 1
end
def move(board_index, player_token = 'X')
#board[board_index] = player_token
end
def position_taken?(board_position)
if #board[board_position] == ' '
false
else
true
end
end
def valid_move?(board_position)
if board_position >= 0 and board_position <= 8
if #board[board_position] == ' '
true
end
else
false
end
end
def current_player
turn_count % 2 == 0 ? "X" : "O"
end
def turn_count
#board.count{|token| token == "X" || token == "O"}
end
def turn
puts "Select your move (1-9)\n"
move = gets.chomp
move_index = input_to_index(move)
if valid_move?(move_index)
token = current_player
move(move_index, token)
display_board
else
puts "Select your move (1-9)\n"
move = gets.chomp
end
end
def won?
WIN_COMBINATIONS.each do |combinations|
if combinations.all? {|combination| combination == 'X' or combination == 'O'}
combinations
else
false
end
end
end
def draw?
if full? and !won?
true
elsif won?
false
else
false
end
end
def over?
end
def winner
end
def play
end
end
[...] it seems to only be returning the entire multidimensional array.
There are several issues with your attempted solution:
WIN_COMBINATIONS is an array of indices. These indices are numeric, so they will never be 'X' or 'O'. You have to check whether their corresponding values are 'X' or 'O'.
or is a control-flow operator intended for do_this or fail scenarios. The boolean "or" operator is ||. Using or instead of || might work but may have unexpected results due to its lower precedence. You almost always want ||.
The expression array.all? { |element| element == 'X' || element == 'O' } checks whether all elements are either 'X' or 'O'. It would be true for ['X','O','O'] and false for ['X',' ','O']. That's because you put the conditional inside the block. What you want is to check whether the elements are all 'X', or all 'O':
array.all?('X') || array.all?('O')
Your method's return value is the result of WIN_COMBINATIONS.each { ... } and Array#each always returns the array itself (i.e. WIN_COMBINATIONS) regardless of the blocks' result. To get the first element matching a condition, use find.
Let's apply all this to your code. Given this board:
#board = %w[
X - O
O X -
- - X
]
You could get the first matching combination via:
WIN_COMBINATIONS.find do |indices|
values = #board.values_at(*indices)
values.all?('X') || values.all?('O')
end
#=> [0, 4, 8]
values_at returns the values for the corresponding indices (* transforms the indices array to a list of arguments, so values_at(*[0,1,2]) becomes values_at(0,1,2)). The block's 2nd line then checks whether these values are all 'X', or all 'O'. Once this evaluates to true, the loop breaks and find returns the matching element. (or nil if there was no match)
Here is how I would approach the problem:
class TicTacToe
class OccupiedError < StandardError; end
attr_reader :rows
def initialize
#rows = 3.times.map{ Array(3, nil) }
end
def place!(player, x:, y:)
raise ArgumentError, "player must be :x or :o" unless [:x, :o].include?(player)
raise OccupiedError, "slot is already occupied" unless #rows[y][x].nil?
#rows[y][x] = player
end
# gets an array of columns instead of rows.
def columns
(0..2).map { |n| #rows.map {|row| row[n] } }
end
def diagonals
[
[#rows[0][0], #rows[1][1], #rows[2][2]], # lrt
[#rows[0][2], #rows[1][1], #rows[2][0]] # rtl
]
end
def all_combos
rows + columns + diagonals
end
# checks all the horizontal, vertical and diagonal combinations
def check_for_winner
# checks all combos for three in a row
(all_combos.find{ |a| a.all?(:x) || a.all?(:o) })&.first
end
end
In the initialize method we create a 3*3 array which represents all the positions on the board. This makes it a lot easier since its already grouped in rows. Intead of an empty string use nil to represent an empty square as nil is falsy.
When we want to check for a winner we gather up the rows, columns and the two diagonals into an array of arrays:
[1] pry(main)> game.rows
=> [[:o, :o, :o], [nil, :x, :x], [:x, nil, nil]]
[2] pry(main)> game.all_combos
=> [[:o, :o, :o],
[nil, :x, :x],
[:x, nil, nil],
[:o, nil, :x],
[:o, :x, nil],
[:o, :x, nil],
[:o, :x, nil],
[:o, :x, :x]]
From there we just have to check if any of them are all :x or :o. We don't actually have to list the winning combinations. In this case game.check_for_winner will return :o.

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.

confused as to the behavior of this ruby fragment

Sorry to ask this but I really need to get this done. I'd like to be able to pass in a string and strip out the stop_words. I have the following:
class Query
def self.normalize term
stop_words=["a","big","array"]
term.downcase!
legit=[]
if !stop_words.include?(term)
legit << term
end
return legit
end
def self.check_parts term
term_parts=term.split(' ')
tmp_part=[]
term_parts.each do |part|
t=self.normalize part
tmp_part << t
end
return tmp_part
end
end
I would think that this would return only terms that are not in the stop_words list but I'm getting back either an empty array or an array of the terms passed in. Like this:
ruby-1.9.2-p290 :146 > Query.check_parts "here Is my Char"
=> [[], [], [], ["char"]]
ruby-1.9.2-p290 :147 >
What am I doing wrong?
thx in advance
If you just want to filter out the terms and get an array of downcased words, it is simple.
module Query
StopWords = %w[a big array]
def self.check_parts string; string.downcase.split(/\s+/) - StopWords end
end
Query.check_parts("here Is my Char") # => ["here", "is", "my", "char"]
Why do you want the result as an array I don't know but
term_parts=term.split(' ')
term_parts.reject { |part| stop_words.include?(part) }
You could write what you expect.
By the way, you have an array for array because
def self.check_parts term
term_parts=term.split(' ')
tmp_part=[] # creates an array
term_parts.each do |part|
t=self.normalize part # normalize returns an empty array
# or one of only one element (a term).
tmp_part << t # you add an array into the array
end
return tmp_part
end

Is the ruby operator ||= intelligent?

I have a question regarding the ||= statement in ruby and this is of particular interest to me as I'm using it to write to memcache. What I'm wondering is, does ||= check the receiver first to see if it's set before calling that setter, or is it literally an alias to x = x || y
This wouldn't really matter in the case of a normal variable but using something like:
CACHE[:some_key] ||= "Some String"
could possibly do a memcache write which is more expensive than a simple variable set. I couldn't find anything about ||= in the ruby api oddly enough so I haven't been able to answer this myself.
Of course I know that:
CACHE[:some_key] = "Some String" if CACHE[:some_key].nil?
would achieve this, I'm just looking for the most terse syntax.
This is extremely easy to test:
class MyCache
def initialize
#hash = {}
end
def []=(key, value)
puts "Cache key '#{key}' written"
#hash[key] = value
end
def [](key)
puts "Cache key '#{key}' read"
#hash[key]
end
end
Now simply try the ||= syntax:
cache = MyCache.new
cache["my key"] ||= "my value" # cache value was nil (unset)
# Cache key 'my key' read
# Cache key 'my key' written
cache["my key"] ||= "my value" # cache value is already set
# Cache key 'my key' read
So we can conclude that no assignment takes place if the cache key already exists.
The following extract from the Rubyspec shows that this is by design and should not be dependent on the Ruby implementation:
describe "Conditional operator assignment 'obj.meth op= expr'" do
# ...
it "may not assign at all, depending on the truthiness of lhs" do
m = mock("object")
m.should_receive(:foo).and_return(:truthy)
m.should_not_receive(:foo=)
m.foo ||= 42
m.should_receive(:bar).and_return(false)
m.should_not_receive(:bar=)
m.bar &&= 42
end
# ...
end
In the same file, there is a similar spec for [] and []= that mandates identical behaviour.
Although the Rubyspec is still a work in progress, it has become clear that the major Ruby implementation projects intend to comply with it.
According to §11.3.1.2.2 of the Draft ISO Specification,
CACHE[:some_key] ||= "Some String"
expands to
o = CACHE
*l = :some_key
v = o.[](*l)
w = "Some String"
x = v || w
l << x
o.[]=(*l)
x
Or, in the more general case
primary_expression[indexing_argument_list] ω= expression
(I am using ω here to denote any operator, so it could be ||=, +=, *=, >>=, %=,…)
Expands to:
o = primary_expression
*l = indexing_argument_list
v = o.[](*l)
w = expression
x = v ω w
l << x
o.[]=(*l)
x
So, according to the specification, []= will always get called. But that is actually not the case in current implementations (I tested MRI, YARV, Rubinius, JRuby and IronRuby):
def (h = {}).[]=(k, v) p "Setting #{k} to #{v}"; super end
h[:key] ||= :value # => :value
# "Setting key to value"
h[:key] ||= :value # => :value
So, obviously either the specification is wrong or all five currently released implementations are wrong. And since the purpose of the specification is to describe the behavior of the existing implementations, it's obviously that the specification must be wrong.
In general, as a first approximation
a ||= b
expands to
a || a = b
However, there's all kinds of subleties involved, for example, whether or not a is undefined, whether a is a simple variable or a more complex expression like foo[bar] or foo.bar and so on.
See also some of the other instances of this same question, that have already been asked and answered here on StackOverflow (for example, this one). Also, the question has been discussed so many times on the ruby-talk mailinglist, that there are now discussion threads whose sole purpose it is to summarize the other discussion threads. (Although please note that that list is far from complete.)
Here's another demonstration that's a bit different than the other answers in that it explicitly shows when the Hash is being written to:
class MyHash < Hash
def []=(key, value)
puts "Setting #{key} = #{value}"
super(key, value)
end
end
>> h = MyHash.new
=> {}
>> h[:foo] = :bar
Setting foo = bar
=> :bar
>> h[:bar] ||= :baz
Setting bar = baz
=> :baz
>> h[:bar] ||= :quux
=> :baz
And by way of comparison:
// continued from above
>> h[:bar] = h[:bar] || :quuux
Setting bar = baz
=> :baz
CACHE[:some_key] ||= "Some String"
is equivalent to
CACHE[:some_key] = "Some String" unless CACHE[:some_key]
(which is equivalent to if + nil? unless CACHE[:some_key] is a boolean value).
In other words: yes, ||= will only write if the LHS is nil or false.
[I removed my example that was less accurate than other's. I leave my answer for the benchmarks that might be interesting to some. My point was:]
So basically
CACHE[:some_key] ||= "Some String"
is the same as
CACHE[:some_key] = "Some String" unless CACHE[:some_key]
I'm more for the first syntax, but then it's up to you since readibility is a bit reduced in that case.
I was curious, so here's some benchmarks:
require "benchmark"
CACHE = {}
Benchmark.bm do |x|
x.report {
for i in 0..100000
CACHE[:some_key] ||= "Some String"
end
}
x.report {
for i in 0..100000
CACHE[:some_key] = "Some String" unless CACHE[:some_key]
end
}
end
user system total real
0.030000 0.000000 0.030000 ( 0.025167)
0.020000 0.000000 0.020000 ( 0.026670)

conditional chaining in ruby

Is there a good way to chain methods conditionally in Ruby?
What I want to do functionally is
if a && b && c
my_object.some_method_because_of_a.some_method_because_of_b.some_method_because_of_c
elsif a && b && !c
my_object.some_method_because_of_a.some_method_because_of_b
elsif a && !b && c
my_object.some_method_because_of_a.some_method_because_of_c
etc...
So depending on a number of conditions I want to work out what methods to call in the method chain.
So far my best attempt to do this in a "good way" is to conditionally build the string of methods, and use eval, but surely there is a better, more ruby, way?
You could put your methods into an array and then execute everything in this array
l= []
l << :method_a if a
l << :method_b if b
l << :method_c if c
l.inject(object) { |obj, method| obj.send(method) }
Object#send executes the method with the given name. Enumerable#inject iterates over the array, while giving the block the last returned value and the current array item.
If you want your method to take arguments you could also do it this way
l= []
l << [:method_a, arg_a1, arg_a2] if a
l << [:method_b, arg_b1] if b
l << [:method_c, arg_c1, arg_c2, arg_c3] if c
l.inject(object) { |obj, method_and_args| obj.send(*method_and_args) }
You can use tap:
my_object.tap{|o|o.method_a if a}.tap{|o|o.method_b if b}.tap{|o|o.method_c if c}
Sample class to demonstrate chaining methods that return a copied instance without modifying the caller.
This might be a lib required by your app.
class Foo
attr_accessor :field
def initialize
#field=[]
end
def dup
# Note: objects in #field aren't dup'ed!
super.tap{|e| e.field=e.field.dup }
end
def a
dup.tap{|e| e.field << :a }
end
def b
dup.tap{|e| e.field << :b }
end
def c
dup.tap{|e| e.field << :c }
end
end
monkeypatch: this is what you want to add to your app to enable conditional chaining
class Object
# passes self to block and returns result of block.
# More cumbersome to call than #chain_if, but useful if you want to put
# complex conditions in the block, or call a different method when your cond is false.
def chain_block(&block)
yield self
end
# passes self to block
# bool:
# if false, returns caller without executing block.
# if true, return result of block.
# Useful if your condition is simple, and you want to merely pass along the previous caller in the chain if false.
def chain_if(bool, &block)
bool ? yield(self) : self
end
end
Sample usage
# sample usage: chain_block
>> cond_a, cond_b, cond_c = true, false, true
>> f.chain_block{|e| cond_a ? e.a : e }.chain_block{|e| cond_b ? e.b : e }.chain_block{|e| cond_c ? e.c : e }
=> #<Foo:0x007fe71027ab60 #field=[:a, :c]>
# sample usage: chain_if
>> cond_a, cond_b, cond_c = false, true, false
>> f.chain_if(cond_a, &:a).chain_if(cond_b, &:b).chain_if(cond_c, &:c)
=> #<Foo:0x007fe7106a7e90 #field=[:b]>
# The chain_if call can also allow args
>> obj.chain_if(cond) {|e| e.argified_method(args) }
Although the inject method is perfectly valid, that kind of Enumerable use does confuse people and suffers from the limitation of not being able to pass arbitrary parameters.
A pattern like this may be better for this application:
object = my_object
if (a)
object = object.method_a(:arg_a)
end
if (b)
object = object.method_b
end
if (c)
object = object.method_c('arg_c1', 'arg_c2')
end
I've found this to be useful when using named scopes. For instance:
scope = Person
if (params[:filter_by_age])
scope = scope.in_age_group(params[:filter_by_age])
end
if (params[:country])
scope = scope.in_country(params[:country])
end
# Usually a will_paginate-type call is made here, too
#people = scope.all
Use #yield_self or, since Ruby 2.6, #then!
my_object.
then{ |o| a ? o.some_method_because_of_a : o }.
then{ |o| b ? o.some_method_because_of_b : o }.
then{ |o| c ? o.some_method_because_of_c : o }
Here's a more functional programming way.
Use break in order to get tap() to return the result. (tap is in only in rails as is mentioned in the other answer)
'hey'.tap{ |x| x + " what's" if true }
.tap{ |x| x + "noooooo" if false }
.tap{ |x| x + ' up' if true }
# => "hey"
'hey'.tap{ |x| break x + " what's" if true }
.tap{ |x| break x + "noooooo" if false }
.tap{ |x| break x + ' up' if true }
# => "hey what's up"
Maybe your situation is more complicated than this, but why not:
my_object.method_a if a
my_object.method_b if b
my_object.method_c if c
I use this pattern:
class A
def some_method_because_of_a
...
return self
end
def some_method_because_of_b
...
return self
end
end
a = A.new
a.some_method_because_of_a().some_method_because_of_b()
If you're using Rails, you can use #try. Instead of
foo ? (foo.bar ? foo.bar.baz : nil) : nil
write:
foo.try(:bar).try(:baz)
or, with arguments:
foo.try(:bar, arg: 3).try(:baz)
Not defined in vanilla ruby, but it isn't a lot of code.
What I wouldn't give for CoffeeScript's ?. operator.
I ended up writing the following:
class Object
# A naïve Either implementation.
# Allows for chainable conditions.
# (a -> Bool), Symbol, Symbol, ...Any -> Any
def either(pred, left, right, *args)
cond = case pred
when Symbol
self.send(pred)
when Proc
pred.call
else
pred
end
if cond
self.send right, *args
else
self.send left
end
end
# The up-coming identity method...
def itself
self
end
end
a = []
# => []
a.either(:empty?, :itself, :push, 1)
# => [1]
a.either(:empty?, :itself, :push, 1)
# => [1]
a.either(true, :itself, :push, 2)
# => [1, 2]

Resources