Is there a better way to write multiple OR statements in an if-statement? - ruby

def get_string(no_of_times)
1.upto(no_of_times) do
string_input = gets.chomp
count_holes(string_input)
end
end
def count_holes(word)
count = 0
word.each_char do |char|
if char == "A" || char == "D" || char == "O" || char == "P" || char == "Q" || char == "R"
count += 1
elsif char == "B"
count += 2
end
end
$arr_of_holes << count
end
test_cases = gets.chomp.to_i
$arr_of_holes = []
get_string(test_cases)
puts $arr_of_holes
Hi all. I do not like the long condition in if statement while iterating over each character. So i wanted to ask you all if there is a better way to do this in ruby.
Thanks

This can be done with a case selection, as multiple terms can be supplied to each when:
case char
when "A", "D", "O", "P", "Q", "R"
count += 1
when "B"
count += 2
end

You can use Array#include?:
if %q{A D O P Q R}.include? char
count += 1
elsif char == "B"
count += 2
end
Alternative way using Hash:
def count_holes(word)
holes = {
'A' => 1,
'D' => 1,
'O' => 1,
'P' => 1,
'Q' => 1,
'B' => 2,
}
count = word.chars.map { |char| holes.fetch(char, 0) }.inject :+
$arr_of_holes << count
end

Slightly more compact than nacyot's answer:
count += case char
when "B" then 2
when "A", "D", "O".."R" then 1
else 0
end
The else line may not be required if there is not such case.

One more way:
word = "BROADLY"
"ADOPQR".each_char.reduce(0) { |t,c| t + word.count(c) } + 2*word.count("B")
#=> 6

Related

What's wrong with my code?

def encrypt(string)
alphabet = ("a".."b").to_a
result = ""
idx = 0
while idx < string.length
character = string[idx]
if character == " "
result += " "
else
n = alphabet.index(character)
n_plus = (n + 1) % alphabet.length
result += alphabet[n_plus]
end
idx += 1
end
return result
end
puts encrypt("abc")
puts encrypt("xyz")
I'm trying to get "abc" to print out "bcd" and "xyz" to print "yza". I want to advance the letter forward by 1. Can someone point me to the right direction?
All I had to do was change your alphabet array to go from a to z, not a to b, and it works fine.
def encrypt(string)
alphabet = ("a".."z").to_a
result = ""
idx = 0
while idx < string.length
character = string[idx]
if character == " "
result += " "
else
n = alphabet.index(character)
n_plus = (n + 1) % alphabet.length
result += alphabet[n_plus]
end
idx += 1
end
return result
end
puts encrypt("abc")
puts encrypt("xyz")
Another way to solve the issue, that I think is simpler, personally, is to use String#tr:
ALPHA = ('a'..'z').to_a.join #=> "abcdefghijklmnopqrstuvwxyz"
BMQIB = ('a'..'z').to_a.rotate(1).join #=> "bcdefghijklmnopqrstuvwxyza"
def encrypt(str)
str.tr(ALPHA,BMQIB)
end
def decrypt(str)
str.tr(BMQIB,ALPHA)
end
encrypt('pizza') #=> "qjaab"
decrypt('qjaab') #=> "pizza"
Alternatively if you don't want to take up that memory storing the alphabet you could use character codings and then just use arithmetic operations on them to shift the letters:
def encrypt(string)
result = ""
idx = 0
while idx < string.length
result += (string[idx].ord == 32 ? (string[idx].chr) : (string[idx].ord+1).chr)
idx += 1
end
result
end
Other strange thing about ruby is that you do not need to explicitly return something at the end of the method body. It just returns the last thing by default. This is considered good style amongst ruby folks.
Your question has been answered, so here are a couple of more Ruby-like ways of doing that.
Use String#gsub with a hash
CODE_MAP = ('a'..'z').each_with_object({}) { |c,h| h[c] = c < 'z' ? c.next : 'a' }
#=> {"a"=>"b", "b"=>"c",..., "y"=>"z", "z"=>"a"}
DECODE_MAP = CODE_MAP.invert
#=> {"b"=>"a", "c"=>"b",..., "z"=>"y", "a"=>"z"}
def encrypt(word)
word.gsub(/./, CODE_MAP)
end
def decrypt(word)
word.gsub(/./, DECODE_MAP)
end
encrypt('pizza')
#=> "qjaab"
decrypt('qjaab')
#=> "pizza"
Use String#gsub with Array#rotate
LETTERS = ('a'..'z').to_a
#=> ["a", "b", ..., "z"]
def encrypt(word)
word.gsub(/./) { |c| LETTERS.rotate[LETTERS.index(c)] }
end
def decrypt(word)
word.gsub(/./) { |c| LETTERS.rotate(-1)[LETTERS.index(c)] }
end
encrypt('pizza')
#=> "qjaab"
decrypt('qjaab')
#=> "pizza"

