Skip to content
loading...

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]
markdown guide
 

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));
 

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)]
 

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
 

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

 

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

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 " - "
  }
}
 

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

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

 

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)]
code of conduct - report abuse