I'm doing my first program, a simple to-do list. I want it to let me type a number, and delete the corresponding item from the list.
Every time though, I get "no implicit conversion from nil to integer". I can't seem to work it out. Any ideas?
$list = Array.new
def mainmethod
puts "Enter new item, or type 'view' to view the list, 'delete' to delete items"
input = gets.chomp
if input.downcase == "view"
puts "Your to do list is as follows:"
puts $list
elsif input.downcase == "delete"
puts "Which item would you like to delete? (Enter a number)"
deletenumber = gets.chomp.to_i
deletenumber-=1
delete_list = [deletenumber]
delete_list.each do |del|
$list.delete_at($list.index(del))
end
else
$list << input
puts "Added to list!"
end
end
loop { mainmethod }
See http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-delete_at for the proper way of using Array#delete_at.
What you do is calling #index with a number that your array does not contain. Therefore $list.index(del) will return nil and the call to #delete_at will fail.
What you need to do is $list.delete_at(del).
The error TypeError: no implicit conversion from nil to integer happens when you try to access an element of an array using nil as the index. In your case it looks like delete_at is being passed nil:
[ 1, 2, 3 ].delete_at(nil)
# => TypeError: no implicit conversion from nil to integer
Related
I created a method to count a substring 'e' in a string passed as an argument. If there isn't a substring 'e' in the string, it should return "There is no \"e\"." I am trying to achieve this:
How many times 'e' is in a string.
If given string doesn't contain any "e", return "There is no "e"."
if given string is empty, return empty string.
if given string is nil, return nil.
This is my code:
def find_e(s)
if !s.include?("e")
"There is no \"e\"."
elsif s.empty?
""
else s.nil?
nil
end
s.count("e").to_s
end
find_e("Bnjamin")
It skips the if statement and it still uses the method count. Why is this?
To achieve what you want you could move your string.count to the else statement in your if, because actually you're making your method return the quantity of e passed in the count method over your string, but what happens inside the if isn't being used:
def find_e(s)
if s.nil?
nil
elsif s.empty?
''
elsif !s.include?("e")
"There is no \"e\"."
else
s.count("e").to_s
end
end
p find_e("Bnjamin") # => "There is no \"e\"."
p find_e("Benjamin") # => "1"
p find_e(nil) # => nil
p find_e('') # => ""
And also your validations must be in order, first check nil values, then empty values, and then the rest, if you don't then you'll get some undefined method ___ for nil:NilClass errors.
You might have a hard time using the method you wrote. In the next method, you'll need a new case statement to test if find_e returned nil, an empty string, a string with a number or "no e".
This method would be a bit more consistent:
def count_e(string_or_nil)
count = string_or_nil.to_s.count("e")
if count == 0
"There is no \"e\"."
else
count
end
end
puts count_e("Covfefe")
# 2
puts count_e("Bnjamin")
# There is no "e".
puts count_e("")
# There is no "e".
puts count_e(nil)
# There is no "e".
But really, if there's no e in the input, just returning 0 would be the most logical behaviour.
You need to put your count method in a branch of the if/else statement, or else it will be evaluated last every time. Without an explicit return statement Ruby will return the last statement, so putting the method outside the if/else branch on the last line guarantees it will always be hit. Also, nil can be converted to an empty string by calling #to_s, so you can remove one of your branches by converting s.to_s, calling empty? and returning s
def find_e(s)
if s.to_s.empty?
s
elsif !s.include?("e")
"There is no \"e\"."
else
s.count("e").to_s
end
end
If you just return 0 whether you get nil, an empty string, or a string without e, you can make it one line
def find_e(s)
s.to_s.count("e").to_s
end
If it were me I'd probably return an Integer, which can always be converted to a String later. puts and "#{}" will implicitly call to_s for you anway. Then you can use that integer return in your presentation logic.
def count_e(input)
input.to_s.count("e")
end
def check_for_e(input)
count = count_e(input)
count > 0 ? count.to_s : "There's no \"e\"."
end
check_for_e("Covfefe") # => "2"
check_for_e("Bnjamin") # => "There's no \"e\"."
check_for_e(nil) # => "There's no \"e\"."
check_for_e("") # => "There's no \"e\"."
In Ruby, methods return the last statement in their body. Your method's last statement is always s.count("e").to_s, since that lies outside of the if statements.
I'm able to work this statement to a point but I'd like to add another outcome. How to get the statement to return a value from the same row (headed 'first_name') in the CSV if the boolean statement returns true?
def customer_check(user_pin)
x = false
CSV.read('customers.csv', headers: true).any? do |row|
x = true if row['pin'] == user_pin and row['work_here'] == "YES"
yellow = row['first_name']
if x == true then
puts "Welcome back #{yellow}."
sleep(1.5)
else
puts "login failed. Please try again in 3 seconds..."
sleep(3.0)
login_start
end
navigation_menu
end
If you are 100% sure the pins in your csv are unique you could try this
def customer_check(user_pin)
user = CSV.read('customers.csv', headers: true).detect { |row|
row['pin'] == user_pin && row['work_here'] == "YES"
}
if user
puts "Welcome back #{user['first_name']}."
sleep(1.5)
else
puts "login failed. Please try again in 3 seconds..."
sleep(3.0)
login_start
end
navigation_menu
end
Variable user will be initialized as the corresponding row of the .csv for which the conditional is true. Otherwise it's value will be nil:
detect method according to documentation:
Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns its result when it is specified, or returns nil otherwise.
If no block is given, an enumerator is returned instead.
Bear in mind that you might have to ensure that row['pin'] matches your user_id data type as it may come in as a String in which case you'd have to row['pin'].to_i == user_pin
How to get the statement to return a value from the same row (headed 'first_name') in the CSV if the boolean statement returns true?
Running your code as is, Ruby expects a final "end" and raises this error:
syntax error, unexpected end-of-input, expecting keyword_end
Adding another ending 'end' to your code will result in the if x == true block getting evaluated, but, note the indentation in the following code is arguably clearer. Using this example .csv file, this code will both print Welcome back... and return the first name if x == true:
require 'csv'
def customer_check(user_pin)
x = false
CSV.read('customers.csv', headers: true).any? do |row|
x = true if row['customerNo'] == user_pin && row['lastName'] == "Dunbar"
yellow = row['firstName']
# The if/else clauses are indented within the CSV.read
if x # <-- "== true" is redundant
# puts "Welcome back #{yellow}." # you can lose "yellow" if you want to
puts "Welcome back #{row['firstName']}."
sleep(1.5)
# return yellow # <-- RETURN VALUE
return row['firstName'] # <-- RETURN VALUE
else
puts "login failed. Please try again in 3 seconds..."
sleep(3.0)
# login_start
end
# navigation_menu
end
end
return_name = customer_check('1') # <-- prints "Welcome back John"
puts return_name # <-- "John" is the RETURN VALUE
I'm not sure how you are using login_start or navigation_menu but i hope this helps to answer your question. If it helps, here's the CSV doc
I am newbie to programming and ruby. I am using a method to identify who somebody's secret santa is. The method takes String and Integer arguments (first name or id). I have different code for String and Integer arguments. This results in repeating the same line of code for different arguments (secret = PERSONS[person[:santa]-1]).
My questions are two-fold:
Is this kind of repetition against DRY principles? Is there another way to avoid the repetition?
See that I initialized local_variable secret outside the iterator and use the iterator to pass to that variable. Is this the most efficient way of doing this? Can I just return a value from the iterator without initializing a local variable?
My code is below. Also, I am enclosing a sample hash of data (PERSONS) that I am running the code on.
def who_is_secret_santa(first_name)
secret = nil
PERSONS.each do |person|
if first_name.is_a? String
if person[:first_name] == first_name
secret = PERSONS[person[:santa]-1]
end
elsif first_name.is_a? Integer
if person[:id] == first_name
secret = PERSONS[person[:santa]-1]
end
else
puts "Bad argument"
end
end
puts "#{first_name}'s Secret Santa " + (secret ? "is #{secret[:first_name]}" : "not found")
end
[{:id=>1,
:first_name=>"Luke",
:last_name=>"Skywalker",
:email=>"<luke#theforce.net>",
:santa=>4},
{:id=>2,
:first_name=>"Leia",
:last_name=>"Skywalker",
:email=>"<leia#therebellion.org>",
:santa=>7},
{:id=>3,
:first_name=>"Toula",
:last_name=>"Portokalos",
:email=>"<toula#manhunter.org>",
:santa=>5},
{:id=>4,
:first_name=>"Gus",
:last_name=>"Portokalos",
:email=>"<gus#weareallfruit.net>",
:santa=>2},
{:id=>5,
:first_name=>"Bruce",
:last_name=>"Wayne",
:email=>"<bruce#imbatman.com>",
:santa=>3},
{:id=>6,
:first_name=>"Virgil",
:last_name=>"Brigman",
:email=>"<virgil#rigworkersunion.org>",
:santa=>1},
{:id=>7,
:first_name=>"Lindsey",
:last_name=>"Brigman",
:email=>"<lindsey#iseealiens.net>",
:santa=>6}]
There is a way to avoid repetition in this case by first checking for a "bad argument" and then afterwards selecting the correct person from the array.
For your second question, you are probably looking for the select iterator instead of the each. It will return all of the elements in your array that make the condition in the block passed to it true.
Below is some code. p will represent the person whose first_name was passed to the method.
def who_is_secret_santa(first_name)
if ! ((first_name.is_a? String) || (first_name.is_a? Integer))
puts "Bad argument"
else
p = (PERSONS.select do |person| person[:first_name] == first_name || person[:id] == first_name end)[0]
puts "#{first_name}'s Secret Santa " + (p ? "is #{PERSONS[p[:santa]-1][:first_name]}" : "not found")
end
end
I would like to ask you how can I match a nil value in a hash, e. g.:
aAnimals = {1=>'dog', 2=>'cat'}
puts laAnimals[1] # dog
puts laAnimals[2] # cat
puts laAnimals[3] # nil
how can I put 'no animal' in case of nil values or higher than lenght of the matrix, e.g.:
laAnimals = {1=>'dog', 2=>'cat'}
laAnimals.default = 'no animal'
puts laAnimals[1] # dog
puts laAnimals[2] # cat
puts laAnimals[3] # no animal
I want something like that: laAnimals = {1=>'dog', 2=>'cat', default='no animal'}...it is possible?
From http://ruby-doc.org/core-2.2.0/Hash.html
Hashes have a default value that is returned when accessing keys that
do not exist in the hash. If no default is set nil is used. You can
set the default value by sending it as an argument to ::new:
So in your case using laAnimals = Hash.new("no animal") will use the string no animal as the default value.
Exupery's answer is correct, but if you don't have access to the creation of the hash you're working with, you can use Hash#fetch (docs).
laAnimals = {1=>'dog', 2=>'cat'}
puts laAnimals.fetch(1, 'no animal') # dog
puts laAnimals.fetch(2, 'no animal') # cat
puts laAnimals.fetch(3, 'no animal') # 'no animal'
I personally prefer this way of accessing hashes, because if the key (in your example, 1, and 2) is not present it will raise an exception.
Below is the code for my script.
As you can see, i have an array, and an index. I pass that to the block called 'raise_clean_exception'. The integer part of it does actually raise a Standard Error exception which is great. I have an issue when I use an index that is out of bounds. So if my array only has 4 elements (0-3) and I use an index of 9, it will not raise the exception, and instead it prints out a blank line because nothing is there. Why would it do this?
#!/usr/bin/ruby
puts "I will create a list for you. Enter q to stop creating the list."
array = Array.new
i = 0
input = ''
print "Input a value: " #get the value from the user
input = STDIN.gets.chomp
while input != 'q' do #keep going until user inputs 'q'
array[i] = input #store the value in an array of strings
i += 1 #increment out index where the inputs are stored
print "Input a value: " #get the value from the user
input = STDIN.gets.chomp
end #'q' has been entered, exit the loop or go back through if not == 'q'
def raise_clean_exception(arr, index)
begin
Integer(index)
puts "#{arr[index.to_i]}"
# raise "That is an invalid index!"
rescue StandardError => e # to know why I used this you can google Daniel Fone's article "Why you should never rescue exception in Ruby"
puts "That is an invalid index!"
end
# puts "This is after the rescue block"
end
# now we need to access the array and print out results / error messages based upon the array index value given by the user
# index value of -1 is to quit, so we use this in our while loop
index = 0
arrBound = array.length.to_i - 1
while index != '-1' do
print "Enter an index number between 0 and #{arrBound} or -1 to quit: "
index = STDIN.gets.chomp
if index == '-1'
exit "Have a nice day!"
end
raise_clean_exception(array, index)
end
Consider using a subclass of StandardError, IndexError, which is specific to the problem you are experiencing. Also, using else prevents a blank space from being printed if the index is out of bounds and when raising exceptions within a method, a begin...end block is implied.
def raise_clean_exception(arr, index)
Integer(index)
raise IndexError if index.to_i >= arr.length
rescue StandardError
puts "That is an invalid index!"
else
puts "#{arr[index.to_i]}"
end
Accessing an array element that's outside the range of existing elements returns nil. That's just the way Ruby works.
You could add the following line before the "puts" to trap that condition...
raise StandardError if index.to_i >= arr.size