Custom Collections
Part of the Object Oriented Programming section of Coddy's Ruby journey. Lesson 40 of 57.
A collection class combines several special methods: each with Enumerable, << for adding, size, and to_s and inspect for printing. each should also work without a block, returning an enumerator with to_enum, as arrays do:
class TodoList
include Enumerable
def initialize
@items = []
end
def <<(item)
@items << item
self
end
def each
return to_enum(:each) unless block_given?
@items.each { |item| yield item }
self
end
end
list = TodoList.new << "wash" << "shop" << "cook"
list.each.with_index(1) { |item, n| puts "#{n}. #{item}" }
p list.select { |i| i.include?("o") }Output:
1. wash
2. shop
3. cook
["shop", "cook"]block_given? tells whether the caller passed a block. The enumerator returned without one supports chaining such as each.with_index, each_slice or next:
class Countdown
include Enumerable
def initialize(from)
@from = from
end
def each
return to_enum(:each) unless block_given?
@from.downto(1) { |n| yield n }
end
end
e = Countdown.new(3).each
puts e.next
puts e.next
p Countdown.new(5).each_slice(2).to_aOutput:
3
2
[[5, 4], [3, 2], [1]]to_s is the friendly text for users and inspect a short summary for programmers. For a collection, inspect usually shows the class and the size rather than every element:
class TodoList
include Enumerable
def initialize(*items)
@items = items
end
def each(&block)
@items.each(&block)
end
def size
@items.size
end
def to_s
map.with_index(1) { |item, n| "#{n}. #{item}" }.join("\n")
end
def inspect
"#<TodoList #{size} items>"
end
end
list = TodoList.new("wash", "shop")
puts list
p listOutput:
1. wash
2. shop
#<TodoList 2 items>Enumerable provides count, but not size, empty?, << or []: those belong to the class, because only it knows how the elements are stored. Defining them makes the collection feel like an array to its callers:
def size
@items.size
end
def empty?
@items.empty?
end
def [](index)
@items[index]
endChallenge
EasyComplete the class TodoList. << adds a task text and returns the list. each yields the tasks and returns to_enum(:each) without a block. Include Enumerable. size returns the number of tasks. to_s returns the tasks as numbered lines, 1. wash, or nothing to do, and inspect returns #<TodoList 3 items>.
The supplied code adds every input line by chaining <<, then prints the list, p of the list, the tasks with ! numbered through each.with_index(1) as 1! wash, the number of tasks containing o, and the first task alphabetically.
Your code goes in todo_list.rb. main.rb holds the supplied input/output code and cannot be edited.
Try it yourself
require_relative 'todo_list'
# Supplied input/output code: keep it as it is
input = []
while (line = gets)
input << line.chomp
end
list = input.reduce(TodoList.new) { |l, task| l << task }
puts list
p list
list.each.with_index(1) { |task, n| puts "#{n}! #{task}" }
puts "with o: #{list.count { |t| t.include?("o") }}"
puts "first: #{list.min}"
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesInstance VariablesMethods and selfto_s and inspectRecap - Library Card7Polymorphism and Duck Typing
PolymorphismDuck TypingAbstract Methodscase and ClassesRecap - Payment Methods2Accessors and Self
Readers and WritersCustom Settersself in MethodsIdentity and EqualityRecap - Contact List8Special Methods
Equality and hashOperator MethodsIndexing and AppendingCustom CollectionsRecap - Fractions11Project: Library Management
Books and MembersBorrowing Books3Class Methods and Variables
Class MethodsClass Instance VariablesConstantsFactory MethodsRecap - Coupon CodesPractice on your own: Online Ruby compiler