I'm trying to make a simple algebra calculator app on ruby, but I encountered a problem while coding it. The .split method, which I was using to divide the equation into "sections" (separated by plus and minus signs), did split the equation, eq, with + signs, but it didn't with - signs.
eq = gets.chomp
a = []
a = eq.split("+")
a.each do |n|
case n
when n.include?("-")
a << n.split("-")
end
end
print a[0], ";", a[1]
I used the case when because if I did not, it returned a NoMethod Error. I already made a regular calculator, so I thought this would made a good next-project. I was also wondering if you had any idea to make my code shorter; maybe by creating a method. Below is my regular calculator's code, which I would also like to know how to make shorter.
loop do
print
equation = gets.chomp
if equation.include?"^"
exponent_e = equation.split("^")
result_e = exponent_e[0].to_f ** exponent_e[1].to_f
print " = #{result_e} "
puts
elsif equation.include?"%"
percent_e = equation.split("%")
number = percent_e[0].to_f / 100
result_p = number * percent_e[1].to_f
print " = #{result_p} "
puts
elsif equation.include?"/"
res_d = equation.split("/")
result_d = res_d[0].to_f / res_d[1].to_f
print " = #{result_d} "
puts
elsif equation.include?"*"
res_m = equation.split("*")
result_m = res_m[0].to_f * res_m[1].to_f
print " = #{result_m} "
puts
elsif equation.include?"+"
res_a = equation.split("+")
result_a = res_a[0].to_f + res_a[1].to_f
print " = #{result_a} "
puts
elsif equation.include?"-"
res_s = equation.split("-")
result_s = res_s[0].to_f - res_s[1].to_f
print " = #{result_s} "
puts
else
puts "Input valid equation"
end
end
The argument passed to the split method will split up your string by the argument passed and return an array with everything else all split up.
For example:
"a+b".split("+")
#=> ["a", "b"]
"c-d".split("+")
#=> ["c-d"]
"c-d".split("-")
#=> ["c", "d"]
I would probably refactor the code by either using OOP creating a class Calculator and then creating the methods for each functionality (i.e. plus, minus, divide, etc..). This would make the code more readable and easier to maintain.
Another cool thing to consider is using metaprogramming.
def calculate(fxn, arr_numbers)
if arr_numbers.size == 2
arr_numbers.send(:reduce, fxn)
end
end
Where fxn is a string (i.e. "+", "-", etc..), and arr_numbers is an array of 2 numbers, not strings (i.e. [2, 5])
You can expand this to take multiple numbers as well or add other functionality..
Related
new to Ruby, new to coding in general...
I'm trying to add new elements into my hash, incrementing the value when necessary. So I used Hash.new(0) and I'm trying to add new values using the "+=" symbol, but when I do this I get an error message -
"/tmp/file.rb:6:in `+': String can't be coerced into Integer (TypeError)
from /tmp/file.rb:6:in `block in stockList'
from /tmp/file.rb:3:in `each'
from /tmp/file.rb:3:in `each_with_index'
from /tmp/file.rb:3:in `stockList'
from /tmp/file.rb:24:in `<main>'
"
Here's my code:
def stockList(stock, cat)
hash = Hash.new(0)
stock.each_with_index do |word, i|
if cat.include?(word[i])
char = word[i]
hash[char] += num(word)
end
end
new_arr = []
hash.each do |k, v|
new_arr.push(k,v)
end
return new_arr
end
def num(word)
nums = "1234567890"
word.each_char.with_index do |char, i|
if nums.include?(char)
return word[i..-1]
end
end
end
puts stockList(["ABAR 200", "CDXE 500", "BKWR 250", "BTSQ 890", "DRTY 600"], ["A", "B"])
Does anyone know why this is happening?
It's a codewars challenge -- I'm basically given two arrays and am meant to return a string that adds the numbers associated with the word that starts with the letter(s) listed in the second array.
For this input I'm meant to return " (A : 200) - (B : 1140) "
Your immediate problem is that num(word) returns a string, and a string can't be added to a number in the line hash[char] += num(word). You can convert the string representation of a numeric value using .to_i or .to_f, as appropriate for the problem.
For the overall problem I think you've added too much complexity. The structure of the problem is:
Create a storage object to tally up the results.
For each string containing a stock and its associated numeric value (price? quantity?), split the string into its two tokens.
If the first character of the stock name is one of the target values,
update the corresponding tally. This will require conversion from string to integer.
Return the final tallies.
One minor improvement is to use a Set for the target values. That reduces the work for checking inclusion from O(number of targets) to O(1). With only two targets, the improvement is negligible, but would be useful if the list of stocks and targets increase beyond small test-case problems.
I've done some renaming to hopefully make things clearer by being more descriptive. Without further ado, here it is in Ruby:
require 'set'
def get_tallies(stocks, prefixes)
targets = Set.new(prefixes) # to speed up .include? check below
tally = Hash.new(0)
stocks.each do |line|
name, amount = line.split(/ +/) # one or more spaces is token delimiter
tally[name[0]] += amount.to_i if targets.include?(name[0]) # note conversion to int
end
tally
end
stock_list = ["ABAR 200", "CDXE 500", "BKWR 250", "BTSQ 890", "DRTY 600"]
prefixes = ["A", "B"]
p get_tallies(stock_list, prefixes)
which prints
{"A"=>200, "B"=>1140}
but that can be formatted however you like.
The particular issue triggering this error is that your def num(word) is essentially a no-op, returning the word without any change.
But you actually don't need this function: this...
word.delete('^0-9').to_i
... gives you back the word with all non-digit characters stripped, cast to integer.
Note that without to_i you'll still receive the "String can't be coerced into Integer" error: Ruby is not as forgiving as JavaScript, and tries to protect you from results that might surprise you.
It's a codewars challenge -- I'm basically given two arrays and am
meant to return a string that adds the numbers associated with the
word that starts with the letter(s) listed in the second array.
For this input I'm meant to return " (A : 200) - (B : 1140) "
This is one way to get there:
def stockList(stock, cat)
hash = Hash.new(0)
stock.each do |word|
letter = word[0]
if cat.include?(letter)
hash[letter] += word.delete('^0-9').to_i
end
end
hash.map { |k, v| "#{k}: #{v}" }
end
Besides type casting, there's another difference here: always choosing the initial letter of the word. With your code...
stock.each_with_index do |word, i|
if cat.include?(word[i])
char = word[i]
... you actually took the 1st letter of the 1st ticker, the 2nd letter of the 2nd ticker and so on. Don't use indexes unless your results depend on them.
stock = ["ABAR 200", "CDXE 500", "BKWR 250", "BTSQ 890", "DRTY 600"]
cat = ["A", "B"]
I concur with your decision to create a hash h with the form of Hash::new that takes an argument (the "default value") which h[k] returns when h does not have a key k. As a first step we can write:
h = stock.each_with_object(Hash.new(0)) { |s,h| h[s[0]] += s[/\d+/].to_i }
#=> {"A"=>200, "C"=>500, "B"=>1140, "D"=>600}
Then Hash#slice can be used to extract the desired key-value pairs:
h = h.slice(*cat)
#=> {"A"=>200, "B"=>1140}
At this point you have all the information you need to display the result any way you like. For example,
" " << h.map { |k,v| "(#{k} : #{v})" }.join(" - ") << " "
#=> " (A : 200) - (B : 1140) "
If h before h.slice(*cat) is large relative to h.slice(*cat) you can reduce memory requirements and probably speed things somewhat by writing the following.
require 'set'
cat_set = cat.to_set
#=> #<Set: {"A", "B"}>
h = stock.each_with_object(Hash.new(0)) do |s,h|
h[s[0]] += s[/\d+/].to_i if cat_set.include?(s[0])
end
#=> {"A"=>200, "B"=>1140}
Can you please tell me why it is passing nil to check method? I am getting error main.rb:5:in `check': undefined method `%' for nil:NilClass (NoMethodError)
my_array = Array.new
$output = String.new
def check(n)
if n%3 == 0
$output = $output + 'Pop '
elsif n.even?
$output = $output + 'Crackle '
elsif n.odd?
$output = $output + 'Snap '
end
end
for x in 1..6
my_array[x] = gets.chomp.to_i
end
my_array.each { |x| check(x) }
puts $output.my_array
The reason you are getting a nil in the beginning of the array is that you are manually setting the keys in the array which creates a hole since arrays are 0 indexed in Ruby:
ary = Array.new
ary[1] = "a"
ary[2] = "b"
ary[3] = "c"
# => [nil, "a", "b", "c"]
While you could salvage this code with:
my_array = Array.new
$output = String.new
def check(n)
if n%3 == 0
$output = $output + 'Pop '
elsif n.even?
$output = $output + 'Crackle '
elsif n.odd?
$output = $output + 'Snap '
end
end
for x in 0..5
my_array[x] = gets.chomp.to_i
end
my_array.each { |x| check(x) }
puts $output.my_array
A more idiomatically correct way to write this in Ruby is:
str = 5.times.map do
n = gets.chomp.to_i
if n%3 == 0
'Pop'
elsif n.even?
'Crackle'
elsif n.odd?
'Snap'
end
end.join(" ")
puts str
for String.new and Array.new are rarely used if ever used. Use blocks instead of methods unless you're planning to reuse it later. In Ruby you can use the methods from Enumerable to both iterate over and transform arrays, hashes, ranges and other types of objects so there rarely is a reason to iterate and modify an external variable like in other languages.
With for x in 0..5 you would then have
t.rb:21:in `<main>': undefined method `my_array' for "":String (NoMethodError)
because my_array is not a method that you can send to $output.
There are many ways to do the same thing in Ruby.
my_array = []
def check(n)
case
when n % 3 == 0
'Pop'
when n.even?
'Crackle'
when n.odd?
'Snap'
else 'boom !' # not necessary in this particular case
end
end
(1..6).each do | i |
print "#{i} Enter a number > "
my_array << gets.to_i
end
puts my_array.collect { |e| check(e) }.join(' ')
Execution :
$ ruby t.rb
1 Enter a number > 44
2 Enter a number > 66
3 Enter a number > 30494
4 Enter a number > 383849
5 Enter a number > 2234
6 Enter a number > 4333
Crackle Pop Crackle Snap Crackle Snap
Don't use global variables, like $output. In the ancient (imperative programming style) languages, it was a common bug to inadvertently modify a variable accessible from anywhere.
The object oriented paradigm has been invented to isolate variables (encapsulated in an
object) to make it more difficult to modify them accidentally.
You could have use an instance variable :
#output = ''
if n%3 == 0
#output << 'Pop '
but beeing defined in the special 'main' object, it is not protected against unwanted access.
chomp is not necessary before to_i, see this post
Use iterators instead of loops. for is imperative style (C, Java), which imposes you to manage
the begin and end indexes. In an object oriented language, you simply send an iterate message to a
collection object, which takes cares of the internal details.
if and case are expressions which return the last computed value. check() returns that value.
Your my_array.each { |x| check(x) } mutates the variable $output and returns no result. In a big program, a later maintenance could insert some processing that modifies $output before you use it (bug).
The functional programming paradigm (Scala, Elixir, Kotlin) tends to use immutable variables and use functions to transform data.
The new my_array.collect { |e| check(e) }.join(' ') iterates over my_array, transforms each element calling the function check(), produces a new (immutable) collection with these transformed elements, which is then transformed by the function join() to produce the final result.
You have
for x in 1..6
my_array[x] = gets.chomp.to_i
end
Which populates the array from indexes 1 through 6, all arrays begin at index 0 so, in your method
my_array.each { |x| check(x) }
The .each method will iterate through each element of the array, starting at 0, which in this case would be nil because you never assigned a value to that index, you could change your range to
for x in 0..6
my_array[x] = gets.chomp.to_i
end
And that would work, remember to use 2 dots and not 3, as
0..6
0...6
are different, the first one is inclusive, the second one is exclusive.
You can check up more about ranges here
I am attempting to create a Caesar Cipher in Ruby for my computer science class. My friend was able to create part of the code:
def cipher(word, n)
new_word = ""
word.each_char do |i|
n.times do
if(i == "z")
i = "a"
next
elsif(i == "Z")
i = "A"
next
end
i.next!
i == "%" ? i = " " : ""
end
new_word += i
end
puts new_word
end
cipher("phrase", 5)
Where the last line is where you would put the phrase you want to scramble, and the number is how much you want to scramble it by. One of the requirements is that we use gets.chomp to specify a phrase and amount to scramble by without editing the .rb file itself. So I came up with this:
puts "What would you like to scramble?"
word = gets.chomp
puts "How much would you like to scramble that?"
n = gets.chomp
def cipher(word, n)
new_word = ""
word.each_char do |i|
n.times do
if(i == "z")
i = "a"
next
elsif(i == "Z")
i = "A"
next
end
i.next!
i == "%" ? i = " " : ""
end
new_word += i
end
puts new_word
end
cipher(word, n)
And I get the following error outputed when run in Terminal:
some.rb:10:in `block in cipher': undefined method `times' for "5":String (NoMethodError)
from some.rb:9:in `each_char'
from some.rb:9:in `cipher'
from some.rb:26:in `<main>'
If someone could help me figure out what I'm doing wrong, that would help me out a lot.
gets.chomp returns a string
word = gets.chomp
So word is a string, as expected, but then you call gets.chomp again, this time to get number of scrabbles that should be applied to the string. So n is a string as well.
n = gets.chomp
When you call the times method on n it's not defined, because it only makes sense on integers. The solution is to convert n to an integer. This should work:
n = gets.chomp.to_i
Update
Documentation on the to_i method on String instances: http://ruby-doc.org/core-2.0.0/String.html#method-i-to_i
Call .to_i on n.
You need to convert that string you got from the user's input into a number before you can run .times on it. .to_i does this for you.
Example:
http://progzoo.net/wiki/Ruby:Convert_a_String_to_a_Number
Did this a while ago, the requirements were only lowercase ASCII alphabet letters, hope you get the general idea to do it your way:
def encrypt(msg, key)
msg.downcase.split("").each_with_index do |char, i|
next if msg[i] == " "
msg[i] = (msg[i].ord + key) > 122 ? (((msg[i].ord + key) % 123) + 97).chr : (msg[i].ord + key).chr
end
msg
end
def decrypt(msg, key)
msg.downcase.split("").each_with_index do |char, i|
next if msg[i] == " "
msg[i] = (msg[i].ord - key) < 97 ? (123 - (97 - (msg[i].ord - key))).chr : (msg[i].ord - key).chr
end
msg
end
gets.chomp return a string, you must convert it to a number in order to call .times method. Change this line n = gets.chomp by n = gets.chomp.to_i
I'm working on a string reconstruction algorithm (a classic in dynamic programming examples, turning space less text into normal spaced text) in Ruby. The code below is pure ruby, you can copy paste and start testing immediately, it's working 80% of the time and tends to break, the larger the dictionary becomes. I've tested it with more then 80k words dictionaries and it works less good, about 70% of the time.
If there's a way to make it work 100% if the word is present in the dictionary, please show me.
Here's the code: (it's well spaced and should be very readable)
# Partially working string reconstruction algo in pure Ruby
# the dictionary
def dict(someWord)
myArray = [" ", "best", "domain", "my", "successes", "image", "resizer", "high", "tech", "crime", "unit", "name", "edge", "times", "find", "a", "bargain", "free", "spirited", "style", "i", "command", "go", "direct", "to", "harness", "the", "force"]
return !!(myArray.index(someWord))
end
# inspired by http://cseweb.ucsd.edu/classes/wi12/cse202-a/lecture6-final.pdf
## Please uncomment the one you wanna use
#
# (all the words used are present in the dictionary above)
#
# working sentences
x = ' ' + "harnesstheforce"
# x = ' ' + "hightechcrimeunit"
#
# non working sentences
# x = ' ' + "findabargain"
# x = ' ' + "icommand"
puts "Trying to reconstruct #{x}"
# useful variables we're going to use in our algo
n = x.length
k = Array.new(n)
s = Array.new(n)
breakpoints = Hash.new
validBreakpoints = Hash.new
begin
# let's fill k
for i in 0..n-1
k[i] = i
end
# the core algo starts here
s[0] = true
for k in 1..n-1
s[k] = false
for j in 1..k
if s[j-1] && dict(x[j..k])
s[k] = true
# using a hash is just a trick to not have duplicates
breakpoints.store(k, true)
end
end
end
# debug
puts "breakpoints: #{breakpoints.inspect} for #{x}"
# let's create a valid break point vector
i=1
while i <= n-1 do
# we choose the longest valid word
breakpoints.keys.sort.each do |k|
if i >= k
next
end
# debug: when the algo breaks, it does so here and goes into an infinite loop
#puts "x[#{i}..#{k}]: #{x[i..k]}"
if dict(x[i..k])
validBreakpoints[i] = k
end
end
if validBreakpoints[i]
i = validBreakpoints[i] + 1
end
end
# debug
puts "validBreakpoints: #{validBreakpoints.inspect} for #{x}"
# we insert the spaces at the places defined by the valid breakpoints
x = x.strip
i = 0
validBreakpoints.each_key do |key|
validBreakpoints[key] = validBreakpoints[key] + i
i += 1
end
validBreakpoints.each_value do |value|
x.insert(value, ' ')
end
puts "Debug: x: #{x}"
# we capture ctrl-c
rescue SignalException
abort
# end of rescue
end
Note that your algorithm fails for strings containing single-character words. This is an off-by-one error. You are ignoring the breakpoints after such words, thus you end up with a word ("abargain") not contained in your dictionary.
Change
if i >= k
next
end
to
if i > k
next
end
or more Ruby-like
next if i > k
Note also that you are running into an endless loop whenever your string contains something which is not a word:
if validBreakpoints[i] # will be false
i = validBreakpoints[i] + 1 # i not incremented, so start over at the same position
end
You better treat this as an error
return '<no parse>' unless validBreakpoints[i] # or throw if you are not in a function
i = validBreakpoints[i] + 1
The problem with "inotifier" is a deficiency of your algorithm. Always choosing the longest word is not good. In this case, the first "valid" breakpoint detected is after the "in" which leaves you the non-word "otifier".
This is probably easy to do! I'm not able envision the loop yet, I was thinking about a nested for loop but not quite sure how to alternate between the two hashes.
Lets say I have a class with a def that containts two hash tables:
class Teststuff
def test_stuff
letters = { "0" => " A ", "1" => " B ", "2" => " C " }
position = {"1" => "one ", "2"=> " two ", "3"=> " three ", "4"=>" four " }
my_array=[0,1,2,2] #this represents user input stored in an array valid to 4 elements
array_size = my_array.size #this represents the size of the user inputed array
element_indexer = my_array.size # parellel assignment so I can use same array for array in dex
array_start_index = element_indexer-1 #give me the ability later to get start at index zero for my array
#for loop?? downto upto??
# trying to get loop to grab the first number "0" in element position "0", grab the hash values then
# the loop repeats and grabs the second number "1" in element position "1" grab the hash values
# the loop repeats and grabs the third number "2" in elements position "2" grab the hash values
# the final iteration grabs the fourth number "2" in elements position "3" grab the hash values
# all this gets returned when called. Out put from puts statement after grabing hash values
# is: **A one B two C three C four**
return a_string
end
end
How do I go about returning string output to the screen like this:
**A one B two C three C four**
or simply letter position letter position...
Thanks for the help, put code up so I can try on my editor!
I think I figured out what it is you want, although I still have no idea what array_size, element_indexer, array_start_index and TestStuff are for.
def test_stuff
letters = { "0" => " A ", "1" => " B ", "2" => " C " }
position = {"1" => "one ", "2"=> " two ", "3"=> " three ", "4"=>" four " }
my_array = [0, 1, 2, 2]
"**#{my_array.map.with_index {|e, i|
"#{letters[e.to_s].strip} #{position[(i+1).to_s].strip}"
}.join(' ')}**"
end
[I took the liberty of reformatting your code to standard Ruby coding style.]
However, everything would be much simpler, if there weren't all those type conversions, and all those superfluous spaces. Also, the method would be much more useful, if it actually had a way to return different results, instead of always returning the same thing, because at the moment, it is actually exactly equivalent to
def test_stuff
'**A one B two C three C four**'
end
Something along these lines would make much more sense:
def test_stuff(*args)
letters = %w[A B C]
position = %w[one two three four]
"**#{args.map.with_index {|e, i| "#{letters[e]} #{position[i]}" }.join(' ')}**"
end
test_stuff(0, 1, 2, 2)
# => '**A one B two C three C four**'
If you don't want to pollute the Object namespace with your method, you could do something like this:
def (TestStuff = Object.new).test_stuff(*args)
letters = %w[A B C]
position = %w[one two three four]
"**#{args.map.with_index {|e, i| "#{letters[e]} #{position[i]}" }.join(' ')}**"
end
TestStuff.test_stuff(0, 1, 2, 2)
# => '**A one B two C three C four**'
You can use enumerators, like this:
l = letters.to_enum
p = position.to_enum
a_string = ''
loop do
a_string << l.next[1] << p.next[1]
end
How about :
a_string = ""
my_array.each_with_index { |x, index|
a_string += letters[my_array[index].to_s] + " " + (position.include?((index+1).to_s) ? position[(index+1).to_s] : "nil")
}