Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <h2>Trees</h2> <p>You were right that you should probably use a tree to store the data. I'll copy how <code>Data.Tree</code> does it:</p> <pre><code>data Tree a = Node a (Forest a) deriving (Show) type Forest a = [Tree a] </code></pre> <h2>Building the Tree</h2> <p>Now we want to take your weakly typed list of tuples and convert it to a (slightly) stronger <code>Tree</code> of <code>String</code>s. Any time you need to convert a weakly typed value and validate it before converting to a stronger type, you use a <code>Parser</code>:</p> <pre><code>type YourData = [(Int, [String])] type Parser a = YourData -&gt; Maybe (a, YourData) </code></pre> <p>The <code>YourData</code> type synonym represents the weak type that you are parsing. The <code>a</code> type variable is the value you are retrieving from the parse. Our <code>Parser</code> type returns a <code>Maybe</code> because the <code>Parser</code> might fail. To see why, the following input does not correspond to a valid <code>Tree</code>, since it is missing level 1 of the tree:</p> <pre><code>[(0, ["val1"]), (2, ["val2"])] </code></pre> <p>If the <code>Parser</code> <strong>does</strong> succeed, it also returns the unconsumed input so that subsequent parsing stages can use it.</p> <p>Now, curiously enough, the above <code>Parser</code> type exactly matches a well known monad transformer stack:</p> <pre><code>StateT s Maybe a </code></pre> <p>You can see this if you expand out the <a href="http://hackage.haskell.org/packages/archive/transformers/0.3.0.0/doc/html/Control-Monad-Trans-State-Strict.html#t%3aStateT" rel="nofollow">underlying implementation of <code>StateT</code></a>:</p> <pre><code>StateT s Maybe a ~ s -&gt; Maybe (a, s) </code></pre> <p>This means we can just define:</p> <pre><code>import Control.Monad.Trans.State.Strict type Parser a = StateT [(Int, [String])] Maybe a </code></pre> <p>If we do this, we get a <code>Monad</code>, <code>Applicative</code> and <code>Alternative</code> instance for our <code>Parser</code> type for free. This makes it very easy to define parsers!</p> <p>First, we must define a primitive parser that consumes a single node of the tree:</p> <pre><code>parseElement :: Int -&gt; Parser String parseElement level = StateT $ \list -&gt; case list of [] -&gt; Nothing (level', strs):rest -&gt; case strs of [str] -&gt; if (level' == level) then Just (str, rest) else Nothing _ -&gt; Nothing </code></pre> <p>This is the only non-trivial piece of code we have to write, which, because it is total, handles all the following corner cases:</p> <ul> <li>The list is empty</li> <li>Your node has multiple values in it</li> <li>The number in the tuple doesn't match the expected depth</li> </ul> <p>The next part is where things get really elegant. We can then define two mutually recursive parsers, one for parsing a <code>Tree</code>, and the other for parsing a <code>Forest</code>:</p> <pre><code>import Control.Applicative parseTree :: Int -&gt; Parser (Tree String) parseTree level = Node &lt;$&gt; parseElement level &lt;*&gt; parseForest (level + 1) parseForest :: Int -&gt; Parser (Forest String) parseForest level = many (parseTree level) </code></pre> <p>The first parser uses <code>Applicative</code> style, since <code>StateT</code> gave us an <code>Applicative</code> instance for free. However, I could also have used <code>StateT</code>'s <code>Monad</code> instance instead, to give code that's more readable for an imperative programmer:</p> <pre><code>parseTree :: Int -&gt; Parser (Tree String) parseTree level = do str &lt;- parseElement level forest &lt;- parseForest (level + 1) return $ Node str forest </code></pre> <p>But what about the <code>many</code> function? What's that doing? Let's look at its type:</p> <pre><code>many :: (Alternative f) =&gt; f a -&gt; f [a] </code></pre> <p>It takes anything that returns a value and implements <code>Applicative</code> and instead calls it repeatedly to return a list of values instead. When we defined our <code>Parser</code> type in terms of <code>State</code>, we got an <code>Alternative</code> instance for free, so we can use the <code>many</code> function to convert something that parses a single <code>Tree</code> (i.e. <code>parseTree</code>), into something that parses a <code>Forest</code> (i.e. <code>parseForest</code>).</p> <p>To use our <code>Parser</code>, we just rename an existing <code>StateT</code> function to make its purpose clear:</p> <p>runParser :: Parser a -> [(Int, [String])] -> Maybe a runParser = evalStateT</p> <p>Then we just run it!</p> <pre><code>&gt;&gt;&gt; runParser (parseForest 0) [(0,["day 1"]),(1,["Person 1"]),(2,["Bill 1"]),(1,["Person 2"]),(2,["Bill 2"])] Just [Node "day 1" [Node "Person 1" [Node "Bill 1" []],Node "Person 2" [Node "Bill 2" []]]] </code></pre> <p>That's just magic! Let's see what happens if we give it an invalid input:</p> <pre><code>&gt;&gt;&gt; runParser (parseForest 0) [(0, ["val1"]), (2, ["val2"])] Just [Node "val1" []] </code></pre> <p>It succeeds on a portion of the input! We can actually specify that it must consume the entire input by defining a parser that matches the end of the input:</p> <pre><code>eof :: Parser () eof = StateT $ \list -&gt; case list of [] -&gt; Just ((), []) _ -&gt; Nothing </code></pre> <p>Now let's try it:</p> <pre><code>&gt;&gt;&gt; runParser (parseForest 0 &gt;&gt; eof) [(0, ["val1"]), (2, ["val2"])] Nothing </code></pre> <p>Perfect!</p> <h2>Flattening the Tree</h2> <p>To answer your second question, we again solve the problem using mutually recursive functions:</p> <pre><code>flattenForest :: Forest a -&gt; [[a]] flattenForest forest = concatMap flattenTree forest flattenTree :: Tree a -&gt; [[a]] flattenTree (Node a forest) = case forest of [] -&gt; [[a]] _ -&gt; map (a:) (flattenForest forest) </code></pre> <p>Let's try it!</p> <pre><code>&gt;&gt;&gt; flattenForest [Node "day 1" [Node "Person 1" [Node "Bill 1" []],Node "Person 2" [Node "Bill 2" []]]] [["day 1","Person 1","Bill 1"],["day 1","Person 2","Bill 2"]] </code></pre> <p>Now, technically I didn't have to use mutually recursive functions. I could have done a single recursive function. I was just following the definition of the <code>Tree</code> type from <code>Data.Tree</code>.</p> <h2>Conclusion</h2> <p>So in theory I could have shortened the code even further by skipping the intermediate <code>Tree</code> type and just parsing the flattened result directly, but I figured you might want to use the <code>Tree</code>-based representation for other purposes.</p> <p>The key take home points from this are:</p> <ul> <li>Learn Haskell abstractions to simplify your code</li> <li>Always write total functions</li> <li>Learn to use recursion effectively</li> </ul> <p>If you do these, you will write robust and elegant code that exactly matches the problem.</p> <h2>Appendix</h2> <p>Here is the final code that incorporates everything I've said:</p> <pre><code>import Control.Applicative import Control.Monad.Trans.State.Strict import Data.Tree type YourType = [(Int, [String])] type Parser a = StateT [(Int, [String])] Maybe a runParser :: Parser a -&gt; [(Int, [String])] -&gt; Maybe a runParser = evalStateT parseElement :: Int -&gt; Parser String parseElement level = StateT $ \list -&gt; case list of [] -&gt; Nothing (level', strs):rest -&gt; case strs of [str] -&gt; if (level' == level) then Just (str, rest) else Nothing _ -&gt; Nothing parseTree :: Int -&gt; Parser (Tree String) parseTree level = Node &lt;$&gt; parseElement level &lt;*&gt; parseForest (level + 1) parseForest :: Int -&gt; Parser (Forest String) parseForest level = many (parseTree level) eof :: Parser () eof = StateT $ \list -&gt; case list of [] -&gt; Just ((), []) _ -&gt; Nothing flattenForest :: Forest a -&gt; [[a]] flattenForest forest = concatMap flattenTree forest flattenTree :: Tree a -&gt; [[a]] flattenTree (Node a forest) = case forest of [] -&gt; [[a]] _ -&gt; map (a:) (flattenForest forest) </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload