Multiple results in ? style if clause - ruby

I have the following code: i ? "x" : "y" But instead of only returning either "x" or "y" I also want to set i either true or false. i ? ("x"; i = false) : ("y"; i = true) however does not work.

(i ? "x" : "y").tap{i = !i}
or
(i = !i) ? "y" : "x"
But if this turns out to be an XY-situation (I don't write "XY-question" here because the OP has not asked any question), then this might be more elegant:
letter = ["x", "y"].cycle
letter.next #=> "x"
letter.next #=> "y"
letter.next #=> "x"
letter.next #=> "y"
...

If you want to return 'x' or y', then 'x' or 'y' needs to be the last statement:
i ? (i = false; 'x') : (i = true; 'y')
If you think of it like this, maybe it would make more sense:
if i
i = false
'x'
else
i = true
'y'
end
Keep in mind that setters in Ruby (and many other languages) return the value being set. For example, i = false returns false.

You should use ternary operator only in the simplest cases because readability and side-effect issues.
Ruby Code Style
But if you really want it you could do something like this:
!(i = !i) ? 'x' : 'y'

Related

how to push a value to an array with a ternary operator?

the array is returning a boolean value instead of the value assigned by the ternary operator
and the code...
arr = []
arr << true == false ? 'a' : 'b'
# Expecting, the output of arr to be ['b']. But instead, I was getting [true]
Why is this behavior?
and to get the right value I have to do this.
arr << if true == false
'a'
else
'b'
end
# and also, = also works fine
arr = true == false ? 'a' : 'b' # arr has 'b'
and why the behavior is different when using the ternary operator?
It is due to Ruby's operator precedence. Operator << has greater precedence than the ternary operator. Your example can be solved by modifying the code as below:
arr = []
arr << (true == false ? 'a' : 'b')
thank you all for helping, I like the solution by iGian i.e. without parenthesis.
arr <<= true == false ? 'a' : 'b'

I am new to ruby language , and trying to create a text adventure game , i want to include two strings

I want the user to type anything, but it should include two strings like "look" and "right", if both are included in the sentence then the program will go to the next line.
I am new to ruby, just getting myself familiar with the language.
I have tried && and || but it doesnt work
puts " in which direction do you want to look"
input = gets.chomp.to_s
if input.include? == "look" && "right"
puts " There is a dead end on the right"
elsif input.include? "look" && "left"
puts "there is a narrow way , going towards the woods"
else
puts "i dont understand what you are trying to say"
end
Instead of input.include? "look" && "right", you need to compare the statements one by one.
if input.include?("look") && input.include?("right")
puts " There is a dead end on the right"
elsif input.include?("look") && input.include?("left")
puts "there is a narrow way , going towards the woods"
else
puts "i dont understand what you are trying to say"
end
Logically, input.include? "look" would either return true or false, and input.include? "right" would also return true or false. Both statements need to be true in order for it to work!
I'd suggest to define a method which handles the input and returns the direction to be use in the if/then/else:
def get_direction(input)
keywords = ['look', 'left', 'right']
directions = {101 => :right, 110 => :left}
directions.default = :none
input = input.scan(/\w+/).map(&:downcase).uniq
key = keywords.map { |k| input.include? k }.map{ |e| e ? 1 : 0 }.join().to_i
directions[key]
end
These are few examples of method calls:
get_direction("Yes. Look at your right now! Right now! Look!") #=> :right
get_direction("Yes. Look at your left now!") #=> :left
get_direction("Look right. Left them there!") #=> :none
get_direction("Stay right here!") #=> :none
Inside the method you can find a String#scan for splitting the input into words. Also, there is a .map(&:downcase) to assure case insensitivity, using Array#map. It uses a Hash to select the output.
Add p to print out this line to understand how it is working:
p key = keywords.map { |k| input.include? k }.map{ |e| e ? 1 : 0 }.join().to_i
Now you can use the method this way:
direction = get_direction(gets.chomp.to_s)
if direction == :right
p 'right'
elsif direction == :left
p 'left'
else
p 'none'
end

Take in string, return true if after "a", a "z" appears within three places

# Write a method that takes a string in and returns true if the letter
# "z" appears within three letters **after** an "a". You may assume
# that the string contains only lowercase letters.
I came up with this, which seems logical, but for some reason if "z" comes directly after "a", it returns false. Can someone explain why?
def nearby_az(string)
i = 0
if string[i] == "a" && string[i+1] == "z"
return true
elsif string[i] == "a" && string[i+2] == "z"
return true
elsif string[i] == "a" && string[i+3] == "z"
return true
else return false
end
i += 1
end
#shivram has given the reason for your problem. Here are a couple of ways to do it.
Problem is tailor-made for a regular expression
r = /
a # match "a"
.{,2} # match any n characters where 0 <= n <= 2
z # match "z"
/x # extended/free-spacing regex definition mode
!!("wwwaeezdddddd" =~ r) #=> true
!!("wwwaeeezdddddd" =~ r) #=> false
You would normally see this regular expression written
/a.{0,2}z/
but extended mode allows you to document each of its elements. That's not important here but is useful when the regex is complex.
The Ruby trick !!
!! is used to convert truthy values (all but false and nil) to true and falsy values (false or nil) to false:
!!("wwwaeezdddddd" =~ r)
#=> !(!("wwwaeezdddddd" =~ r))
#=> !(!3)
#=> !false
#=> true
!!("wwwaeezdddddd" =~ r)
#=> !(!("wwwaeeezdddddd" =~ r))
#=> !(!nil)
#=> !true
#=> false
but !! is not really necessary, since
puts "hi" if 3 #=> "hi"
puts "hi" if nil #=>
Some don't like !!, arguing that
<condition> ? true : false
is more clear.
A non-regex solution
def z_within_4_of_a?(str)
(str.size-3).times.find { |i| str[i]=="a" && str[i+1,3].include?("z") } ? true : false
end
z_within_4_of_a?("wwwaeezdddddd")
#=> true
z_within_4_of_a?("wwwaeeezdddddd")
#=> false
This uses the methods Fixnum#times, Enumerable#find and String#include? (and String#size of course).
Your solution is incorrect. You are considering only the case where String starts with a (with i = 0 at the start of your method). I can see you are incrementing i at the end, but its of no use as its not in a loop.
I can think of a solution as to find the index of a in string, then take substring from that index + 3 and look for z. Something like:
s = "wwwaeezdddddd"
s[s.index("a")..s.index("a")+3]
#=> "aeez"
s[s.index("a")..s.index("a")+3] =~ /z/ # checking if z is present
#=> 3
If a can occur more than once in input String, you need to find all indices of a and run the above logic in a loop. Something like:
s = "wwwaesezddddddaz"
indexes = (0 ... s.length).find_all { |i| s[i,1] == 'a' }
#=> [3, 14]
indexes.each { |i| break if #is_present = s[i..i+3] =~ /z/ }
#is_present
#=> 1
Let’s implement the FSM ourselves :)
input = "wwwaeezdddddd"
!(0...input.length).each do |idx|
next unless input[idx] == 'a' # skip unrelated symbols
current = (idx..[idx + 3, input.length - 1].min).any? do |i|
input[i] == 'z' # return true if there is 'z'
end
# since `each` returns truthy (range itself),
# in case of success we return falsey and negate
break false if current
end
#⇒ true
Please note, that the above implementation is O(length(input)) and does not use any built-in ruby helpers, it is just iterating a string char by char.
While the regexp solution is the most elegant, here is one for completion, which is more in spirit to your original attempt:
def nearby_az(string)
!!(apos = string.index('a') and string[apos,3].index('z'))
end

Best way to switch values

I would like to find the most effective and short way to do the following:
if current_value == "hide"
current_value = "show"
elsif current_value == "show"
current_value = "hide"
end
So, i would like to set the opposite to what the current situation is.
Thanks!
What about ternary?
current_value == "hide" ? current_value = "show" : current_value = "hide"
Maybe this would be better :
current_value = (current_value == "hide") ? "show" : "hide"
Four ways:
#1
c = 'show'
c = c['hide'] ? 'show' : 'hide'
#2
c = case c
when 'hide' then 'show'
else 'show'
end
#3
c, = ['show','hide']-[c]
#4
show = ['show', 'hide'].cycle
p show.next #=> 'show'
p show.next #=> 'hide'
p show.next #=> 'show'
One way that is not too pretty but will work:
current_value = (["hide", "show"] - [current_value])[0]
If current_value can only be "show" or "hide", why don't you use a boolean variable, say is_visible?
Then just toggle it like this:
is_visible = !is_visible
How about this?
VALUES = {
'show' => 'hide',
'hide' => 'show',
}
current_value = VALUES[current_value]
Another unorthodox approach :)
VALUES = %w[hide show hide]
current_value = 'show'
current_value = VALUES[VALUES.index(current_value) + 1] # => "hide"
I think the right way to go is to keep a boolean value like Yanhao suggests, and if you are to call that in a CSS class, use a ternary there.
current_value = true # initial setting
...
current_value ^= true # alternating between `true` and `false`
...
current_value ? "hide" : "show" # using it to call a string value
A way that can be reused more easily (edit toggle values more easily)
c = 'show'
TOGGLE_VALUES = ['show', 'hide']
c = TOGGLE_VALUES[ TOGGLE_VALUES.index(c) - 1]
When one is late to the party (this is answer #8), one must dig deep. The best solution does not often result, but the grey cells do get some exercise. Here are a few more ways to flip 'show' and 'hide' (in no particular order):
SH = 'showhide'
a = ['show', 'hide']
h = {'show'=>'hide'}
c = 'show'
1
c = SH.sub(c,'') #=> 'hide'
2
c = SH[/.+(?=#{c})|(?<=#{c}).+/] #=> 'hide'
3
c = (SH.end_with? c) ? "show" : "hide" #=> 'hide'
4
d = "hide"
c, = (c,d = d,c) #=> 'hide'
5
c = SH.chomp(c).reverse.chomp(c.reverse).reverse #=> 'hide'
6
c, = a.reverse! #=> 'hide'
7
c = (h=h.invert)[c] #=> 'hide'
In #2, (?=#{c}) and (?<=#{c}) are positive lookahead and positive lookbehind, respectively.

Ruby: Best way to convert a String to Integer or leave it as String if necessary?

Developing a little survey webapp, ran into problem that deals with ranges for rating type questions.
So a rating's range could be:
1..10
-5..0
-5..5
'a'..'z'
'E'..'M'
and so on
The range is stored as a pair of varchars in database (start and end of range). So range always starts off as a string input.
What is the best way to take these string values and build a Ruby Range accordingly.
I can't just go value.to_i as this won't work for string iteration. Having a bunch of if's seems ugly. Any better way?
Not as important, but worth asking:
Also what if I wanted to make it all work with reversed range? Say 5-to-0 or G-to-A. I know that Ruby doesn't support reverse range (since it uses succ() to iterate). What would be the best way here?
Thanks in advance!
Update:
Based on Wouter de Bie's suggestion I've settled for this:
def to_int_or_string(str)
return str.match(/^-?\d+$/) ? str.to_i : str.strip
end
def ratings_array(from, to)
from = to_int_or_string(from)
to = to_int_or_string(to)
from > to ? Range.new(to, from).to_a.reverse : Range.new(from, to).to_a
end
Any thoughts?
Use Range.new:
Range.new("a","z")
=> "a".."z"
Range.new(-5,5)
=> -5..5
If you're varchars contain quotes, you can use eval to get the right ranges:
from = "'a'"
to = "'z'"
eval("Range.new(#{from},#{to})")
Otherwise you could use value.to_i to figure out if it was a number or a string in the varchar:
a = "x"
a = (a.to_i == 0 && a != "0") ? a : a.to_i
=> "x"
a = "5"
a = (a.to_i == 0 && a != "0") ? a : a.to_i
=> 5
Which of course can be nicely extracted into a method:
def to_int_or_string(value)
return (value.to_i == 0 && value != "0") ? value : value.to_i
end
def to_range(from, to)
return Range.new(to_int_or_string(from), to_int_or_string(to))
end
To reverse your range, you have to convert it to an array first:
Range.new("a","g").to_a.reverse
=> ["g", "f", "e", "d", "c", "b", "a"]
You can do something like the following.
str = 'Z..M'
v1 = str[0,str.index('.')]
v2 = str[str.index('.')+2, str.length]
unless v1.to_i == 0
v1 = v1.to_i
v2 = v2.to_i
end
if v2>v1
final_arr = (v1..v2).to_a
else
final_arr = (v2..v1).to_a.reverse
end
puts final_arr
This takes care of both the positive and the negative ranges

Resources