ruby access non-vowels of a string in condition line without arrays

I need to turn a string that contains (among other letters) a sequence of 3 letters consisting of a non-vowel, an "o" and the same non-vowel again
into
a string that contains (other letters and) only that non-vowel.
like
"kok" #=> "k"
"mom" #=> "m"
"lol" #=> "l"
"kokmomloljk" #=> "kmljk"
I would like my code to be as compact as possible and only use string methods.
str.each_char { | i | if i == /[^aeiou]/ and i == str[i.index + 2] and str[i.index + 1] == "o"
str = str.delete(str.slice(str[i.index + 1], 2))
end
}
The output is the unchanged string. Thank you in advance.
R = /
([^aeiou]) # match a consonant in capture group 1
o # match an 'o'
\1 # match the contents of capture group 1
/x # free-spacing regex definition mode
def my_method(str)
str.gsub(R,'\1')
end
my_method "my dog kok has fleas"
#=> "my dog k has fleas"
my_method "much momentum"
#=> "much mentum"
my_method "'plolly' is not a word"
#=> "'plly' is not a word"
my_method "abkokcmomloljkde"
#=> "abkcmljkde"
my_method "bub"
#=> "bub"
I was wondering if you could do this in a more functional non-destructieve way with map and, yes you can, but not more compact than the other answers:
str = "iskakmomlol"
VOWEL = /[aeiou]/
RESONANT = /[^aeiou]/
str.chars.map.with_index { |c, i|
prevb, prev, nxt, scnd = str[i - 2], str[i - 1], str[i + 1], str[i + 2]
if i > str.length - 1 || i == 0 then c
elsif c =~ RESONANT && nxt =~ VOWEL && c == scnd then c
elsif c =~ VOWEL && prev =~ RESONANT && nxt =~ RESONANT
elsif c =~ RESONANT && prevb == c && prev =~ VOWEL
else c
end
}.compact.join # "iskmljk"
Actually, this can be shorter:
R = /([^aeiou])[aeiou]\1/
str.chars.map.with_index { |c, i|
c unless str[i-1..i+1][R] || str[i-2..i][R]
}.compact.join # "iskmljk"
i figured since the "o" is a fix character in the sequence to be accepted, i can just go through with a count var and see if the characters next to it are equal to each other and non-vowels. also i found that slice can be also passed two parameters so that it slices starting at an offset and stopping after the given length.
index = 0
while index < str.length
index = index + 1
if str[index] == "o" and str[index-1] == str[index+1] and str[index-1] != /[^aeiou]/
str.slice!(index, 2)

Ruby method conditions

