.any? inside a case block not evaluating as expected - ruby

I have the following situation:
type = "stringX"
someArray = ["stringX", "string1", "string2"]
case type
when "stringA"
puts "a"
when "stringB"
puts "b"
when someArray.any? { |x| x.include?(type) }
puts "x"
when "stringC"
puts "c"
end
What I was expecting to happen was that it would go through the case and once it evaluates the .any? method as true (because by itself it does evaluate to true), it would puts "x". However, that's not what's happening here, it just goes through the rest of the case and reaches a raise somewhere below that.
I'm wondering what's going on here?

Use * operator
value = "stringX"
some_array = ["stringX", "string1", "string2"]
case type
when "stringA"
puts "a"
when "stringB"
puts "b"
when *some_array # notice the * before the variable name!
puts "x"
when "stringC"
puts "c"
end
How does this work?
when *some_array checks whether value is an element in some_array

For this particular case one should use the brilliant answer by #akuhn
Whether you need to put any random condition inside the case, you can do it using Proc#===:
type = "stringX"
someArray = ["stringX", "string1", "string2"]
case type
when "stringA" then puts "a"
when "stringB" then puts "b"
# ⇓⇓⇓⇓⇓⇓⇓⇓ HERE
when ->(type) { someArray.any? { |x| x.include?(type) } }
puts "x"
when "stringC" then puts "c"
end

EDIT: I will not delete the answer, because I think there might be something in it you didn't know before, but it does not work for your usecase. For that you should look at mudasobwas answer
It does not quite work this way, because basically the case statement will compare the given object with the object(s) passed to when, about similar to this:
if type == "stringA"
# ...
elsif type == "stringB"
# ...
and so on, unless you use an empty case statement.
case
when type == "stringA"
# ...
This is similar to an if elsif statement though, so you don't really see that very often.
In your case however, we can make use of Ruby's splat operator
case type
when "stringA"
puts "a"
when "stringB"
puts "b"
when *someArray
puts "x"
when "stringC"
puts "c"
Ruby's case statement can take multiple arguments with when which kind of works like an "or"
case "A"
when "B"
puts "B"
when "C", "A"
puts "C or A"
end
# => C or A
and the splat operator will fan out your array:
p ["a", "b"]
# => ["a", "b"]
p *["a", "b"]
# => "a"
# => "b"
p "a", "b"
# => "a"
# => "b"

Related

Is there a way to group a block of statements?

I am working on some refactoring tool. It would be great if I can replace some method call in place by its definition, which would generally be a block of statements. For example, the original code may be:
some_condition ? a : b
def a
...
# statements1
...
end
def d
...
# statements2
...
end
and I want my inlining tool to replace the method call by the blocks of code directly.
To do that, I want to group a list of statements together. How do we usually do that? Is there a way of writing code like this?
some_condition ? {
...
# statements1
...
} : {
...
# statements2
...
}
Yes. You can either use parentheses, or begin...end.
true ? (
puts "a"
puts "b"
puts "c"
) : (
puts "d"
)
true ? begin
puts "a"
puts "b"
puts "c"
end : begin
puts "d"
end
Your code can be reduced to just one line.
%w(a b c).each { |char| puts char }
The else statement is irrelevant here, because true will always return true.

Search for a specific item in a Array

I want to search through the array:
letters = ["a", "b", "c", "d", "e"]
to see if "b" is in it; if it is, then it should say yes. I understand:
letters[0..0] == ["a"]
I tried this:
if letters[0..5] == ["b"]
puts "Yes, the letter 'b' in there."
else
puts "No 'b' in the array."
end
There's an in-build method to do that:
letters.include? "b"
Try: http://www.ruby-doc.org/core-2.1.3/Array.html#method-i-include-3F
if letters.include?("b")
puts "Yes, the letter 'b' in there."
else
puts "No 'b' in the array."
end
if letters.index('b')
puts "yes"
else
puts "no"
end

One line block and if condition

