Home > Software engineering >  How to validate command line input from multiple choice prompt in Ruby?
How to validate command line input from multiple choice prompt in Ruby?

Time:09-23

I'm writing a multiple choice quiz where the user has to choose an answer by entering a letter from a to d. Each question prompt looks like this:

What is the last letter in alphabet?
(a) Y
(b) R
(c) Z
(d) Q

If the user enters anything else, I want to show a message and print the whole question again until the input is a, b, c or d.

Here's what I've tried: (simplified)

question = "What is the last letter in alphabet?\n(a) Y\n(b) R\n(c) Z\n(d) Q"

puts question
answer = gets.chomp.to_s
while answer > "d"
  puts "Please enter a, b, c, or d"
  puts
  puts question
  answer = gets.chomp.to_s
end

It works fine when entering e, f, g etc. but it doesn't catch input like ab, 1, Z, or when the user just presses enter.

CodePudding user response:

You have only four allowed inputs. Check for those directly. Even if as inelegantly as if !(answer == "a" || answer == "b" ...). Anything else will accept some invalid input. In particular, if answer > "d" allows "ab" as a valid answer.

CodePudding user response:

Your approach doesn't work because answer > "d" compares both strings character-wise using their (Unicode) code points. To understand what this means, take a look at the Basic Latin chart: (it's equivalent to an ASCII table)

Basic Latin chart

"d" has a codepoint of U 0064. Any smaller codepoint – i.e every character in the chart before "d" – is regarded smaller. This includes all (regular) digits, all (basic latin) uppercase letters and several symbols:

"0" > "d"  #=> false
"Z" > "d"  #=> false

You could add a lower bound like answer < "a" || answer > "d" but this would still allow all strings starting with one of the allowed characters, e.g.:

"apple" < "a" || "apple" > "d" #=> false

To actually limit the answer to the four allowed values, you have to compare the string to each of them. You could combine these comparisons:

answer == 'a' || answer == 'b' || answer == 'c' || answer == 'd'

use a loop over an array of allowed values:

['a', 'b', 'c', 'd'].any? { |letter| answer == letter }

check whether the array includes the answer:

['a', 'b', 'c', 'd'].include?(answer)

# or

%w[a b c d].include?(answer)

or use a regular expression to match a to d:

answer.match?(/\A[a-d]\z/)

Note that the examples above become true if answer is between a and d. You could either negate the condition via !(...):

while !(%w[a b c d].include?(answer))
  # ...
end

or use until instead of while:

until %w[a b c d].include?(answer)
  # ...
end
  • Related