For convenient analysis of data I'd like to use a library which for the following code:
data SomeType = A [String] Int | B | C Int deriving (Eq, Ord, Show)
main = do
let theData = A ["a", "b", "c"] 9 : C 3 : B : []
putStr $ treeString theData -- `treeString` is the implied library function
would produce an output similar to the following:
- A:
| - - a
| | - b
| | - c
| - 9
- C:
| - 3
- B
Is there such a library? Or maybe a better approach to such a problem?
Data.Tree has drawTree and drawForest functions with similar formatting, so you can write a function to convert your data structure to a Tree String and then use drawTree.
import Data.Tree
data SomeType = A [String] Int | B | C Int deriving (Eq, Ord, Show)
toTree :: SomeType -> Tree String
toTree (A xs n) = Node "A" [Node "*" (map (flip Node []) xs), Node (show n) []]
toTree B = Node "B" []
toTree (C n) = Node "C" [Node (show n) []]
main = do
let theData = A ["a", "b", "c"] 9 : C 3 : B : []
putStr $ drawTree (Node "*" (map toTree theData))
Output:
*
|
+- A
| |
| +- *
| | |
| | +- a
| | |
| | +- b
| | |
| | `- c
| |
| `- 9
|
+- C
| |
| `- 3
|
`- B
Adding to hammar's answer. Here's how one can do a generic conversion to Data.Tree:
import Data.Tree
import Data.Generics
import Control.Applicative
dataTree = fix . genericTree
where
genericTree :: Data a => a -> Tree String
genericTree = dflt `extQ` string
where
string x = Node x []
dflt a = Node (showConstr (toConstr a)) (gmapQ genericTree a)
fix (Node name forest)
| name == "(:)"
, a : b : [] <- forest
= Node "*" $ (fix a) : (subForest $ fix b)
| otherwise = Node name $ fix <$> forest
But for this to work on one's data types, they must have an instance of Data, which can easily be achieved by adding a {-# LANGUAGE DeriveDataTypeable #-} pragma and making the type derive from Typeable and Data like so:
data SomeType = A [String] Int | B | C Int | D [[String]]
deriving (Typeable, Data)
Related
I am trying to convert CSV files into a table using the Boxes package
.
I am using IHaskell (Jupyter notebook extension) to do data analysis and would like to include nice looking tables. I could write a program to convert the CSV to a Jupyter markdown native table but that's not as fluid a process as generating the table on the fly.
Below is the code which produces a reasonable looking table but I had to do some manual stuff which made me think I wasn't using Boxes correctly.
There doesn't appear to be a way to set a fixed width Box (with padding) and the actual data centered within it. It's easy to get it to center the data but the box width is dependent on the width of the data resulting in a 'jagged' column.
My complicated code is below .. I've been doing Haskell for awhile but I still consider myself a novice.
Any comments greatly appreciated.
Tom
module Main (main) where
{-
Notes:
- This converst a CSV into a table
- Am I using the Boxes package but I had to do a lot
of work, using only punctuateH, punctuateV from the
package.
- Is there a better way to do it?
- Things I had to do 'manually'
- Center the data in a fixed width box. If I didn't
do this the columns would not align since the data
has various widths.
- Add the outside borders
- The input is rows of data separated by commas .. (CSV)
on,pinput,pout,eff,ledi
5.0e-6,8.43764e-2,7.88486e-2,0.934486,8.63554e-3
5.4e-5,3.04731,2.90032,0.951765,0.214559
1.03e-4,6.34257,6.03162,0.950973,0.434331
- Produces,
|--------------------------------------------------------------|
| on | pinput | pout | eff | ledi |
| ------------------------------------------------------------ |
| 5.0e-6 | 8.43764e-2 | 7.88486e-2 | 0.934486 | 8.63554e-3|
| ------------------------------------------------------------ |
| 5.4e-5 | 3.04731 | 2.90032 | 0.951765 | 0.214559 |
| ------------------------------------------------------------ |
| 1.03e-4 | 6.34257 | 6.03162 | 0.950973 | 0.434331 |
|--------------------------------------------------------------|
-}
import Text.PrettyPrint.Boxes
replace :: Eq a => a -> a -> [a] -> [a]
replace a b = map $ \c -> if c == a then b else c
setLen :: Int -> String -> String
setLen n str =
let fullPad = n - (length str)
halfPadLen = truncate $ (fromIntegral fullPad) / (2.0::Double)
halfPad = replicate halfPadLen ' '
in
if (even fullPad)
then halfPad ++ str ++ halfPad
else halfPad ++ " " ++ str ++ halfPad
padEnds :: String -> String
padEnds str =
let theLines = lines str
tableW = (length (head theLines)) + 2
allData = concatMap (\aLine -> "|" ++ aLine ++ "|\n") theLines
bar = replicate (tableW-2) '-'
in "|" ++ bar ++ "|\n" ++ allData ++ "|" ++ bar ++ "|\n"
renderTable :: String -> String
renderTable str = toStr ( lines (replace ',' ' ' str) )
where
toStr :: [String] -> String
toStr theLines =
let allBoxes = map (\aLine -> makeBoxes aLine) theLines
hs = map (\row -> punctuateH center1 (text " | ") row) allBoxes
box = punctuateV center1 (text (replicate 60 '-')) hs
rawStr = render box
in padEnds rawStr
makeBoxes :: String -> [Box]
makeBoxes aLine =
let sameLen = map (setLen 10) (words aLine)
in map text sameLen
main :: IO ()
main = do
fileData <- readFile "test2.csv"
putStrLn $ renderTable fileData
Yes, I think you're using boxes wrong. Here's what my go would look like:
import Prelude hiding ((<>)) -- ugh
import Data.List
import Text.PrettyPrint.Boxes
table :: [[String]] -> Box
table sss = border <> punctuateH center1 sep cs <> border where
border = vtext (replicate height '|')
sep = vtext (take height (cycle "-|"))
height = maxPosOn rows cs
cs = map column (transpose sss)
column :: [String] -> Box
column ss = sep // punctuateV center1 sep (map text ss) // sep where
sep = text (replicate (maxPosOn length ss+2) '-')
vtext :: String -> Box
vtext = vcat center1 . map char
maxPosOn :: (a -> Int) -> [a] -> Int
maxPosOn f = maximum . (0:) . map f
Try it out in ghci:
> printBox $ table [["on","pinput","pout","eff","ledi"],["5.0e-6","8.43764e-2","7.88486e-2","0.934486","8.63554e-3"],["5.4e-5","3.04731","2.90032","0.951765","0.214559"],["1.03e-4","6.34257","6.03162","0.950973","0.434331"]]
|-----------------------------------------------------------|
| on | pinput | pout | eff | ledi |
|-----------------------------------------------------------|
| 5.0e-6 | 8.43764e-2 | 7.88486e-2 | 0.934486 | 8.63554e-3 |
|-----------------------------------------------------------|
| 5.4e-5 | 3.04731 | 2.90032 | 0.951765 | 0.214559 |
|-----------------------------------------------------------|
| 1.03e-4 | 6.34257 | 6.03162 | 0.950973 | 0.434331 |
|-----------------------------------------------------------|
I have a music note datatype defined like so:
data Note = Ab | A | Bb | B | C | Db | D | Eb | E | F | Gb | G deriving (Eq, Ord)
How can i make it an instace of Enum so that succ G returns Ab ?
You have to define the Enum instance yourself:
instance Enum Note where
fromEnum note = case note of
Ab -> 0
A -> 1
...
toEnum n = case n `mod` 12 of
0 -> Ab
1 -> A
...
The "modulo 12" part in toEnum will cycle your notes.
For the sake of demonstration, I have two nearly-identical files: ListSuccess.hs and ListFailure.hs:
-- File: ListSuccess.hs
module ListSuccess where
import Prelude hiding ( head
, tail
)
{-#
data List a = Nil | Cons { lh :: a , lt :: List a }
#-}
data List a = Nil |Cons { lh :: a , lt :: List a }
-- File: ListFailure.hs
module ListFailure where
import Prelude hiding ( head
, tail
)
{-#
data List a = Nil | Cons { head :: a , tail :: List a }
#-}
data List a = Nil | Cons { head :: a , tail :: List a }
The only difference between these files is that ListSuccess names the fields of Cons as lh and lt, and ListFailure names the fields of Cons as head and tail.
Compiling with liquid, ListSuccess.hs compiles successfully, however ListFailure fails to compile, yielding this error:
10 | data List a = Nil | Cons { head :: a , tail :: List a }
^^^^^
ListFailure.head :: forall a .
lq$recSel:(ListFailure.List a) -> {VV : a | VV == head lq$recSel}
Sort Error in Refinement: {VV : a##xo | VV == head lq$recSel}
Cannot unify fix$36$$91$$93$ with ListFailure.List in expression: head lq$recSel
/Users/henry/Documents/Prototypes/liquidhaskell/list-fields-bug/ListFailure.hs:12:21-55: Error: Illegal type specification for `ListFailure.Cons`
12 | data List a = Nil | Cons { head :: a , tail :: List a }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ListFailure.Cons :: forall a .
head:a -> tail:(ListFailure.List a) -> {VV : (ListFailure.List a) | tail VV == tail
&& head VV == head}
Sort Error in Refinement: {VV : (ListFailure.List a##agi) | (tail VV == tail##ListFailure.Cons
&& head VV == head##ListFailure.Cons)}
Cannot unify fix$36$$91$$93$ with ListFailure.List in expression: tail VV
As far as I can tell, using head and tail as the field names should not be an issue, especially because I imported prelude hiding those names. The same error occurs even when I use import qualified Prelude and similar variants.
Is this a bug, or is there something that I'm missing here?
Configuration:
LiquidHaskell Version 0.8.6.0.
Let's say I have a list of twelve musical notes (which have their own data type), and I want a function that returns a list of notes starting with a given note and looping around.
data Note = C | CsDb | D | DsEb | E | F | FsGb | G | GsAb | A | AsBb | B deriving (Read, Eq, Ord, Enum, Bounded)
getNotes :: Note -> [Note]
getNotes root = take 12 $ doSomething root $ cycle noteList
where noteList :: [Note]
noteList = [minBound..maxBound]
such that
ghci> getNotes E
[E, F, FsGb, G, GsAb, A, AsBb, B, C, CsDb, D, DsEb]
I can think of a few sloppy ways to do this, but it feels like there should be an obvious, very Haskellian way. Any recommendations?
I'd just
getNotes root = [root .. maxBound] ++ init [minBound .. root]
but I can see how you prefer the cyclic approach. How about
getNotes root = map snd . take 12 $ [(0,root) .. ]
...sadly, that doesn't in fact work: it would need a (Enum a, Enum b, Bounded b) => Enum (a,b) instance, which for some reason isn't defined, at least not in the prelude.
Alternatively, you can use the index of root:
getNotes root = take 12 . drop (fromEnum root) $ cycle [minBound .. maxBound]
The smallest change you can make that works is to use dropWhile:
getNotes :: Note -> [Note]
getNotes root = take 12 . dropWhile (/= root) . cycle $ [minBound .. maxBound]
Based on the second idea of #leftaroundabout this is a working version - just in case you are curious and want to play with it:
{-# LANGUAGE ScopedTypeVariables #-}
module Stackoverflow where
data Note = C | CsDb | D | DsEb | E | F | FsGb | G | GsAb | A | AsBb | B
deriving (Show, Enum, Bounded)
instance (Enum a, Enum b, Bounded b) => Enum (a,b) where
toEnum i =
let (d,m) = i `divMod` (fromEnum (maxBound :: b) + 1)
in (toEnum d, toEnum m)
fromEnum (a, b) = fromEnum a * (fromEnum (maxBound :: b) + 1) + fromEnum b
getNotes :: Note -> [Note]
getNotes root = map snd . take 12 $ [(0,root) .. ]
example:
λ> getNotes E
[E,F,FsGb,G,GsAb,A,AsBb,B,C,CsDb,D,DsEb]
PS: the idea is extremely smart #leftaroundabout <- so guys make sure to give him lot's of upvotes ;)
How about
getNotes :: Note -> [Note]
getNotes root = ys ++ xs where (xs,ys) = break (==root) [minBound..maxBound]
? This is more-or-less the same as #leftaroundabout's first suggestion, avoids the init, but incurs a number of equality comparisons :-)
I have my own data type that states:
data Commands = MoveLeft |
MoveRight |
MoveUp |
MoveDown |
IfVertical |
IfHorizontal |
InputChar |
InputInt |
OutputChar |
OutputInt |
OutputNewline |
PushInt Int |
Add |
Sub |
Mult |
Div |
Exp |
Pop |
Dup |
Switch |
Noop |
End
deriving (Show, Eq)
and I have a function, with which I'm trying to extract the number from the PushInt with:
extractNum :: PushInt -> Int
extractNum (PushInt n) = n
But when I try to run this, I get an error stating:
Parser.hs:32:19:
Not in scope: type constructor or class `PushInt'
A data constructor of that name is in scope; did you mean -XDataKinds?
As far as I knew I was allowed to extract a field from data with this method. I'm pretty sure that this is just a really simple mistake, but any help is appreciated.
Wow, was I right about a 2 am mistake. The function
extractNum :: PushInt -> Int
extractNum (PushInt n) = n
should be
extractNum :: Commands -> Int
extractNum (PushInt n) = n