# FPKATA #01: In the library… (Soundtrack: Arch Enemy — War Eternal) (Inspired from this exercice: http://www.codewars.com/kata/54dc6f5a224c26032800005c/train/haskell) ## REPL compatible copy/paste (TL;DR) ``` import Dict type Stock = Stock { name : String, count : Int } library1 = [ Stock { name = "ABART", count = 20 } , Stock { name = "CDXEF", count = 50 } , Stock { name = "BKWRK", count = 25 } , Stock { name = "BTSQZ", count = 89 } , Stock { name = "DRTYM", count = 60 } ] categories1 = [ 'A', 'B', 'C', 'W' ] toUplet : List Repl.Stock -> List ( String, Int ) toUplet = List.map (\x -> case x of Stock v -> (v.name,v.count)) onlyInitials : List (String, Int) -> List (Char, Int) onlyInitials = List.map (\x -> case x of (s, i) -> ((Maybe.withDefault '\0' << List.head << String.toList) s,i) ) howMany : Char -> List Char Int -> Int howMany c = (List.foldl (\t acc -> acc + Tuple.second t) 0) << List.filter (\t -> (Tuple.first t) == c) stockList : List Stock -> List Char -> List ( Char, Int ) stockList sl = List.map (\c -> (c, sl |> toUplet >> onlyInitials >> howMany c)) ``` ## Prologue You are stuck in a library with a very talkative and experimental science-minded IA. It keeps repeating the following: > — Pleeease, can you help me? > — … > — Pleaaase, can you help? > — … > — Hellooooo? > — … Since you would really like to have peace, you dare ask: > — What's the matter? > — My code is incomplete, I've lost my code, please help! > — What code? What are you talking about? > — I really need to sort the library index and count how many books have the > same first-letter reference, but only in from a list… > — Wait! > — … of characters that my master will provide. > This makes no sense, can't you write it down? A few seconds pass in total silence. Then, there's a sound coming from your right, something like an inkjet printer work at a fast pace. It's actually a printer. It throws the printed sheet of paper at you in a furious “TWEEEEEEEEEEEP”. You pick up the paper and read: INPUT................: LIBRARY COUNT......: [("ABOOK", 12), ("BPDEXU", 45), ("BJLDRSA", 15), ("CLDAVB", 98), ("DAJLDI", 32)] INDICES............: ['A','B','C','W'] OUTPUT...............: INDEX COUNT........: [('A',12),('B',60),('C',98),('W',0)] TIME TO GOOD ANSWER..: 203 years You frown: > — It took you so long to find this? > — Can you help me? I've lost my code, my dedicated code for this task. > — Well, you managed to get this one right, didn't you? > — Nooooo! Master is going to unplug me if I use bruteforce again! Master was very unhappy with my performance! I want to live! Please help me! > — Ok, all right, calmn down, there, there… What are programmed with? > — Elm. Of course it's Elm… ## Outline ``` module Main exposing (..) import Html exposing (text) type Stock = Stock String Int library1 : List Stock library1 = [ Stock "ABART" 20 , Stock "CDXEF" 50 , Stock "BKWRK" 25 , Stock "BTSQZ" 89 , Stock "DRTYM" 60 ] categories1 : List Char categories1 = [ 'A' , 'B' , 'C' , 'W' ] stockList : List Stock -> List Char -> List ( Char, Int ) stockList st cs = main = text (toString (stockList library1 categories1)) ``` ## Walkthrough All is needed is to fill the gaps from `library1`, `categories1` to `stockList`. So, what is needed for successfully building a stockList? Let's look at the types: We have a `List Stock` but we don't have any function to work on it. We could make some functions for this, but we'll just be lazy and turn a List Stock into a much lower level `List (String, Int)`: toUplet : List Stock -> List ( String, Int ) toUplet = List.map (\x -> case x of Stock v -> (v.name,v.count)) We'll need to count stock by initials, so we write a function for that: onlyInitials : List (String, Int) -> List (Char, Int) onlyInitials = List.map (\x -> case x of (s, i) -> ((Maybe.withDefault '\0' << List.head << String.toList) s,i) ) So, for every tuple, we operate of the left part. There is some strange machinery for turning a `String -> Char` involving a Maybe value, because `List.head` may fail. Of course in our case, no book should ever have an empty title, but Elm doesn't know that. We mitigate this by setting a default `null` char if the title is empty. Good! We can already chain the two preceding functions since the output type of `toUplet` matches the input type of `onlyInitials`. Ain't that a coincidence? > library1 |> toUplet >> onlyInitials [('A',20),('C',50),('B',25),('B',89),('D',60)] : List ( Char, Int ) Oh, but oh. There are two Bs in there! Those should go together and their counts added together, and for that, nothing is better than a fold. Let's write a function that answers the question: “How many X is there?” where X is a Char. howMany : Char -> List Char Int -> Int howMany c = List.filter (\t -> (Tuple.first t) == c) >> (List.foldl (\t acc -> acc + Tuple.second t) 0) `howMany` is made of two functions: the first is one that only keeps tuples of interest (they match our Char c) and then we blindly sum the count of the resulting list. We always start the count from 0, so that if we don't find anything, it's still correct. Let's try to find how may books that have a title starting with B in the library: > library1 |> toUplet >> onlyInitials >> howMany 'B' 114 : Int Nice! Well, we're almost done actually. We just need to do that for every letter we were requested in `categories1`. Let's use a map for that: stockList sl = List.map (\c -> (c, sl |> toUplet >> onlyInitials >> howMany c) ) And voilà! Let's run this: > stockList library1 categories1 [('A',20),('B',114),('C',50),('W',0)] : List ( Char, Int ) Mission accomplished. ## Epilogue