This is really driving me up a wall.
I have an instance method I'm trying to debug, but I'm running into an issue where my puts and gets aren't showing up inside the instance method.
Code:
#! /usr/bin/env ruby
class Calculator
def evaluate(string)
ops = string.split(' ')
ops.map! do |item|
if item.is_a? Numeric
return item.to_i
else
return item.to_sym
end
end
puts "Got #{string}" #Doesn't output
puts "Converted to #{ops}" #This too
opscopy = ops.clone
ops.each.with_index do |item, index|
if item == :* || item == :/
opscopy[index] = ops[index-1].send(item, ops[index+1])
opscopy[index-1] = opscopy[index+1] = nil
end
end
ops = opscopy.compact
puts "After multi/div #{ops}"
ops.each.with_index do |item, index|
if item == :+ || item == :-
opscopy[index] = ops[index-1].send(item, ops[index+1])
opscopy[index-1] = opscopy[index+1] = nil
end
end
puts "After +/- #{opscopy.compact}"
opscopy.compact.first
end
end
item = Calculator.new.evaluate "4 * 2"
puts "#{item} == 8" #Prints :(
Output:
action#X:~/workspace/ruby$ ./calculator.rb
4 == 8
That return in your map! block is where the problem is.
ops.map! do |item|
if item.is_a? Numeric
return item.to_i # returns from method
else
return item.to_sym # returns from method
end
end
You are returning the method in your map! block before the puts is called.
Change the map! block to:
ops.map! do |item|
item.send(item.is_a?(Numeric) ? :to_i : :to_sym)
end
Related
I'm working through these problems and am a little stuck on how to finish this out. This is the RSPEC and what's specifically troubling me is the last "it indents" test:
# # Topics
#
# * method_missing
# * blocks
# * strings
# * hashes
require "13_xml_document"
describe XmlDocument do
before do
#xml = XmlDocument.new
end
it "renders an empty tag" do
#xml.hello.should == "<hello/>"
end
it "renders a tag with attributes" do
#xml.hello(:name => 'dolly').should == "<hello name='dolly'/>"
end
it "renders a randomly named tag" do
tag_name = (1..8).map{|i| ('a'..'z').to_a[rand(26)]}.join
#xml.send(tag_name).should == "<#{tag_name}/>"
end
it "renders block with text inside" do
#xml.hello do
"dolly"
end.should == "<hello>dolly</hello>"
end
it "nests one level" do
#xml.hello do
#xml.goodbye
end.should == "<hello><goodbye/></hello>"
end
it "nests several levels" do
xml = XmlDocument.new
xml.hello do
xml.goodbye do
xml.come_back do
xml.ok_fine(:be => "that_way")
end
end
end.should == "<hello><goodbye><come_back><ok_fine be='that_way'/></come_back></goodbye></hello>"
end
it "indents" do
#xml = XmlDocument.new(true)
#xml.hello do
#xml.goodbye do
#xml.come_back do
#xml.ok_fine(:be => "that_way")
end
end
end.should ==
"<hello>\n" +
" <goodbye>\n" +
" <come_back>\n" +
" <ok_fine be='that_way'/>\n" +
" </come_back>\n" +
" </goodbye>\n" +
"</hello>\n"
end
end
I feel like I understand the problem and the solution would be something like
"<#{method}>\n" + " #{yield}" + "</#{method}>\n"
given my code:
class XmlDocument
#use method missing so that arbitrary methods
#can be called and converted to XML
def method_missing(method, hash=nil, &block)
if (hash == nil && block == nil)
"<#{method}/>"
elsif hash.is_a?(Hash)
#renders tag with attributes (from hash)
my_key = nil
my_val = nil
hash.each do |key, value|
my_key = key
my_val = value
end
"<#{method} #{my_key}='#{my_val}'/>"
else
#passes whatever to between tags including nested methods.
"<#{method}>#{yield}</#{method}>"
end
end
end
My problem is I don't know how to distinguish the "it nests several levels" test from the "it indents" test so I can fit it into my "if" statement. The only thing that seems to distinguish them is the the "it indents" test has
#xml = XmlDocument.new(true)
What does it mean to have "true" as an argument of #new? Is it relevant to my problem?
You can pass an argument to the object when it is initialized. In this case, you want the value to default to false. This way, the code for indents only runs when XmlDocument.new(true) is called.
class XmlDocument
def initialize(indent = false)
#indent = indent
end
def method_missing(method, args=nil, &block)
if #indent == true
#run with indents
else
#run without indents
end
end
end
UPDATE: OK, so I implemented your code, but now the indentation is not showing up! Any ideas what might be wrong? I modified the code so that it would attempt to pass my original test (this is only an exercise so in real life I would not be overriding the XmlDocument class) and here is the modified code:
class XmlDocument
attr_reader :indent_depth, :bool
def initialize(bool = false, indent_depth = 0)
#indent_depth = indent_depth
#bool = bool
end
def method_missing(name, *args)
indentation = ' '*indent_depth
attrs = (args[0] || {}).map { |k, v| " #{k}='#{v}'" }.join(' ')
if block_given?
puts indent_depth
opening = "#{indentation}<#{name}#{attrs}>"
contents = yield(XmlDocument.new(true,indent_depth+1))
closing = "#{indentation}</#{name}>"
bool ? opening + "\n" + contents + "\n" + closing : opening + contents + closing
else
"#{indentation}<#{name}#{attrs}/>"
end
end
end
I'm trying to get the method to pass this test:
it "indents" do
#xml = XmlDocument.new(true)
#xml.hello do
#xml.goodbye do
#xml.come_back do
#xml.ok_fine(:be => "that_way")
end
end
end.should ==
"<hello>\n" +
" <goodbye>\n" +
" <come_back>\n" +
" <ok_fine be='that_way'/>\n" +
" </come_back>\n" +
" </goodbye>\n" +
"</hello>\n"
...but I'm unsure as to where to go with my code, below. I was thinking of using a counter to keep track of how far indented we have to go. I tried some code, but then deleted it because it was getting too messy and I have a feeling that the indentation should not be too complicated to implement.
class XmlDocument
def initialize(bool = false)
#bool = bool
end
def send(tag_name)
"<#{tag_name}/>"
end
def method_missing(meth, arg={}, &block)
arbitrary_method = meth.to_s
tag_string = ''
# 1) test for block
# 2) test for arguments
# 3) test for hash
if block_given? # check for #xml.hello do; #xml.goodbye; end
if yield.class == String # base case: #xml.hello do; "yellow"; end
"<#{arbitrary_method}>#{yield}</#{arbitrary_method}>"
else # in the block we do not have a string, we may have another method
method_missing(yield)
end
elsif arg.empty? # no arguments e.g. #xml.hello
send(arbitrary_method)
else # hash as argument e.g. #xml.hello(:name => 'dolly')
send("#{arbitrary_method} #{arg.keys[0]}='#{arg.values[0]}'")
end
end
end
Your code needs a lot of work - some pointers:
Do not override the send method!
Don't call yield over and over - you don't know what side effects you might cause, not to mention a performance hit - call it once, and remember the return value.
You might want to read up on how to write a DSL (here is a blogpost on the subject), to see how it was done correctly in other places.
Ignoring the above, I will try to answer your question regarding indentation.
In a DSL use case, you might want to use a context object which holds the indentation depth as state:
class Indented
attr_reader :indent_depth
def initialize(indent_depth = 0)
#indent_depth = indent_depth
end
def method_missing(name, *args)
indentation = ' ' * indent_depth
attrs = (args[0] || {}).map { |k, v| "#{k}='#{v}'" }.join(' ')
if block_given?
"#{indentation}<#{name} #{attrs}>\n" +
yield(Indented.new(indent_depth + 1)) +
"\n#{indentation}</#{name}>"
else
"#{indentation}<#{name} #{attrs}/>"
end
end
end
xml = Indented.new
puts xml.hello do |x|
x.goodbye do |x|
x.come_back do |x|
x.ok_fine(:be => "that_way")
end
end
end
# => <hello >
# => <goodbye >
# => <come_back >
# => <ok_fine be='that_way'/>
# => </come_back>
# => </goodbye>
# => </hello>
Don't understand why #nums.pop won't work in the value method. It seems to tell me that it can't do that for nil, but if I just say #nums, it shows that there is indeed something in the array. So then why can't I pop it out?
class RPNCalculator
def initialize
#value = value
nums ||= []
#nums = nums
end
def push(num)
#nums << num
end
def plus
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums.pop + #nums.pop
#nums.push(#value)
end
end
def minus
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums[-2] - #nums[-1]
#nums.pop(2)
#nums.push(#value)
end
end
def divide
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums[-2].to_f / #nums[-1].to_f
#nums.pop(2)
#nums.push(#value)
end
end
def times
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums.pop.to_f * #nums.pop.to_f
#nums.push(#value)
end
end
def value
#nums #Don't understand why #nums.pop won't work here
end
def tokens(str)
str.split(" ").map { |char| (char.match(/\d/) ? char.to_i : char.to_sym)}
end
def evaluate(str)
tokens(str).each do |x|
if x == ":-"
minus
elsif x == ":+"
plus
elsif x == ":/"
divide
elsif x ==":*"
times
else
push(x)
end
end
value
end
end
Error relates to the following part of a spec:
it "adds two numbers" do
calculator.push(2)
calculator.push(3)
calculator.plus
calculator.value.should == 5
end
Error says either:
Failure/Error: calculator.value.should == 5
expected: 5
got: [5] <using ==>
OR if .pop is used
Failure/Error: #calculator = RPNCalculator.new
NoMethodError:
undefined method 'pop' for nil:NilClass
Your initialize method assigning #value = value calls the function at def value which returns #nums which has not yet been created in initialize since #nums is created afterwards with nums ||= []; #nums = nums therefore it's nil. This is why .pop won't work.
You've created #nums as an array with nums ||= [] and you're using it with push and pop so why are you checking for the value with value.should == 5 (Integer) when calling value returns an (Array). You would need to write it like value.first.should == 5 or value[0].should == 5 ... otherwise you should change value to return just the element you want
def value
#nums.pop # or #nums[0], or #nums.first or #nums.last however you plan on using it
end
The problem is #value = value in your initialize method. Fix that then you can add the .pop in value.
EDIT
Also your evaluation is calling methods before you've populated #nums with the values. Then the methods "raise" errors. You can't call minus after only one value has been pushed to #nums.
Here's how I would do the flow for splitting the string
# Multiplication and Division need to happen before addition and subtraction
mylist = "1+3*7".split(/([+|-])/)
=> ["1", "+", "3*7"]
# Compute multiplication and division
mylist = mylist.map {|x| !!(x =~ /[*|\/]/) ? eval(x) : x }
=> ["1", "+", 21]
# Do the rest of the addition
eval mylist.join
=> 22
I realize this isn't exactly how you're going about solving this... but I think splitting by order of mathematical sequence will be the right way to go. So first evaluate everything between (), then only multiplication and division, then all addition and subtraction.
EDIT I just looked into what a RPN Calculator is. So don't mind my splitting recommendation as it doesn't apply.
I am trying to make this code return when called without a block. The uncommented lines at the bottom is what I'm trying to get to return. The first uncommented line should return in tut, second line converted to english and the last should be in english. And why is the line " puts eng " returning up and down and not in sentence form? Thanks for any and all help.
Here's my code:
class Tut
##consonants = ["b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z"]
def is_tut? string
if string =~ /^(([b-df-hj-np-z]ut)|([aeiou\s])|[[:punct:]])+$/i
yield
else
false
end
end
def self.to_tut string
string.each_char do |c|
c += "ut" if ##consonants.find { |i| i == c.downcase }
yield c if block_given?
end
end
def self.to_english string
array = string.split //
array.each do |c|
if ##consonants.find { |i| i == c.downcase }
array.shift
array.shift
end
yield c if block_given?
end
end
end
#Tut.to_tut( "Wow! Look at this get converted to Tut!" ) { |c| print c }
# should output : Wutowut! Lutookut atut tuthutisut gutetut cutonutvuteruttutedut tuto Tututut!
#puts
#puts
tut = Tut.to_tut( "Wow! Look at this get converted to Tut!" )
puts "from return: #{tut}"
puts
#Tut.to_tut( "Wutowut! Lutookut atut tuthutisut gutetut cutonutvuteruttutedut tuto Tututut!" ) { |c| print c }
#should outout : Wutowut! Lutookut atut tuthutisut gutetut cutonutvuteruttutedut tuto Tututut!
#puts
#puts
tut = Tut.to_tut( "Wutowut! Lutookut atut tuthutisut gutetut cutonutvuteruttutedut tuto Tututut!" )
puts "from return: #{tut}"
#puts
#tut_string = ""
#Tut.to_tut( "I'm in tut but I want to be in english." ) { |c| tut_string += c }
#puts tut_string
# should output : I'mut inut tututut bututut I wutanuttut tuto bute inut enutgutlutisuthut.
puts
#Tut.to_english( tut_string ){ |c| print c }
# should output : I'm in tut but I want to be in english.
lan = Tut.to_english( tut )
puts lan
(Opening note: You normally don't want to modify an Enumerable object while iterating over it, since that makes it much harder to read the code and debug it.)
Your to_tut doesn't retain your modifications because the "c" block variable is a copy of the string slice, instead of a reference to part of the string (if it were a ref, you'd be able to use << to append; "+=" still wouldn't work because it reassigns rather than changing the ref). That's just how each_char works, since a String doesn't contain references.
If you wanted to modify the string in place, you'd probably have to count backwards and then insert the 'ut' by index via string#[]= . But that's way complicated so I'll present a couple alternates.
Working to_tut #1:
def self.to_tut string
string.chars.map do |c|
yield c if block_given?
# this must be the last expression the block
if ##consonants.find { |i| i == c.downcase }
c + 'ut'
else
c
end
end.join
end
Working to_tut #2 - this is probably the most ruby-ish way to do it:
def self.to_tut string
string.gsub(/[#{##consonants.join}]/i) {|match|
yield match if block_given?
# this must be the last expression in the block
match + 'ut'
}
end
Your to_english doesn't work because array.shift always removes the first element of the array. Instead, you want to track the current index, and remove 2 chars starting from index+1.
Working to_english:
def self.to_english2 string
array = string.split //
array.each_with_index do |c, idx|
if ##consonants.find { |i| i == c.downcase }
array.slice!(idx+1, 2)
end
yield c if block_given?
end
array.join
end
Regarding why your "puts lan" returns one char per line - it's because your to_english returns an array. You'll want to call join to convert it.
The methods to_tut and to_english are giving you wrong answers when used without a block. This happens because ruby always returns the last value evaluated in your method. In your code that will be the result of the string.each_char for to_tut or array.each for to_english. In both cases, the result contains the original input, which is consequently returned and printed.
As to the puts eng, it prints the array returned by array.each of to_english.
def get_type
x = [{:type=>'A', :patterns=>['foo.*']}, {:type=>'B', :patterns=>['bar.*']}]
name = 'foo.txt'
result = x.each { |item|
item[:patterns].each { |regex|
puts "Checking #{regex} against #{name}"
if !name.match(regex).nil?
puts "Found match: #{item[:type]}"
return item[:type]
end
}
}
end
result = get_type
puts "result: #{result}"
Expected output:
Checking foo.* against foo.txt
Found match: A
result: A
However, all I see is:
Checking foo.* against foo.txt
Found match: A
My current work around is this:
def get_type
x = [{:type=>'A', :patterns=>['foo.*']}, {:type=>'B', :patterns=>['bar.*']}]
name = 'foo.txt'
result = []
x.each { |item|
item[:patterns].each { |regex|
puts "Checking #{regex} against #{name}"
if !name.match(regex).nil?
puts "Found match: #{item[:type]}"
result << item[:type]
end
}
}
result[0] unless result.empty?
end
Why doesn't the first approach work? or maybe it is 'working', I just don't understand why I'm not getting what I'd expect.
May I suggest a refactor? your code looks kind of clunky because you are using each loops (imperative) when you in fact need a map+first (functional). As Ruby enumerables are not lazy this would be inefficient, so people usually build the abstraction Enumerable#map_detect (or find_yield, or find_first, or map_first):
def get_type_using_map_detect(name)
xs = [{:type => 'A', :patterns => ['foo.*']}, {:type => 'B', :patterns => ['bar.*']}]
xs.map_detect do |item|
item[:patterns].map_detect do |regex|
item[:type] if name.match(regex)
end
end
end
This is a possible implementation of the method:
module Enumerable
# Like Enumerable#map but return only the first non-nil value
def map_detect
self.each do |item|
if result = (yield item)
return result
end
end
nil
end
end
Works fine for me. Are you actually invoking it with
result = get_type puts "result: #{result}"
? Because that shouldn't work at all, though I'm assuming there's a linefeed that got eaten when you posted this.