9

我有一个使用语句块的程序 EDSL。

尽管语句之间可能存在依赖关系,但这些语句以没有特定顺序添加到块中。

然而,在 EDSL 的编译过程中,我需要确保语句按依赖顺序排序,例如

B := A
C := B
E := D

由于并非所有语句都具有依赖关系,因此没有总顺序(例如E := D,上面是独立的,可以放在任何地方)。没有循环依赖,因此列表排序应该是可能的。

我试图通过使用Data.List.sortBy和定义Ordering将返回EQ意味着语句没有依赖关系来破解解决方案。这适用于一些示例,但不适用于一般情况,例如,订购以下内容没有任何作用:

C := B                           B := A
D := C    = should produce =>    C := B
B := A                           D := C

这是因为默认排序插入排序并且只确保插入的项小于或等于下一项。

我在互联网上搜索了一个 Poset 实现,但没有找到任何适用的:

altfloat:Data.Poset定义Ordering = LT | GT | EQ | NCNC对于不可比较的)这很好,但提供的sort假设NaN- 类似不可比较的项目,只是把它们扔掉。

logfloat:Data.Number.PartialOrd与上面的类似,除了用途Maybe Ordering,我在包的任何地方都没有看到排序功能。

Math.Combinatorics.Poset我还没有弄清楚如何使用它或它是否适用。

下面是一个最小的例子,它同时具有绑定和非绑定语句。非约束语句的顺序很重要,它们必须保持原始顺序(即排序需要是稳定的 wrt 语句,没有依赖关系)。

我希望在不使用完整的依赖图的情况下有一个简单的解决方案......

module Stmts where

import Data.List ( sortBy )

data Var = A | B | C | D | E | F | G | H deriving (Eq, Show)
data Stmt = Var := Var
          | Inc Var
  deriving (Show)

-- LHS variable
binds :: Stmt -> Maybe Var
binds (v := _) = Just v
binds _        = Nothing

-- RHS variables
references :: Stmt -> [Var]
references (_ := v) = [v]
references (Inc v)  = [v]

order :: [Stmt] -> [Stmt]
order = sortBy orderStmts

orderStmts :: Stmt -> Stmt -> Ordering
orderStmts s1 s2 = ord mbv1 mbv2
 where
  ord Nothing   Nothing   = EQ  -- No dep since they don't bind vars
  ord (Just v1) Nothing   = LT  -- Binding statements have precedence
  ord Nothing   (Just v2) = GT  -- ^^^
  ord (Just v1) (Just v2)       -- Both statements are binding:
    | v1 `elem` refs2 = LT      --  * s2 depends on s1
    | v2 `elem` refs1 = GT      --  * s1 depends on s2
    | otherwise       = EQ      --  * neither

  -- *Maybe* they bind variables
  mbv1  = binds s1
  mbv2  = binds s2

  -- Variables they reference  
  refs1 = references s1
  refs2 = references s2

-- The following should return [B := A, C := B, D := C, Inc F, Inc G]
test = order [Inc F, Inc G, C := B, D := C, B := A]
4

2 回答 2

7

您的方法的问题在于您orderStmts既不是排序也不是部分排序。特别是,它不是传递的,这就是为什么尝试使用它进行排序失败的原因。

您正在寻找的是拓扑排序。您有一个顶点图(语句),它们之间有边(它们的依赖项),并且您希望确保排序与边匹配。

我将只关注声明,因为非约束性声明很容易(我们只需要将列表分成两部分,对声明进行排序并再次连接)。

拓扑排序已经在Data.Graph中实现,这使得任务非常简单:

module Stmts where

import Data.Graph

data Var = A | B | C | D | E | F | G | H deriving (Eq, Ord, Show)

data Decl = Var := Var 
  deriving (Show, Eq)

data Stmt = Decl
          | Inc Var
  deriving (Show, Eq)

sortDecls :: [Decl] -> [SCC Decl]
sortDecls = stronglyConnComp . map triple
  where
    triple n@(x := y)   = (n, x, [y])

-- The following should return [B := A, C := B, D := C]
test = map flattenSCC . sortDecls $ [C := B, D := C, B := A]

调用flattenSCC仅用于测试,SCC没有Show实例。您可能需要检查SCCs 是否有循环(循环将是语言编译错误),如果没有,则提取已排序的序列。

于 2014-10-03T12:42:11.593 回答
2

我认为对您的陈述组进行排序的唯一方法是从根走到孩子

import Data.List

data Var = A | B | C | D | E | F | G | H deriving (Eq, Show)
data Stmt = Var := Var deriving (Show)

parent :: Stmt -> Var
parent (_ := p) = p

child :: Stmt -> Var
child (c := _) = c

steps :: [Stmt] -> [[Stmt]]
steps st = step roots st
  where step _ [] = []
        step r s = let (a, b) = partition (flip elem r . parent) s
                       (v, u) = partition (flip elem (map child b) . child ) a
                   in  if null u then error "Cycle!"
                                 else u : step (r ++ (nub $ map child u)) (v ++ b)

        roots = let cs = map child st
                    rs = nub $ filter (not . flip elem cs) (map parent st)
                in  if null rs then error "No roots!"
                               else rs

main = mapM_ print $ steps [F := H, G := H, C := B, D := C, B := A]

带输出

[F := H,G := H,B := A]
[C := B]
[D := C]

当“排序”超过组(不是语句)时。

partition(此代码具有稳定性,因为通过, map, ++, ...是不变的)

(添加)

如果您真的希望某些稳定性属性(对语句进行排序),则必须添加一些其他限制(定义“稳定性”)。

让两个“排序”直接算法(只需将语句重新排序到前面或后面)

orderToFront :: [Stmt] -> [Stmt]
orderToFront [] = []
orderToFront (s@(_ := p):xs) = let (l, r) = splitAtFirst ((==p).child) xs
                               in  if null r then s: orderToFront xs
                                             else head r: s: orderToFront (l ++ tail r)

orderToBack' :: [Stmt] -> [Stmt]
orderToBack' [] = []
orderToBack' (s@(c := _):xs) = let (l, r) = splitAtFirst ((==c).parent) xs
                               in  if null r then s: orderToBack' xs
                                             else orderToBack' (l ++ head r: s: tail r)
orderToBack = reverse . orderToBack'

splitAtFirst :: (a -> Bool) -> [a] -> ([a], [a])
splitAtFirst f xs = let rs = dropWhile (not.f) xs
                    in  (take (length xs - length rs) xs, rs)


main = do

    let q = [F := H, C := B, D := C, G := F, B := A]

    putStrLn "-- orderToFront"
    mapM_ print $ orderToFront q

    putStrLn "-- orderToBack"
    mapM_ print $ orderToBack q

输入相同,orderToFront输出与输出不同,orderToBack但两者都有效

-- orderToFront
F := H
B := A
C := B
D := C
G := F
-- orderToBack
B := A
F := H
G := F
C := B
D := C

(只有相等关系,您的算法不能低于 O(n^2) 但如果您定义稳定性限制,则可以减少它)

于 2014-10-02T10:50:30.390 回答