diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3d8a6f2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ep0002"] + path = ep0002 + url = https://github.com/MasseR/katas.git diff --git a/ep0001/Solution.md b/ep0001/Solution.md index 8023d29..55df11e 100644 --- a/ep0001/Solution.md +++ b/ep0001/Solution.md @@ -4,7 +4,29 @@ (Inspired from this exercice: http://www.codewars.com/kata/54dc6f5a224c26032800005c/train/haskell) -## Introduction +## 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: @@ -28,7 +50,8 @@ Since you would really like to have peace, you dare ask: > — 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… +> — 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! @@ -36,7 +59,10 @@ Since you would really like to have peace, you dare ask: > 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: +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)] @@ -104,60 +130,63 @@ main = ## Walkthrough -Ok, so you'll have to fill the blank. After a few minutes, you identify two -main components: one is about taking the first letter of every `Stock` name. The -second is finding the amount of each `Char` in a `List (Char, Int)`. -> -Let's start with the second point and assume we have a value of `List (Char, -Int)` already available. We know at least to functions to recurse on lists: -maps and folds. Clearly, we'll have to choose a fold, since some aggregating is -needed when several identical `Char` are in the list. Let's pick foldl, it does -not really matter right now. So, the type of `List.foldl` is: +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)`: - foldl : (a -> b -> b) -> b -> List a -> b + toUplet : List Stock -> List ( String, Int ) + toUplet = List.map (\x -> case x of Stock v -> (v.name,v.count)) -In fact, you notice you'll need two folds! One inner fold for counting of many -book for initial X, and one outer fold for going through all the initials -required by the master. +We'll need to count stock by initials, so we write a function for that: -### Inner foldl: how many books with Initial X + 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) ) -You think about using type aliases to make your code easier to read, the IA -won't mind. To specialize your inner foldl, which is morally a foldlForX, you -identify `a` as an element of a library stock (even though the title has been -shortened already to its initial), and `b` as the final index count. +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. - type alias Initial = Char - type alias Count = Int - type alias TitleCount = (Initial, Int) - type alias FinalCount = (Initial, Int) +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? -You rewrite your specialized foldl type signature to account for the type -aliases: + > library1 |> toUplet >> onlyInitials + [('A',20),('C',50),('B',25),('B',89),('D',60)] : List ( Char, Int ) - foldForX : (TitleCount -> FinalCount -> FinalCount) - -> FinalCount - -> List TitleCount - -> FinalCount +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. -After a call to this, you'll get how many books have a title that starts with -the initial X. + howMany : Char -> List Char Int -> Int + howMany c = List.filter (\t -> (Tuple.first t) == c) + >> (List.foldl (\t acc -> acc + Tuple.second t) 0) -You are missing the function with type `(TitleCount -> FinalCount -> -FinalCount)`, and you reflect a little bit on its name and on its usage. Hmmm, -it takes a TitleCount and a FinalCount and returns a (new) FinalCount? You -choose to call it: +`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. - updateFinalCount : (TitleCount -> FinalCount -> FinalCount) +Let's try to find how may books that have a title starting with B in the library: -Actually it takes another (hidden) parameter, you change its name to reflect -that: + > 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 - updateFinalCountForX : (TitleCount -> FinalCount -> FinalCount) - updateFinalCountForX t f = -NB: -addBio : String -> User -> User -addBio bio user = - { user | bio = bio } diff --git a/ep0002 b/ep0002 new file mode 160000 index 0000000..859fdd4 --- /dev/null +++ b/ep0002 @@ -0,0 +1 @@ +Subproject commit 859fdd499ee97d486846d621235035b2c7ea4383