2016-03-19 11 views
4

私はサブセットのサブセットを生成する関数を書いています。 subsets [1..]で使用すると、スタックオーバーフローが発生しました。それは "普通の"(怠惰な)言語になると "正常な"振る舞いです。そして今、私は自分の機能を怠惰に改善したいと思います。セットのサブセットを生成する。怠惰?

P.S.私は怠惰を理解していません(そして私はそれを理解しようとします)ので、おそらく私の問題はあなたのために奇妙です - 説明してください。 :)

P.S. 2私はHaskellでの私の障害について何かを言うこと自由に感じ;)

subsets :: [a] -> [[a]] 
subsets (x:xs) = (map (\ e -> x:e) (subsets xs)) ++ (subsets xs) 
subsets [] = [[]] 

答えて

4

この機能には2つの問題があります。まず、を2回、を再帰的に返します。これは、重複するサブセットのたびに各サブツリーが再計算されるため、指数関数的に必要以上に不便になります。これは再帰呼び出しが同じ値であるINGのletで固定することができます。

subsets' :: [a] -> [[a]] 
subsets' [] = [[]] 
subsets' (x:xs) = let s = subsets' xs 
        in map (x:) s ++ s 

これはすでによく、私は待っていなかった... length $ subsets [1..25]がかかる中に、数秒でlength $ subsets' [1..25]を計算できるようになります。)

他の問題は、あなたのバージョンで無限のリストを与えると、そのリストのが最初に無限小尾に再発することです。意味のある方法ですべての有限部分集合を生成するには、まず、小さな集合から各集合を構築して(終了を保証するため)、次にフェアの順序を保証する必要があります。最初にリスト[[1], [2], ...]を生成し、残りの部分は決して得られない)。このために、我々は[[]]から開始し、再帰的に我々がすでに生成されているすべてのものに現在の要素を追加し、次のステップのための新しいリストを覚えている:

*Main> take 100 $ subsets'' [1..] 
[[],[1],[2],[2,1],[3],[3,1],[3,2],[3,2,1],[4],[4,1],[4,2],[4,2,1],[4,3],[4,3,1],[4,3,2],[4,3,2,1],[5],[5,1],[5,2],[5,2,1],[5,3],[5,3,1],[5,3,2],[5,3,2,1],[5,4],[5,4,1],[5,4,2],[5,4,2,1],[5,4,3],[5,4,3,1],[5,4,3,2],[5,4,3,2,1],[6],[6,1],[6,2],[6,2,1],[6,3],[6,3,1],[6,3,2],[6,3,2,1],[6,4],[6,4,1],[6,4,2],[6,4,2,1],[6,4,3],[6,4,3,1],[6,4,3,2],[6,4,3,2,1],[6,5],[6,5,1],[6,5,2],[6,5,2,1],[6,5,3],[6,5,3,1],[6,5,3,2],[6,5,3,2,1],[6,5,4],[6,5,4,1],[6,5,4,2],[6,5,4,2,1],[6,5,4,3],[6,5,4,3,1],[6,5,4,3,2],[6,5,4,3,2,1],[7],[7,1],[7,2],[7,2,1],[7,3],[7,3,1],[7,3,2],[7,3,2,1],[7,4],[7,4,1],[7,4,2],[7,4,2,1],[7,4,3],[7,4,3,1],[7,4,3,2],[7,4,3,2,1],[7,5],[7,5,1],[7,5,2],[7,5,2,1],[7,5,3],[7,5,3,1],[7,5,3,2],[7,5,3,2,1],[7,5,4],[7,5,4,1],[7,5,4,2],[7,5,4,2,1],[7,5,4,3],[7,5,4,3,1],[7,5,4,3,2],[7,5,4,3,2,1],[7,6],[7,6,1],[7,6,2],[7,6,2,1]] 
+0

ありがとう!それはモナドの解です。彼らなしではどうですか? – Gilgamesz

+1

@Gilgameszはモナドが必要でもないことが判明しました。とにかく以前のバージョンは間違っていました - 今は普通のリストで動作し、うまくいけば正しく動作します。 – phg

+0

@Gilgamesz怠惰は、ハスケルの中核であるが、最も高度なトピックの1つです。例えば、phgの最初の点は、グラフの構造を使って進行中の評価を表す、ハーケルの具体的な怠惰の実装の結果です。IIRC、いくつかの限られたケースでは、コンパイラ*は重複をキャッチし、同じデータ構造を両方のインスタンス---必ずしもそうではありません。これは怠け者のために支払う価格の1つです。パフォーマンスに焦点を当てるときや無限大を扱うときには、コンパイラの限界があります。 – jpaugh

3

あなたは無限集合のすべてサブセットを生成することができません:彼らは非可算集合を形成します。カーディナリティはそれを不可能にします。

ほとんどすべて有限のサブセットを生成しようとすることができます。そのため、[]には到着することはないので、[]以降の誘導では進めません。あなたはリストの始めから誘導の代わりに誘導的に進める必要があります。

+0

あなたは正しいです。あなたはセット理論からいくつかの事実を思い出します:D。さて、有限集合のみを考えてみましょう。私の機能を怠け者にする方法は? – Gilgamesz

3
:この順序になり

subsets'' :: [a] -> [[a]] 
subsets'' l = [[]] ++ subs [[]] l 
    where subs previous (x:xs) = let next = map (x:) previous 
           in next ++ subs (previous ++ next) xs 
     subs _ [] = [] 

右折りソリューションは次のようになります。その後、

powerset :: Foldable t => t a -> [[a]] 
powerset xs = []: foldr go (const []) xs [[]] 
    where go x f a = let b = (x:) <$> a in b ++ f (a ++ b) 

\> take 8 $ powerset [1..] 
[[],[1],[2],[2,1],[3],[3,1],[3,2],[3,2,1]] 
+0

+1は簡潔ですが、説明が必要です。とりわけ、これはnot-quite-idiomaticというテクニックを使用しています。「foldr'と* four *引数」を呼び出すことで、簡単に新入社員を混乱させる可能性があります。私はまだわかりやすい再帰を好むだろう。 – chi

+0

再帰的なソリューションに比べて、メモリの使用量が少なくて済み、パフォーマンスが向上します!この解決法と 'ghc --make -O2'を' length $ powerset [1..24] 'と再帰的に比較してみてください。 –

+0

ありがとうございました!しかし、いくつかの言葉であなたの解決策を説明することができれば、それはすばらしいことになります。特に私は '(const [])'と '<$>'を理解していません:) – Gilgamesz

関連する問題