loading...

Daily Challenge #137 - Help the Bookseller

thepracticaldev profile image dev.to staff ・2 min read

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!

Discussion

pic
Editor guide
Collapse
citizen428 profile image
Michael Kohl

F#, quick and dirty:

let bookQuantities (l : string list) (m : char list) =
    let counts = List.map (fun (s : string) -> s.[0], s.Split(' ').[1]) 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.[0], s.Split(' ').[1] |> 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)]
Collapse
kashyaprahul94 profile image
Rahul Kashyap

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));
Collapse
charukiewicz profile image
Christian

Haskell, quick and dirty.

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:_) ->
                        ( head label
                        , read count :: Int
                        )
                  ) <$> words <$> invInput

Output:

*Main> findBooks l m
[('A',20),('B',114),('C',50),('W',0)]
Collapse
sabbin profile image
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(" ");
    return total + parseInt(code.indexOf(key) === 0 ? nr : 0, 10);
  }, 0);
  return { ...o, [key]: c };
}, {});

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

console.log(printResult(count));

CodeSandbox example

Collapse
saschalalala profile image
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
Collapse
avalander profile image
Avalander

Scala

object Bookstore {
  case class Book(code: String, val quantity: Int) {
    val category = code.head
  }

  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)
  }
}
Collapse
avalander profile image
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(' ')
    (c.head, q.toInt)
  }

  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
      q = stocklist.getOrElse(c.head, 0)
    } yield s"($c : $q)"
    cats mkString " - "
  }
}
Collapse
nickholmesde profile image
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(' ').[1] |> int)
                              |> (sprintf "(%s : %d)" cat))
        |> String.concat " - "
Collapse
citizen428 profile image
Michael Kohl

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