The instructions for my program are to make a method that takes a string and returns dashes around any odd numbers, but with the exception that the end result may not begin or end with a dash "-". For example, if I enter "95601137", it should return
"9 - 5 - 6 0 - 1 - 1 - 3 - 7"
Here's the code:
def dashes(number)
string = number.to_s
i=0
final = []
while i<string.length
digit = string[i].to_i
if ((digit)%2 != 0) && (i==0) && (string.length == 1) && ((string.length - 1) == 0)
final.push(string[i])
elsif ((digit)%2 != 0) && (i==0) && (string.length != 1) && ((string.length - 1) > 0)
final.push(string[i] + "-")
elsif ((digit)%2 != 0) && (i>0) && (i!=(string.length - 1)) && ((string.length != 1))
final.push("-" + string[i] + "-")
elsif ((digit)%2 != 0) && (i!=0) && (i==(string.length - 1))
final.push("-" + string[i])
else final.push(string[i])
end
i+=1
end
return final
end
puts("Give me any number.")
answer = gets
puts dashes(answer)
My program has two issues:
When I enter 9, it returns "9-". How come?
When I enter a string that ends with an odd number, it puts a dash at the end, which it's not expected. I thought I made my if conditions strict, too.
I'd like to propose another solution:
def dashes(num)
str = num.to_s
dashed_arr = str.chars.map.with_index do |digit, index|
next digit if digit.to_i.even?
if index.zero? # is it first digit?
"#{digit} -"
elsif index == str.length - 1 # is it last digit?
"- #{digit}"
else
"- #{digit} -"
end
end
dashed_arr.join(' ').gsub('- -', '-')
end
puts dashes(95601137)
puts dashes(909)
# => "9 - 5 - 6 0 - 1 - 1 - 3 - 7"
# => "9 - 0 - 9"
Let's review it step by step:
str = num.to_s - convert our number to a string representation, i.e. "95601137"
str.chars - split our string by chars. We get an array of chars, like this: ["9", "5", "6", "0", "1", "1", "3", "7"], because we want to iterate them, one by one.
map takes one array and converts it to another array by the code supplied in do ... end.
For example, let's take this code:
[1, 2, 3].map do |num|
num * 2
end
# => [2, 4, 6]
If you need also an index of your element, you can similarly use .map.with_index (index starts from zero):
[1, 2, 3].map.with_index do |num, index|
num * index # index takes values 0, 1, 2
end
# => [0, 2, 6]
So, in the block in code above, we have each digit from our number as digit and its 0-based position as index.
next digit if digit.to_i.even?. We don't need even digits, so we can skip them. .to_i is needed to convert digit to integer, so we can query is it even or odd. next digit just returns current digit and moves execution to next digit.
Now we're left only with odd digits, so we just need to put dashes correctly, depending on the digit position: if it's a first digit, append a dash, if it's a last digit then put the dash in front of the digit, otherwise just surround digit with dashes:
if index.zero? # first digit, index is zero-based
"#{digit} -"
elsif index == str.length - 1 # last digit
"- #{digit}"
else
"- #{digit} -"
end
We save intermediate result in dashed_arr variable to make code readable. Right now it contains the following: ["9 -", "- 5 -", "6", "0", "- 1 -", "- 1 -", "- 3 -", "- 7"]. As you can see, we're almost done, we just need to connect all the elements of array back to string.
dashed_arr.join(' '). Join back elements of array into a string with single space as a separator. We get this string: 9 - - 5 - 6 0 - 1 - - 1 - - 3 - - 7. Hmm, it seems we need to delete some consecutive dashes.
Let's do it with gsub: dashed_arr.join(' ').gsub('- -', '-'). gsub just searches all occurrences of first string and replaces them with the second string, which is exactly what we need: 9 - 5 - 6 0 - 1 - 1 - 3 - 7.
We're done! I hope it was also fun for you as for me.
One more implementation, inspired by Cary Swoveland's first implementation:
str = '95601137'
separators = str.each_char.map(&:to_i).each_cons(2).map do |pair|
pair.any?(&:odd?) ? ' - ' : ' '
end
str.chars.zip(separators).join
# => "9 - 5 - 6 0 - 1 - 1 - 3 - 7"
Here are two ways, for:
str = "95601137"
Use an enumerator
With this method you would use String#each_char to create an enumerator to which you would send the methods Enumerator#next and Enumerator#peek:
enum = str.each_char
s = ''
loop do
c = enum.next
s << c
n = enum.peek
s << ((c.to_i.odd? || n.to_i.odd?) ? ' - ' : ' ')
end
s #=> "9 - 5 - 6 0 - 1 - 1 - 3 - 7"
The key here is that when the enumerator is at the last element ('7') peek raises a StopIteration exception. The exception is handled by Kernel#loop by breaking out of the loop.
Use String#gsub
We can use gsub three times, each with a regular expression, to:
put the dashes in the correct locations
space as desired around the dashes
space as desired between even digits
r = /
(\A\d) # match beginning of string followed by a digit, save in group 1
| # or
(\d\z) # match a digit followed by end of string, save in group 2
| # or
(\d) # match a digit, save in capture group 3
/x # extended mode
str = "95601137"
str.gsub(r) do |s|
if $1
$1.to_i.odd? ? "#{$1}-" : $1
elsif $2
$2.to_i.odd? ? "-#{$2}" : $2
elsif $3
$3.to_i.odd? ? "-#{$3}-" : $3
else
raise ArgumentError, "'#{s}' is not a digit"
end
end.gsub(/-+/, ' - ').gsub(/(\d)(\d)/,'\1 \2')
#=> "9 - 5 - 6 0 - 1 - 1 - 3 - 7"
Replace:
answer = gets
with:
answer = gets.strip
Then, you will get the correct output for input: 9
Because, when you give 9 as input and hit enter, your input is actually: "9\n" which has length 2. That's why it was printing 9-.
Using: gets.strip you just strip out the \n.
To remove the last - (if any), you can use chomp:
final_result = final.join('').chomp('-')
And, to remove the first - (if any), you can use this logic:
if final_result[0] == '-'
final_result[1..-1]
else
final_result
end
Here is the full working version of the code (keeping as much as of your code):
def dashes(number)
string = number.to_s
i = 0
final = []
while i < string.length
digit = string[i].to_i
if ((digit)%2 != 0) && (i==0) && (string.length == 1) && ((string.length - 1) == 0)
final.push(string[i])
elsif ((digit)%2 != 0) && (i==0) && (string.length != 1) && ((string.length - 1) > 0)
final.push(string[i] + "-")
elsif ((digit)%2 != 0) && (i>0) && (i!=(string.length - 1)) && ((string.length != 1))
final.push("-" + string[i] + "-")
elsif ((digit)%2 != 0) && (i!=0) && (i==(string.length - 1))
final.push("-" + string[i])
else
final.push(string[i])
end
i += 1
end
final_result = final.join('').gsub('--', '-').chomp('-')
puts "final_result: #{final_result.inspect}"
if final_result[0] == '-'
final_result[1..-1]
else
final_result
end
end
puts("Give me any number.")
answer = gets.strip
puts dashes(answer)
# > Give me any number.
# > 95601137
# > "9-5-60-1-1-3-7"
A naive implementation, there's probably some smarter ways to do it:
def dashes(number)
number # => 95601137
.to_s # => "95601137"
.chars # => ["9", "5", "6", "0", "1", "1", "3", "7"]
.map(&:to_i) # => [9, 5, 6, 0, 1, 1, 3, 7]
.map {|i| i.odd? ? "-#{i}-" : i } # => ["-9-", "-5-", 6, 0, "-1-", "-1-", "-3-", "-7-"]
.join # => "-9--5-60-1--1--3--7-"
.gsub(/^-|-$/, '') # => "9--5-60-1--1--3--7"
.gsub('--', '-') # => "9-5-60-1-1-3-7"
end

