Menu
Coddy logo textTech

カスタムコレクション

CoddyのRubyジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 40/57。

コレクションクラスは、いくつかの特殊メソッドを組み合わせたものです。Enumerable と each、追加用の <<、size、そして表示用の to_s と inspect を使います。配列と同じように、each はブロックなしでも動作し、to_enum を使って列挙子を返す必要があります。

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") }

出力:

1. wash
2. shop
3. cook
["shop", "cook"]

block_given?は、呼び出し元がブロックを渡したかどうかを示します。ブロックなしで返される列挙子は、each.with_index、each_slice、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_a

出力:

3
2
[[5, 4], [3, 2], [1]]

to_s はユーザー向けの親しみやすいテキストで、inspect はプログラマー向けの簡潔な概要です。コレクションの場合、inspect は通常、すべての要素ではなく、クラスとサイズを表示します。

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 list

出力:

1. wash
2. shop
#<TodoList 2 items>

Enumerable は count を提供しますが、size、empty?、<<、[] は提供しません。これらはクラスに属します。要素がどのように格納されているかを知っているのはクラスだけだからです。これらを定義すると、コレクションを利用する側からは配列のように扱えるようになります。

def size
  @items.size
end

def empty?
  @items.empty?
end

def [](index)
  @items[index]
end
challenge icon

チャレンジ

簡単

TodoList クラスを完成させてください。<< はタスクのテキストを追加し、リストを返します。each はタスクを yield し、ブロックがない場合は to_enum(:each) を返します。Enumerable を include してください。size はタスク数を返します。to_s はタスクを番号付きの行(1. wash)として返すか、タスクがない場合は nothing to do を返し、inspect は #<TodoList 3 items> を返します。

提供されたコードは、<< を連鎖させて入力されたすべての行を追加し、その後リスト、リストに対する p、each.with_index(1) で ! を付けて番号付けしたタスク(1! wash)、o を含むタスクの数、そしてアルファベット順で最初のタスクを出力します。

コードは todo_list.rb に記述してください。main.rb には提供された入出力コードが含まれており、編集できません。

自分で試してみよう

require_relative 'todo_list'

# 提供された入出力コード: そのままにしておく
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}"
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Rubyオンラインコンパイラ