Emping-0.4: src/Reduce.hs
{- | Emping 0.4 (provisional)
Module Reduce transforms a list of facts into a partition of rules for each value of a selected attribute, and returns all shortest rules (reduced normal form).
The reduction takes a coded fact list, which may have been checked for doubles (see module Codec). The original rules can be checked first for ambiguities. Ambiguous rules have the same antecedent, but a different consequent.
-}
module Reduce ( getAmbiguous, facts2Rules, reduceAll )where
import Data.List ( nub, (\\), partition, nubBy, delete )
import Aux ( partitionBy, isEq, minLs, isSub, AVp, Ant, Rule )
-- A,B and C: the reduction algorithm in its three steps
-- A: formulate hypothesis from original antecedents of a consequent
-- | returns a disjunction of all antecedent av's
hypot :: [Ant] -> [AVp]
hypot = nub . concat
-- B: falsify the hypothesis
-- B1. match with all the antecedents of other than the consequent
-- | the reduction as a conjunction of disjunctions
match :: [AVp] -> [[AVp]] -> [[AVp]]
match h = map (h \\)
-- B2. transform conjunction of disjunctions into disjunction of conjunctions
-- | count the occurrences in a list and return the list without it (if it is present)
delCount :: AVp -> [AVp] -> (Int, [AVp])
delCount _ [] = (0,[])
delCount x (y:ys)
| x == y = (fst(delCount x ys) + 1,snd (delCount x ys))
| otherwise = (fst(delCount x ys), y:snd(delCount x ys))
-- | return the elements of a list with their counts
elWCnt :: [AVp] -> [(AVp, Int)]
elWCnt [] = []
elWCnt (x:xs) = (x, cnt) : elWCnt res where
cnt = fst (delCount x (x:xs))
res = snd (delCount x (x:xs))
-- | sort the frequency list, most occurring first
freqSort :: [(AVp,Int)] -> [(AVp,Int)]
freqSort [] = []
freqSort [x] = [x]
freqSort (x:xs) =
(freqSort high) ++ [x] ++ (freqSort low) where
high = [h | h <- xs, (snd h) >= (snd x)]
low = [l | l <- xs, (snd l) < (snd x)]
-- | make the list from which the roots and root children are built.
mkavRtLs :: [[AVp]] -> [AVp]
mkavRtLs = fst . unzip . freqSort . elWCnt . concat
-- | delete an element with a property, if it is there, and report its presence. Lists without that element unchanged.
findDel :: (AVp -> Bool) -> [AVp] -> Bool -> ([AVp],Bool)
findDel _ [] s = ([],s)
findDel p (y:ys) s
| p y = (fst $ findDel p ys s, True)
| otherwise = (y:(fst $ findDel p ys s), snd $ findDel p ys s)
{- | fst: list without element that satisfies p (may be [])
snd: True if there was such an element, else False
-}
findDelPred :: (AVp -> Bool) -> [AVp] -> ([AVp],Bool)
findDelPred p ls = findDel p ls False
{- | from an av and an or-list, get the children or-list
and the or-list for the next av. There are 4 possibilities.
-}
-- | test a possible child or-list
chldsrc :: Int -> [([AVp],Bool)] -> Maybe [[AVp]]
chldsrc att porls2
| porls2 == [] = Just [] -- will be leaf
| otherwise =
let porls3 = map fst porls2
chorls = map (findDelPred (\x -> att == fst x)) porls3 in
if ([],True) `elem` chorls -- or-list contradiction
then Nothing
else Just (map fst chorls)
-- | test a possible next in a forest
tnextsrc :: [([AVp],Bool)] -> Maybe [[AVp]]
tnextsrc porls1 | porls1 == [] = Nothing
| otherwise = if ([],True) `elem` porls1
then Nothing -- contradiction
else Just (map fst porls1)
-- | split an original or-list into children (fst) and next (snd)
splitOrls :: AVp-> [[AVp]] -> (Maybe [[AVp]],Maybe [[AVp]])
splitOrls av orls = (x,y) where
x = chldsrc (fst av) v
y = if (tnextsrc u) == Nothing
then Nothing
else Just (map fst imls)
(u,v) = partition snd imls
imls = map (findDelPred (av ==)) orls
-- | make root list with children from sorted possibles
rootLs :: [AVp]->[[AVp]]->[Maybe (AVp,[[AVp]])]
rootLs _ [] = []
rootLs [] _ = error "Reduce rootLs: root source is []"
rootLs (x:xs) orls = tsrc:rootLs xs next where
tsrc = case pchld of
Nothing -> Nothing
Just chld -> Just (x,chld)
next = case pnext of
Nothing -> []
Just nxt -> nxt
(pchld, pnext) = splitOrls x orls
-- | make root list with childsource from or list
makeRtChldLs :: [[AVp]] -> [Maybe (AVp,[[AVp]])]
makeRtChldLs [] = error "Reduce makeRtChldLs: list is []"
makeRtChldLs orls = rootLs (mkavRtLs orls) orls
-- | define a rose tree that can have empty branches
data Maytree a = Niets | Wel {avLabel::a, avChils :: Mayfor a}
deriving Eq
type Mayfor a = [Maytree a]
-- | make a tree and forest from Maybe roots and source children
mkAVMTree :: Maybe (AVp,[[AVp]]) -> Maytree AVp
mkAVMTree rtchls =
case rtchls of
Nothing -> Niets
Just (x,[]) -> Wel {avLabel = x, avChils = []}
Just (x, suborls) -> Wel {avLabel = x,
avChils = mkAVMFor suborls }
mkAVMFor:: [[AVp]] -> Mayfor AVp
mkAVMFor suborls = map mkAVMTree rtchls where
rtchls = makeRtChldLs suborls
-- | get the branches of a Maytree. The empty lists are lost because of concatMap
brMayTree :: Maytree AVp -> [[AVp]]
brMayTree t = case t of
Niets -> []
(Wel x []) -> [[x]]
(Wel x for) -> map (x:) brls where
brls = concatMap brMayTree for
-- | extract the smallest sublists from the orlist of andlists
extrMin :: [[AVp]] -> [[AVp]]
extrMin ls = nubBy isEq [ getMinin x ls | x <-ls ]
where getMinin x y = foldr minLs x y
-- | final transformation of a conjunction of disjunctions to a disjunction of conjunctions
trAndOr :: [[AVp]] -> [Ant]
trAndOr orls =
extrMin (concatMap brMayTree for) where for = mkAVMFor orls
-- C: Verify the falsification result with the original positive antecedents
-- | erify the falsification result with the original positive antecedents
verify :: [Ant] -> [Ant] -> [Ant]
verify flsd orig = [x | x <- flsd , x `isIn` orig ] where
isIn y ls = or (map (isSub y) ls)
-- A, B and C: reduce a list of positive original antecedents
-- | the implementation of the rule reduction (for one consequent attribute-value
redPos :: [Ant] -> [Ant] -> [Ant]
redPos p n = verify (trAndOr (match (hypot p) n)) p
-------------------------------------------------------
-- Functions on the list of facts
-- | f2r works because only one value of an attribute can be in a fact.
-- and there is always one (possibly value -1 as defined by Codec)
f2r :: Int -> [AVp] -> (Ant,AVp)
f2r att fact = (ant, cons) where
(ant, [cons]) = partition (\u -> (fst u) /= att) fact
-- | remove all rules with blank consequent value.
f2rules :: Int -> [[AVp]] -> [Rule]
f2rules att facls = filter (\u -> (snd $ snd u) /= -1) raw where
raw = map (f2r att) facls
{- | partition a fact list according to the attribute-values of the consequent
Important: all rules with blank consequent values should have been removed by f2rules.
-}
facts2Rules :: Int -> [[AVp]] -> [[Rule]]
facts2Rules at facls =
partitionBy (\x y -> (snd x) == (snd y)) ruls where
ruls = f2rules at facls
-- | the reduction algorithm for each consequent is implemented by redPos p n
redOne :: [[Rule]] -> [Rule] -> [Rule]
redOne grp rls = map addcons (redPos p n) where
p = map fst rls
n = map fst (concat $ (delete rls grp))
addcons x = (x,cons)
cons= (snd . head) rls
-- | reduce a rule model for all consequent attribute-value pairs
reduceAll :: [[Rule]] -> [[Rule]]
reduceAll rlgrp = map (redOne rlgrp) rlgrp
-----------------------------------------------------------------------------
{- | find ambiguities in a rule group. A rule group partitions rules according to the consequent values.
Equal facts must have been removed, otherwise they will show up too.
Warning: only works if the Antedent rows have the same attribute order.
-}
getAmbiguous :: [[Rule]] -> [[Rule]]
getAmbiguous grp = filter (\x -> (length x) > 1) anteqs where
anteqs = partitionBy (\x y -> (fst x) == (fst y)) ols
ols = concat grp