I have:
foos.each do |foo|
unless foo
puts "Foo is missing"
next
end
# rest of business logic goes here
end
I would like to write the last part of it better, something like
{ puts "Foo is missing"; next } unless foo
Unfortunately, this does not work. Does anybody know a way to write two (blocks of) commands inline with if condition?
Just use parentheses:
(puts 'a'; puts 'b') if true
#=> a
#=> b
What you are looking for can be done with parentheses:
(puts "Foo is missing"; next) unless foo
But in this particular case, it is better to write:
next puts "Foo is missing" unless foo
Use begin..end block:
begin puts "Foo is missing"; next end unless foo
foos.each { |foo| foo or ( puts "Foo is missing"; next )
# the rest of the business logic goes here
}
You can use the or syntax
[1,2,3].each do |x|
puts 'two' or next if x == 2
puts x
end
#=> 1
#=> "two"
#=> 3

When case is included in Array

Is this even possible with a switch case? I looked at the post here, but it's not adaptable.
step = 'a'
arr = ['a', 'b', 'c']
case step
when arr.include?
puts "var is included in array"
when "other"
puts "nothing"
end
when clauses can accept multiple values:
case step
when *arr
puts "var is included in array"
when "other"
puts "nothing"
end
This option deserves to be mentioned:
step = 'a'
arr = ['a', 'b', 'c']
case
when arr.include?(step)
puts "arr matches"
when arr2.include?(step)
puts "arr2 matches"
end
You can supply a proc to a case statement:
case step
when ->(x){ arr.include?(x) }
puts "var is included"
when "other"
puts "nothing"
end
This works because ruby uses the === operator to determine equality in a case statement, and Proc#=== executes the proc using the compared value as an argument. So:
arr = [1,2,3]
proc = ->(x){ arr.include?(x) }
proc === 2 #=> true
...although I rather like #Chuck's splat operator for this particular situation.

How do I replace a for-loop in Ruby?

In Ruby it is bad style to use for-loops. This is commonly understood.
A style guide recommended to me:
(https://github.com/bbatsov/ruby-style-guide#source-code-layout)
says:
"Never use for, unless you know exactly why. Most of the time iterators should be used instead. for is implemented in terms of each (so you're adding a level of indirection), but with a twist - for doesn't introduce a new scope (unlike each) and variables defined in its block will be visible outside it."
The example given is:
arr = [1, 2, 3]
#bad
for elem in arr do
puts elem
end
# good
arr.each { |elem| puts elem }
I have researched and I can't find an explanation as to how to simulate a for loop that provides an iterating value I can pass to places or perform arithmetic on.
For example, with what would I replace:
for i in 0...size do
puts array1[i]
puts array2[size-1 - i]
puts i % 2
end
It's easy if it's one array, but I often need the current position for other purposes.
There's either a simple solution I'm missing, or situations where for is required. Additionally, I hear people talk about for as if it is never needed. What then is their solution to this?
Can it be improved? And what is the solution, if there is one? Thanks.
If you want to iterate over a collection and keep track of the index, use each_with_index:
fields = ["name", "age", "height"]
fields.each_with_index do |field,i|
puts "#{i}. #{field}" # 0. name, 1. age, 2. height
end
Your for i in 0...size example becomes:
array1.each_with_index do |item, i|
puts item
puts array2[size-1 - i]
puts i % 2
end
Don't forget you can do cool things like this too
fields = ["name", "age", "height"]
def output name, idx
puts "#{idx}. #{name}"
end
fields.each_with_index &method(:output)
Output
0. name
1. age
2. height
You can use this technique as a class or instance method too
class Printer
def self.output data
puts "raw: #{data}"
end
end
class Kanon < Printer
def initialize prefix
#prefix = prefix
end
def output data
puts "#{#prefix}: #{data}"
end
end
def print printer, data
# separating the block from `each` allows
# you to do interesting things
data.each &printer.method(:output)
end
example using class method
print Printer, ["a", "b", "c"]
# raw: a
# raw: b
# raw: c
example using instance method
kanon = Kanon.new "kanon prints pretty"
print kanon, ["a", "b", "c"]
# kanon prints pretty: a
# kanon prints pretty: b
# kanon prints pretty: c

Resources