### Daily Challenge #137 - Help the Bookseller

#### dev.to staff on December 13, 2019

A bookseller has lots of books classified in 26 categories labeled A, B, ... Z. Each book has a code c of 3, 4, 5 or more capital letters. The 1st ... [Read Full] F#, quick and dirty:

``````let bookQuantities (l : string list) (m : char list) =
let counts = List.map (fun (s : string) -> s., s.Split(' ').) l
List.map (fun c ->
c,
List.sumBy (fun (cat, score) ->
if cat = c then int score else 0) counts) m
``````

Cleaned up version:

``````let bookQuantities (l : string list) (m : char list) =
let findOrZero map key = Map.tryFind key map |> Option.defaultValue 0

let counts =
List.fold (fun m (s : string) ->
let (cat, count) = s., s.Split(' '). |> int
Map.add cat (count + findOrZero m cat) m) Map.empty l
List.map (fun c -> c, findOrZero counts c) m
``````

Usage:

``````let l = [ "ABART 20"; "CDXEF 50"; "BKWRK 25"; "BTSQZ 89"; "DRTYM 60" ]
let m = [ 'A'; 'B'; 'C'; 'W' ]

printf "%A" (bookQuantities l m)
// [('A', 20); ('B', 114); ('C', 50); ('W', 0)]
``````

#### JavaScript, with map ( for constant lookups )

``````const l = ["ABART 20", "CDXEF 50", "BKWRK 25", "BTSQZ 89", "DRTYM 60"];
const m = ["A", "B", "C", "W"];

const store = l.reduce((acc, inventory) => {
const [[code], qty] = inventory.split(" ");
const stock = (acc[code] || 0) + Number(qty);
return {
...acc,
[code]: stock
}
}, {});

const counts = m.reduce((result, key) => ({
...result,
[key]: store[key] || 0
}), {})

const print = obj => Object.entries(obj)
.map(([k, v]) => `(\${k} : \${v})`)
.join(" - ");

console.log(print(counts));
``````

In javascript

``````const l = ["ABART 20", "CDXEF 50", "BKWRK 25", "BTSQZ 89", "DRTYM 60"];
const m = ["A", "B", "C", "W"];

const count = m.reduce((o, key) => {
const c = l.reduce((total, item) => {
const [code, nr] = item.split(" ");
}, 0);
return { ...o, [key]: c };
}, {});

const printResult = obj => {
return Object.keys(obj)
.map(k => `(\${k} : \${obj[k]})`)
.join(" - ");
};

console.log(printResult(count));
``````

CodeSandbox example

Scala

``````object Bookstore {
case class Book(code: String, val quantity: Int) {
}

case class Category(code: Char, quantity: Int) {
override def toString(): String =
s"( \$code : \$quantity )"
}

type Stocklist = Seq[Book]

def categorise (cat: Seq[String], stock: Stocklist): Seq[Category] = {
def sorted = stock
.groupBy(_.category)
.map {
case (code, books) => code -> countBooks(code, books)
}
cat map (_.head) map (x => sorted.getOrElse(x, Category(x, 0)))
}

private def countBooks (code: Char, books: Stocklist): Category =
Category(code, books.map(_.quantity).sum)

def of (raw: Seq[String]): Stocklist =
raw map (_ split ' ') map {
case Array(c, q) => Book(c, q.toInt)
}

def pretty (stock: Seq[Category]): String =
stock map {
case Category(c, q) => s"(\$c : \$q)"
} mkString " - "

def solve (cat: Seq[String], stock: Seq[String]): String = {
val stocklist = of(stock)
val sorted = categorise(cat, stocklist)
pretty(sorted)
}
}
``````

I was under a strong sleep deprivation when I wrote that solution, I think it can be done in a simpler way:

``````object Bookstore2 {
type Book = (Char, Int)

def parseBook (s: String): Book = {
val Array(c, q) = s.split(' ')
}

def countBooks (books: Seq[Book]): Int = {
(books map (_._2)).sum
}

def solve (cat: Seq[String], stock: Seq[String]): String = {
val stocklist = stock map parseBook groupBy (_._1) mapValues countBooks
val cats = for {
c <- cat
} yield s"(\$c : \$q)"
cats mkString " - "
}
}
``````

Another F# one.

(But as I read it, the return value should be a string, not a list, and therefore a little input validation is needed to handle the input arrays being empty).

``````let stockByCategory(books: string[]) (cats: string[]): string =
if books = [||] || cats = [||] then
""
else
cats
|> Seq.map
(fun cat -> books |> Seq.filter (fun book -> book.StartsWith cat)
|> Seq.sumBy(fun book -> book.Split(' '). |> int)
|> (sprintf "(%s : %d)" cat))
|> String.concat " - "
``````

You're right about the requirements, should have been a string. 🤷‍♂️

``````l = [ "ABART 20", "CDXEF 50", "BKWRK 25", "BTSQZ 89", "DRTYM 60" ]
m = [ 'A', 'B', 'C', 'W' ]

findBooks :: [String] -> [Char] -> [(Char,Int)]
findBooks invInput searchInput =
(\searchCode -> (,) searchCode \$
foldr (\(invCode, count) runningTotal ->
if searchCode == invCode then
runningTotal + count
else
runningTotal
)
0
invList
) <\$> searchInput
where
invList = (\(label:count:_) ->
)
) <\$> words <\$> invInput
``````

Output:

``````*Main> findBooks l m
[('A',20),('B',114),('C',50),('W',0)]
``````

Python, with a quick pytest:

``````from collections import defaultdict

books = ["ABART 20", "CDXEF 50", "BKWRK 25", "BTSQZ 89", "DRTYM 60"]

stocks = ["A", "B", "C", "W"]

def do_137(book_list: list, stock_list: list) -> dict:
d = defaultdict(int)
for book in book_list:
for stock in stock_list:
if book.startswith(stock):
d[stock] += int(book.split(" ")[-1])
else:
d[stock] = 0
return d
``````

Test:

``````from dev137 import do_137

def test_137():
l = ["ABART 20", "CDXEF 50", "BKWRK 25", "BTSQZ 89", "DRTYM 60"]

m = ["A", "B", "C", "W"]

result = {"A": 20, "B": 114, "C": 50, "W": 0}

assert dict(do_137(l, m)) == result
``````
code of conduct - report abuse  