Created
April 22, 2016 05:32
-
-
Save bbuck/09aea975bdd83efdc313a528cba15e51 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is the challenge -- you should figure out what you | |
# don't know here. | |
class Calculator | |
class << self | |
def exit_command(cmd = nil) | |
if cmd.nil? | |
@exit_command || "exit" | |
else | |
@exit_command = cmd | |
end | |
end | |
def tasks | |
@tasks ||= {} | |
end | |
def define_task(name, &block) | |
tasks[name.to_sym] = block | |
end | |
def perform(task_name, args) | |
tasks[task_name].call(*args) | |
end | |
def run | |
loop do | |
task = ask_user_for_task | |
break if task.nil? | |
numbers = ask_user_for_numbers | |
puts perform(task.to_sym, numbers) | |
end | |
end | |
def ask_user_for_task | |
loop do | |
print "Please select one of the following options (or '#{exit_command}' to quit)\n#{tasks.keys.join(", ")}\n >> " | |
task = gets.chomp.to_sym | |
if tasks.has_key?(task) | |
break task | |
elsif task.to_s == exit_command | |
break | |
else | |
puts "Invalid response\n" | |
end | |
end | |
end | |
def ask_user_for_numbers | |
[].tap do |numbers| | |
2.times do | |
print "Enter a number >> " | |
numbers << gets.to_i | |
end | |
end | |
end | |
end | |
end | |
Calculator.define_task("add") do |a, b| | |
"#{a} + #{b} = #{a + b}" | |
end | |
Calculator.define_task("sub") do |a, b| | |
"#{a} - #{b} = #{a - b}" | |
end | |
Calculator.define_task("mul") do |a, b| | |
"#{a} * #{b} = #{a * b}" | |
end | |
Calculator.run | |
# It's not _as_ magical as I intended... but it's still pretty neat. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# First and foremost, you probably want to use a class and or module for some | |
# of this. I imagine you probably have not learned much about these constructs | |
# here. | |
# Your naming needs work. The primary goal of Ruby code as I've seen it is | |
# readability. You want things to be named in such a way that they really just | |
# imply exactly what they're doing immediately. You're off to a good start, | |
# but maybe `ask_for_task`, `prompt_for_task` or something a bit more readable | |
# (as a sentence). | |
# | |
# Another note, this method does more than ask for a task. This is usually a | |
# bad sign. Now you can find me preaching _against_ strict adherence to the | |
# single responsiblity principle but in some cases it's a good idea. You don't | |
# want changes to your input fetching also working closely to your input | |
# processing. | |
def ask_task | |
# People have mentioned it, but this idiom is better with a `loop do` | |
# construct since continues _only_ purpose is to break out of the loop and | |
# (most langauges have it) there is a keyword to do this actual thing called | |
# `break`. | |
continue = true | |
until !continue | |
# This was the first "major" issue I had with your code. Ruby lets you | |
# blindly do things like this without realizing what you've done. And this | |
# is an issue I have with the language. You'll just have to learn to watch | |
# for moments like this -- so what's so wrong here? You're creating a new | |
# Array instance every loop iteration along with three new String instances | |
# as well, check this output: | |
# | |
# [70335426081500, 70335426081560, 70335426081540, 70335426081520] | |
# [70335426081120, 70335426081200, 70335426081180, 70335426081140] | |
# [70335426080880, 70335426080940, 70335426080920, 70335426080900] | |
# [70335426080600, 70335426080700, 70335426080660, 70335426080640] | |
# [70335426080300, 70335426080360, 70335426080340, 70335426080320] | |
# [70335426079980, 70335426080060, 70335426080020, 70335426080000] | |
# [70335426079700, 70335426079760, 70335426079740, 70335426079720] | |
# [70335426079380, 70335426079460, 70335426079440, 70335426079400] | |
# [70335426079040, 70335426079100, 70335426079080, 70335426079060] | |
# [70335426078540, 70335426078600, 70335426078580, 70335426078560] | |
# | |
# I ran this code to generate that: | |
# | |
# 10.times do | |
# arr = ["one", "two", "three"] | |
# ids = arr.map do |i| | |
# i.object_id | |
# end | |
# ids.unshift(arr) | |
# p ids | |
# end | |
# | |
# So you can see, from this list of object ID, the Array and all strings | |
# have different object IDs (which is auto assigned to instances in Ruby) | |
# so you can see you're creating these _every_ iteration. It's best to avoid | |
# this. You probably should move this outside the loop or a constant. | |
tasks = ["add", "subtract", "multiply"] | |
# If you're ending with \n probably best to use `puts` which will handle | |
# this for you. | |
print "\nWhat would you like to do? (type 'exit' to quit the program)\n" | |
# So the name `string` for a String means nothing. I literally had no idea | |
# what you were doing with this until I continued reading. Now, while this | |
# isn't always preventable -- in this case it could have been. `task_string` | |
# or `task_list` or something _other_ than it's type. | |
string = "" | |
# It's been driven in the ground already, but for the sake of providing my | |
# opinions for you I'm going to repeat it. This is better as #join which | |
# optionally takes a string ot join the values in the list on (by default | |
# it's `""`). | |
tasks.each do |task| | |
string += "#{task}, " | |
end | |
# If you have a string and you're not modifying then you shouldn't interpolate | |
# it into another string. This line is equivalen to `puts "" + string` which | |
# creates an instance of a string that isn't even necessary! Again, you're | |
# going to want to keep an eye on situations like these. | |
puts "#{string}" | |
# It's been pointed out before you don't need this variable, but I'd like | |
# to mention there are better names than `answer`. Answer to what? The user | |
# gave you a task and you asked for a task. This is probably a task, and | |
# naming it `task` would be much more clear. | |
answer = gets.chomp | |
# just to reiterate here, the best case for this is avoid the answer variable | |
# and go with `gets.chomp.downcase` | |
case answer.downcase | |
# I'm chalking this one up to lack of experience with Ruby, but this doesn't | |
# do what you expect. Inputting "e" doesn't fire this case (I'm actually | |
# surprised this even worked for "exit", to be honest). The way you should | |
# structure this so it works as you expect is with commas! | |
# | |
# when "exit", "e", "quit" | |
when "exit" || "e" || "quit" | |
continue = false | |
when "add" | |
# These methods are repetitive. Notice anything? All three of them fit | |
# this exact format: `calc_{task_name}(ask_numbers)` | |
# | |
# Anytime you find repition you probably have the ability to refactor to | |
# reduce code. | |
puts calc_add(ask_numbers) | |
when "subtract" | |
puts calc_subtract(ask_numbers) | |
when "multiply" | |
puts calc_multiply(ask_numbers) | |
else | |
puts "Sorry, try again" | |
end | |
end | |
end | |
# Naming again here. What about numbers are you asking? `ask_for_numbers`? | |
def ask_numbers | |
# So, again, already run into the ground, but this should be `2.times do` | |
i = 0 | |
numbers = [] | |
print "\nType two numbers\n\n" | |
while i < 2 | |
print "Enter a number: " | |
# Again, don't need answer here, you an push this directly into the array. | |
answer = gets.chomp.to_i | |
# While using #push on an array is perfeclty legal and there is nothing | |
# wrong with it. The more common method to use here is `<<` so: | |
# | |
# numbers << gets.to_i | |
numbers.push(answer) | |
i += 1 | |
end | |
# You can write this without need for this -- I'll not discuss it comments | |
# but in my "here's a rewrite" version. | |
return numbers | |
end | |
# The name `array` here is another "name it what it is" example. Instead of | |
# calling it `array` I'm really suprised you didn't stick with `numbers`. There | |
# are two approach here, the first as mentioned is using a splat operator and | |
# defining this as a method that takes two arguments. | |
def calc_add(array) | |
a = array.first | |
b = array.last | |
c = (a + b) | |
answer = "#{a} + #{b} = #{c}" | |
return answer | |
end | |
def calc_subtract(array) | |
a = array.first | |
b = array.last | |
c = (a - b) | |
answer = "#{a} - #{b} = #{c}" | |
return answer | |
end | |
def calc_multiply(array) | |
a = array.first | |
b = array.last | |
c = (a * b) | |
answer = "#{a} * #{b} = #{c}" | |
return answer | |
end | |
puts ask_task |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# We start with a class that namespaces our work. | |
class Calculator | |
# Here I define this globally. I could have done the fancy %w() blah blah | |
# which I realize a lot of people prefer. But I prefer to keep things | |
# representative of what's going on and %w() never screams "I'm creating an | |
# array here." | |
TASKS = ["add", "sub", "mul"] | |
# This is magical. I'm going to way oversimplify this to explain it for now, | |
# I highly suggest you look it up to get a real idea of what is going on here. | |
# For all intents and purposes this is a way to define class methods in such | |
# a way you that you don't have to prefix everything with `self.` and you | |
# can use standard instance variables as class level values since Ruby's | |
# `@@` variables are extraordinarily odd (you'll find out eventually). | |
class << self | |
# Double parenthisis!! Actually, I'm deconstructing the array with this. | |
# The magic here is that calling this `Calculator.add([1, 2])` assigns | |
# 1 == a and 2 == b. | |
def add((a, b)) | |
# Notice I didn't use `return`. In Ruby the best practice is to avoid | |
# using an explicity return as much as possible. | |
"#{a} + #{b} = #{a + b}" | |
end | |
def sub((a, b)) | |
"#{a} - #{b} = #{a - b}" | |
end | |
def mul((a, b)) | |
"#{a} * #{b} = #{a * b}" | |
end | |
def run | |
loop do | |
task = ask_user_for_task | |
# I used a naked return to return nil as the task here. So now I'm | |
# testing if the task was nil and breaking out of this loop and | |
# exiting the program. | |
break if task.nil? | |
numbers = ask_user_for_numbers | |
puts send(task, numbers) | |
end | |
end | |
def ask_user_for_task | |
loop do | |
print "Please select one of the following options (or 'exit' to quit)\n#{TASKS.join(", ")}\n >> " | |
task = gets.chomp | |
if TASKS.include?(task) | |
# Why break instead of return? In Ruby you can break with values. | |
# And all things in Ruby are expressions (meaning they return values) | |
# which includes a `loop` which will return the value that I break | |
# with. And since `loop` is the last expression in this method it's | |
# return value is automatically going to be returned from this method. | |
# Yay! | |
break task | |
elsif task == "exit" | |
break | |
else | |
puts "Invalid response\n" | |
end | |
end | |
end | |
def ask_user_for_numbers | |
# Enter #tap, just as I promised. This is a "magical" method that can be | |
# called on an object. It takes a block (the do thing after the method | |
# call) and the block takes the object as it's parameter. Inside you can | |
# transform the object as you see fit and the return from tap is -- again | |
# the return object. | |
[].tap do |numbers| | |
2.times do | |
print "Enter a number >> " | |
numbers << gets.to_i | |
end | |
end | |
end | |
end | |
end | |
Calculator.run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment