Making ruby calculator run continuously - arrays

I have made a simple calculator in ruby. However after running once the program stops and has to be re-run. I tried the following but it ended in an infinite loop. How can I write this so that it will run until the users tells it to quit?
puts "Please enter two digits separated by operation you would like to calculate:"
input = gets.chomp.split(' ')
while input != nil
if input[1] == "+"
puts input[0].to_i + input[2].to_i
elsif input[1] == "-"
puts input[0].to_i - input[2].to_i
elsif input[1] == "*"
puts input[0].to_i * input[2].to_i
else
puts input[0].to_i / input[2].to_i
end
end

split(' ') returns an array and not nil, so input != nil always evaluates to true, which makes an infinite loop.
You may want to try while input.size == 2, or break the while loop at certain input like:
while true
# ... if / elsif for operators
elsif input[1] == 'exit'
break # end the 'while' loop
end
end

make changes like below:
msg = "Please enter two digits separated by operation you would like to calculate:"
puts msg
while input = gets.chomp.split(' ')
if input[1] == "+"
puts input[0].to_i + input[2].to_i
elsif input[1] == "-"
puts input[0].to_i - input[2].to_i
elsif input[1] == "*"
puts input[0].to_i * input[2].to_i
else
puts input[0].to_i / input[2].to_i
end
puts msg
end

This whole thing is a big mess of Ruby that can be drastically simplified by degrees. The key here is to apply the Ruby way to things and elegant solutions become immediately obvious.
Consider replacing that messy while loop with an actual loop structure. Also name the elements resulting from the split based on their meaning to you:
loop do
puts "Please enter two digits separated by operation you would like to calculate:"
left, operator, right = gets.chomp.split(' ')
result = case (operator)
when '+'
left.to_i + right.to_i
when '-'
left.to_i - right.to_i
when '*'
left.to_i * right.to_i
when '/'
left.to_i / right.to_i
end
puts result
end
Now this looks like a lot of repetition, the only thing changing here is the operator. Since Ruby is a highly dynamic language we can actually collapse this some more:
loop do
puts "Please enter two digits separated by operation you would like to calculate:"
left, operator, right = gets.chomp.split(' ')
result = case (operator)
when '+', '-', '*', '/'
left.to_i.send(operator, right.to_i)
end
puts result
end
Now there's still some duplication here. What about trying to reduce this further and also allow for more flexibility:
loop do
puts "Please enter digits separated by the operations you would like to calculate:"
values = gets.chomp.split(' ')
while (values.length > 2)
left = values.shift.to_i
operator = values.shift
right = values.shift.to_i
values.unshift(left.send(operator, right))
end
puts values[0]
end
That means you can have arbitrarily long lists of numbers to add together. It's not much harder to make this tokenize your string into numbers and non-number components, but that's something you can try on your own.

A recursive approach:
def recurs_calc
puts "Please enter two digits separated by operation you would like to calculate:"
input = gets.chomp.split(' ')
if input[1] == "+"
puts input[0].to_i + input[2].to_i
recurs_calc
elsif input[1] == "-"
puts input[0].to_i - input[2].to_i
recurs_calc
elsif input[1] == "*"
puts input[0].to_i * input[2].to_i
recurs_calc
elsif input[1] == '/'
puts input[0].to_f / input[2].to_i
recurs_calc
elsif input[0] == 'exit'
exit
else
puts 'please enter two numbers with SPACES i.e. 4 + 4 then enter'
recurs_calc
end
end
recurs_calc
Here we recall recurs_calc as per conditions and exit when the user types 'exit'. Note I've also used to_f in the / branch to get a more accurate result and included a final branch that takes into account an 'incorrect' input. Try running it.
Just another approach using eval that you may find helpful although this is probably cheating a bit. Also it's considered bad practice to use eval for security reasons. Also a special thanks to #nicael for the string substitution:
loop do
puts "Enter calculation"
input = gets.chomp
if input == 'exit'
exit
else
p eval (input.gsub(/(\d+)/, '\1.0'))
end
end

Related