Ruby: Reducing Repetition

I'm a beginner and wrote a script for the following question below in ruby. I read that repetition isn't recommended and would like to reduce the repetition of if, elsif, else statements but can't seem to find a way.
Old-school Roman numerals. In the early days of Roman numer- als, the Romans didn’t bother with any of this new-fangled sub- traction “IX” nonsense. No sir, it was straight addition, biggest to littlest—so 9 was written “VIIII,” and so on. Write a method that when passed an integer between 1 and 3000 (or so) returns a string containing the proper old-school Roman numeral. In other words, old_roman_numeral 4 should return 'IIII'. Make sure to test your method on a bunch of different numbers. Hint: Use the inte- ger division and modulus methods on page 37.
For reference, these are the values of the letters used:
I =1 V=5 X=10 L=50 C=100 D=500 M=1000
Here is my script...
puts "What is your number?"
n = gets.chomp
num = n.to_i
number = ""
l = n.length
i = 0
while true
if num > 3000
puts "Enter another number."
elsif l == 0
break
else
if l == 4
number += "M" * n[i].to_i
l -= 1
i += 1
elsif l == 3
if 1 <= n[i].to_i && n[i].to_i <= 4
number += "C" * n[i].to_i
elsif n[i].to_i == 5
number += "D"
elsif 6 <= n[i].to_i && n[i].to_i <= 9
number += "D" + "C" * (n[i].to_i - 5)
end
l -= 1
i += 1
elsif l == 2
if 1 <= n[i].to_i && n[i].to_i <= 4
number += "X" * n[i].to_i
elsif n[i].to_i == 5
number += "L"
elsif 6 <= n[i].to_i && n[i].to_i <= 9
number += "L" + "X" * (n[i].to_i - 5)
end
l -= 1
i += 1
else
if 1 <= n[i].to_i && n[i].to_i <= 4
number += "I" * n[i].to_i
elsif n[i].to_i == 5
number += "V"
elsif 6 <= n[i].to_i && n[i].to_i <= 9
number += "V" + "I" * (n[i].to_i - 5)
end
l -= 1
i += 1
end
end
end
This doesn't use integer division or modulus, but it might be instructive.
puts "What is your number?"
input = gets.to_i
numerals = {
1000 => "M",
500 => "D",
100 => "C",
50 => "L",
10 => "X",
5 => "V",
1 => "I"
}
digits = []
numerals.each do |n, digit|
while input >= n
digits << digit
input = input - n
end
end
puts digits.join
Another way, that builds a string, as #sawa suggested, rather than constructing an array and then using join:
numerals = {
1000 => "M",
500 => "D",
100 => "C",
50 => "L",
10 => "X",
5 => "V",
1 => "I"
}
input = 9658
numerals.each_with_object('') do |(n, digit),str|
nbr, input = input.divmod(n)
str << digit*nbr
end
#=> "MMMMMMMMMDCLVIII"

