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.
Related
Lets say I create a Calculator class that works by manipulating elements in an array - within this class I define several methods: add, subtract, multiply, divide. I want each method to raise the same error if there exist only 1 or fewer elements in the array, something like:
class Calculator
# ...
def add
if #array.length < 2
raise 'Not Enough Elements'
else
#array << #array.pop + #array.pop
end
end
# ...
end
I could write a condition to raise the error into each method, but that seems very tedious and un-Ruby. Would there be a way to apply the raised error to all the methods that would need it that would save all that typing?
One of the options would be moving the length checking logic into it's own method and using it where needed:
class Calculator
def add
check_array_length
# rest of the method
end
private
def check_array_length
raise 'Not Enough Elements' if #array.length < 2
end
end
If you are setting #array in the initialize method, you could raise on the early stage, saying that you can't proceed because of too less elements in the #array:
class Calculator
def initialize(array)
raise 'Not Enough Elements' if array.length < 2
#array = array
end
end
Here's a possible structure :
module Validator
[:add, :substract, :multiply, :divide].each do |method|
define_method(method) do
validate_array_length(2)
super()
end
end
private
def validate_array_length(min,max=min)
raise 'Not Enough Elements' if #array.length < min
raise 'Too Many Elements' if #array.length > max
end
end
class Calculator
prepend Validator
def initialize(*values)
#array = values
end
def add
#array << #array.pop + #array.pop
end
# def substract ....
end
c = Calculator.new(3,2)
c.add
c.add
# => calculator.rb:12:in `validate_array_length': Not Enough Elements (RuntimeError)
class Calculator
def initialize(arr)
#arr = arr
end
def add; binary(:+); end
def subtract; binary(:-); end
def multiply; binary(:*); end
def divide; binary(:/); end
def power; binary(:**); end
def modulo; binary(:%); end
# ... (others)
def negate; unary(:-#); end
def odd?; unary(:odd?); end
def even?; unary(:even?); end
def to_f; unary(:to_f); end
# ... (others)
private
def binary(op)
raise ArgumentError, 'Too few elements' if #arr.length < 2
#arr.pop.send(op, #arr.pop)
end
def unary(op)
raise ArgumentError, 'Too few elements' if #arr.length.zero?
#arr.pop.send(op)
end
end
# add neg mod pow div mult sub add
calc = Calculator.new [ 1, 5, 2,3, 4,5, 6,7, 8,9, 10,11, 12,13]
#=> #<Calculator:0x007fa192030968 #arr=[1, 5, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]>
calc.add #=> 25 (13+12)
calc.subtract #=> 1 (11-10)
calc.multiply #=> 72 (8*9)
calc.divide #=> 1 (7/6)
calc.power #=> 625 (5**4)
calc.modulo #=> 1 (3%2)
calc.negate #=> -5 (5)
calc.add #=> ArgumentError: Too few elements
It appears that you are constructing an RPN calculator. If so, you probably want to push the result of each calculation back onto the stack. For binary operators you could change the method binary as follows:
def binary(op)
raise ArgumentError, 'Too few elements' if #arr.length < 2
#arr << #arr.pop.send(op, #arr.pop)
#arr[-1]
end
#arr[-1], the result of the calculation, is the return value. The modification of unary is similar.
You may wish to add some stack manipulation methods, such as
def pop
#arr.pop
end
def push(n)
#arr << n
end
def swap
#arr[-1], #arr[-2] = #arr[-2], #arr[-1]
end
def rotate
#arr.rotate
end
Lastly, you might find it it clearer to make the beginning (rather than end) of #arr the top of the stack, in which you would use unshift/shift rather than push/pop.
I am building a Tic-Tac-Toe game to be played on the command line.
module TicTacToe
class Player
attr_accessor :symbol
def initialize(symbol)
#symbol = symbol
end
end
class Board
attr_reader :spaces
def initialize
#spaces = Array.new(9)
end
def to_s
output = ""
0.upto(8) do |position|
output << "#{#spaces[position] || position}"
case position % 3
when 0, 1 then output << " | "
when 2 then output << "\n-----------\n" unless position == 8
end
end
output
end
def space_available(cell, sym)
if spaces[cell].nil?
spaces[cell] = sym
else
puts "Space unavailable"
end
end
end
class Game < Board
attr_reader :player1, :player2
def initialize
play_game
end
def play_game
#player1 = Player.new("X")
#player2 = Player.new("O")
puts Board.new
#current_turn = 1
turn
end
def move(player)
while victory != true
puts "Where would you like to move?"
choice = gets.chomp.to_i
space_available(choice, player.symbol)
puts Board
#current_turn += 1
turn
end
end
def turn
#current_turn.even? ? move(#player2) : move(#player1)
end
def victory
#still working on this
end
end
end
puts TicTacToe::Game.new
The method that is to take a user's cell choice (space_available) and alter the array with their piece ('X' or 'O') is giving me an error. I can't find why my code is throwing this particular error.
The problem is that you don't call the parent constructor in your Game class, therefore #spaces is not initialized.
Your hierarchy decision is questionable, but to make it work, you can simply change the Game constructor to:
def initialize
super
play_game
end
You are calling spaces[cell]. The error is telling you that you are calling [] on nil, which means that spaces must be nil.
Perhaps you mean #spaces? Otherwise - you need to tell the program how spaces is defined and how it is initialized. A simple spaces = {} unless spaces would work
Another way of initialising your spaces variable would be to call super when you initialize the Game:
class Game < Board
attr_reader :player1, :player2
def initialize
super
play_game
end
...
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?
I'm stuck with this exercise from exercism.io:
part of sum_of_multiples_test.rb
...
def test_sum_to_1000
skip
assert_equal 233168, SumOfMultiples.to(1000)
end
def test_configurable_7_13_17_to_20
assert_equal 51, SumOfMultiples.new(7, 13, 17).to(20)
end
...
sum.rb
class SumOfMultiples
def initialize(*args)
#args = args ||= [3,5]
end
def to(max)
ary = []
return 0 if max < 2
#args.each do |m|
for i in 0..max-1
ary << i if i % m == 0
end
end
ary.uniq!.inject(:+)
end
end
If I use class method self.to, it can't see my instance variable #args, if I use
instance method "def to" first test don't pass. Is there a way to somehow "merge" both?
Add another method to your class:
def self.to(max)
new.to(max)
end
You can now call each of these and the result will be the same:
SumOfMultiples.to(1000)
SumOfMultiples.new.to(1000)
I'm pretty new to Ruby(and programming in general), but I thought there was a way to call the attributes of all of a classes objects?
class Player
attr_reader :number
def initialize(name, number)
#name = name
#number = number
end
def self.all_numbers
[] << Player.each {|person| person.number}
end
end
guy1 = Player.new('Bill', 23)
guy2 = Player.new('jeff', 18)
I would like to just access the numbers for all objects by calling the class..
Player.all_numbers
hoping to return..
[23, 18]
The problem you have right now is that Player is your custom class. It does not respond to a class method each. Another issue is that the Player class has no knowledge of the instances created outside of it.
There's many ways to go about this. The way I would do this is to implement another class called Team like this
class Team
def initialize(*players)
#players = players
end
def player_numbers
#players.map { |player| player.number }
end
end
class Player
attr_reader :number
def initialize(name, number)
#name = name
#number = number
end
end
guy1 = Player.new('Bill', 23)
guy2 = Player.new('jeff', 18)
team = Team.new(guy1, guy2)
team.player_numbers
#=> [23, 18]
Write as below with the help of ObjectSpace#each_object :
Calls the block once for each living, nonimmediate object in this Ruby process. If module is specified, calls the block for only those classes or modules that match (or are a subclass of) module. Returns the number of objects found. Immediate objects (Fixnums, Symbols true, false, and nil) are never returned.
If no block is given, an enumerator is returned instead.
class Player
attr_reader :number
def initialize(name, number)
#name = name
#number = number
end
def self.all_numbers
ObjectSpace.each_object(self).map(&:number)
end
end
guy1 = Player.new('Bill', 23)
guy2 = Player.new('jeff', 18)
Player.all_numbers
# => [18, 23]
Another approach
class Player
attr_reader :number
#class_obj = []
def initialize(name, number)
#name = name
#number = number
self.class.add_object(self)
end
def self.add_object(ob)
#class_obj << ob
end
def self.all_numbers
#class_obj.map(&:number)
end
end
guy1 = Player.new('Bill', 23)
guy2 = Player.new('jeff', 18)
Player.all_numbers
# => [23, 18]
Use a Class Variable
Using a Team to hold references to complete Player objects is probably more desirable from a design perspective, but you can do what you want with a simple class variable. For example:
class Player
attr_reader :number
##numbers = []
def initialize(name, number)
#name = name
#number = number
##numbers << #number
end
def self.all_numbers
##numbers
end
end
guy1 = Player.new 'Bill', 23
guy2 = Player.new 'jeff', 18
Player.all_numbers
#=> [23, 18]
For this simple use case, that's probably sufficient. However, if you find yourself trying to stuff entire Player objects into a Player class variable then you will definitely want to find a better noun (e.g. Team or Players) to hold your collection.
I would simply override new and save the instances in a class variable:
class Player
attr_reader :number
def initialize(name, number)
#name = name
#number = number
end
def self.new(*args)
(##players ||= []) << super
##players.last
end
def self.all_numbers
##players.reduce([]) {|arr, player| arr << player.number}
end
end
Player.new('Bill', 23)
Player.new('jeff', 18)
p Player.all_numbers # => [23, 18]
Carpk, I should explain a few things:
Normally, Player.new('Bill', 23) would send the method Class#new to class Player, together with the two arguments. By defining self.new here, I am overriding Class#new. self.new invokes super, which invokes Class#new with self.new's arguments (there is no need to explicitly pass the arguments, unless you only want to pass some or none). Class#new returns the new class instance to self.new, which returns it as well, but first saves it in the array ##player.
(##players ||= []) is a typical Ruby trick. This has the effect of setting ##players equal to an empty array if it has not yet been defined (is nil), and leaves it unchanged if it already an array (empty or not). This is because ##players ||= [] is the same as ##players = ##players || []. If ##players => nil, this becomes ##players = []; else, ##players remains unchanged ([] is never evaluated). Cute, eh?
##players.last returns the class instance that was just returned by Class#new and added to the end of ##players.
##players.reduce([]) {|arr, player| arr << player.number} creates and returns an array arr of class instances, which in turn self.all_numbers returns. reduce's argument is the initial value of the accumulator, here an empty array named arr within the block. Note reduce and inject are synonyms.
Carpk, don't read any further; it will just be confusing.
I lied. I actually wouldn't do it that way. I suggested using a class variable and class methods because Carpk is new to Ruby and probably not into metaprogramming in a big way. What I'd actually do is define a class instance variable #players and make new and all_numbers singleton methods of the class Player. That way, instances of the class Player would not have easy access to that variable or those two methods. To do this, simply replace the above definitions of self.new and self.all_numbers with the following:
class << self
def new(*args)
(#players ||= []) << super
#players.last
end
def all_numbers
#players.reduce([]) {|arr, player| arr << player.number}
end
end
Carpk, I warned you.