Reputation: 223
I'm using Ruby to get input from a user to provide new names for a list of files. I'm storing the names in an array, but before I store it, I have a series of conditions that I'm looping through to make sure that the user's input is valid. It essentially boils down to this (I've removed parts of the code that aren't relevant to the question):
puts "Rename file to:"
new_name = gets.chomp
new_name = check_input(new_name,@all_names)
@all_names << new_name
def check_input(new_name,all_names)
while new_name.match(/\s/)
puts "Names must not contain spaces:"
new_name = gets.chomp
end
while new_name.empty?
puts "Please enter a name:"
new_name = gets.chomp
end
while all_names.include? new_name
puts "That name already exists. Please enter a different name:"
new_name = gets.chomp
end
return new_name
end
Overall this works pretty well, but I want to make sure to loop through each "while" condition again and again, until all of the conditions are met. If, for instance, a name "abc" already exists, the user follows this order:
The last entry works successfully, but I don't want it to, since it's skipping over the condition that checks for duplicates. Is there a better way to loop through these conditions simultaneously, with each new entry?
Thank you for any help!
Upvotes: 1
Views: 1507
Reputation: 11035
Right idea with the loop, just the wrong place for it. You need to check each gets
from the user against all possible invalid cases. What you were doing was checking until a single invalid case was passed and then going on to a different one, which didn't check if the previous case(s) still passed:
# outputs an error message and returns nil if the input is not valid.
# Otherwise returns the input
def check_input(input, all_names)
if input.match(/\s/)
puts "Name must not contain spaces:"
elsif input.empty?
puts "Please enter a name:"
elsif all_names.include?(input)
puts "That name already exists. Please enter a different name:"
else
input
end
end
@all_names = ['abc']
puts "Rename file to:"
# keep gets-ing input from the user until the input is valid
name = check_input(gets.chomp, @all_names) until name
@all_names << name
puts @all_names.inspect
Since puts
returns nil
, check_input
will return nil
if the input is not valid. Otherwise, in the final else
, we'll return the valid input and assign it to the variable name
and stop executing the until
loop.
Example run:
Rename file to:
abc
That name already exists. Please enter a different name:
a b c
Name must not contain spaces:
abc
That name already exists. Please enter a different name:
abc23
["abc", "abc23"]
Upvotes: 3
Reputation: 110665
Code
def rename_files(fnames)
fnames.each_with_object({}) do |fn,h|
loop do
puts "Rename file '#{fn}' to:"
new_name = gets.chomp
bad_name = bad_name?(new_name, h)
if bad_name
print bad_name
else
h.update(new_name=>fn)
break
end
end
end.invert
end
def bad_name?(new_name, h)
if new_name.include?(' ')
"Names must not contain spaces. "
elsif new_name.empty?
"Names cannot be empty. "
elsif h.key?(new_name)
"That name already exists. Duplicates are not permitted. "
else
nil
end
end
Example
rename_files(["cat", "dog", "pig"])
Rename file 'cat' to:
# <enter "three blind mice">
Names must not contain spaces. Rename file 'cat' to:
# <enter ENTER only>
Names cannot be empty. Rename file 'cat' to:
# <enter "three_blind_mice">
Rename file 'dog' to:
# <enter "four_blind_mice">
Rename file 'pig' to:
# <enter "three_blind_mice?>
That name already exists. Duplicates are not permitted. Rename file 'pig' to:
# <enter "five_blind_mice"
#=> {"cat"=>"three_blind_mice", "dog"=>"four_blind_mice", "pig"=>"five_blind_mice"}
Notes
bad_name?
returns a (truthy) message string if the proposed file name is invalid for one of the three specified tests; else nil
is returned.bad_name?
returns a truthy value, it is printed using print
, rather than puts
, as it will be followed by puts "Rename file '#{fn}' to:"
on the same line. The latter message is in part to remind the user which file is being renamed."f1"
changed to "f2"
and "f2"
changed to "f1"
.) If none of the original names are to be used as new filenames an additional test must be added to bad_name?
(and fnames
must be passed as a third argument to that method).Upvotes: 1
Reputation: 589
Yep, recursion is the right way to do something like this I think. Just throw this in a test.rb
file and run ruby test.rb
:
@all_names = []
def check_name(name = nil)
# Find out if it's invalid and why
invalid_reason = if name.empty?
"Please enter a name:"
elsif name.match(/\s/)
"Names must not contain spaces:"
elsif @all_names.include?(name)
"That name already exists. Please enter a different name:"
end
# Either return the name or ask for it again
if invalid_reason
puts invalid_reason
name = check_name(gets.chomp)
end
# Once we have it return the name!
name
end
puts "Rename file to:"
new_name = check_name(gets.chomp)
puts "Successfully storing name '#{new_name}'..."
@all_names << new_name
Let me know if that's doing what you were looking for!
Upvotes: 0
Reputation: 26758
This could be a good use for recursion (just showing one of the conditions here, the others are the same structure):
def check_input(new_name,all_names)
# using if instead of while ... recursion provides the 'loop' here
if new_name.match(/\s/)
puts "Names must not contain spaces:"
new_name = check_input(gets.chomp, all_names)
end
# etc, other conditionals
new_name
end
Essentially, none of these new_name
assignments will resolve until the input passes all the checks. The program moves deeper into the stack, but everything will resolve as soon as some input passes all the checks.
Upvotes: 0