Let's say i have two classes
car class
class Car
include Comparable
attr_reader :wheel
def initialize(wheel)
#wheel = wheel
end
end
big car class
class BigCar
include Comparable
attr_reader :wheel, :big_car
def initialize(wheel)
#wheel = wheel
#size = ["big", "truck", "monster"].sample
end
end
generate array of them
cars = []
4.times do |x|
car = Car.new(x)
cars << car
end
big_cars = []
3.times do |x|
big_car = BigCar.new(x)
big_cars << big_car
end
i want to do
cars - big_cars
then i want it to result 1 car left, because it 4 - 3 = 1
but instead of 1, it returns all car.
i want to minus operator compare the wheel variable between two array.
so i add method in Car class
def <=>(other)
car.wheel <=> other.wheel
end
but it does not works.
What's the right way to do it?
thanks
As cars is an array, then the method you should be looking at is Array#-.
You could create a new class that holds cars without making a distinction whether they're big or not, just so they can be compared by their wheel.
class Cars
attr_reader :cars
def initialize(cars)
#cars = cars
end
def -(other)
cars.each_with_object([]) do |car, arr|
other.cars.each do |another_car|
arr.push(car) if car.wheel == another_car.wheel
end
end
end
end
big_cars = 3.times.map { |n_of_wheels| BigCar.new(n_of_wheels) }
# [#<Car:0x00007f92171ab750 #wheel=0>, #<Car:0x00007f92171ab6b0 #wheel=1>, #<Car:0x00007f92171ab688 #wheel=2>, #<Car:0x00007f92171ab660 #wheel=3>]
cars = 4.times.map { |n_of_wheels| Car.new(n_of_wheels) }
# [#<BigCar:0x00007f92171ab598 #wheel=0, #size="truck">, #<BigCar:0x00007f92171ab3b8 #wheel=1, #size="monster">, #<BigCar:0x00007f92171ab110 #wheel=2, #size="truck">]
Cars.new(big_cars) - Cars.new(cars)
# [#<BigCar:0x00007f92171afb48 #wheel=0, #size="monster">, #<BigCar:0x00007f92171af9b8 #wheel=1, #size="monster">, #<BigCar:0x00007f92171af8c8 #wheel=2, #size="truck">]
You don't really need the new class, you could just use the body of Cars#- extracting the variables it depends on.
And the method name doesn't necessarily have to be -, it can be any other (like diff, difference, compare_by_wheels, etc). It just gives a quick idea on what it does.
From the docs of Array#-:
items are compared using eql?
By including Comparable and implementing <=>, you only get ==, but not eql?. You have to add the latter yourself, e.g. as an alias for ==:
class Car
include Comparable
attr_reader :wheel
def initialize(wheel)
#wheel = wheel
end
def <=>(other)
self.wheel <=> other.wheel
end
alias eql? ==
end
Note that in order to get Array#- working, you just need eql?, i.e. a reduced version could look like this: (without Comparable)
class Car
attr_reader :wheel
def initialize(wheel)
#wheel = wheel
end
def eql?(other)
self.wheel == other.wheel
end
end
With both of the above, you'd get your expected result:
cars = Array.new(4) { |x| Car.new(x) }
big_cars = Array.new(3) { |x| BigCar.new(x) }
cars - big_cars
#=> [#<Car:0x00007fd0908cd710 #wheel=3>]
You probably want to implement eql? for BigCar, too. Or maybe make BigCar a subclass of Car and inherit its methods.
For your first question you want to compare the lengths of those arrays, which would look like this
cars.length - big_cars.length
because length gives you the number of items in the array.
Regarding the second Question, could you explain the expected output?
Related
I'm trying to wrap my head around delegation vs. inheritance so I'm manually delegating a version of Array. One of the specific reasons I read to do this is because when you use things like enumerables, your returned value on the inherited methods reverts back to the parent class (i.e. Array). So I did this:
module PeepData
# A list of Peeps
class Peeps
include Enumerable
def initialize(list = [])
#list = list
end
def [](index)
#list[index]
end
def each(...)
#list.each(...)
end
def reverse
Peeps.new(#list.reverse)
end
def last
#list.last
end
def join(...)
#list.join(...)
end
def from_csv(csv_table)
#list = []
csv_table.each { |row| #list << Peep.new(row.to_h) }
end
def include(field, value)
Peeps.new(select { |row| row[field] == value })
end
def exclude(field, value)
Peeps.new(select { |row| row[field] != value })
end
def count_by_field(field)
result = {}
#list.each do |row|
result[row[field]] = result[row[field]].to_i + 1
end
result
end
protected
attr_reader :list
end
end
When I instantiate this, my include and exclude function great and return a Peeps class but when using an enumerable like select, it returns Array, which prevents me from chaining further Peeps specific methods after the select. This is exactly what I'm trying to avoid with learning about delegation.
p = Peeps.new
p.from_csv(csv_generated_array_of_hashes)
p.select(&:certified?).class
returns Array
If I override select, wrapping it in Peeps.new(), I get a "SystemStackError: stack level too deep". It seems to be recursively burying the list deeper into the list during the select enumeration.
def select(...)
Peeps.new(#list.select(...))
end
Any help and THANKS!
I would recommend using both Forwardable and Enumerable. Use Forwardable to delegate the each method to your list (to satisfy the Enumerable interface requirement), and also forward any Array methods you might want to include that are not part of the Enumerable module, such as size. I would also suggest not overriding the behavior of select as it is supposed to return an array and would at the very least lead to confusion. I would suggest something like the subset provided below to implement the behavior you are looking for.
require 'forwardable'
class Peeps
include Enumerable
extend Forwardable
def_delegators :#list, :each, :size
def initialize(list = [])
#list = list
end
def subset(&block)
selected = #list.select(&block)
Peeps.new(selected)
end
protected
attr_reader :list
end
Example usage:
peeps = Peeps.new([:a,:b,:c])
subset = peeps.subset {|s| s != :b}
puts subset.class
peeps.each do |peep|
puts peep
end
puts peeps.size
puts subset.size
produces:
Peeps
a
b
c
3
2
I think that if Peeps#select will return an Array, then it is OK to include Enumerable. But, you want Peeps#select to return a Peeps. I don't think you should include Enumerable. It's misleading to claim to be an Enumerable if you don't conform to its interface. This is just my opinion. There is no clear consensus on this in the ecosystem. See "Examples from the ecosystem" below.
If we accept that we cannot include Enumerable, here's the first implementation that comes to my mind.
require 'minitest/autorun'
class Peeps
ARRAY_METHODS = %i[flat_map map reject select]
ELEMENT_METHODS = %i[first include? last]
def initialize(list)
#list = list
end
def inspect
#list.join(', ')
end
def method_missing(mth, *args, &block)
if ARRAY_METHODS.include?(mth)
self.class.new(#list.send(mth, *args, &block))
elsif ELEMENT_METHODS.include?(mth)
#list.send(mth, *args, &block)
else
super
end
end
end
class PeepsTest < Minitest::Test
def test_first
assert_equal('alice', Peeps.new(%w[alice bob charlie]).first)
end
def test_include?
assert Peeps.new(%w[alice bob charlie]).include?('bob')
end
def test_select
peeps = Peeps.new(%w[alice bob charlie]).select { |i| i < 'c' }
assert_instance_of(Peeps, peeps)
assert_equal('alice, bob', peeps.inspect)
end
end
I don't normally use method_missing, but it seemed convenient.
Examples from the ecosystem
There doesn't seem to be a consensus on how strictly to follow interfaces.
ActionController::Parameters used to inherit Hash. Inheritance ceased in Rails 5.1.
ActiveSupport::HashWithIndifferentAccess still inherits Hash.
As mentioned in the other answer, this isn't really proper usage of Enumerable. That said, you could still include Enumerable and use some meta-programming to override the methods that you want to be peep-chainable:
module PeepData
class Peeps
include Enumerable
PEEP_CHAINABLES = [:map, :select]
PEEP_CHAINABLES.each do |method_name|
define_method(method_name) do |&block|
self.class.new(super(&block))
end
end
# solution for select without meta-programming looks like this:
# def select
# Peeps.new(super)
# end
end
end
Just so you know, this really has nothing to do with inheritance vs delegation. If Peeps extended Array, you would have the exact same issue, and the exact solution above would still work.
I'm creating a module for a deck of cards. I want the Deck object to be able to accept another implementation of the shuffle method I have written below.
I was originally thinking that my shuffle method should accept a parameter that is any object that responds to shuffle. Is that a good approach here?
I'm not sure exactly how to approach this in Ruby.
module PlayingCards
class Deck
RANKS = [*2..10, 'Jack', 'Queen', 'King', 'Ace']
SUITS = %w{ Clubs Diamonds Hearts Spades }
attr_accessor :cards, :discarded
def initialize(options={})
#cards = []
#discarded = []
options[:number_decks] ||= 1
options[:number_decks].times do
(RANKS).product(SUITS).each do |rank, suit|
#cards << Card.new(rank, suit)
end
end
end
def shuffle()
cards.push(*discarded).shuffle!
discarded = []
self
end
end
end
This is the hand class that is responsible for drawing cards from the deck and also folding a hand.
module PlayingCards
class Hand
attr_accessor :deck, :draw_count, :cards, :discarded
def initialize(args = {})
#deck = args[:deck]
#discarded = args[:deck].discarded
#draw_count = args[:draw_count]
#cards = []
end
def draw
draw_count.times do
cards.push(deck.cards[0])
deck.cards.shift
end
cards
end
def fold
discarded.push(cards).flatten!
end
end
end
I want the Deck object to be able to accept another implementation of the shuffle method I have written below.
There are a few ways to implement this. There's no single right answer. Here's a few techniques:
Just define multiple methods:
def shuffle
cards.push(*discarded).shuffle!
discarded = []
self
end
# For example
def shuffle_before_replacing
cards.shuffle!
cards.push(*discarded)
discarded = []
self
end
Define a higher order method, for example:
SHUFFLE_MODES = {
standard: ->(cards, discarded) { cards.push(*discarded).shuffle },
# ...
}
def shuffle(mode: :standard)
cards = SHUFFLE_MODES.fetch(mode).call(cards, discarded)
discarded = []
self
end
Inject a dependency to perform the shuffling, for example:
def shuffle(shuffler: Shuffler::Standard)
shuffler = shuffler.new(cards, discarded)
shuffler.shuffle
cards = shuffler.cards
shuffler = shuffler.discarded
self
end
# ...
module Shuffler
class Base
attr_reader :cards, :discarded
def initialize(cards, discarded)
#cards = cards
#discarded = discarded
end
end
end
module Shuffler
class Standard < Base
def shuffle
#cards.push(*discarded).shuffle
#discarded = []
self
end
end
end
module Shuffler
class BeforeReplacing < Base
def shuffle
#cards.shuffle!
#cards.push(*discarded)
#discarded = []
self
end
end
end
end
One main benefits of this third approach is that you can define Shufflers independently to the Deck class - so for example, perhaps you'll eventually have multiple types of Deck that can each use the Shuffler interface in different ways. (For example, maybe you'll want to shuffle different things, or shuffle multiple times.)
Likewise, you can also test the Shufflers independently to the Deck. This works both ways; you can also now test the Deck independently to the Shuffler (by passing a mock object into the Deck#shuffle method).
This technique is the most advanced of the three, and is something I'd only usually expect an experienced developer to fully utilise, in more complex scenarios. It's hard to say whether such an abstraction is even with the effort yet in your case, given the limited information I have!
All code above is untested, so may not be spot on, but hopefully this gives you some inspiration.
I need to make a program in ruby to generate a robot name like KU765 or NG274 style
and to store them and check it to avoid repetition.
I also need to make a "reset" method to delete all stored names and start again.
This program is not working for some reason. I hope somebody helps me to find the mistake.
Thanks a lot.
class Robot
attr_accessor :named , :stored_names , :rl
def self.name
new.name
end
##rl = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def name
named = ""
named << ##rl[rand(26).to_i]
named << ##rl[rand(26).to_i]
named << rand(100..999).to_s
named.save_name
named.check_name
end
def save_name
stored_names = []
stored_names << named
end
def check_name
stored_names.uniq!
end
def reset
stored_names = Array.new
end
end
Here's another way to construct the Robot class that you may wish to consider. (My answers are not normally this long or detailed, but I wrote this in part to clarify aspects of Ruby's object model in my own mind. I hope it might help others do the same.)
Code
PREFACE = ('A'..'Z').to_a << ?_
SUFFIX = ('0'..'9').to_a
PREFACE_SIZE = 2
SUFFIX_SIZE = 3
class Robot
def self.reset() #bots = [] end
reset
def self.new() (#bots << super).last end
def self.bots() #bots end
def self.delete(bot) #bots.delete(bot) end
def self.bot_names() #bots.map { |b| b.name } end
attr_reader :name
def initialize() #name = add_name end
private
def add_name
loop do
#name = gen_name
return #name unless self.class.bot_names.include?(#name)
end
end
def gen_name
PREFACE.sample(PREFACE_SIZE).join << SUFFIX.sample(SUFFIX_SIZE).join
end
end
Example
Robot.bots #=> []
robbie = Robot.new #=> #<Robot:0x000001019f4988 #name="AP436">
robbie.name #=> "AP436"
Robot.bots #=> [#<Robot:0x000001019f4988 #name="AP436">]
r2d2 = Robot.new #=> #<Robot:0x000001019cd450 #name="KL628">
r2d2.name #=> "KL628"
Robot.bots #=> [#<Robot:0x000001019f4988 #name="AP436">,
# #<Robot:0x000001019cd450 #name="KL628">]
Robot.bot_names #=> ["AP436", "KL628"]
Robot.delete(robbie) #=> #<Robot:0x000001019f4988 #name="AP436">
Robot.bots #=> [#<Robot:0x000001019cd450 #name="KL628">]
Robot.bot_names #=> ["KL628"]
Robot.reset #=> []
c3po = Robot.new #=> #<Robot:0x000001018ff8c0 #name="VO975">
Robot.bots #=> [#<Robot:0x000001018ff8c0 #name="VO975">]
Explanation
When the class is parsed, the class method reset is first created, then the line reset is executed. As self => Robot when that occurs, the class method reset is executed, initializing #bots to an empty array.
The responsibility for saving and modifying a list of instances of Robot lies with the class. This list is held in the class instance variable #bots.
Instance of Robot are created by invoking Robot::new, which allocates memory and then invokes the (private) instance method initialize. Where is new? Since we have not defined it as a class method in Robot, there are two possibilities: it is inherited from one of Robot's ancestors (Robot.ancestors => [Robot, Object, Kernel, BasicObject]) or it is an instance method of the class Class, as that is the class for which Robot is an instance (i.e., Robot.class => Class) Let's find out which: Class.instance_method(:new) => #<UnboundMethod: Class#new> (or Class.instance_methods.include?(:new) => true), Object.method(:new) => #<Method: Class#new>. It's both! But that makes sense, because all classes are instances of Class, including Robot's superclass, Object. #<Method: Class#new> returned by Object.method(:new) shows new is defined in Class (which can alternatively be seen with Robot.method(:new).owner => Class. Very cool, eh? If you didn't know this already, and can follow what I've said in this paragraph, you've just learned the essence of Ruby's object model!
Suppose we add the class method new, shown below, to Robot. super invokes the class method Object::new (which is the instance method Class#new), passing any arguments of new (here there aren't any). Object::new returns the instance that it creates, which Robot::new in turn returns. Therefore, this method would simply be a conduit and and have no effect on the results.
def self.new
super
end
We can make a small change to the above method to add a copy of the instance that is created by Object::new to the array #bots:
def self.new
instance = super
#bots << instance
instance
end
I have written this a little more compactly as:
def self.new
(#bots << super).last
end
I've used the method Array#sample to randomly draw PREFACE_SIZE characters from PREFACE and SUFFIX_SIZE characters from SUFFIX_SIZE. sample samples without replacement, so you will not get, for example, "UU112". If you want to sample with replacement, replace the method gen_name with the following:
def gen_name
str = PREFACE_SIZE.times.with_object('') { |_,s| s << PREFACE.sample }
SUFFIX_SIZE.times { str << SUFFIX.sample }
str
end
I have created a class method bots to return the value of the class instance variable #bots. This could alternatively be done by defining a read accessor for #bots on Robots' singleton class:
class << self
attr_reader :name
end
When Robot.reset is invoked, what happens to all the instances of Robot it had contained? Will they be left to wander the forest, rejected and homeless? In languages like C you need to release their memory before casting them aside. In Ruby and many other modern languages that's not necessary (and generally can't be done). Ruby's "garbage collection" keeps track of all objects, and kills off (after releasing memory) any that are no longer referenced by any other object. Nice, eh?
The task itself is not hard, but I don't like the way your code is organised. This is what I would do in the first stage:
class Robot
class Name < String
class << self
def sign
"#{[*?A..?Z].sample}#{[*?A..?Z].sample}"
end
def number
"#{rand 1..9}#{rand 0..9}#{rand 0..9}"
end
def new
super << sign << number
end
end
end
end
And then:
Robot::Name.new
When constructing a list of names it is easy to check that they are unique. This is how I'd go about it:
class Robot
class Names < Array
def self.generate n
new.tap { |array| n.times do array.add_random_name end }
end
def add_random_name
name = Name.new
include?( name ) ? add_random_name : self << name
end
end
end
And then:
Robot::Names.generate 7
def save_name
stored_names = []
stored_names << named
end
Every time, you create a name, and call save_name you delete all previously created names, by assigning an empty array to stored_names
EDIT:
There were a few more errors, let me first post a working solution:
class Robot
attr_accessor :named , :stored_names , :rl
def initialize()
#stored_names = []
end
##rl = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars.to_a
def name
#named = ""
#named << ##rl.sample
#named << ##rl.sample
#named << rand(100..999).to_s
save_name
check_name
end
def save_name
#stored_names << #named
end
def check_name
#stored_names.uniq!
end
def reset
#stored_names = Array.new
end
end
To access the members of your object, you have to prefix them with #.
You called save_name and check_name on #named, which is a string and doesn't provide these methods
#stored_names must be initialized to an empty array, before you can push elements into it with <<. This is normally done in the class's constructor initialize()
I understand this isn't efficient, but this will work.
letters = [*'A'..'Z'] =>
numbers = [*100..999]
names = []
2.times{names.push(letters.shuffle.first)} => Randomizing array and choosing element
names.push(numbers.shuffle.first)
names.join => Creates a String object out of the array elements
This isn't pretty, but it gets the job done.
This is how I automate Cary's approach with my y_support/name_magic:
require 'y_support/all'
class Robot
★ NameMagic
def name_yourself
begin
self.name = "#{[*?A..?Z].sample( 2 ).join}#{rand 100..999}"
rescue NameError; retry end
end
end
3.times { Robot.new.name_yourself }
Robot.instances #=> [PR489, NS761, OE663]
Robot.forget "PR489"
Robot.instances #=> [NS761, OE663]
Robot.forget_all_instances
Robot.instances #=> []
Robot.new.name_yourself
Robot.instances #=> [IB573]
I have a class game which contains some arrays of custom objects (dinosaurs, cacemen etc.), that are returned by different accessors, such as game.dinosaurs, game.cavemen etc.
At present, all these accessors just return the internally stored arrays. But now I'd like to add some custom iteration methods to these arrays returned by those accessors, to be able to write code such as game.dinosaurs.each_carnivore { ... } etc. similarly to each_element and each_attr iterators in LibXML::XML::Node. But the objects returned from my accessors game.dinosaurs and game.cavemen have to behave like arrays still.
How are things like that usually done in Ruby?
Should I make the objects returned from my accessors to be some custom classes derived from Ruby's Array class? Or maybe should I just create a custom class with Enumerable mixed in?
I know I can use map or select externally on my collections, but I wanted to encapsulate these iterations internally that my class's users won't need to bother how to set up an iteration to select only carnivore dinosaurs from the internal array.
Edit: I'm not asking about how to use iterators or how to implement them, but how to add just some custom iterators to object which previously were just plain arrays (and still need to be).
It depends (as always). You could use an array subclass and you you could build a custom class and use composition and delegation. Here's a simple example with an array subclass:
class DinosaurArray < Array
def carnivores
select { |dinosaur| dinosaur.type == :carnivore }
end
def herbivores
select { |dinosaur| dinosaur.type == :herbivore }
end
def each_carnivore(&block)
carnivores.each(&block)
end
def each_herbivore(&block)
herbivores.each(&block)
end
end
And here's a simple one with composition and delegation:
class DinosaurArray
def initialize
#array = []
end
def <<(dinosaur)
#array << dinosaur
end
def carnivores
#array.select { |dinosaur| dinosaur.type == :carnivore }
end
def herbivores
#array.select { |dinosaur| dinosaur.type == :herbivore }
end
def each(&block)
#array.each(&block)
end
def each_carnivore(&block)
carnivores.each(&block)
end
def each_herbivore(&block)
herbivores.each(&block)
end
end
Both implementation can be used like this:
require 'ostruct'
dinosaurs = DinosaurArray.new
dinosaurs << OpenStruct.new(type: :carnivore, name: "Tyrannosaurus")
dinosaurs << OpenStruct.new(type: :carnivore, name: "Allosaurus")
dinosaurs << OpenStruct.new(type: :herbivore, name: "Apatosaurus")
puts "Dinosaurs:"
dinosaurs.each.with_index(1) { |dinosaur, i| puts "#{i}. #{dinosaur.name}" }
puts
But also has custom iterators:
puts "Carnivores:"
dinosaurs.each_carnivore.with_index(1) { |dinosaur, i| puts "#{i}. #{dinosaur.name}" }
puts
puts "Herbivores:"
dinosaurs.each_herbivore.with_index(1) { |dinosaur, i| puts "#{i}. #{dinosaur.name}" }
Output:
Dinosaurs:
1. Tyrannosaurus
2. Allosaurus
3. Apatosaurus
Carnivores:
1. Tyrannosaurus
2. Allosaurus
Herbivores:
1. Apatosaurus
You can do this via using ruby blocks. Read more
Simple example here:
class Game
def initialize
#carnivoures = [1,2,3]
end
def each_carnivoures
#carnivoures.each do |carni|
yield carni
end
end
end
Game.new.each_carnivoures{ |c| p c}
It also would be nice to have a possibility for chaining such filters. You can achieve this simply by wrapping select method into custom one, returning your new class instead of array. You may wrap some other methods as well, e.g. map:
class Units < Array
def select
self.class.new(super)
end
def dinosaurs
select{ |unit| unit.kind == 'dinosaur' }
end
def cavemen
select{ |unit| unit.kind == 'caveman' }
end
def carnivore
select{ |unit| unit.type == 'carnivore' }
end
def herbivore
select{ |unit| unit.type == 'herbivore' }
end
end
Units.dinosaurs.carnivore
Units.cavemen.herbivore
I have an object Results that contains an array of result objects along with some cached statistics about the objects in the array. I'd like the Results object to be able to behave like an array. My first cut at this was to add methods like this
def <<(val)
#result_array << val
end
This feels very c-like and I know Ruby has better way.
I'd also like to be able to do this
Results.each do |result|
result.do_stuff
end
but am not sure what the each method is really doing under the hood.
Currently I simply return the underlying array via a method and call each on it which doesn't seem like the most-elegant solution.
Any help would be appreciated.
For the general case of implementing array-like methods, yes, you have to implement them yourself. Vava's answer shows one example of this. In the case you gave, though, what you really want to do is delegate the task of handling each (and maybe some other methods) to the contained array, and that can be automated.
require 'forwardable'
class Results
include Enumerable
extend Forwardable
def_delegators :#result_array, :each, :<<
end
This class will get all of Array's Enumerable behavior as well as the Array << operator and it will all go through the inner array.
Note, that when you switch your code from Array inheritance to this trick, your << methods would start to return not the object intself, like real Array's << did -- this can cost you declaring another variable everytime you use <<.
each just goes through array and call given block with each element, that is simple. Since inside the class you are using array as well, you can just redirect your each method to one from array, that is fast and easy to read/maintain.
class Result
include Enumerable
def initialize
#results_array = []
end
def <<(val)
#results_array << val
end
def each(&block)
#results_array.each(&block)
end
end
r = Result.new
r << 1
r << 2
r.each { |v|
p v
}
#print:
# 1
# 2
Note that I have mixed in Enumerable. That will give you a bunch of array methods like all?, map, etc. for free.
BTW with Ruby you can forget about inheritance. You don't need interface inheritance because duck-typing doesn't really care about actual type, and you don't need code inheritance because mixins are just better for that sort of things.
Your << method is perfectly fine and very Ruby like.
To make a class act like an array, without actually inheriting directly from Array, you can mix-in the Enumerable module and add a few methods.
Here's an example (including Chuck's excellent suggestion to use Forwardable):
# You have to require forwardable to use it
require "forwardable"
class MyArray
include Enumerable
extend Forwardable
def initialize
#values = []
end
# Map some of the common array methods to our internal array
def_delegators :#values, :<<, :[], :[]=, :last
# I want a custom method "add" available for adding values to our internal array
def_delegator :#values, :<<, :add
# You don't need to specify the block variable, yield knows to use a block if passed one
def each
# "each" is the base method called by all the iterators so you only have to define it
#values.each do |value|
# change or manipulate the values in your value array inside this block
yield value
end
end
end
m = MyArray.new
m << "fudge"
m << "icecream"
m.add("cake")
# Notice I didn't create an each_with_index method but since
# I included Enumerable it knows how and uses the proper data.
m.each_with_index{|value, index| puts "m[#{index}] = #{value}"}
puts "What about some nice cabbage?"
m[0] = "cabbage"
puts "m[0] = #{m[0]}"
puts "No! I meant in addition to fudge"
m[0] = "fudge"
m << "cabbage"
puts "m.first = #{m.first}"
puts "m.last = #{m.last}"
Which outputs:
m[0] = fudge
m[1] = icecream
m[2] = cake
What about some nice cabbage?
m[0] = cabbage
No! I meant in addition to fudge
m.first = fudge
m.last = cabbage
This feels very c-like and I know Ruby
has better way.
If you want an object to 'feel' like an array, than overriding << is a good idea and very 'Ruby'-ish.
but am not sure what the each method
is really doing under the hood.
The each method for Array just loops through all the elements (using a for loop, I think). If you want to add your own each method (which is also very 'Ruby'-ish), you could do something like this:
def each
0.upto(#result_array.length - 1) do |x|
yield #result_array[x]
end
end
If you create a class Results that inherit from Array, you will inherit all the functionality.
You can then supplement the methods that need change by redefining them, and you can call super for the old functionality.
For example:
class Results < Array
# Additional functionality
def best
find {|result| result.is_really_good? }
end
# Array functionality that needs change
def compact
delete(ininteresting_result)
super
end
end
Alternatively, you can use the builtin library forwardable. This is particularly useful if you can't inherit from Array because you need to inherit from another class:
require 'forwardable'
class Results
extend Forwardable
def_delegator :#result_array, :<<, :each, :concat # etc...
def best
#result_array.find {|result| result.is_really_good? }
end
# Array functionality that needs change
def compact
#result_array.delete(ininteresting_result)
#result_array.compact
self
end
end
In both of these forms, you can use it as you want:
r = Results.new
r << some_result
r.each do |result|
# ...
end
r.compact
puts "Best result: #{r.best}"
Not sure I'm adding anything new, but decided to show a very short code that I wish I could have found in the answers to quickly show available options. Here it is without the enumerator that #shelvacu talks about.
class Test
def initialize
#data = [1,2,3,4,5,6,7,8,9,0,11,12,12,13,14,15,16,172,28,38]
end
# approach 1
def each_y
#data.each{ |x| yield(x) }
end
#approach 2
def each_b(&block)
#data.each(&block)
end
end
Lets check performance:
require 'benchmark'
test = Test.new
n=1000*1000*100
Benchmark.bm do |b|
b.report { 1000000.times{ test.each_y{|x| #foo=x} } }
b.report { 1000000.times{ test.each_b{|x| #foo=x} } }
end
Here's the result:
user system total real
1.660000 0.000000 1.660000 ( 1.669462)
1.830000 0.000000 1.830000 ( 1.831754)
This means yield is marginally faster than &block what we already know btw.
UPDATE: This is IMO the best way to create an each method which also takes care of returning an enumerator
class Test
def each
if block_given?
#data.each{|x| yield(x)}
else
return #data.each
end
end
end
If you really do want to make your own #each method, and assuming you don't want to forward, you should return an Enumerator if no block is given
class MyArrayLikeClass
include Enumerable
def each(&block)
return enum_for(__method__) if block.nil?
#arr.each do |ob|
block.call(ob)
end
end
end
This will return an Enumerable object if no block is given, allowing Enumerable method chaining