SalmaFG
SalmaFG

Reputation: 2202

Haskell - Flattening Objects of Lists

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

Answers (2)

effectfully
effectfully

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 Maybes, 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)

Nils 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 Maybes: http://ideone.com/1yRDXo

Upvotes: 1

Sassa NF
Sassa NF

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

Related Questions