リストの分割
一定の数で頭から要素を確保する方法
この方法は最後の部分リストだけが短くなる可能性がある.一定の数で頭から要素を確保するという関数は展開のインスタンスとして書ける.
code:haskell
slice n = unfoldr phi
where
phi [] = Nothing
phi xs = Just $ splitAt n xs
slice に渡す一定数は元のリストの長さから計算できるが,元のリストの長さではなく分割数ずつスライスしてできたリストの長さでよい.
code:haskell
dividInto :: Int -> a -> a dividInto n xs = slice (length (slice n xs)) xs
SコンビネータであるControl.Applicative.<*>を使うと
code:haskell
dividInto n = flip slice <*> length . slice n
上のslice n xsではリストのリストを構成しているが,長さが欲しいだけなので
code:haskell
count = iter 0
where
iter c _ [] = c
iter c n xs = iter (c+1) n (drop n xs)
があればいい.
code:haskell
dividInto n = flip slice <*> count n
部分要素の長さの差を1以下にする方法
slice n xsを転置transposeするというアイデアが考えられるが要素の順番がいれかわってしまうので工夫が必要.転置で作ったものの部分リストの長さを計算してそれを使うという手はある.mapAccumLを使えば,以下のように定義できる.
code:haskell
dividInto n = snd . (
mapAccumL ((swap .) . flip splitAt)
<*>
map length . transpose . slice n
)
ここでswapは対の第1要素と第2要素を入れ替える関数である.
長さではなく,別のリストを定規にしてリストを分けるsplitWith :: [b] -> [a] -> ([a],[a])を用意すれば部分リストの長さを求めなくてもよい.
code:haskell
dividInto n = snd . (
mapAccumL ((swap .) . flip splitWith)
<*>
transpose . slice n
)
splitWithは以下のように定義できる.
code:haskell
splitWith :: b -> a -> (a,a) splitWith _ [] = ([],[])
splitWith [] ys = ([],ys)
splitWith (_:xs) (y:ys) = case splitWith xs ys of
(ys',ys'') -> (y:ys',ys'')
すべての分割を生成する
前節まででとりあげている分割には2種類あった.単にリストをn個の部分リストに分割せよといった場合には分割はいくつも存在する.そこでリストの分割が満すべき性質を考えてみる.必須になりそうなのは以下の3つであろう.
1. length (dividInto n xs) == n
2. concat (dividInto n xs) == xs
3. all ((0<) . length) (dividInto n xs)
この3つの性質をもつ分割をすべて生成する関数ndivids :: Int -> [a] -> [[[a]]]は以下のように定義できる
code:haskell
ndivids 0 _ = []
ndivids _ [] = []
ndivids 1 xs = xs
ndivids n (x:xs) = map (x:) (ndivids (n-1) xs) ++ map (hd x) (ndivids n xs)
where hd y (zs:zss) = (y:zs):zss
この関数ndivids n xsで生成された分割が前述の仕様を満すことの証明は省略.