Haskellで接続されたすべてのサブグラフを見つける問題を解決しようとしています。使用するアルゴリズムについては、こちらで説明しています。その論文からの引用:
すべてのパス アルゴリズムと同様に、前進ステップと後退ステップがあります。与えられた接続されたサブグラフがエッジ k の追加によって拡張できる場合、つまり、エッジ k がまだ与えられたサブグラフの一部ではない場合、k が与えられたサブグラフの少なくとも 1 つのエッジに隣接している場合、さらに次の場合にステップ フォワードが行われます。のエッジ k は、以下に示すいくつかの制限によって禁止されていません。指定された接続されたサブグラフをそれ以上引き伸ばすことができなくなるとすぐに、ステップ バックが実行されます。この場合、最後に追加されたエッジが文字列から削除され、一時的に「禁止」ステータスが与えられ、以前の長い文字列からのバックトラックによって禁止された他のエッジが同時に再び「許可」されます。対照的に、現在のものよりも短い文字列から削除することによって禁止されているエッジは、禁止されたままです。
このアルゴリズムを実行するために、グラフをエッジのリストとして表しました。
type Edge = (Int,Int)
type Graph = [Edge]
まず、addEdge
グラフを拡張できるかどうかを確認し、不可能な場合は返すNothing
か、拡張する関数を作成しEdge
ました。
私は"parent"
グラフとグラフを持っているので、グラフに存在し、グラフに接続され、グラフにまだ含まれておらず、セットに含まれていない"extensible"
エッジを1つだけ見つけようとします。 "parent"
"extensible"
"extensible"
forbidden
この関数を以下に書きました。
addEdge :: Graph -> Graph -> [Edge] -> Maybe Edge
addEdge !parent !extensible !forb = listToMaybe $ intersectBy (\ (i,j) (k,l) -> (i == k || i == l || j == k || j == l)) (parent \\ (extensible `union` forb)) extensible
仕事だ!しかし、プログラム全体のプロファイリングからわかるようにaddEdge
、最も重い関数です。私のコードが最適ではないことは確かです。少なくとも、 intersectBy
考えられるすべての解を見つける関数ですが、必要なのは 1 つだけです。このコードをより速くする方法はありますか? たぶん、標準のリストを使用しないでくださいSet
from Data.Set
。まず注目ポイントです。
以下に示す主な再帰関数ext
:
ext :: Graph -> [Graph] -> Maybe Graph -> [(Edge,Int)] -> Int -> [Graph]
ext !main !list !grow !forb !maxLength | isEnd == True = (filter (\g -> (length g /= 1)) list) ++ (group main)
| ((addEdge main workGraph forbEdges) == Nothing) || (length workGraph) >= maxLength = ext main list (Just workGraph) forbProcess maxLength
| otherwise = ext main ((addedEdge:workGraph):list) Nothing forb maxLength where
workGraph = if grow == Nothing then (head list) else (bite (fromJust grow)) -- [Edge] graph now proceeded
workGraphLength = length workGraph
addedEdge = fromJust $ addEdge'
addEdge' = addEdge main workGraph forbEdges
bite xz = if (length xz == 1) then (fromJust (addEdge main xz forbEdges)):[] else tail xz
forbProcess = (head workGraph,workGraphLength):(filter ((<=workGraphLength).snd) forb)
forbEdges = map fst forb -- convert from (Edge,Level) to [Edge]
isEnd = (grow /= Nothing) && (length (fromJust grow) == 1) && ((addEdge main (fromJust grow) forbEdges) == Nothing)
グラフでプログラムをテストします
c60 = [(1,4),(1,3),(1,2),(2,6),(2,5),(3,10),(3,7),(4,24),(4,21),(5,8),(5,7),(6,28),(6,25),
(7,9),(8,11),(8,12),(9,16),(9,13),(10,20),(10,17),(11,14),(11,13),(12,28),(12,30),(13,15),
(14,43),(14,30),(15,44),(15,18),(16,18),(16,17),(17,19),(18,47),(19,48),(19,22),(20,22),(20,21),
(21,23),(22,31),(23,32),(23,26),(24,26),(24,25),(25,27),(26,35),(27,36),(27,29),(28,29),(29,39),
(30,40),(31,32),(31,33),(32,34),(33,50),(33,55),(34,37),(34,55),(35,36),(35,37),(36,38),(37,57),
(38,41),(38,57),(39,40),(39,41),(40,42),(41,59),(42,45),(42,59),(43,44),(43,45),(44,46),(45,51),
(46,49),(46,51),(47,48),(47,49),(48,50),(49,53),(50,53),(51,52),(52,60),(52,54),(53,54),(54,56),(55,56),(56,58),(57,58),(58,60),(59,60)] :: Graph
たとえば、長さが 1 ~ 7 のサブグラフをすべて検索します。
length $ ext c60 [[(1,2)]] Nothing [] 7
>102332
問題は計算速度が遅すぎることです。元の記事で指摘したように、プログラムはFORTRAN 77
150MHz ワークステーションで作成および起動され、最新の i5 プロセッサで私のコードよりも少なくとも 30 倍速くテスト タスクを実行します。理解できません。なぜ私のプログラムはこんなに遅いのですか? このコードをリファクタリングする方法はありますか? または、C に移植し、FFI 経由で C ライブラリにバインディングを書き込むのが最善の解決策ですか?