Ruby method unable to pass Rspec test - ruby

I am trying to create a method that takes in a user input. It turns that user input into an integer, it then subtracts one from the user input. It also returns -1 if the user input is not a number. However the tests throws an error.
describe '#input_to_index' do
it 'converts a user_input to an integer' do
user_input = "1"
expect(input_to_index(user_input)).to be_a(Fixnum)
end
it 'subtracts 1 from the user_input' do
user_input = "6"
expect(input_to_index(user_input)).to be(5)
end
it 'returns -1 for strings without integers' do
user_input = "invalid"
expect(input_to_index(user_input)).to be(-1)
end
end
Here is my method:
def input_to_index(user_input)
user_input = user_input.to_i
user_input = user_input - 1
return -1 if !user_input.is_a? Numeric
end

It's because you're only returning something if !user_input.is_a?(Numeric) and you've already cast user_input to an integer.
-1 if false # => nil
-1 if true # => -1
So that last line in the method returns nil because the condition is never going to be met.
"a".to_i # => 0
"a".to_i.is_a?(Numeric) # => true
("a".to_i - 1).is_a?(Numeric) # => true
You don't even need that last line at all and things will work fine:
def input_to_index(user_input)
user_input = user_input.to_i
user_input = user_input - 1
end
input_to_index("1") # => 0
input_to_index("6") # => 5
input_to_index("invalid") # => -1
and more succinctly:
def input_to_index(user_input)
user_input.to_i - 1
end
input_to_index("1") # => 0
input_to_index("6") # => 5
input_to_index("invalid") # => -1

I'm sure there is a more eloquent way to do this, but you could do this:
def input_to_index(user_input)
user_input = user_input.to_i
user_input = user_input - 1
if !user_input.is_a? Numeric
-1
else
user_input
end
end
EDIT
This might be a more eloquent way to do it:
def input_to_index(user_input)
user_input = user_input.to_i - 1
!user_input.is_a?(Numeric) ? -1 : user_input
end
Below is the most eloquent way to do that:
def input_to_index(user_input)
user_input.to_i - 1
end
Credit: Simple Lime

Related

How can we replicate irb behaviour using binding method as below?

