192 lines
5.8 KiB
Markdown
192 lines
5.8 KiB
Markdown
# 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 =
|
|
<THIS IS BLANK, FILL ME>
|
|
|
|
|
|
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
|
|
|
|
|
|
|