Take Two

I’m much happier with the second version:

out3 a =
    let
        renderGroup g =
            let 
                char = head g;
                count = length g;
                renderChars :: Int -> String;
                renderChars n =
                    (fromChar char) ++
                    (if n >1 then  " " ++ (renderChars $ n-1) else "");
            in
                if count > 2 then
                    (renderChars 2) ++ 
                    " <span>" ++ (renderChars (count - 2)) ++ "</span>"
                else
                    renderChars count;
        concatWithSep list = (head list) ++ concatMap (\s -> " " ++ s) (tail list);
    in
        concatWithSep $ map renderGroup (group a);

This only uses one ‘exotic’ list function, group:

group :: Eq a => [a] -> [[a]]
    Splits the specified list into a list of lists of equal, adjacent elements.

My program is longer than Matt’s — roughly twice as long — but I think this version is easier to understand.

Matt’s Javascript for loop has some complicated state, contained in some loop variables which are modified in the body of the loop as well as in the for statement itself. I think that’s a recipe for confusion.

3 Comments

  1. Posted July 29, 2008 at 5:54 am | Permalink

    Unsurprisingly, if allow ourselves to use a few more list functions than things can improve still further:

    out4 lst =
        let 
            processSplitSequence leading trailing = 
                (map fromChar leading) ++ (if isEmpty trailing then [] else ([""] ++ (map fromChar trailing) ++ [""]));
        in
            unwords $ concatMap (uncurry processSplitSequence) (map (splitAt 2) (group lst));

    This does the same as out3, except that spaces are rendered between the tags and the span sequence. This approach has the overt concept that sequences are composed of leading elements (the 2 items rendered ahead of the tagged block), and trailing elements (the remainder of any sequence).

    Again, ‘group’ really does most of the heavy-lifting. After that, we simply split groups into leading and trailing sequences, then project each of these sequence pairs as the appropriate list of strings. Then we concatenate the renderings from each group. Finally, we use ‘unwords’ a library function that renders a list of strings as a single string with space separated elements.

    For the uninitiated, the weird ‘uncurry’ is only there because I chose to define processSplitSequence as a function of two clearly named arguments. This has to be applied to a single pair (2-tuple), which is what uncurry does here. An alternative could be:

    out4a lst =
        let 
            processSplitSequence seqPair = 
                (map fromChar seqPair.#1) ++ (if isEmpty seqPair.#2 then [] else ([""] ++ (map fromChar seqPair.#2) ++ [""]));
        in
            unwords $ concatMap processSplitSequence (map (splitAt 2) (group lst));

    …though it would be good in this case to create a let definition in processSplitSequence to capture “seqPair.#2″ once.

  2. Posted July 29, 2008 at 5:55 am | Permalink

    Oops. Forgot to escape the code blocks. Oh well. [fixed :-) ]

  3. Posted August 5, 2008 at 6:25 pm | Permalink

    Hi Tom,

    Thanks for fixing the formatting. Looks like the tags in the string quotations got nixed by my not escaping the code blocks too. As it stands, the code doesn’t quite conform to specification ;-)

    – Lwe

Post a Comment

Your email is never shared. Required fields are marked *

*
*