# Daily Challenge #137 - Help the Bookseller

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 letter of code is the capital letter of the book category.

In the bookseller's stocklist, each code c is followed by a space and by a positive integer n (int n >= 0) which indicates the quantity of books of this code in stock.

For example an extract of one of the stocklists could be:

`L = {"ABART 20", "CDXEF 50", "BKWRK 25", "BTSQZ 89", "DRTYM 60"}`

or

`L = ["ABART 20", "CDXEF 50", "BKWRK 25", "BTSQZ 89", "DRTYM 60"] or ....`

You will be given a stocklist (e.g. : L) and a list of categories in capital letters e.g :

`M = {"A", "B", "C", "W"}`

or

`M = ["A", "B", "C", "W"] or ...`

and your task is to find all the books of L with codes belonging to each category of M and to sum their quantity according to each category.

For the lists L and M of example you have to return the string (in Haskell/Clojure/Racket a list of pairs):

`(A : 20) - (B : 114) - (C : 50) - (W : 0)`

where A, B, C, W are the categories, 20 is the sum of the unique book of category A, 114 the sum corresponding to "BKWRK" and "BTSQZ", 50 corresponding to "CDXEF" and 0 to category 'W' since there are no code beginning with W.

If L or M are empty return string is `""` (Clojure and Racket should return an empty array/list instead).

Note:
In the result codes and their values are in the same order as in M.

This challenge comes from g964 on CodeWars. Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge idea for a future post? Email yo+challenge@dev.to with your suggestions! Rahul Kashyap • Edited on

#### 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));
`````` Nick Holmes

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 " - "
`````` Avalander

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)
}
}
`````` Avalander

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 " - "
}
}
`````` Christian • Edited on

``````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)]
`````` Sabin Pandelovitch

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 Sascha

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
``````