Wrapped and done

This commit is contained in:
eeva 2018-01-27 16:43:16 +02:00
parent 476f7e4356
commit 47ee21da0a
3 changed files with 79 additions and 46 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "ep0002"]
path = ep0002
url = https://github.com/MasseR/katas.git

View file

@ -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 }

1
ep0002 Submodule

@ -0,0 +1 @@
Subproject commit 859fdd499ee97d486846d621235035b2c7ea4383