4

Ord a => [a]->[a]まだ単純な署名を持つHas​​kellで(RANDOM-PIVOTを使用して)クイックソートを実装することは可能ですか?

私はモナドを理解し始めており、今のところ、モナドを「コマンドパターン」のようなものとして解釈しています。これは IO に最適です。

したがって、乱数を返す関数は、実際には IO のようなモナド値を返す必要があることを理解しています。そうしないと、参照透過性が損なわれるからです。また、返されたモナド値からランダムな整数を「抽出」する方法がないことも理解しています。そうしないと、参照の透過性が再び失われるためです。

[a]->[a]しかし、それが参照透過であるため、ランダムピボットを使用していても、「純粋な」クイックソート機能を実装することは可能だと思います。私の見解では、ランダム ピボットは単なる実装の詳細であり、関数のシグネチャを変更するべきではありません。

OBS:私は実際には特定のクイックソートの問題には興味がありません(したがって、失礼に聞こえたくありませんが、 「マージソートを使用する」または「ランダムピボットは実際にはパフォーマンスを向上させません」という種類の答えを探しているわけではありません) 私は実際に、関数が実際に純粋なものであることを保証できるクイックソートのような場合に、内部で「不純な」関数を使用する「純粋な」関数を実装する方法に興味があります。

クイックソートはその良い例です。

4

4 に答える 4

7

あなたは、ピボットポイントの選択が単なる実装の詳細であるという誤った仮定をしています。セットの半順序を考えてみましょう。カードのクイックソートのように

カードa<カードb額面が小さいが、ブール値を評価する場合:

  4 spades < 4 hearts (false)
  4 hearts < 4 spades (false)
  4 hearts = 4 spades (false)

その場合、ピボットの選択によってカードの最終的な順序が決まります。まったく同じように

次のような関数の場合

a = get random integer  
b = a + 3
print b 

によって決定されます。ランダムに何かを選択している場合、計算は非決定論的であるか、非決定論的である可能性があります。

于 2011-03-09T03:25:46.640 に答える
4

わかりました、これをチェックしてください。

ハッシュ可能なパッケージからコピーされた部分とブードゥー魔法の言語プラグマを選択します

{-# LANGUAGE FlexibleInstances, UndecidableInstances, NoMonomorphismRestriction, OverlappingInstances #-}

import System.Random (mkStdGen, next, split)
import Data.List (foldl')
import Data.Bits (shiftL, xor)

class Hashable a where
    hash :: a -> Int

instance (Integral a) => Hashable a where
    hash = fromIntegral

instance Hashable Char where
    hash = fromEnum

instance (Hashable a) => Hashable [a] where
    hash = foldl' combine 0 . map hash

-- ask the authors of the hashable package about this if interested
combine h1 h2 = (h1 + h1 `shiftL` 5) `xor` h2

OK、これで何かのリストを取り、Hashableそれを に変換できIntます。ここでインスタンスを提供CharしましIntegral aた。より多くのより良いインスタンスがハッシュ可能なパッケージに含まれており、ソルティングなども可能です。

これはすべて、数値ジェネレーターを作成できるようにするためです。

genFromHashable = mkStdGen . hash

それでは、楽しい部分です。乱数ジェネレーター、コンパレーター関数、およびリストを取る関数を書きましょう。次に、ジェネレーターを参照してピボットを選択し、コンパレーターを参照してリストを分割することにより、リストを並べ替えます。

qSortByGen _ _ [] = []
qSortByGen g f xs = qSortByGen g'' f l ++ mid ++ qSortByGen g''' f r
    where (l, mid, r) = partition (`f` pivot) xs
          pivot = xs !! (pivotLoc `mod` length xs)
          (pivotLoc, g') = next g
          (g'', g''') = split g'

partition f = foldl' step ([],[],[])
    where step (l,mid,r) x = case f x of
              LT -> (x:l,mid,r)
              EQ -> (l,x:mid,r)
              GT -> (l,mid,x:r)

ライブラリ関数:ジェネレーターから をnext取得しInt、新しいジェネレーターを生成します。splitジェネレーターを 2 つの異なるジェネレーターにフォークします。

My functions :リストを 3 つのリストに分割するためにpartition使用します。f :: a -> Ordering折り目を知っていれば、それは非常に明確なはずです。(サブリスト内の要素の最初の順序を保持しないことに注意してください。逆にします。foldr を使用すると、問題が解決する可能性があります。)qSortByGen前に述べたように機能します。ピボットのジェネレーターを参照し、リストを分割します。 、2 つの再帰呼び出しで使用するジェネレーターをフォークし、左辺と右辺を再帰的に並べ替え、すべてを連結します。

便利な関数はここから簡単に作成できます

qSortBy f xs = qSortByGen (genFromHashable xs) f xs
qSort = qSortBy compare

最終関数のシグネチャに注意してください。

ghci> :t qSort
qSort :: (Ord a, Hashable a) => [a] -> [a]

Hashableリスト内の型は、 と の両方を実装する必要がありOrdます。あなたが求めていた「純粋な」機能があり、論理的な要件が1つ追加されています。より一般的な機能は、要件の制限が少なくなります。

ghci> :t qSortBy
qSortBy :: (Hashable a) => (a -> a -> Ordering) -> [a] -> [a]
ghci> :t qSortByGen
qSortByGen
  :: (System.Random.RandomGen t) =>
     t -> (a -> a -> Ordering) -> [a] -> [a]

最終的な注意事項

qSortすべての入力に対してまったく同じように動作します。「ランダムな」ピボット選択は. 実際、決定論的です。しかし、リストをハッシュしてから乱数ジェネレーターをシードすることで不明瞭になり、私にとっては十分に「ランダム」になります。;)

qSortまた、長さが 未満のリストに対してのみ機能しますmaxBound :: Int。負のインデックスに問題があると思っていましたが、アドホック テストではまだ問題に遭遇していません。


または、「真の」ランダム性のために IO モナドと一緒に暮らすこともできます。

qSortIO xs = do g <- getStdGen -- add getStdGen to your imports
                return $ qSortByGen g compare xs


ghci> :t qSortIO
qSortIO :: (Ord a) => [a] -> IO [a]
ghci> qSortIO "Hello world"
" Hdellloorw"
ghci> qSort "Hello world"
" Hdellloorw"
于 2011-03-09T02:56:58.313 に答える
3

関数が参照透過的であることはわかっているが、コンパイラに対してそれを証明できない場合unsafePerformIO :: IO a -> a、モジュールの関数を使用できますData.Unsafe

たとえば、 を使用unsafePerformIOして初期ランダム状態を取得し、この状態だけを使用して何かを行うことができます。

ただし、本当に必要でない場合は使用しないでください。それでも、よく考えてみてください。unsafePerformIOこの関数を使用すると、さまざまな型を強制することから RTS をクラッシュさせることまで、あらゆる可能性があります。

于 2011-03-08T20:49:46.147 に答える
2

Haskell は、STモナドを提供して、非参照透過的なアクションを実行し、参照透過的な結果をもたらします。

参照透過性を強制しないことに注意してください。潜在的に非参照透過的な一時的な状態が漏れないようにするだけです。再現不可能な方法で再配置された操作された純粋な入力データを返すことを妨げるものは何もありません。最善の方法は、ST と純粋な方法の両方で同じことを実装し、QuickCheck を使用してランダムな入力でそれらを比較することです。

于 2011-03-08T20:29:49.703 に答える