Data.Graph: detect cycles utility functions
メンテナーはふだん 2 日以内に返信
まだ誰も着手していません。
評価
調査の方向性
まず Data.Graph SCC API、特に stronglyConnComp と flattenSCC を読み、提案されているサイクルユーティリティを issue に含まれているテストケースと比較します。公開するサイクル表現と期待される動作を、自己ループと互いに素なサイクルを含めて定義し、その後、これらのケースを Data.Graph のテストスイートに保持します。
索引モデルが issue の本文から書いたものです。
説明
It would be helpful if Data.Graph provided utility functions for detecting cycles in graphs, which may be problematic and represent infinite loops. (As there is no standard 'utility' module I see for Data.Graph, it would be a helper function for the SCC part.)
I use some sets of rewrite rules like 'A->B', where, due to changes elsewhere and updates over many years, they can inadvertently wind up defining a non-obvious cycle like 'A->B->C->A' which would loop infinitely. These are nasty surprises when they surface, so I look into detecting cycles in the graph defined by sets of rewrite rules. This has gotten me some utility functions of the form:
isCycleLess :: (Eq a, Ord a, Show a) => [(a,a)] -> [(a,a)]
isCycleLess xs = if not (cycleExists xs) then xs else error "Association list of rewrite-rules has cycles! Errors related to:" (show $ findCycles xs)
cycleExists :: Ord a => [(a, a)] -> Bool
cycleExists tuples = any (uncurry (==)) tuples ||
-- There's a cycle if one of the strongly connected components has more than one node
any ((> 1) . length . flattenSCC)
-- Generate strongly connected components from edges
(stronglyConnComp $
-- Create edges by converting a tuple (a, b) to (a, b, [b]) to reflect a -> b
map (\(a, b) -> (a, a, [b])) tuples)
-- *Which* rewrite rules are responsible for an infinite loop? Here's one way to find bad nodes easily (albeit inefficiently):
-- start with the list of rewrites and two empty temporary lists;
-- from the rewrite list, take & incrementally add rules to the first list if they do not create a cycle in the first list;
-- if they do, add them to the second list instead (otherwise ignoring the second list);
-- when all rules are used up, return the second list. Those are the bad rules.
findCycles :: Ord a => [(a, a)] -> [(a, a)]
findCycles xs = snd $ foldl f ([], []) xs
where
f (good, bad) rule
| cycleExists (rule : good) = (good, rule : bad)
| otherwise = (rule : good, bad)
-- `cycleExists` testsuite:
testCycleExists :: [([(Int,Int)], Bool)] -> [[(Int,Int)]]
testCycleExists testCases = [ rules | (rules, expected) <- testCases, cycleExists rules /= expected]
testCycleDetection :: [[(Int,Int)]]
testCycleDetection = testCycleExists testCases
where testCases :: [([(Int, Int)], Bool)]
testCases = [ ([], False) -- no rules, no cycles
, ([(1, 2)], False) -- one rule, no cycles
, ([(1, 1)], True), ([(1, 2), (2, 3), (3, 4), (5, 5)], True), ([(1, 2), (2, 3), (4, 4), (5, 6)], True) -- self loop
, ([(1, 2), (2, 3), (3, 4)], False) -- rules with no cycles
, ([(1, 2), (2, 1)], True) -- simple cycle
, ([(1, 2), (2, 3), (3, 1)], True) -- cycle with more than 2 nodes: where there is a cycle of nodes that all point to one another, but no node points to itself
, ([(1, 2), (2, 3), (3, 4), (4, 1)], True) -- larger cycle
, ([(1, 2), (2, 1), (3, 4), (4, 3), (5, 6), (6, 5)], True) -- Multiple disjoint cycles within a larger rule set
, ([(1, 2), (1, 3), (2, 4), (2, 5), (3, 6), (3, 7)], False)
, ([(1, 2), (2, 3), (4, 5), (5, 6)], False) -- separate set of rules, no cycles
, ([(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)], True) -- separate set of rules with cycles
, ([(1, 2), (2, 3), (3, 2), (4, 5), (5, 4)], True) -- there is a cycle within subset of rules
, ([(1, 2), (3, 4), (5, 6)], False) -- separate set of rules, no cycles
, ([(1, 2), (1, 2), (2, 3), (2, 3)], False) -- repetition
, ([(1, 2), (1, 3), (2, 4), (3, 4)], False) -- Multiple paths to the same node, but no cycles
, ([(1, 2), (1, 3), (2, 4), (3, 4), (4, 1)], True) -- where there are multiple paths leading to a node that is part of a cycle.
, ([(1, 1), (2, 2), (3, 3)], True) --where every node in the list points to itself (simple loop for every node)
]
Which turned up plenty of latent infinite loops, and thus far, has not missed any new infinite loops in the months I've had it.
It's not exactly obvious how you'd turn your convenient little rewrite rule list into a proper graph cycle-detection when you've never used Data.Graph before, even if the resulting code using stronglyConnComp+flattenSCC is pretty short and doubtless seems trivial to a Data.Graph savant, so I think it'd be nice to wrap that up as a utility function of some sort like cycles :: [vertex] -> [[vertex]], which would return a list of cycles (where the sublist is length > 1).
This would then be very easy to check graphs for issues and support other use-cases where a list of cycles might be useful.
(The helper functions isCycleLess and findCycles might not be useful enough to include, but the test-suite should probably be kept in some form to avoid regressions and define expected behavior.)
- 主要言語
- Haskell
- スター
- 355
- フォーク
- 194
- 平均マージ
- 3日 4時間
- マージ済み PR(30日)
- 7
環境構築
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
haskell/containers のほかの issue
-
major-release strictness Tree
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
haskell/containers#1260 ·
メンテナーはふだん 2 日以内に返信
-
IntSet low-hanging-fruit performance
難易度 3/5 1〜2日 初心者へのやさしさ 58/100
haskell/containers#1251 ·
メンテナーはふだん 2 日以内に返信
-
maintainability major-release
難易度 3/5 1〜2日 初心者へのやさしさ 70/100
haskell/containers#1250 ·
メンテナーはふだん 2 日以内に返信
-
performance Tree
難易度 3/5 1〜2日 初心者へのやさしさ 55/100
haskell/containers#1247 ·
メンテナーはふだん 2 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 50/100
haskell/containers#1242 ·
メンテナーはふだん 2 日以内に返信
haskell/containers の issue をすべて見る
似ている issue
-
Preserve bind arrowオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
alunduil/siren-json.hs#233 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 1/5 1時間未満 初心者へのやさしさ 84/100
alunduil/network-arbitrary#180 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
jgm/asciidoc-hs#14 ·