Menu
CoddyTech

Паттерн Iterator

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

Когда у тебя есть коллекция элементов, хранящаяся внутри объекта, часто возникает необходимость перебрать её элементы. Универсальный цикл for в Lua работает с любой функцией-итератором: он вызывает эту функцию перед каждой итерацией и останавливается, когда функция возвращает nil.

Паттерн «Итератор» предоставляет объекту метод, возвращающий такую функцию. Функция хранит свою позицию в локальной переменной, поэтому позиция относится к одному циклу, а не к коллекции:

local Collection = {}
Collection.__index = Collection

function Collection:new()
    local obj = { items = {} }
    setmetatable(obj, Collection)
    return obj
end

function Collection:add(item)
    table.insert(self.items, item)
end

function Collection:iter()
    local index = 0
    return function()
        index = index + 1
        return self.items[index]
    end
end

Каждый вызов bag:iter() создаёт новую функцию со своим index, начинающимся с 0. Эта функция является замыканием: она по-прежнему может использовать index и self после того, как iter вернула управление. После последнего элемента self.items[index] равно nil, и цикл останавливается:

local bag = Collection:new()
bag:add("apple")
bag:add("banana")

for item in bag:iter() do
    print(item)  -- apple, затем banana
end

for item in bag:iter() do
    print(item)  -- снова apple, затем banana
end

Храни позицию отдельно от самой коллекции. Если бы объект хранил self.index и сам был итератором, после первого цикла индекс оказался бы в конце, поэтому второй цикл ничего бы не вывел, а если цикл досрочно остановился бы с помощью break, следующий цикл начался бы с середины.

challenge icon

Задание

Легко

Давай создадим класс Playlist, по которому можно проходить циклом с помощью обобщённого цикла for в Lua! Ты реализуешь паттерн «Итератор» с методом :iter(), который возвращает функцию-итератор.

Ты разместишь код в двух файлах:

  • Playlist.lua: создай класс Playlist, который хранит песни. В классе должны быть конструктор :new(), инициализирующий пустой список песен, и метод :addSong(title), который добавляет в него песню. Самое важное — добавь метод :iter(), который каждый раз при вызове возвращает новую функцию. Эта функция хранит свою позицию в локальной переменной и при каждом вызове возвращает следующую песню или nil, если песен больше нет.
  • main.lua: подключи модуль Playlist и создай новый плейлист. Добавь в него песни на основе полученных значений, затем используй обобщённый цикл for по playlist:iter(), чтобы вывести название каждой песни на отдельной строке.

Поскольку каждый вызов :iter() начинает с первой песни, один и тот же плейлист можно перебирать любое количество раз.

Ты получишь три входных значения, каждое из которых представляет название песни для добавления в плейлист (в указанном порядке).

Например, если входные значения — Bohemian Rhapsody, Stairway to Heaven и Hotel California, вывод должен быть таким:

Bohemian Rhapsody
Stairway to Heaven
Hotel California

Если входные значения — Yesterday, Imagine и Let It Be, вывод должен быть таким:

Yesterday
Imagine
Let It Be

Попробуйте сами

-- Подключите модуль Playlist
local Playlist = require('Playlist')

-- Считайте из входных данных названия трёх песен
local song1 = io.read()
local song2 = io.read()
local song3 = io.read()

-- TODO: Создайте новый плейлист с помощью Playlist:new()

-- TODO: Добавьте три песни в плейлист с помощью :addSong()

-- TODO: Используйте обобщённый цикл for для перебора playlist:iter()
-- и выведите название каждой песни на отдельной строке
-- Подсказка: for song in playlist:iter() do ... end
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Lua