Best methods to extract substring into arguments and allow arguments with multiple "main split character" indicated by a character in Lua?

Let's say I have this string
"argument \"some argument\""
which prints out as
argument "some argument"
And now as example, let's say I would want to split it using the "space character" as the "main split character" to split, but allow me to indicate with a character which part to have multiple arguments of. So let's say it would be the quotation mark ". At the it should extract the arguments like so
> [1] = "argument"
> [2] = "some argument"
I am able to do this with the code here:
ExtractArgs = function(text,splitKey)
local skip = 0
local arguments = {}
local curString = ""
for i = 1, text:len() do
if (i <= skip) then continue end
local c = text:sub(i, i)
if (c == "\"") and (text:sub(i-1, i-1) ~= "\\") then
local match = text:sub(i):match("%b\"\"")
if (match) then
curString = ""
skip = i + match:len()
arguments[#arguments + 1] = match:sub(2, -2)
else
curString = curString..c
end
elseif (c == splitKey and curString ~= "") then
arguments[#arguments + 1] = curString
curString = ""
else
if (c == splitKey and curString == "") then
continue
end
curString = curString..c
end
end
if (curString ~= "") then
arguments[#arguments + 1] = curString
end
return arguments
end;
print(ExtractArgs("argument \"some argument\"", " "))
So what's the issue?
I am wondering for better ways. This current way, doesn't allow me to use " as it is being used to process.
Here is another way: Extract substring inbetween quotation marks, but skip \" and turn it into " instead in Lua
but I am wondering if there are even better ways.
Ways that would allow me to have something like this:
argument " argument2" "some argument" "some argument with a quotation mark " inside of it" another_argument"
to turn into something like this
> [1] = 'argument'
> [2] = 'argument2"'
> [3] = 'some argument'
> [4] = 'some argument with a quotation mark " inside of it'
> [5] = 'another_argument"'
The example that I made right now, might sound impossible because of no character really indicating what should be processed and what not.
But I am looking for better ways to extract substrings as arguments while allowing to have arguments as one argument that would normally just get split into arguments.
So if it wouldn't be for the ", \"some arguments\" would have just splitted into "some" and "arguments" instead of "some arguments".
Maybe a method that uses something like Lua does with ' " could be a way.
Because it would be probably impossible to have a perfect working system, that would turn this input """ into " as an extracted argument. I would imagine it just extracting it into this "", an empty string.
However, not if it would look like this '"'. But then the other question would be, how could you allow ''' to extract into '. I would imagine it working if it would be so "'". But this is getting too complicated.
I am wondering, is there even a better way to extract arguments, but allow certain special operation like keeping multiple arguments into one argument, by wrapping it around something, or just in any way?

How can I loop through a concatenation

I'm trying to split a word with a '.' after every letter which I was successful in doing, however, my next step is to split the current splitted words again but I dont want to repeat variations.
my expected output is this:
input word: amaxa
first loop will give - a.maxa, am.axa, ama.xa, amax.a
then the next split should give - a.m.axa, a.ma.xa,a.max.a,
Essentially I wanted different variations of the word with '.' being added when a full loop had been exhausted however, my main issue was I had '.'s appearing next to each other and I tried to use a continue statement but it didn't work. Below is my source code
print("enter email without #gmail.com")
word = input("word: ")
lenofword = len(word) - 1
for i in range(0,lenofword):
sliceword = word[:1+i] + "." + word[1+i:]
lis.append(sliceword)
print(sliceword)
for j in range(0,lenofword):
slices = sliceword[:1+j] + "." + sliceword[j+1:]
if slices[i:] == slices[:]:
continue
print(slices)
ouput given:
enter email without #gmail.com
word: amax
a.max
am.ax
a.m.ax
am..ax
am..ax
ama.x
a.ma.x
am.a.x
ama..x
basically i want to get rid of the 'am..ax' and 'ama..x'
It is easier to choose the two locations where the dots should be placed (may be the same location for the single dot case). For example:
for i in range(1, lenofword + 1):
for j in range(i, lenofword + 1):
sliced = ".".join(filter(None, [word[:i], word[i:j], word[j:]]))
print(sliced)
This will print the following for the input word "amax":
a.max
a.m.ax
a.ma.x
am.ax
am.a.x
ama.x

Ruby Array Elements

I am trying to create password Generate in ruby. At the moment all is working just got stuck at the final piece of generating the password.
I asked user if he/she would like the password to include numbers, lowercase or uppercase.
If YES, user will enter 1 and 0 for NO.
I used the code below to generate password if everything is 1. Meaning user want to include numbers, lowercase and uppercase.
if numbers == 1 && lowercase == 1 && uppercase == 1
passGen = [(0..9).to_a + ('A'..'Z').to_a + ('a'..'z').to_a].flatten.sample(10)
end
p passGen
This works 90% of the time. 10% of the time the generated password will not include say any numbers. But everything else present. I am not sure if this is because of the size or length of Array from which the password is sampled.
Anyway lets go to the main problem below
Here is the problem, I am struggling to write the code to generate password if one or more of input is 0. That's if user don't want to include numbers. Or no numbers and uppercase etc . As I can't predict what user may want or not want. I need help on this please.
Thank you.
You will need to make your input array more dynamic:
passGen = []
passGen += (0..9).to_a if numbers == 1
passGen += ('A'..'Z').to_a if uppercase == 1
passGen += ('a'..'z').to_a if lowercase == 1
passGen.sample(10).join
Now, to tackle your other issue with missing characters - this is caused as you are simply taking 10 random characters from an array. So it can just take, for example, all digits.
To tackle this you need to get one character from each generator first and then generate the remaining characters randomly and shuffle the result:
def generators(numbers:, lowercase:, uppercase:)
[
(0..9 if numbers),
('A'..'Z' if uppercase),
('a'..'z' if lowercase)
].compact.map(&:to_a)
end
def generate_password(generators:, length:, min_per_generator: 1)
chars = generators.flat_map {|g| Array.new(min_per_generator) { g.sample }}
chars += Array.new(length - chars.length) { generators.sample.sample }
chars.shuffle.join
end
gens = generators(numbers: numbers == 1, uppercase == 1, lowercase: lowercase == 1)
Array.new(10) { generate_password(generators: gens, length: 10) }
The code doesn't know it needs to include a digit/letter from every group. The sample takes random signs and since you a basically sampling 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz there is a possibility that all the signs will not be digits.
The easiest way to fix it is to check if a sign from every group is in the "password" and then replace a random sign with a sign from group that is not present.
If I were to program this I would do it like that
def random_from_range(range)
range.to_a.sample.to_s
end
def passGen(numbers, lowercase, uppercase)
result = ''
possibleSigns = []
if numbers == 1
range = (0..9)
result += random_from_range(range)
possibleSigns += range.to_a
end
if lowercase == 1
range = ('A'..'Z')
result += random_from_range(range)
possibleSigns += range.to_a
end
if uppercase == 1
range = ('a'..'z')
result += random_from_range(range)
possibleSigns += range.to_a
end
desired_lenth = 10
while result.length < desired_lenth
result += possibleSigns.sample.to_s
end
result
end
puts passGen(1,1,1)
By saying (0..9).to_a + ('A'..'Z').to_a + ('a'..'z').to_a, you're creating an Array of 10 + 26 + 26 = 62 elements, and then you pick only 10 elements out of it.
At your place I'd wrap password generation around an until block:
def generate_password_with_digits_and_caps
[(0..9).to_a + ('A'..'Z').to_a + ('a'..'z').to_a].flatten.sample(10).join
end
passGen = ''
until passGen.match(/[A-Z]/) && passGen.match(/[a-z]/) && passGen.match(/\d/)
passGen = generate_password_with_digits_and_caps
end
This could also work (closer to your snipppet):
if numbers == 1 && lowercase == 1 && uppercase == 1
passGen = ''
until passGen.match(/[A-Z]/) && passGen.match(/[a-z]/) && passGen.match(/\d/)
passGen = [(0..9).to_a + ('A'..'Z').to_a + ('a'..'z').to_a].flatten.sample(10).join
end
end
Start with something simple and stupid:
passGen = (('0'..'9').to_a.sample(1)+ ('A'..'Z').to_a.sample(1)+('a'..'z').to_a.sample(8).shuffle).join
Technically speaking, this already fulfills your requirement. From the viewpoint of aesthetics and security, the disadvantage here is that the number of upper case characters is always 8. A more elegant solution would be to find three non-zero integers which add up to 10, and can be used as the arguments for the sample call. Also, if no numbers are requested, you simply pass 0 as argument to sample.
Since this exceeds the scope of your question, and I don't even know whether you want to go so far, I don't elaborate on this here further.

Change users input but store original input separately

My assignment is to take the user's input and change every 'c' and 's' to 's' and 'th'.
After that it asked me to store the original user's input so it would not get changed.
I can't figure it out in Ruby.
print "What can we do for you?"
user_input = gets.chomp
user_input.downcase!
if user_input.length == 0
puts "Well you will have to write something...!"
elsif user_input.include?("s") || user_input.include?("c")
puts "We got ourselves some 's's and some 'c's"
user_input.gsub!(/s/, "th")
user_input.gsub!(/c/, "s")
puts "The changed version: '#{user_input}!'"
else
print "Nope, no 's' or 'c' found"
end
Don’t use inplace version of gsub:
# user_input.gsub!(/s/, "th")
# user_input.gsub!(/c/, "s")
gsubbed = user_input.gsub(/s/, "th").gsub(/c/, "s")
puts "Changed version: '#{gsubbed}'"
puts "Original version: '#{user_input}'"
Note the lack of exclamation signs in functions names.

Array to String with specific format

I have a hash like this:
#password_constraints = {
length: 'Must be at least 6 character long.',
contain: [
'one number',
'one lowercase letter',
'one uppercase letter.'
]
}
And I want to write a method that returns an human-readable string built from this hash like this one:
Must be at least 6 character long. Must contain: one number, one lowercase letter and an uppercase letter.
Currently, I have a very verbose method with an each that iterates over the #password_constraints[:contain] array and has several conditions to check if I have to put a ,, or an and or nothing.
But I want almost the same behavior as join(', ') but the last delimiter must be and.
I'm looking for some sort of solution like this one with the special join:
def password_constraints
#password_constraints[:length] << ' Must contain.' << #password_constraints[:contain].join(', ')
end
Is there a way to make this more compact or Ruby-like?
You can use the splat operator to split the array and just check the case if there's only one string:
def message(constraints)
*others, second_last, last = constraints[:contain]
second_last += " and #{last}" unless last.nil?
"#{constraints[:length]} Must contain: #{(others << second_last).join(', ')}"
end
#password_constraints = {
length: 'Must be at least 6 character long.',
contain: [
'one number',
'one lowercase letter',
'one uppercase letter.'
]
}
message(#password_constraints)
# => "Must be at least 6 character long. Must contain: one number, one lowercase letter and one uppercase letter."
# if #password_constraints[:contain] = ['one lowercase letter', 'one uppercase letter.']
message(#password_constraints)
# => "Must be at least 6 character long. Must contain: one lowercase letter and one uppercase letter."
# if #password_constraints[:contain] = ['one lowercase letter']
message(#password_constraints)
# => "Must be at least 6 character long. Must contain: one lowercase letter"
I would use a monkey patch here for succinctness (yeah my join_requirements method could probably look a little nicer).
class Array
def join_requirements
result = ""
self.each do |requirement|
if requirement == self.last
result << "and " << requirement
else
result << requirement + ", "
end
end
result
end
end
Then in your code you just see this:
contain = ['one number','one lowercase letter','one uppercase letter.']
puts contain.join_requirements
# => one number, one lowercase letter, and one uppercase letter.
Here's a fun little shorthand trick in Ruby that you can do with arrays to cut out the need for a verbose join.
def password_constraints
#password_constraints[:length] << ' Must contain.' << #password_constraints[:contain] * ", "
end
The Array class' * operator explicitly checks for a string second operand, then converts to calling join under the covers.

Resources