この章ではリストから一歩進み、
ハッシュを実現できる木構造
関数プログラミングと
もちろん、
木構造の特徴
木構造は永続データとして利用できます。図1を見てください。左の探索木に要素oを挿入すると、

木構造を使えば、
ハッシュのAPI
これから赤黒木を用いてハッシュを実装します。まず、
empty :: Hash k v
insert :: Ord k => (k,v) -> Hash k v -> Hash k v
fromList :: Ord k => [(k,v)] -> Hash k v
search :: Ord k => k -> Hash k v -> Maybe v
ハッシュの型はHash k vとします。キーの型がkで、
empty──空のハッシュ
emptyは空のハッシュを表します。emptyはO(1)です。
insert──要素を追加する
insertは、Ord k =>
というのは、
まだ実装していませんが、
> empty
Leaf
> insert (1,'a') empty
Node B Leaf (1,'a') Leaf
返ってくる値は赤黒木のリテラル表現ですが、
> insert (2,'b') (insert (1,'a') empty)
Node B Leaf (1,'a') (Node R Leaf (2,'b') Leaf)
fromList──要素をまとめて登録する
上記の例のようにinsertを何回も使うのは面倒なので、
> fromList [(1,'a'),(2,'b')]
Node B Leaf (1,'a') (Node R Leaf (2,'b') Leaf)
search──要素を検索する
ハッシュを検索するには関数searchを使います。searchはO(log N)です。
> let hash = fromList [(1,'a'),(2,'b')]
> search 1 hash
Just 'a'
> search 3 hash
Nothing
ハッシュにキーが格納されている場合は
型の定義
Maybeという新しい型が出てきましたので、
列挙型
Haskellで最も簡単な型は真理値Boolです。次のように定義されています。
data Bool = False | True
Haskellで新しい型を定義する際は、=
の右側に値を|
で区切って列挙します。この値のことを構成子と呼びます。型と構成子は大文字で始める必要があります。
列挙型の例として、
data Ordering = LT | EQ | GT
LT、
> compare 1 2
LT
型変数を取る型
前節で出てきたMaybeは次のように定義されています。
data Maybe a = Nothing | Just a
Maybeは型変数を取る型です。まだ型変数に親しみが湧かない方は、
-- 以下は疑似コード
data Maybe Char = Nothing | Just Char
Maybeは、
重要なのできちんと区別していただきたいのですが、
一方、
構造体
実はMaybeは、
data Pair = MkPair Int Char
構造体Pairは、
data Pair k v = Pair k v
再帰型
構造体では、
data Tree = Leaf | Node Tree Int Tree
Leafと Nodeが構成子、
赤黒木を実装しよう
赤黒木とは
赤黒木の説明に戻りましょう。赤黒木とは、

※丸がノードで、
ノードにはキーとなる整数のみを表示している
二分木が赤黒木として成り立つためには、
- 不変条件1:
(図2で最上部にある) 根は黒である - 不変条件2:根から葉に至るすべてのパスで、
黒の数が同じ - 不変条件3:赤は連続してはならない
図2では、
不変条件から中間ノードが最も少ない葉
赤黒木の定義
赤黒木の定義には、
data Color = R | B deriving Show
Rが赤、
data Hash k v = Leaf -- 葉は黒
| Node Color (Hash k v) (k,v) (Hash k v)
deriving Show
Haskellでは、
empty :: Hash k v
empty = Leaf
insertの実装
赤黒木へ要素を追加するには次のようにします。
- 新しい要素は赤いノードとして、
根から下に向かって挿入する。赤を挿入するので、 不変条件2は守られる - 新しい赤いノードが黒と黒の間に収まれば問題ない。しかし、
赤の下に収まった場合、 赤が連続し、 不変条件3が満たせなくなる。そこで、 不変条件2と不変条件3を満たすように、 上に向かってバランスを取りながら戻る - バランスを取った結果、
最終的に根が赤になって、 不変条件1が満たされないことがある。そこで、 根を黒にして不変条件1を満たす。黒ノードが増えるのは根に限る。根で黒が増えた場合は、 すべてのパスで黒の数が1増えることになる
ここでは、
turnB :: Hash k v -> Hash k v
turnB (Node c l (k,v) r) = Node B l (k,v) r
insert :: Ord k => (k,v) -> Hash k v -> Hash k v
insert (k,v) t = turnB (insert' k v t)
insert'の定義は次のようになります。
insert' :: Ord k => k -> v -> Hash k v -> Hash k v
insert' kx vx Leaf = Node R Leaf (kx,vx) Leaf
insert' kx vx (Node c l (k,v) r) = case compare kx k of
-- 左部分木に挿入してバランスを回復
LT -> balance c (insert' kx vx l) (k,v) r
-- 右部分木に挿入してバランスを回復
GT -> balance c l (k,v) (insert' kx vx r)
-- 元のノードをそのまま返す
EQ -> Node c l (k,v) r
葉にたどり着いた場合は、
対象がノードの場合を扱うコードに、
つまり、

新しい赤いノードを挿入したら、
balance :: Color -> Hash k v -> (k,v) -> Hash k v -> Hash k v
-- 場合1
balance B (Node R (Node R a x b) y c) z d =
Node R (Node B a x b) y (Node B c z d)
-- 場合2
balance B (Node R a x (Node R b y c)) z d =
Node R (Node B a x b) y (Node B c z d)
-- 場合3
balance B a x (Node R b y (Node R c z d)) =
Node R (Node B a x b) y (Node B c z d)
-- 場合4
balance B a x (Node R (Node R b y c) z d) =
Node R (Node B a x b) y (Node B c z d)
-- それ以外
balance c a x b = Node c a x b
balanceの引数の意味は、

バランスが回復される様子を図5に示します。

fromListの実装
fromListは、
insert'' :: Ord k => Hash k v -> (k, v) -> Hash k v
insert'' t kv = insert kv t
fromList :: Ord k => [(k,v)] -> Hash k v
fromList = foldl insert'' empty
searchの実装
searchの実装は簡単です。もし、
ノードを対象にしている場合、
search :: Ord k => k -> Hash k v -> Maybe v
search kx Leaf = Nothing
search kx (Node c l (k,v) r) = case compare kx k of
LT -> search kx l
GT -> search kx r
EQ -> Just v
命令的な実装との比較
赤黒木のinsertを命令プログラミングで実装するとどうなるでしょうか? 『アルゴリズムイントロダクション 改訂2 版 第1 巻 数学的基礎とデータ構造』
関数型も命令型と同様の開発手順
これまで書いたコードだけでも、