Reputation: 2202
I am dealing with first order logic propositions expressions as objects:
data Prop
= Atom String
| Var String
| Pred String [Prop]
| Not Prop
| And [Prop]
| Or [Prop]
| Nil
deriving (Show,Eq)
I take an expression as input in the form of a list of conjunctions or disjunctions in prefix form as such:
And [Or [Var "x",Var "y"],Or [Var "x",Or [Var "z",Nil]]]
What I should return in this case is a flattened expression that merges nested Ands
or Ors
together and eliminate any Nils
so that it becomes in this form:
And [Or [Var "x",Var "y"],Or [Var "x",Var "z"]]
What I did is:
flatten :: Prop -> Prop
flatten (And[]) = Nil
flatten (And (v:(And w):x:(And y):z)) = And ([flatten(And (w ++ y))] ++ [flatten (And (v:x:z))])
flatten (And (x:(And y):z)) = And ([flatten(And (x:z))] ++ [flatten (And y)])
flatten (And (x:Nil:[])) = x
flatten (Or[]) = Nil
flatten (Or (v:(Or w):x:(Or y):z)) = Or ([flatten(Or (w ++ y))] ++ [flatten (Or (v:x:z))])
flatten (Or (x:(Or y):z)) = Or ([flatten(Or (x:z))] ++ [flatten (Or y)])
flatten (Or (x:Nil:[])) = Or([flatten(Or [x])])
flatten (And [x]) = And [flatten x]
flatten (Or [x]) = Or [flatten x]
flatten x = x
The code logic seems fine to me but it returns the expression in its original form and I'm not sure why exactly.
Upvotes: 0
Views: 224
Reputation: 12715
Are you sure, you need the Nil
constructor? That's how flatten
looks without it:
cutAndAppend :: Prop -> [Prop] -> [Prop]
cutAndAppend (And ps) qs = ps ++ qs
cutAndAppend p qs = p : qs
cutOrAppend :: Prop -> [Prop] -> [Prop]
cutOrAppend (Or ps) qs = ps ++ qs
cutOrAppend p qs = p : qs
flatten (Var v) = Var v
flatten (Not p) = Not (flatten p)
flatten (And ps) = And (foldr (cutAndAppend . flatten) [] ps)
flatten (Or ps) = Or (foldr (cutOrAppend . flatten) [] ps)
Equivalently you can write
flatten (And ps) = And (foldr cutAndAppend [] $ map flatten ps)
flatten (Or ps) = Or (foldr cutOrAppend [] $ map flatten ps)
i.e. flatten every p
in ps
, and, for example, in the Or
case transform the list
[Var "x", Or [Var "y", And [Var "z"]], And [Var "x"]]
into
[Var "x", Var "y", And [Var "z"], And [Var "x"]]
But if you really want Nil
, it's better to treat Nil
as Nothing
, because it's more idiomatic and there are some useful functions, acting on Maybe
s, in the standard library.
nonEmpty :: [a] -> Maybe [a]
nonEmpty [] = Nothing
nonEmpty xs = Just xs
flatten' :: Prop -> Maybe Prop
flatten' Nil = Nothing
flatten' (Var v) = Just (Var v)
flatten' (Not p) = Not <$> flatten' p
flatten' (And ps) = And <$> nonEmpty (foldr cutAndAppend [] $ mapMaybe flatten' ps)
flatten' (Or ps) = Or <$> nonEmpty (foldr cutOrAppend [] $ mapMaybe flatten' ps)
Nil
s are filtered by mapMaybe flatten'
. Other stuff is the same. flatten
then is simply
flatten :: Prop -> Prop
flatten = fromMaybe Nil . flatten'
i.e. Nothing
back to Nil
and Just p
to p
.
Some examples:
vx = Var "x"; vy = Var "y"; vz = Var "z"
main = mapM_ (print . flatten)
[ And [Or [Nil], Nil, And [Nil]]
-- Nil
, And [Or [vx, vy], Or [vx, Or [vz, Nil]]]
-- And [Or [Var "x",Var "y"],Or [Var "x",Var "z"]]
, Or [Or [vx, vy], Or [vx, Or [vz, Nil]]]
-- Or [Var "x",Var "y",Var "x",Var "z"]
, Not (And [And [Or [Or [Nil], Or [Not vx]], Nil, And [vy]]])
-- Not (And [Or [Not (Var "x")],Var "y"])
]
EDIT
The first code doesn't remove expressions like Or []
, and both the codes do not replace expressions like And [p]
with p
. It's not hard to recover this behavior, though. Relevant cases are:
smart :: ([Prop] -> Prop) -> [Prop] -> Maybe Prop
smart cons [] = Nothing
smart cons [x] = Just x
smart cons xs = Just (cons xs)
flatten' (And ps) = smart And $ foldr cutAndAppend [] $ mapMaybe flatten' ps
flatten' (Or ps) = smart Or $ foldr cutOrAppend [] $ mapMaybe flatten' ps
Some tests:
main = mapM_ (print . flatten)
[ Not (And [And [Or [Or [Nil], Or [Not vx]], Nil, And [vy]]])
-- Not (And [Not (Var "x"),Var "y"])
, And []
-- Nil
, Or [And [vx]]
-- Var "x"
, And [vx, Or []]
-- Var "x"
, And [Or [And [vx, Or [], vy, Nil]], And [vz, Or [vx]]]
-- And [Var "x",Var "y",Var "z",Var "x"]
]
The whole code: http://ideone.com/0DwEae
A slightly modified version without Maybe
s: http://ideone.com/1yRDXo
Upvotes: 1
Reputation: 5406
I'd go about this differently.
flatten (And []) = Nil
flatten (And (h:t)) = case (flatten h, flatten $ And t) of
(Nil, t) -> t -- t is already flatten And t
(h, Nil) -> h -- And [h] == h
(And h, And t) -> And $ h++t
(And h, t) -> And $ h++[t]
(h, And t) -> And $ h:t
(h, t) -> And [h,t]
... the cases for And are covered; etc for Or
(flatten h, flatten $ And t)
is the implementation of the assertion that And (h:t) == h && (And t)
. Using the tuple and case ... of
permits to pattern-match to test all combinations of results of flattening h
and And t
.
The only combinations that need taking care of are the combinations involving Nil
- which are based on my understanding of how you intended to interpret Nil
- a discardable proposition, neutral for any operator - and the cases where one level of And
can be eliminated.
Upvotes: 2