[a]をピボット値で([a]、[a])に分割したいのですが、コードがあります
splitList :: (Ord a) => a -> [a] -> ([a],[a])
splitList pivot list =
([x | x <- list, x <= pivot], [x | x <- list, x > pivot])
しかし、リストを2回繰り返して、2つのリストを生成します。一度だけ繰り返す方法はありますか?
[a]をピボット値で([a]、[a])に分割したいのですが、コードがあります
splitList :: (Ord a) => a -> [a] -> ([a],[a])
splitList pivot list =
([x | x <- list, x <= pivot], [x | x <- list, x > pivot])
しかし、リストを2回繰り返して、2つのリストを生成します。一度だけ繰り返す方法はありますか?
末尾再帰ソリューションが必要な場合 (および要素の順序を逆にすることを気にしない場合)、またはその引数を遅延して消費するソリューションが必要な場合に応じて、2 つの可能性があります。
怠惰なソリューションは、リストの最初の要素が最初の部分に入るか、2 番目の部分に入るかを決定し、単純な再帰を使用してリストの残りを処理します。通常、末尾再帰よりも遅延の方が重要であるため、ほとんどの場合、これが推奨される解決策になります。
splitList :: (Ord a) => a -> [a] -> ([a],[a])
splitList _ [] = ([], [])
splitList p (x : xs)
| x <= p = (x : l, r)
| otherwise = (l, x : r)
where
~(l, r) = splitList p xs
ただし、場合によっては、要素の順序付けも遅延も気にせず、代わりに速度を気にします。(たとえば、並べ替えアルゴリズムを実装する場合。) 次に、アキュムレータを使用して結果を構築するバリアント (「パラメーターの蓄積: 「ほぼ末尾の再帰」の「ほぼ」を取り除く」を参照) を使用して、末尾の再帰を実現する方が適切です。
splitListR :: (Ord a) => a -> [a] -> ([a],[a])
splitListR pivot = sl ([], [])
where
sl acc [] = acc
sl (l, g) (x : xs)
| x <= pivot = sl (x : l, g) xs
| otherwise = sl (l, x : g) xs
一般に、再帰を手動でローリングすることを避けるのは良いスタイルと考えられています。代わりに、次のような折りたたみ関数を使用できます。
splitList pivot = foldr triage ([],[])
where
triage x ~(lows, highs)
| x <= pivot = (x:lows, highs)
| otherwise = (lows, x:highs)
もちろん、必要なことを正確に実行する既存の関数、つまりパーティションを利用する方がより良いスタイルです。:)
これをゼロから作成する場合は、小さな項目用と大きな項目用の 2 つのリストを維持できます。まず、ラッパーを書きます。
splitList :: (Ord a) => a -> [a] -> ([a],[a])
splitList pivot input = spL input [] [] where
では、spL を呼び出して、最初に 2 つの空のリストを指定するだけです。where ブロックを使用しているため、ピボットを渡す必要はなく、変化している 3 つのリストだけが渡されます。入力に何も残っていない場合は完了であり、答えを返す必要があります。
spL [] smalls larges = (smalls,larges)
ご覧のとおり、実際には小さいものと大きいものを逆に作成するので、それが気に入らない場合は、最終的な回答のペアを (reverse smalls,reverse larges) に置き換えます。いくつかの入力を処理しましょう。
spL (i:input) smalls larges | i <= pivot = spL input (i:smalls) larges
| otherwise = spL input smalls (i:larges)
そのため、十分に小さい場合は、スモールの前面にポップします。
リストの先頭にプッシュする理由は、毎回リストの最後まで反復する手間を省くためです。私が言ったように、それが重要な場合は、元の順序を取得するためにいつでも逆にすることができます。
まとめると、次のようになります。
splitList :: (Ord a) => a -> [a] -> ([a],[a])
splitList pivot input = spL input [] [] where
spL [] smalls larges = (smalls,larges)
spL (i:input) smalls larges | i <= pivot = spL input (i:smalls) larges
| otherwise = spL input smalls (i:larges)
import Data.List (partition)
splitList pivot = partition (<= pivot)
http://www.cs.indiana.edu/pub/techreports/TR27.pdf of 1976 1は次のことを示唆しています。
import Control.Applicative
partition3 [] p = ZipList [[], [], []]
partition3 (x:xs) p
| x < p = ZipList [(x:),id,id] <*> partition3 xs p
| x > p = ZipList [id,id,(x:)] <*> partition3 xs p
| True = ZipList [id,(x:),id] <*> partition3 xs p
それを使って、私たちは書きます
splitList pivot list = (a++b, c)
where
[a,b,c] = getZipList $ partition3 list pivot
1ここに見られるように。