What is the best way to handle this type of inclusive logic in Ruby?

Is there a better way of handling this in Ruby, while continuing to use the symbols?
pos = :pos1 # can be :pos2, :pos3, etc.
if pos == :pos1 || pos == :pos2 || pos == :pos3
puts 'a'
end
if pos == :pos1 || pos == :pos2
puts 'b'
end
if pos == :pos1
puts 'c'
end
The obvious way would be swapping out the symbols for number constants, but that's not an option.
pos = 3
if pos >= 1
puts 'a'
end
if pos >= 2
puts 'b'
end
if pos >= 3
puts 'c'
end
Thanks.
EDIT
I just figured out that Ruby orders symbols in alpha/num order. This works perfectly.
pos = :pos2 # can be :pos2, :pos3, etc.
if pos >= :pos1
puts 'a'
end
if pos >= :pos2
puts 'b'
end
if pos >= :pos3
puts 'c'
end
Not sure if this is the best way......
I would make use of the include? method from array:
puts 'a' if [:pos1, :pos2, :pos3].include? pos
puts 'b' if [:pos1, :pos2].include? pos
puts 'c' if [:pos1].include? pos
Just use the case statement
pos = :pos1 # can be :pos2, :pos3, etc.
case pos
when :pos1 then %w[a b c]
when :pos2 then %w[a b]
when :pos3 then %w[a]
end.each {|x| puts x }
There are lots of different ways to get your output. Which one you
want depends on your specific objections to your if statements.
I've added a bunch of extra formatting to make the output easier
to read.
If you don't like the logical ORs and how they separate the results
from the output, you can use a lookup table:
puts "Lookup table 1:"
lookup_table1 = {
:pos1 => %w{a b c},
:pos2 => %w{a b },
:pos3 => %w{a },
}
[:pos1, :pos2, :pos3].each { |which|
puts "\t#{which}"
lookup_table1[which].each { |x| puts "\t\t#{x}" }
}
Or, if you want all the "work" in the lookup table:
puts "Lookup table 2:"
lookup_table2 = {
:pos1 => lambda do %w{a b c}.each { |x| puts "\t\t#{x}" } end,
:pos2 => lambda do %w{a b }.each { |x| puts "\t\t#{x}" } end,
:pos3 => lambda do %w{a }.each { |x| puts "\t\t#{x}" } end,
}
[:pos1, :pos2, :pos3].each { |which|
puts "\t#{which}"
lookup_table2[which].call
}
If your problem is that symbols aren't ordinals, then you can
ordinalize them by converting them to strings:
puts "Ordinals by .to_s and <="
[:pos1, :pos2, :pos3].each { |which|
puts "\t#{which}"
if which.to_s <= :pos3.to_s
puts "\t\ta"
end
if which.to_s <= :pos2.to_s
puts "\t\tb"
end
if which.to_s <= :pos1.to_s
puts "\t\tc"
end
}
Or you could monkey patch a comparison operator into the Symbol
class (not recommended):
puts "Ordinals by Symbol#<="
class Symbol
def <= (x)
self.to_s <= x.to_s
end
end
[:pos1, :pos2, :pos3].each { |which|
puts "\t#{which}"
if which <= :pos3
puts "\t\ta"
end
if which <= :pos2
puts "\t\tb"
end
if which <= :pos1
puts "\t\tc"
end
}
Or you could use a lookup table to supply your ordinal values:
puts "Ordinals through a lookup table:"
ordinal = {
:pos1 => 1,
:pos2 => 2,
:pos3 => 3,
}
[:pos1, :pos2, :pos3].each { |which|
puts "\t#{which}"
if ordinal[which] <= 3
puts "\t\ta"
end
if ordinal[which] <= 2
puts "\t\tb"
end
if ordinal[which] <= 1
puts "\t\tc"
end
}
Those are the obvious ones off the top of my head. It is hard to say what would be best without more specifics on what your problem with your if approach is; your second example indicates that what you really want is a way to make symbols into ordinals.
More generically, you can use this:
pos = :pos3
arr = [:pos1,:pos2,:pos3]
curr = 'a'
idx = arr.length
while idx > 0
puts curr if arr.last(idx).include? pos
curr = curr.next
idx -= 1
end
Or this, for your specific example:
puts 'a'
puts 'b' if pos != :pos3
puts 'c' if pos == :pos1

Resources