Menu
CoddyTech

イテレータパターン

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

オブジェクト内に項目のコレクションが格納されている場合、それらをループで順に処理したいことがよくあります。Luaの汎用的なforループは、任意のイテレータ関数で動作します。各反復の前に関数を呼び出し、その関数がnilを返すと停止します。

Iteratorパターンでは、オブジェクトにそのような関数を返すメソッドを持たせます。その関数はローカル変数に位置を保持するため、その位置はコレクションではなく、1つのループに属します。

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から始まります。この関数はクロージャです。iterが戻った後も、indexとselfを使うことができます。最後の項目を過ぎると、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 を格納し、自身のイテレーターとして機能すると、最初のループでインデックスが末尾に達するため、2回目のループでは何も出力されません。また、break で途中終了したループの後では、次のループが途中から始まってしまいます。

challenge icon

チャレンジ

簡単

Luaの汎用forループで繰り返し処理できるPlaylistクラスを作りましょう!イテレータ関数を返す:iter()メソッドを使って、Iteratorパターンを実装します。

コードは2つのファイルに分けて書きます。

  • Playlist.lua:曲を内部に保存するPlaylistクラスを作成します。このクラスには、空の曲リストを初期化する:new()コンストラクタと、曲をリストに追加する:addSong(title)メソッドが必要です。最も重要なのは、呼び出されるたびに新しい関数を返す:iter()メソッドを追加することです。その関数はローカル変数で位置を保持し、呼び出されるたびに次の曲を返します。曲が残っていない場合はnilを返します。
  • main.lua:Playlistモジュールを読み込んで、新しいプレイリストを作成します。受け取った入力に基づいて曲を追加し、playlist:iter()を使った汎用forループで各曲名を1行ずつ出力します。

:iter()を呼び出すたびに最初の曲から始まるため、同じプレイリストを何度でも繰り返し処理できます。

プレイリストに追加する曲名を表す入力が3つ、順番に与えられます。

たとえば、入力が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')

-- 入力から曲名を3つ読み取る
local song1 = io.read()
local song2 = io.read()
local song3 = io.read()

-- TODO: Playlist:new() を使って新しいプレイリストを作成する

-- TODO: :addSong() を使って3曲をプレイリストに追加する

-- TODO: playlist:iter() に対して汎用 for ループを使用する
-- そして、各曲名をそれぞれ別の行に出力する
-- ヒント: for song in playlist:iter() do ... end
quiz icon腕試し

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

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

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