Menu
CoddyTech

Пользовательские коллекции

Часть раздела Объектно-ориентированное программирование путешествия по Ruby на Coddy. Урок 40 из 57.

Класс коллекции объединяет несколько специальных методов: each с Enumerable, << для добавления, 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 элемента>

Enumerable предоставляет count, но не size, empty?, << или []: они относятся к классу, потому что только он знает, как хранятся элементы. Их определение делает коллекцию похожей на массив для вызывающего кода:

def size
  @items.size
end

def empty?
  @items.empty?
end

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

Задание

Легко

Завершите класс TodoList. << добавляет текст задачи и возвращает список. each выдаёт задачи и возвращает to_enum(:each), если блок не передан. Подключите Enumerable. 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