リスト
1. リストとは
リスト(list)は,複数のデータを一列に並べたデータ構造のひとつです. ML では,複数のデータを扱う際には,配列よりもリストがよく使われます.
これは,リスト [1, 2, 3]
の概念的な構造を表した図です.
リストはこの図のように,値をもつ単位(LISP ではこれを "cons セル" とよぶ)を一列に並べた構造となっています.
最後は空リスト(nil)で終わります.
配列と違って注意しないといけないのは,リストは "n 番目の要素を取り出す" みたいな処理が不得意という点です. 配列ではこの操作は一瞬ですが,リストの場合には,列を先頭から順番に辿っていかなければならないため,時間がかかります. ただし,Haskell ではインデックスを指定して要素を取り出す操作はあまり書かないため,このことはそれほど問題になりません. |
2. ML のリスト
ML では,次の 2 通りの方法でリストを書くことができます. 構文上はまるで見た目が違いますが,どちらも同一のリストを表します.
1 :: 2 :: 3 :: nil -- cons 構成子による書き方
[1, 2, 3] -- リストリテラルによる書き方
すべてのリストは nil
と ::
という2つの構成子を用いて記述できます.
角括弧を用いたリストの式 [1, 2, 3]
は 1 :: 2 :: 3 :: nil
の構文糖という位置づけです.
::
はしばしば "cons" と呼ばれ("construct" の意),第 1 引数に先頭要素,第 2 引数に後続のリストをとります.
::
は右結合なので,1 :: 2 :: 3 :: nil
の結合は 1 :: (2 :: (3 :: nil))
です.
ML で扱うリストは,同一の型の要素のみで構成されるリストです.
つまり,[1, 1.0, #"a"]
のようなリストは許されません.
型が 'a
である要素から構成されるリストの型を 'a list
と書きます.
- 1 :: 2 :: 3 :: nil;
val it = [1,2,3] : int list
ML が採用しているような,すべて同じデータ型の要素からなるリストは,等質的 (homogeneous) なリストとよばれます. |
3. リストを扱う関数
リストを扱う標準関数のうち,トップレベルで定義されているものは以下の通りです.
関数 | 型 | 説明 |
---|---|---|
|
|
空リストであれば true |
|
|
先頭要素を返す |
|
|
先頭要素を除去する |
|
|
リストの長さを返す |
|
|
各要素に関数 f を適用する |
|
|
要素の並びを逆にする |
|
|
リストを連結する |
|
|
右から左へリストを畳み込む |
|
|
左から右へリストを畳み込む |
型を見ればだいたいどんな関数なのか想像がつくのが面白いですね.
他にも,リストを操作する様々な関数が List
ストラクチャで定義されています.
主なものには以下のようなものがあります.
これらの関数は List.nth
や List.concat
のように書けばアクセスできます.
関数 | 型 | 説明 |
---|---|---|
|
|
最終要素を返す |
|
|
先頭から n 番目の要素を返す |
|
|
先頭から n 要素分のリストを返す |
|
|
先頭から n 要素分を除去する |
|
|
要素のリストを連結する |
|
|
述語 p を満たす要素を残す |
|
|
述語 p を満たす要素があれば true |
|
|
すべての要素が述語 p を満たせば true |
|
|
リスト [f 0, f 1, …, f (n-1)] を返す |
いくつか使用例を見てみましょう.
map f xs
: リストの各要素に関数f
を適用します.- map (fn x => x * x) [1, 2, 3, 4, 5]; val it = [1,4,9,16,25] : int list
xs @ ys
: リストを連結します(@
はおそらく "append" の意).- [1, 2] @ [3, 4, 5]; val it = [1,2,3,4,5] : int list
filter p xs
: 述語p
が真を返す要素だけを残します.- List.filter (fn x => x mod 2 = 1) [1, 2, 3, 4, 5]; val it = [1,3,5] : int list
4. リストの畳込み
foldr
は二項演算子を使って右からリストを畳み込む関数です.
二項演算子を ⊕ とすると,式 foldr ⊕ 0 [1, 2, 3]
は 1 ⊕ (2 ⊕ (3 ⊕ 0))
と展開されます.
- foldr (op +) 0 [1, 2, 3] (* 1 + (2 + (3 + 0)) = 6 *) ;
val it = 6 : int
- foldr (op -) 0 [1, 2, 3] (* 1 - (2 - (3 - 0)) = 2 *) ;
val it = 2 : int
foldl
は逆に,二項演算子を使って左からリストを畳み込みます.
二項演算子を ⊕ とすると,式 foldl ⊕ 0 [1, 2, 3]
は,3 ⊕ (2 ⊕ (1 ⊕ 0))
と展開されます.
- foldl (op +) 0 [1, 2, 3] (* 3 + (2 + (1 + 0)) = 6 *) ;
val it = 6 : int
- foldl (op -) 0 [1, 2, 3] (* 3 - (2 - (1 - 0)) = 2 *) ;
val it = 2 : int
畳込み関数はかなり表現力が高く,リストを操作するだいたいの関数は foldr
を使って記述できます.
例えば,map
関数は foldr
を使って以下のように書くこともできます.
- fun map' f = foldr (fn (x, s) => f x :: s) nil;
val map' = fn : ('a -> 'b) -> 'a list -> 'b list
- map' (fn x => x * x) [1,2,3,4,5];
val it = [1,4,9,16,25] : int list