Emping-0.5.1: src/CSVTable.hs
{- | Emping 0.5 (provisional)
Module CSVTable converts results to a CSV format that can be read by Open Office Calc
and a rule graph to .dot format that can be read by a Graphviz viewer.
Fri 04 Apr 2008 07:19:30 PM CEST
-}
module CSVTable ( allDup2CSVTb , allAmb2CSVTb, rnf2CSVTb, allTops2CSVTb, allNodes2CSVTb, graph2DOT ) where
import Data.Char (isDigit )
import Data.Array
import Data.Graph.Inductive
import Aux (AVp, Ant, Rule)
import CSVParse (blankId)
-- | check whether a string is a number or text
allDigit :: String -> Bool
allDigit [] = True
allDigit (x:xs) | isDigit x = allDigit xs
| otherwise = False
-- | convert string or number to OO Calc format
toCalc :: String -> String
toCalc x | allDigit x = x
| otherwise = "\"" ++ x ++ "\""
{- | get an OO Calc value String (-1 is \" \") from an attribute-value pair
Warning: for the time being all values of -1 are written as an NA field, not as blanks.
-}
showValue :: Array Int (String, [String]) -> AVp -> String
showValue attvarr (att, val)
| val == -1 = toCalc "NA" -- blank field
| otherwise = toCalc ((snd (attvarr ! att)) !! val)
-- | get an OO Calc value string from a Maybe AVp (Nothing is \" \")
showMaybeValue :: Array Int (String, [String]) -> Maybe AVp -> String
showMaybeValue attvarr mbav =
case mbav of
Just x -> showValue attvarr x
Nothing -> " "
-- | get an OO Calc attribute string from an attribute index (blankId is \" \")
showAttribute :: Array Int (String, [String]) -> Int -> String
showAttribute attvarr att
| attstr == blankId = " "
| otherwise = toCalc attstr
where attstr = fst (attvarr ! att)
-- | insert a separator s between two strings
inSep :: String -> String -> String -> String
inSep s x y = x ++ s ++ y
-- | from an ORIGINAL rule, get the display sequence (fst is the antecedent)
displayGuide :: Rule -> ([Int], Int)
displayGuide (ant, cons) = (map fst ant, fst cons)
-- | lookup an attribute-value pair for an attribute in a reduced antecedent
lkp :: Int -> Ant -> Maybe AVp
lkp _ [] = Nothing
lkp att (x:xs) | att == fst x = Just x
| otherwise = lkp att xs
-- | order a reduced antecedent according to a display guide first, including Nothing
formatAnt :: [Int] -> Ant-> [Maybe AVp]
formatAnt [] _ = []
formatAnt (x:xs) antec = (lkp x antec): (formatAnt xs antec)
-- | get a list of OO Calc value strings from an antecedent
ant2CalStr :: Array Int (String, [String]) -> [Int] -> Ant -> [String]
ant2CalStr attvarr attls antec =
map (showMaybeValue attvarr) frmant where
frmant = formatAnt attls antec
-- | convert a reduced rule to a .csv table row (with \\n)
red2CSV :: Array Int (String,[String]) -> ([Int],Int) -> Rule -> String
red2CSV attvarr (attls, _ ) (ant,cons) =
(foldr1 (inSep ",") calstr)
++ ",\" is \","
++ (showValue attvarr cons)
++ "\n"
where calstr = ant2CalStr attvarr attls ant
-- | convert the table header to a .csv row
hdr2CSV :: Array Int (String, [String]) -> ([Int], Int) -> String
hdr2CSV attvarr (attls, consatt) =
(foldr1 (inSep ",") calstr)
++ ", ,"
++ (showAttribute attvarr consatt)
++ "\n"
where calstr = map (showAttribute attvarr) attls
--------------------------------------------------
-- | convert a list of reductions with the same consequent to .csv
sameCons2CSV :: Array Int (String,[String]) -> ([Int], Int) -> [Rule] -> String
sameCons2CSV attvarr dispg redcons =
concatMap (red2CSV attvarr dispg) redcons
-- | convert the reductions for all values of an attribute to .csv
allReds2CSV :: Array Int (String,[String]) -> ([Int], Int) -> [[Rule]] -> String
allReds2CSV attvarr dispg redgrp =
concatMap (sameCons2CSV attvarr dispg) redgrp
--------------------------------------------------
{- | convert a reduced normal form to .csv table with a header.
Warning: Get the display guide from the original rules
-}
rnf2CSVTb :: Array Int (String,[String]) -> [[Rule]] -> [[Rule]] -> String
rnf2CSVTb attvarr origs redgrp =
(hdr2CSV attvarr dispg) ++ (allReds2CSV attvarr dispg redgrp) where
dispg = displayGuide (head (head origs))
----------------------- .csv output of equals ---------------
-- | convert a duplicate and its occurrence count into a .csv String
dup2CSV :: Array Int (String, [String]) -> ([AVp], Int) -> String
dup2CSV attvarr (fct, cnt) =
(foldr1 (inSep ",") strls) ++ "," ++ ((toCalc . show) cnt) ++ "\n" where
strls = map (showValue attvarr) fct
-- | get a header from a duplicate and convert it .csv String WITHOUT newline
dup2HdrCSV :: Array Int (String, [String]) -> ([AVp],Int) -> String
dup2HdrCSV attvarr (fct, _) =
(foldr1 (inSep ",") strls) ++ "," ++ (toCalc "Number") ++ "\n" where
strls = map ((showAttribute attvarr) . fst) fct
-- | transform a list of representatives of duplicate and their counts to a .csv String
allDup2CSVTb :: Array Int (String,[String]) -> [([AVp],Int)] -> String
allDup2CSVTb attvarr dbls = (dup2HdrCSV attvarr (head dbls)) ++
concatMap (dup2CSV attvarr) dbls
-----------------------------------------------------
{- | show ambiguous original rules in .csv format
Note: original rules do not need intermediate Maybe's, like reduced rules
-}
org2CSV :: Array Int (String, [String]) -> Rule -> String
org2CSV attvarr orule =
antstr ++ ",\" is \"," ++ constr ++ "\n" where
antstr = foldr1 (inSep ",") antstrls
antstrls = map (showValue attvarr) (fst orule)
constr = showValue attvarr (snd orule)
-- | ambiguous rules to .csv (always more than one) with extra newline
ambs2CSV :: Array Int (String,[String]) -> [Rule] -> String
ambs2CSV attvarr ambrls =
(concatMap (org2CSV attvarr) ambrls) ++ "\n"
-- | a group of ambiguous rules, with different consequents, to .csv
ambgrp2CSV :: Array Int (String,[String]) -> [[Rule]] -> String
ambgrp2CSV attvarr ambgrp
| length ambgrp == 1 = ambs2CSV attvarr (head ambgrp)
| otherwise = concatMap (ambs2CSV attvarr) ambgrp
-- | display ambiguous rules in a .csv table. The guide for the header is obtained from the first rule.
allAmb2CSVTb :: Array Int (String,[String]) -> [[Rule]] -> String
allAmb2CSVTb attvarr ambgrp =
(hdr2CSV attvarr (antatt,consatt)) ++ ambstr where
(antatt, consatt) = ((map fst ant), (fst cons))
(ant, cons) = head (head ambgrp)
ambstr = ambgrp2CSV attvarr ambgrp
----------------------------------------------------------
-- ------------------------------------ The .csv output of top level abductions ---------------------
-- | show the antecedent as a .csv string. Analogous to red2CSV, but without extras for the consequent
ant2CSV :: Array Int (String,[String]) -> [Int] -> Rule -> String
ant2CSV attvarr attls red =
foldr1 (inSep ",") calcs where
calcs = ant2CalStr attvarr attls (fst red)
-- | show a list of equal antecedents in .csv format. Warning: as before, the guide is the first of a display guide, NO consequent yet.
eqs2CSV :: Array Int (String,[String]) -> [Int] -> [Rule] -> String
eqs2CSV attvarr attls eqls
| length eqls == 1 =
ant2CSV attvarr attls (head eqls)
| otherwise =
foldr1 (inSep ",\"equals\"\n") strls where
strls = map (ant2CSV attvarr attls) eqls
-- | append the consequent value, 2 newlines for readability.
eqsWCons2CSV :: Array Int (String, [String]) -> [Int] -> [Rule] -> String
eqsWCons2CSV attvarr attls eqls = (eqs2CSV attvarr attls eqls)
++ ",\" is \","
++ (showValue attvarr ( snd (head eqls)))
++ "\n\n"
-- convert all rule equivalences in a top level for a consequent
tops2CSV :: Array Int (String, [String]) -> [Int] -> [[Rule]] -> String
tops2CSV attvarr attls abdtops = concatMap (eqsWCons2CSV attvarr attls) abdtops
-- convert all rule equivalences to .csv for all the top level abductions
allTops2CSV :: Array Int (String, [String]) -> [Int] -> [[[Rule]]] -> String
allTops2CSV attvarr attls topsgrp = concatMap (tops2CSV attvarr attls) topsgrp
-------------------------------------------------------------
-- | transform the abduction result to .csv table with header
allTops2CSVTb :: Array Int (String,[String]) -> [[Rule]] -> [[[Rule]]] -> String
allTops2CSVTb attvarr origs topsgrp =
(hdr2CSV attvarr guide) ++ allTops2CSV attvarr (fst guide) topsgrp where guide = displayGuide (head (head origs))
---------------------------- Graph Legend to .csv ------------------------
-- | display a list of equals in .csv, but allow for the first column to be empty, for any antecedent AFTER the first
nods2CSV :: Array Int (String,[String]) -> [Int] -> [Rule] -> String
nods2CSV attvarr attls eqls
| length eqls == 1 = fstline
| otherwise = foldr1 (inSep ",\"equals\"\n") strls
where fstline = ant2CSV attvarr attls (head eqls)
strls = fstline:(map foline (tail eqls))
foline x = ',': (ant2CSV attvarr attls x)
-- | exactly like eqsWCons2CSV
nodsWCons2CSV :: Array Int (String, [String]) -> [Int] -> [Rule] -> String
nodsWCons2CSV attvarr attls eqls = (nods2CSV attvarr attls eqls)
++ ",\" is \","
++ (showValue attvarr ( snd (head eqls)))
++ "\n\n"
-- | put the node in front of the equals with cons
lnode2CSV :: Array Int (String,[String]) -> [Int] -> LNode [Rule] -> String
lnode2CSV attvarr attls (nd,eqls) =
((toCalc .show) nd) ++ "," ++ (nodsWCons2CSV attvarr attls eqls)
-- | get all nodes from a graph legend
allNodes2CSV :: Array Int (String,[String]) -> [Int] -> [LNode [Rule]] -> String
allNodes2CSV attvarr attls legend = concatMap (lnode2CSV attvarr attls) legend
-- | prepend the header with Node in the first column
allNodes2CSVTb :: Array Int (String,[String]) -> [[Rule]] -> [LNode [Rule]] -> String
allNodes2CSVTb attvarr origs legend =
(toCalc "Node")
++ ","
++ (hdr2CSV attvarr guide)
++ (allNodes2CSV attvarr (fst guide) legend ) where guide = displayGuide (head (head origs))
------------------------ transform a graph to .dot format ----------------------------------
-- | use the graphviz function with defaults. Page matrix is sqrt of number of nodes.
-- Page is smallest of A4 and US Letter. Specific graph type used, not class.
graph2DOT :: Gr (Int,Int) () -> String -> String
graph2DOT graph name =
graphviz graph name (8.3,11.0) (pgn,pgn) Portrait where
pgn = 1 + (round . sqrt . (/ nodpp) . fromIntegral) (noNodes graph)
nodpp = 50.0 :: Double