web-dev-qa-db-ja.com

関数型プログラミング言語でグラフとグラフアルゴリズムを実装するにはどうすればよいですか?

基本的に、私はグラフデータ構造を作成し、副作用が許容されるプログラミング言語でダイクストラのアルゴリズムを使用する方法を知っています。通常、グラフアルゴリズムは構造を使用して特定のノードを「訪問済み」としてマークしますが、これには副作用があり、回避しようとしています。

これを関数型言語で実装する1つの方法を考えることができますが、基本的には大量の状態をさまざまな関数に渡す必要があり、よりスペース効率の高いソリューションがあるかどうか疑問に思っています。

45
brad

MartinErwigのHaskell 関数グラフライブラリ がどのように機能するかを確認してください。たとえば、その 最短経路関数 はすべて純粋であり、その実装方法については ソースコード を確認できます。

別のオプション 前述のfmarkのように は、状態の観点から純粋関数を実装できるようにする抽象化を使用することです。彼は州のモナド( lazystrict の両方の種類で利用可能)について言及しています。別のオプション、GHC Haskellコンパイラ/インタプリタ(または、ランク2タイプをサポートするHaskell実装)で作業している場合、別のオプションは STモナド です。内部で可変変数を処理する純粋関数を記述します。

17

私が精通している唯一の関数型言語であるhaskellを使用している場合は、State monadを使用することをお勧めします。状態モナドは、状態を取り、中間値といくつかの新しい状態値を返す関数の抽象化です。これは 大きな状態を維持する必要がある状況では慣用的なhaskell と見なされます。

これは、初心者の関数型プログラミングチュートリアルで強調されている、単純な「関数の結果として状態を返し、パラメーターとして渡す」イディオムに代わる優れた方法です。ほとんどの関数型プログラミング言語は同様の構造を持っていると思います。

3
fmark

訪問したセットをセットとして保持し、パラメーターとして渡します。任意の順序付けされたタイプのセットと非常に効率的な整数のセットの効率的なログ時間の実装があります。

グラフを表すために、隣接リストを使用するか、各ノードを後続のリストにマップする有限マップを使用します。それは私が何をしたいかによります。

AbelsonとSussmanではなく、Chris Okasakiの 純粋関数型データ構造 をお勧めします。私はクリスの論文にリンクしましたが、あなたがお金を持っているなら、彼はそれを 優れた本 に拡張しました。


ニヤリと言うだけで、Haskellで継続渡しスタイルで行われる少し怖い逆ポストオーダー深さ優先探索があります。これは Hoopl オプティマイザーライブラリから直接出ています:

postorder_dfs_from_except :: forall block e . (NonLocal block, LabelsPtr e)
                          => LabelMap (block C C) -> e -> LabelSet -> [block C C]
postorder_dfs_from_except blocks b visited =
 vchildren (get_children b) (\acc _visited -> acc) [] visited
 where
   vnode :: block C C -> ([block C C] -> LabelSet -> a) 
                      -> ([block C C] -> LabelSet -> a)
   vnode block cont acc visited =
        if setMember id visited then
            cont acc visited
        else
            let cont' acc visited = cont (block:acc) visited in
            vchildren (get_children block) cont' acc (setInsert id     visited)
      where id = entryLabel block
   vchildren bs cont acc visited = next bs acc visited
      where next children acc visited =
                case children of []     -> cont acc visited
                                 (b:bs) -> vnode b (next bs) acc     visited
   get_children block = foldr add_id [] $ targetLabels bloc
   add_id id rst = case lookupFact id blocks of
                      Just b -> b : rst
                      Nothing -> rst
2
Norman Ramsey

これがSwiftの例です。これはもう少し読みやすいかもしれません。変数は、超不可解なHaskellの例とは異なり、実際には記述的な名前が付けられています。

https://github.com/gistya/Functional-Swift-Graph

0
CommaToast

本当に賢いテクニックについて聞きたいのですが、2つの基本的なアプローチがあると思います。

  1. 一部のグローバル状態オブジェクトを変更します。つまり、副作用
  2. グラフを引数として関数に渡します。戻り値は変更されたグラフです。これが「大量の国家を回る」というあなたのアプローチだと思います

それが関数型プログラミングで行われていることです。コンパイラ/インタプリタが優れている場合は、メモリの管理に役立ちます。特に、関数のいずれかで再帰が発生した場合は、必ず末尾再帰を使用する必要があります。

0
G__