TDD:
describe RPNCalculator do
attr_accessor :calculator
before do
#calculator = RPNCalculator.new
end
it "adds two numbers" do
calculator.push(2)
calculator.push(3)
calculator.plus
calculator.value.should == 5
end
end
my code :
class RPNCalculator
attr_accessor :calculator
def initialize()
#calculator = []
end
def RPNCalculator(x=0,y=0)
#calculator.push(x)
#calculator.push(y)
#calculator.map {|x,y| x + y }
end
error:
RPNCalculator
adds two numbers (FAILED - 1)
Failures:
1)RPNCalculator adds two numbers
Failure/Error: calculator.push(2)
NoMethodError:
undefined method `push' for #<RPNCalculator:0x000000021b85a8 #calculator=[]>
The problem is in your test where you call calculator.push(2). You are calling that on the object RPNCalculator which does not have a method called push. It is rather the instance variable #calculator within the class, which you instantiated to an Array, which has that method.
Your test should actually be:
it "adds two numbers" do
#calculator.RPNCalculator(2,3).value.should == 5
end
I should point out that it is considered bad form to have a method name that starts with a capital letter.
If I may be so bold as to guess what your test is trying to get at, you are wanting to push numbers into your RPNCalculator and continue to push numbers and get the sum of those numbers at will. If this is indeed the case, you may be looking for something like this:
class RPNCalculator
attr_accessor :calculator
def initialize()
#calculator = []
end
def push(*num)
# Use is_a? in order to ensure anything added to
# the Array can later be added together
#calculator.push(*num) if num.all? {|n| n.is_a? Numeric}
# If you would like this to work with any class that can be added
# then change 'n.is_a? Numeric' to 'n.respond_to? :+'
end
def plus
#calculator.reduce(:+)
end
def value
plus
end
Doing that should make your original test work with no alteration.
Example of usage:
calc = RPNCalculator.new
calc.push(3)
calc.push(2)
calc.plus #=> 5
calc.push(6)
calc.plus #=> 8
calc.push(1,2,3,4)
calc.plus #=> 18
Notice I use the splat operator (the asterisk) in the method definition. I would suggest doing some reading on the topic. It is worth learning and loving.
Try this:
class RPNCalculator
attr_accessor :calculator
def initialize()
#calculator = []
end
def RPNCalculator(x=0,y=0)
#calculator.push(x)
#calculator.push(y)
p #calculator.reduce(&:+)
end
end
cal = RPNCalculator.new
cal.RPNCalculator(10,20) #30
Related
I have this program that basically uses a method called reverse_complement to reverse a string and replaces some characters with other characters; However, if I use the method twice for the same instance it gives me an undefined error. So, puts dna1.reverse_complement.reverse_complement == dna1.nucleotide should give me a true value. However, it gives an undefined method error.
class DNA
attr_reader :nucleotide
def initialize (nucleotide)
#nucleotide = nucleotide
end
def reverse_complement()
#nucleotide.reverse.tr("ATCG", "TAGC")
end
end
dna1 = DNA.new("ATTGCC")
puts dna1.reverse_complement
puts dna1.nucleotide
puts dna2 = dna1.reverse_complement
puts dna1.reverse_complement.reverse_complement == dna1.nucleotide
I think your method works but it's your chaining that's the problem:
puts dna1.reverse_complement.reverse_complement
you've defined reverse_complement to return a string, and you can't call String#reverse_compliment.
Instead, write this:
dna2 = DNA.new(dna1.reverse_compliment)
puts dna2.reverse_compliment == dna1.nucleotide
Why can't I use the same method twice for one instance in ruby?
Because you aren't using it for one instance. You are calling it on dna1, which is an instance of DNA, and then you call it again on the return value of reverse_complement, which is a completely different instance of class String.
Personally, I find it very confusing that a method called reverse_complement would return an object of a completely different type than the one it was called on. I would rather expect it to return a reversed and complementary instance of the same type, i.e. DNA:
class DNA
def initialize(nucleotide)
self.nucleotide = nucleotide.dup.freeze
end
def reverse_complement
self.class.new(#nucleotide.reverse.tr('ATCG', 'TAGC'))
end
def ==(other)
nucleotide == other.nucleotide
end
def to_s
"DNA: #{nucleotide}"
end
protected
attr_reader :nucleotide
private
attr_writer :nucleotide
end
dna1 = DNA.new('ATTGCC')
puts dna1.reverse_complement
# DNA: GGCAAT
puts dna2 = dna1.reverse_complement
# DNA: GGCAAT
puts dna1.reverse_complement.reverse_complement == dna1
# true
Would something like this suffice?:
class DNA < String
def reverse_complement
reverse.tr("ATCG", "TAGC")
end
end
dna1 = DNA.new("ATTGCC")
puts dna1
# ATTGCC
puts dna1.reverse_complement
# GGCAAT
puts dna1.reverse_complement.reverse_complement == dna1
# true
Notes
def reverse_complement
reverse.tr("ATCG", "TAGC")
end
can be written as:
def reverse_complement
self.reverse.tr("ATCG", "TAGC")
end
where self is the current object. In the examples above, self is dna1 which is an instance of DNA.
caveat: inheriting from core classes is a bad idea. This answer will be deleted if OP kindly unaccepts this answer. See link for more details about inheriting from core classes.
What is difference between these two methods in Ruby?
class Mod
def doc(str)
...
end
def Mod::doc(aClass)
...
end
end
Mod::doc()
is a class method, whereas
doc()
is an instance method. Here's an example of how to use both:
class Mod
def doc()
puts 1
end
def Mod::doc()
puts 2
end
end
a = Mod.new
a.doc #=> 1
Mod.doc #=> 2
Here's a question that compares it with
self.doc()
Sandi Metz says in SOLID OOPS concepts from GORUCO that presence of if..else blocks in Ruby can be considered to be a deviation from Open-Close Principle. What all methods can be used to avoid not-urgent if..else conditions? I tried the following code:
class Fun
def park(s=String.new)
puts s
end
def park(i=Fixnum.new)
i=i+2
end
end
and found out that function overloading does not work in Ruby. What are other methods through which the code can be made to obey OCP?
I could have simply gone for:
class Fun
def park(i)
i=i+2 if i.class==1.class
puts i if i.class=="asd".class
end
end
but this is in violation to OCP.
With your current example, and wanting to avoid type detection, I would use Ruby's capability to re-open classes to add functionality you need to Integer and String:
class Integer
def park
puts self + 2
end
end
class String
def park
puts self
end
end
This would work more cleanly when altering your own classes. But maybe it doesn't fit your conceptual model (it depends what Fun represents, and why it can take those two different classes in a single method).
An equivalent but keeping your Fun class might be:
class Fun
def park_fixnum i
puts i + 2
end
def park_string s
puts s
end
def park param
send("park_#{param.class.to_s.downcase}", param)
end
end
As an opinion, I am not sure you will gain much writing Ruby in this way. The principles you are learning may be good ones (I don't know), but applying them forcefully "against the grain" of the language may create less readable code, regardless of whether it meets a well-intentioned design.
So what I would probably do in practice is this:
class Fun
def park param
case param
when Integer
puts param + 2
when String
puts param
end
end
end
This does not meet your principles, but is idiomatic Ruby and slightly easier to read and maintain than an if block (where the conditions could be far more complex so take longer for a human to parse).
You could just create handled classes for Fun like so
class Fun
def park(obj)
#parker ||= Object.const_get("#{obj.class}Park").new(obj)
#parker.park
rescue NameError => e
raise ArgumentError, "expected String or Fixnum but recieved #{obj.class.name}"
end
end
class Park
def initialize(p)
#park = p
end
def park
#park
end
end
class FixnumPark < Park
def park
#park += 2
end
end
class StringPark < Park
end
Then things like this will work
f = Fun.new
f.park("string")
#=> "string"
f.instance_variable_get("#parker")
#=> #<StringPark:0x1e04b48 #park="string">
f = Fun.new
f.park(2)
#=> 4
f.instance_variable_get("#parker")
#=> #<FixnumPark:0x1e04b48 #park=4>
f.park(22)
#=> 6 because the instance is already loaded and 4 + 2 = 6
Fun.new.park(12.3)
#=> ArgumentError: expected String or Fixnum but received Float
You could do something like this:
class Parent
attr_reader :s
def initialize(s='')
#s = s
end
def park
puts s
end
end
class Child1 < Parent
attr_reader :x
def initialize(s, x)
super(s)
#x = x
end
def park
puts x
end
end
class Child2 < Parent
attr_reader :y
def initialize(s, y)
super(s)
#y = y
end
def park
puts y
end
end
objects = [
Parent.new('hello'),
Child1.new('goodbye', 1),
Child2.new('adios', 2),
]
objects.each do |obj|
obj.park
end
--output:--
hello
1
2
Or, maybe I overlooked one of your twists:
class Parent
attr_reader :x
def initialize(s='')
#x = s
end
def park
puts x
end
end
class Child1 < Parent
def initialize(x)
super
end
def park
x + 2
end
end
class Child2 < Parent
def initialize(x)
super
end
def park
x * 2
end
end
objects = [
Parent.new('hello'),
Child1.new(2),
Child2.new(100),
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
[nil, 4, 200]
And another example using blocks, which are like anonymous functions. You can pass in the desired behavior to park() as a function:
class Function
attr_reader :block
def initialize(&park)
#block = park
end
def park
raise "Not implemented"
end
end
class StringFunction < Function
def initialize(&park)
super
end
def park
block.call
end
end
class AdditionFunction < Function
def initialize(&park)
super
end
def park
block.call 1
end
end
class DogFunction < Function
class Dog
def bark
puts 'woof, woof'
end
end
def initialize(&park)
super
end
def park
block.call Dog.new
end
end
objects = [
StringFunction.new {puts 'hello'},
AdditionFunction.new {|i| i+2},
DogFunction.new {|dog| dog.bark},
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
woof, woof
[nil, 3, nil]
Look at the is_a? method
def park(i)
i.is_a?(Fixnum) ? (i + 2) : i
end
But even better not to check a type, but use duck typing:
def park(i)
i.respond_to?(:+) ? (i + 2) : i
end
UPD: After reading comments. Yes, both examples above don't solve the OCP problem. That is how I would do it:
class Fun
# The method doesn't know how to pluck data. But it knows a guy
# who knows the trick
def pluck(i)
return __pluck_string__(i) if i.is_a? String
__pluck_fixnum__(i) if i.is_a? Fixnum
end
private
# Every method is responsible for plucking data in some special way
# Only one cause of possible changes for each of them
def __pluck_string__(i)
puts i
end
def __pluck_fixnum__(i)
i + 2
end
end
I understand or equal to operation in ruby but can you explain what
you have done with:
Object.const_get("#{obj.class}Park").new(obj)
In ruby, something that starts with a capital letter is a constant. Here is a simpler example of how const_get() works:
class Dog
def bark
puts 'woof'
end
end
dog_class = Object.const_get("Dog")
dog_class.new.bark
--output:--
woof
Of course, you can also pass arguments to dog_class.new:
class Dog
attr_reader :name
def initialize(name)
#name = name
end
def bark
puts "#{name} says woof!"
end
end
dog_class = Object.const_get("Dog")
dog_class.new('Ralph').bark
--output:--
Ralph says woof!
And the following line is just a variation of the above:
Object.const_get("#{obj.class}Park").new(obj)
If obj = 'hello', the first portion:
Object.const_get("#{obj.class}Park")
is equivalent to:
Object.const_get("#{String}Park")
And when the String class object is interpolated into a string, it is simply converted to the string "String", giving you:
Object.const_get("StringPark")
And that line retrieves the StringPark class, giving you:
Object.const_get("StringPark")
|
V
StringPark
Then, adding the second portion of the original line gives you:
StringPark.new(obj)
And because obj = 'hello', that is equivalent to:
StringPark.new('hello')
Capice?
It's a fairly well know one. Determining the rank of a poker hand. I created the following classes: Card:
class Card
attr_accessor :suite, :rank, :value
def initialize(suite, rank, value)
#suite = suite
#rank = rank
#value = value
end
def to_s
puts "#{#value}, #{#suite}, #{#rank}"
end
end
Deck:
class Deck
def initialize()
#cardsInDeck = 52
#deck = Array.new()
end
def add_card(card)
#deck.push(card)
end
def deck_size
#deck.length
end
def to_s
#deck.each do |card|
"#{card.rank}, #{card.suite}"
end
end
def shuffle_cards
#deck.shuffle!
end
def deal_cards
#Here I create a new hand object, and when popping cards from the deck
# stack I insert the card into the hand. However, when I want to print
# the cards added to the hand I get the following error:
# : undefined method `each' for #<Hand:0x007fa51c02fd50> (NoMethodError)from
# driver.rb:36:in `<main>'
#hand = Hand.new
for i in 0..51 do
card = #deck.pop
#cardsInDeck -= 1
puts "#{card.value}, #{card.rank}, #{card.suite}"
#hand.add_cards(card)
end
#hand.each do |index|
"#{index.value}, #{index.rank}, #{index.suite}"
end
puts "Cards In Deck: #{#cardsInDeck}"
end
end
Hand
require_relative 'deck'
require_relative 'card'
class Hand
def initialize()
#hand = Array.new()
end
def to_s
count = 0
#hand.each do |card|
"#{card.value}, #{card.rank}, #{card.suite}"
count += 1
end
end
def add_cards(card)
#hand.push(card)
end
def hand_size()
#hand.length
end
end
and Driver File:
require 'logger'
require_relative 'card'
require_relative 'deck'
require_relative 'hand'
suite = ["Hearts", "Diamonds", "Clubs", "Spades"]
rank = ["Ace", 2, 3, 4, 5, 6, 7, 8, 9, 10, "Jack", "Queen", "King"]
deck = Deck.new()
suite.each do |i|
v = 1
rank.each do |j|
deck.add_card(Card.new(i, j, v))
v += 1
end
end
In the Deck class, the deal_card method, I do not understand why looping for an array causes a method error
#hand.each do |index|
"#{index.value}, #{index.rank}, #{index.suite}"
end
puts "Cards In Deck: #{#cardsInDeck}"
#hand is an instance of Hand, and there is no instance method each defined for Hand, so that is why #hand.each is generating an undefined method error.
My answer is not very direct on the error but could hopefully help you in this case.
You method deal_cards is where Dependency Injection can play its role. By the original design Deck has a hard dependency on Hand which is not good and harder to test. You need to change it like
def deal_cards(hand=nil)
#hand = hand || Hand.new
# Others
end
By this you can accept instance outside of Hand, say Foot as long as somebody can play cards by feet!
You can also unit testing this method without writing Hand class at all.
Better to unit test the class instead of manual checking, then you can inject any instance you like into this method during testing.
For a pack of playing cards:
How can I use the suit hash (below) when creating a pack?
I have:
class PackOfCards
SUITS={H: 'Hearts', S:'Spades', D:'Diamonds', C:'Clubs'}
CARDS=['A','2','3','4','5','6','7','8','9','10','J','Q','K']
attr_accessor :pack_name, :cards
def initialize(pack_name)
#pack_name= pack_name
#cards = []
(0..3).each do |suit|
(0..12).each do |number|
#cards << PlayingCard.new(self, (SUITS[suit].value), CARDS[number])
end
end
end
end
class PlayingCard
attr_accessor :pack, :card_number, :card_suit
def initialize(pack, suit, number)
#card_suit = suit
#card_number = number
end
end
but I get:
pack_of_cards.rb:16:in `block (2 levels) in initialize':
undefined method `value' for
{:H=>"Hearts", :S=>"Spades", :D=>"Diamonds", :C=>"Clubs"}:Hash (NoMethodError)
Your SUITS is invalid expression. Perhaps you wanted to do this:
SUITS = %w[Hearts Spades Diamonds Clubs]
And it is not clear what you are doing, but perhaps you should be doing this:
#cards =
SUITS.flat_map{|suit| CARDS.map{|number| PlayingCard.new(self, suit, number)}}
Here is a corrected version, check the comments :
class PackOfCards
SUITS={H: 'Hearts', S:'Spades', D:'Diamonds', C:'Clubs'} # Use curly braces to define a hash, [] braces will define an array containing one hash
CARDS=['A','2','3','4','5','6','7','8','9','10','J','Q','K']
attr_accessor :pack_name, :cards
def initialize(pack_name)
#pack_name= pack_name
#cards = []
SUITS.each_key do |suit| # each_key is better since it gives you the key of the hash
(0..12).each do |number|
puts PackOfCards::SUITS[suit]
#cards << PlayingCard.new(self, (PackOfCards::SUITS[suit]), PackOfCards::CARDS[number]) # Call the hash with the right key to get the Suit
end
end
end
end
class PlayingCard
attr_accessor :pack, :card_number, :card_suit
def initialize(pack, suit, number)
#card_suit = suit
#card_number = number
end
end
Your Suit definition and lookup don't look valid.
How about something like this (assuming the output is a pack of cards with all suit and numbers) -
class PackOfCards
SUITS = ['Hearts', 'Spades', 'Diamonds', 'Clubs']
CARDS=['A','2','3','4','5','6','7','8','9','10','J','Q','K']
attr_accessor :pack_name, :cards
def initialize(pack_name)
#pack_name= pack_name
#cards = []
(0..3).each do |suit|
(0..12).each do |number|
#cards << PlayingCard.new(self, (SUITS[suit]), CARDS[number])
end
end
end
end
class PlayingCard
attr_accessor :pack, :card_number, :card_suit
def initialize(pack, suit, number)
#card_suit = suit
#card_number = number
end
end
You've actually put a hash in an array. To access the key, value pairs you'd have to access the array element first like this:
SUITS.first[:H]