Trying to write a program interactively which can take inputs from command line as an expression or attributes like -
irb : 3+2
Should evaluate to => 5
Attribute
irb : abc = 1
=> 1
irb : jkl(or def) = 1
=> 1
irb : abc + def
=> 2
Also the evaluation should take place once user inputs blank line.
My efforts : I created a method attr_accessor which iterates through the array of *secret passed to it, and calls define_method on each attr, creating an instance variable getter and setter for each attribute.
Part of code working :
I made a success in evaluating the expressions and returning string values.
irb : 3+2
Should evaluate to => 5
irb : True
=> True
But still stuck with evaluation of assignment to attributes and unable to dynamically store those values in my interactive irb. Below expected results are not working :
Attribute
irb : abc = 1
=> 1
irb : def = 1
=> 1
irb : abc + def
=> 2
Note - I don't want to use "require 'irb' " or " "require 'pry'". Can this be achieved with simple ruby code ?
My Solution:
class Demo
def self.attr_accessor(*secret)
secret.each do |attr|
define_method(attr) { instance_variable_get("##{attr}") }
define_method("#{attr}=") { |val| instance_variable_set("##{attr}", val) }
end
get_binding
end
def self.method_new(input)
#object = attr_accessor(input)
end
def self.method(secret)
#object = Regexp.new(/\A[\d+\-*\/=. ]+\z/).match(secret.to_s) ? eval(secret) : "Invalid expression"
get_binding
end
def self.simple_method(secret)
#object = secret
get_binding
end
def self.get_binding
binding
end
end
user_input = ''
until user_input == 'q' do
user_input = gets.chomp
if user_input =~ /^.*=.*$/
b2 = Demo.method_new(*user_input)
puts eval('#object', b2)
elsif user_input =~ /\A[\d+\-*\/=. ]+\z/
b3 = Demo.method(user_input)
puts eval('#object', b3)
else
b4 = Demo.simple_method(user_input)
puts eval('#object', b4)
end
end
Expected Result:
irb : 3+2
#note - each result evaluated after user enters blank line
Should evaluate to => 5
Attributes ---
irb : abc = 1
#note - each result evaluated after user enters blank line
=> 1
irb : def = 1
#note - each result evaluated after user enters blank line
=> 1
irb : abc + def( or jkl)
#note - each result evaluated after user enters blank line
=> 2
Actual Result : Output is "Invalid expression" for all other inputs except expressions and simple strings.
I believe, I have partly reached to the solution of above problem. Now I can store the values of attributes in a hash map. I tried accessing these values through keys and thus can easily store and display values for assignments like:
rb : x = 1
=> 1
or
rb : y = 1
But the part of code I have written for evaluating 'x + y' is trying to partition it on operator and then accessing value of each attribute.
I am doing something wrong in line of code marked with comment #faulty. Due to which I got output like
=> x y
I am unable to access key values after partitioning.
Can someone please advise on this piece of code alone ?
Solution:
class Module
def initialize(args)
args.each do |key, value|
# the instance_variable_set method dynamically creates instance variables
# with the key as the name and value as the assigned value
instance_variable_set("##{key}",value)
# define_singleton_method creates a getter method with the same name as the
# key and inside the block you define what it returns
define_singleton_method(key){ value }
#defining the setter method
define_singleton_method("#{key}=") do |val|
instance_variable_set("##{key}", val)
end
end
end
end
class Demo
#var :bar
def self.eval_binary_expr(expr)
if expr =~ /^.*=.*$/
obj = Module.new(:name => expr)
#object1 = eval(obj.name)
get_binding
else
obj = Module.new(:name => expr)
l_operand, op, r_operand = (obj.name).partition(%r{[/*+-]}) #Faulty
if op.empty?
raise ArgumentError, "Invalid operation or no operation in expression: #{expr}"
end
case op
when '/'; then #object1 = (l_operand / r_operand); get_binding
when '*'; then #object1 = (l_operand * r_operand); get_binding
when '+'; then #object1 = (l_operand + r_operand); get_binding
when '-'; then #object1 = (l_operand - r_operand); get_binding
end
end
end
def self.method(secret)
#object2 = Regexp.new(/\A[\d+\-*\/=. ]+\z/).match(secret.to_s) ? eval(secret) : "Invalid expression"
get_binding
end
def self.new_method(secret)
#object3 = secret
get_binding
end
def self.get_binding
binding
end
end
user_input = ''
until user_input == 'q' do
user_input = gets.chomp
if user_input =~ /\A[\w+\-*\/=. ]+\z/
b2 = Demo.eval_binary_expr(user_input)
puts eval('#object1', b2)
elsif user_input =~ /\A[\d+\-*\/=. ]+\z/
b3 = Demo.method(user_input)
puts eval('#object2', b3)
else
b4 = Demo.new_method(user_input)
puts eval('#object3', b4)
end
end

Stuck in while loop even though condition has been met

I'm writing this script to build a tic-tac-toe game. This is only the beginning (no turns yet). I want to let the user input again if the previous input is invalid.
def display_board(board)
first_row = " #{board[0]} | #{board[1]} | #{board[2]} "
second_row = " #{board[3]} | #{board[4]} | #{board[5]} "
third_row = " #{board[6]} | #{board[7]} | #{board[8]} "
row_divider = "-----------"
puts first_row
puts row_divider
puts second_row
puts row_divider
puts third_row
end
def valid_move?(board,index)
if (index >= 0) && (index <= board.length - 1) && (position_taken?(board,index) != FALSE)
return TRUE
else
return FALSE
end
end
def input_to_index(user_input)
index = user_input.to_i - 1
return index
end
def move(board, index, character = "X")
board[index] = character
end
def position_taken?(board,index)
if (board[index] == "X") || (board[index]=="O")
return FALSE
end
end
def turn(board)
puts "Please enter 1-9:"
user_input = gets.strip
index = input_to_index(user_input)
while valid_move?(board,index) == FALSE
puts "invalid"
turn(board)
end
move(board, index, character = "X")
display_board(board)
end
I am stuck on the while loop. If I input an invalid input and then a valid input, it runs through the while loop instead of ending the program. It should be true. The problem is fixed if I use an if statement instead of a while loop, but I want to learn to use while loops better.
Because you call "turn" (internal) from "turn" (external) and then the internal "turn" called is valid but the board and index on the external
"turn" didn't change.
try this:
def turn(board)
loop do
puts "Please enter 1-9:"
user_input = gets.strip
index = input_to_index(user_input)
if valid_move?(board,index) == TRUE
break
end
puts "invalid"
end
move(board, index, character = "X")
display_board(board)
end

How can I tell if x.to_i is really 0 (zero)?

"0".to_i == 0
also:
"abcdefg".to_i == 0
I want to make sure the string I'm parsing really is just a number (0 included).
Integer("0").zero? rescue false
# => true
Integer("1").zero? rescue false
# => false
Integer("abcdefg").zero? rescue false
# => false
def string_is_integer?(string)
!string.match(/^(\d)+$/).nil? # \d looks for digits
end
def string_is_float?(string)
!string.match(/^(\d)+\.(\d)+$/).nil?
end
def string_is_number?(string)
string_is_integer?(string) || string_is_float?(string)
end
Or, if you don't mind:
def string_is_number?(string)
begin
true if Float(string)
rescue ArgumentError
end || false
end
def is_zero?(string)
(Integer(string) rescue 1).zero?
end
is_zero? "0" #=> true
is_zero? "00000" #=> true
is_zero? "0cat" #=> false
is_zero? "1" #=> false
is_zero? "-0" #=> true
is_zero? "0_000" #=> true
is_zero? "0x00" #=> true
is_zero? "0b00000000" #=> true
Several of these examples illustrate why it's preferable to use Kernel#Integer rather than a regular expression.
First test if the string is integer or not and then match
def is_i?(x)
!!(x =~ /\A[-+]?[0-9]+\z/)
end
def is_zero?(x)
return is_i?(x) && x.to_i == 0
end
# is_zero?("0") will return true
# is_zero?("abcde") will return false
Or you can put these methods in String class like this
class String
def is_i?
!!(self =~ /\A[-+]?[0-9]+\z/)
end
def is_zero?
self.is_i? && self.to_i == 0
end
end
"0".is_zero? # true
"abcde".is_zero? # false
I want to make sure the string I'm parsing really is just a number.
There are two steps involved:
Parsing, i.e. converting the string into a number
Validation, i.e. checking if the string actually is a number
The built-in conversion functions like Integer and Float already perform both steps: they return a numeric result if the string is valid and raise an error otherwise.
Using these functions just for validation and discarding the return value is wasteful. Instead, use the return value and handle the exception, e.g.:
begin
puts 'Enter a number:'
number = Float(gets)
rescue
puts 'Not a valid number'
retry
end
# do something with number
or something like:
def parse_number(string)
Float(string) rescue nil
end
number = parse_number(some_string)
if number
# do something with number
else
# not a number
end

undefined method (NoMethodError) ruby

I keep getting the following error message:
text.rb:2:in `<main>': undefined method `choices' for main:Object (NoMethodError)
But I can't seem to understand why my method is "undefined":
puts "Select [1] [2] [3] or [q] to quit"; users_choice = gets.chomp
choices(users_choice)
def choices (choice)
while choice != 'q'
case choice
when '1'
puts "you chose one!"
when '2'
puts "you chose two!"
when '3'
puts "you chose three!"
end
end
end
This is because you are calling method choices, before defining it. Write the code as below:
puts "Select [1] [2] [3] or [q] to quit"
users_choice = gets.chomp
def choices (choice)
while choice != 'q'
case choice
when '1'
break puts "you chose one!"
when '2'
break puts "you chose two!"
when '3'
break puts "you chose three!"
end
end
end
choices(users_choice)
I used break, to exit from the while loop. Otherwise it will create an infinite loop.
def main
puts "Select [1] [2] [3] or [q] to quit"; users_choice = gets.chomp
choices(users_choice)
end
def choices (choice)
while choice != 'q'
case choice
when '1'
puts "you chose one!"
break
when '2'
puts "you chose two!"
break
when '3'
puts "you chose three!"
break
end
end
end
main
The method only needs to be called prior to being executed. Here I wrap the definition in the main method, but only call main after the definition of choices().
I was getting the same error running Ruby in Eclipse working out the App Academy practice exercises. I forgot to add "object." to the supplied test cases. The following syntax works:
#!/usr/bin/ruby
class Prime
# Write a method that takes in an integer (greater than one) and
# returns true if it is prime; otherwise return false.
#
# You may want to use the `%` modulo operation. `5 % 2` returns the
# remainder when dividing 5 by 2; therefore, `5 % 2 == 1`. In the case
# of `6 % 2`, since 2 evenly divides 6 with no remainder, `6 % 2 == 0`.
# More generally, if `m` and `n` are integers, `m % n == 0` if and only
# if `n` divides `m` evenly.
#
# You would not be expected to already know about modulo for the
# challenge.
#
# Difficulty: medium.
def primer(number)
if number < 2
return false
end
i = 10
while i > 1
if number > i && number != i
if number % i == 0
return false
end
end
i -= 1
end
return true
end
end
object = Prime. new
# These are tests to check that your code is working. After writing
# your solution, they should all print true.
puts("\nTests for #primer")
puts("===============================================")
puts('primer(2) == true: ' + (object.primer(2) == true).to_s)
puts('primer(3) == true: ' + (object.primer(3) == true).to_s)
puts('primer(4) == false: ' + (object.primer(4) == false).to_s)
puts('primer(9) == false: ' + (object.primer(9) == false).to_s)
puts("===============================================")

Ruby if/elsif/else

I am attempting to write a game. In the following code, it keeps skipping to the bottom else even if a valid integer is entered. Why?
puts 'You will be O\'s and I will be X\'s'
puts
puts '1,2,X'
puts '4,5,6'
puts '7,8,9'
puts
puts 'Your move...'
puts
moveOne = gets.chomp
if moveOne == 5
puts = '1,2,X'
puts = '4,O,6'
puts = 'X,8,9'
elsif moveOne == 1
puts = 'O,2,X'
puts = '4,5,6'
puts = 'X,8,9'
elsif moveOne == 7
puts = 'X,2,X'
puts = '4,5,6'
puts = 'O,8,9'
elsif moveOne == 9
puts = 'X,2,X'
puts = '4,5,6'
puts = '7,8,O'
elsif moveOne == 2
puts = '1,O,X'
puts = '4,X,6'
puts = '7,8,9'
elsif moveOne == 4
puts = '1,2,X'
puts = 'O,X,6'
puts = '7,8,9'
elsif moveOne == 6
puts = '1,2,X'
puts = '4,X,O'
puts = '7,8,9'
elsif moveOne == 8
puts = '1,2,X'
puts = '4,X,6'
puts = '7,O,9'
else
puts'please enter a number!'
end
puts
puts 'Your move again'
Because chomp is giving you a string, not an integer.
moveOne = gets.chomp.to_i
This is happening because gets.chomp returns a String, not an Integer. if you do:
"a".to_i --> 0 , Which your program might think the user has actually entered a 0.
So, first, you want to make sure that what the user has entered is a number character, even though it's of class String.
Here is what you could do:
1 - Create a method that will check if a String is number-like:
def is_a_number?(s)
s.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
end
2 - If it is number like, just cast it to integer using .to_i
So, your code would look like:
moveOne = gets.chomp
if is_a_number?(moveOne)
number_entered = moveOne.to_i
if number_entered == 5
...
elsif number_entered == 1
...
else
puts "enter a number..."
end

Resources