fpkata-relex/ep0001/Solution.md

4.5 KiB

FPKATA #01: In the library…

(Soundtrack: Arch Enemy — War Eternal)

(Inspired from this exercice: http://www.codewars.com/kata/54dc6f5a224c26032800005c/train/haskell)

Introduction

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

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:

foldl : (a -> b -> b) -> b -> List a -> b

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.

Inner foldl: how many books with Initial X

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.

type alias Initial = Char
type alias Count = Int
type alias TitleCount = (Initial, Int) 
type alias FinalCount = (Initial, Int)

You rewrite your specialized foldl type signature to account for the type aliases:

foldForX : (TitleCount -> FinalCount -> FinalCount)
      -> FinalCount
      -> List TitleCount
      -> FinalCount

After a call to this, you'll get how many books have a title that starts with the initial X.

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:

updateFinalCount : (TitleCount -> FinalCount -> FinalCount)

Actually it takes another (hidden) parameter, you change its name to reflect that:

updateFinalCountForX : (TitleCount -> FinalCount -> FinalCount)
updateFinalCountForX t f =    

NB: addBio : String -> User -> User addBio bio user = { user | bio = bio }