今回は簡単な例題として、「連結リスト (Linked List)」という基本的なデータ構造を作ってみましょう。なお、今回のプログラムは拙作のページ「お気楽 Python プログラミング入門第 5 回」の "連結リスト" を Lua で書き直したものです。内容は重複していますが、あしからずご了承ください。
連結リストはデータを一方向につなげたデータ構造です。リストを操作するプログラミング言語では Lisp が有名ですが、Lisp で扱うリストが連結リストです。図 1 に連結リストの構造を示します。
(1)変数 ┌─┐ ┌─┬─┐ ┌─┬─┐ ┌─┬─┐ │・┼─→│10│・┼→│20│・┼→│30│/│ /:終端(null) └─┘ └─┴─┘ └─┴─┘ └─┴─┘ (2)ヘッダセル ┌─┬─┐ ┌─┬─┐ ┌─┬─┐ ┌─┬─┐ │ │・┼─→│10│・┼→│20│・┼→│30│/│ /:終端(null) └─┴─┘ └─┴─┘ └─┴─┘ └─┴─┘ 図 1 : 連結リスト
連結リストはセル (cell) というデータを繋げて作ります。セルにはデータを格納する場所と、次のセルを指し示す場所から構成されます。図 1 でいうと、箱がひとつのセルを表していて、左側にデータを格納し、右側に次のセルへの参照を格納します。リストの終わりを示すため、最後のセルの右側には特別な値(たとえば null)を格納します。そして、図 1 (1) のように先頭セルへの参照を変数に格納しておけば、この変数を使って連結リストにアクセスすることができます。また、図 1 (2) のようにヘッダセルを用意する方法もあります。
それではプログラムを作りましょう。まずは、セル Cell と連結リスト List のコンストラクタを作成します。
リスト 8 : コンストラクタの定義 -- セルの定義 Cell = {} -- コンストラクタ function Cell.new(data, link) return {data = data, link = link} end -- リストの定義 List = {} -- コンストラクタ function List.new(...) local obj = {top = Cell.new(nil, nil)} local cp = obj.top setmetatable(obj, {__index = List}) for i = 1, select("#", ...) do local x = select(i, ...) cp.link = Cell.new(x, nil) cp = cp.link end return obj end
Cell.new はセルを作成します。フィールド data にデータを格納し、フィールド link に接続するセルへの参照を格納します。List.new は連結リストのオブジェクトを生成します。今回は図 1 (2) の方法でプログラムします。List が生成したインスタンスのフィールド top にはヘッダーセルを格納します。List.new は可変個の引数を受け取るようにすると便利です。可変引数式から要素 x を取り出し、連結リストの最後尾に x を追加していきます。
あとはメソッドを定義するだけです。今回作成するメソッドを表 1 に示します。
メソッド | 機能 |
---|---|
ls:at(n) | n 番目の要素を求める |
ls:set(n, x) | n 番目の要素 x に書き換える |
ls:insert(n, x) | n 番目の位置にデータ x を挿入する |
ls:remove(n) | n 番目の要素を削除する |
ls:isEmpty() | 連結リストが空の場合は真を返す |
ls:foreach(func) | 要素に関数 func を適用する |
ls:each() | 要素を順番に取り出す (イテレータ) |
プログラムは次のようになります。
リスト 9 : メソッドの定義 (1) -- 作業用メソッド : n 番目のセルを返す function List:_nth(n) local cp = self.top local i = 0 while cp do if n == i then return cp else cp = cp.link i = i + 1 end end return nil end -- n 番目の要素を返す function List:at(n) local cp = self:_nth(n) if cp then return cp.data else return nil end end -- n 番目の要素を書き換える function List:set(n, x) local cp = self:_nth(n) if cp then cp.data = x return x else return nil end end -- n 番目にデータを挿入 function List:insert(n, x) local cp = self:_nth(n - 1) if cp then cp.link = Cell.new(x, cp.link) return x else return nil end end -- n 番目の要素を削除 function List:remove(n) local cp = self:_nth(n - 1) if cp and cp.link then local data = cp.link.data cp.link = cp.link.link return data else return nil end end -- 巡回 function List:foreach(func) local cp = self.top.link while cp do func(cp.data) cp = cp.link end end -- イテレータ function List:each() return coroutine.wrap( function() self:foreach(function(x) coroutine.yield(x) end) return nil end) end -- 空リストか function List:isEmpty() return self.top.link == nil end
_nth は n 番目のセルを求める作業用のメソッドです。at, set, insert, remove は _nth を使うと簡単にプログラムすることができます。詳しい説明は拙作のページ「お気楽 Python プログラミング入門第 5 回」 "連結リスト" をお読みください。
それでは、簡単な実行例を示しましょう。
> a = List.new() > for x = 1, 5 do a:insert(1, x) end > a table: 0x7fffef67ff20 > a:at(1) 5 > a:at(5) 1 > a:foreach(print) 5 4 3 2 1 > for x in a:each() do print(x) end 5 4 3 2 1 > a:remove(1) 5 > a:foreach(print) 4 3 2 1 > a:set(1, 10) 10 > a:foreach(print) 10 3 2 1
正常に動作していますね。
このほかに、連結リストを配列に変換するメソッド toArray と文字列に変換するメソッド toString を定義すると便利です。リスト 10 を見てください。
リスト 10 : データの変換 -- 配列に変換 function List:toArray() local ary = {} self:foreach(function(x) table.insert(ary, x) end) return ary end -- 文字列に変換 function List:toString() return '(' .. table.concat(self:toArray(), ',') .. ')' end
toArray はメソッド foreach を使うと簡単です。foreach で先頭から順番に要素にアクセスし、それを配列 ary に insert で追加するだけです。最後に配列を返します。
toString も簡単です。連結リストを toArray で配列に変換し、要素を関数 concat で連結します。各要素は concat で文字列に変換され、カンマ ( , ) をはさんで連結されます。
それでは簡単な実行例を示しましょう。
> a = List.new(1,2,3,4,5) > print(table.unpack(a:toArray())) 1 2 3 4 5 > a:toString() (1,2,3,4,5)
-- -- linklist.lua : 連結リスト -- -- Copyright (C) 2011-2019 Makoto Hiroi -- -- セルの定義 Cell = {} -- コンストラクタ function Cell.new(data, link) return {data = data, link = link} end -- リストの定義 List = {} function List.new(...) local obj = {top = Cell.new(nil, nil)} local cp = obj.top setmetatable(obj, {__index = List}) for i = 1, select("#", ...) do local x = select(i, ...) cp.link = Cell.new(x, nil) cp = cp.link end return obj end -- メソッドの定義 -- 作業用メソッド : n 番目のセルを返す function List:_nth(n) local cp = self.top local i = 0 while cp do if n == i then return cp else cp = cp.link i = i + 1 end end return nil end -- n 番目の要素を返す function List:at(n) local cp = self:_nth(n) if cp then return cp.data else return nil end end -- n 番目の要素を書き換える function List:set(n, x) local cp = self:_nth(n) if cp then cp.data = x return x else return nil end end -- n 番目にデータを挿入 function List:insert(n, x) local cp = self:_nth(n - 1) if cp then cp.link = Cell.new(x, cp.link) return x else return nil end end -- n 番目の要素を削除 function List:remove(n) local cp = self:_nth(n - 1) if cp and cp.link then local data = cp.link.data cp.link = cp.link.link return data else return nil end end -- 巡回 function List:foreach(func) local cp = self.top.link while cp do func(cp.data) cp = cp.link end end -- イテレータ function List:each() return coroutine.wrap( function() self:foreach(function(x) coroutine.yield(x) end) return nil end) end -- 空リストか function List:isEmpty() return self.top.link == nil end -- 配列に変換 function List:toArray() local ary = {} self:foreach(function(x) table.insert(ary, x) end) return ary end -- 文字列に変換 function List:toString() return '(' .. table.concat(self:toArray(), ',') .. ')' end