# A day at a corner shop: a longer Zag program, written to be read.## It keeps a small catalog, takes orders written as text, prices them in exact decimals (`deci64`,# so 0.10 plus 0.20 is 0.30), applies discounts that a range type keeps between 0 and 50 percent,# prints receipts, tracks stock and loyalty points in maps, and ends the day with a report, a# restocking plan, the day's busy hours, the week's trade and a catalog search. Along the way it# shows most of Zag's everyday features:## structs and enums with data, `match` ownership said aloud: `give`, `copy`, `lend`# failures a caller must handle, `otherwise` generics with an interface, and `Number`# functions as values a type's own function as an iterator (`Ticket.next`)# range types, `int32[min 0, max 50]` counting loops, `for 'i' from 1 to 'n' by 1`# slices, `from pos 1, to pos 3` consts, at the top and inside a body# tasks and channels, `concur` `print.useful`, `print.stored`, `print.format`## Nothing here is clever on purpose. A shop's code is read far more often than it is written.# ---------------------------------------------------------------------------------------------------# The shop's fixed facts# ---------------------------------------------------------------------------------------------------const'shop_name':="Corner Shop";const'points_per_item':=2;# loyalty points earned for every item boughtconst'low_stock':=3;# at or below this, an item goes on the reorder list# ---------------------------------------------------------------------------------------------------# Types# ---------------------------------------------------------------------------------------------------enumAisle{fresh,bakery,pantry,household}structItem{name:str.utf8,aisle:Aisle,price:deci64}# One line of an order: what, and how many.structLine{name:str.utf8,count:int32[min1,max99]}# How a customer pays. A card carries its last four digits, a voucher its value.enumPayment{cash,card:str.utf8,voucher:deci64}structOrder{customer:str.utf8,lines:list[Line],payment:Payment,discount:int32[min0,max50]}# What one order came to, kept for the end-of-day report.structSale{ticket:int32,customer:str.utf8,units:int32,total:deci64}# Receipt numbers come from a counter that hands out the next one each time it is asked.structTicket{now:int32,last:int32}fnTicket.next[lent.mut't':Ticket]->optional[int32]{if't'.nowequal_to't'.last{returnoptional.none;}change't'.now='t'.nowplus1;returnoptional.value['t'.now];}# ---------------------------------------------------------------------------------------------------# Small generic helpers# ---------------------------------------------------------------------------------------------------interfaceNumber{int32int64bin64}# The larger of two numbers, for any kind of number.fnlarger<T:Number>['a':<T>,'b':<T>]-><T>{if'a'>'b'{return'a';}return'b';}# The total of a list of numbers.fntotal_of<T:Number>[lent'xs':list[<T>]]-><T>{let'sum':<T>=0;for'x'inlend'xs'{change'sum'='sum'plus'x';}return'sum';}# The list sorted from largest to smallest: a plain insertion sort, easy to check by eye.fnsorted_down[lent'xs':list[int32]]->list[int32]{let'out':list[int32]=list.empty;for'x'inlend'xs'{list.append[lend.mut'out',value'x'];let'i':=list.length[lend'out'];while'i'>1and.then'out'[pos'i']>'out'[pos'i'minus1]{let'held':='out'[pos'i'];change'out'[pos'i']='out'[pos'i'minus1];change'out'[pos'i'minus1]='held';change'i'='i'minus1;}}returngive'out';}# ---------------------------------------------------------------------------------------------------# The catalog and the stock room# ---------------------------------------------------------------------------------------------------fncatalog[]->list[Item]{return[Item[name="apple",aisle=Aisle.fresh,price=0.45],Item[name="banana",aisle=Aisle.fresh,price=0.30],Item[name="milk",aisle=Aisle.fresh,price=1.20],Item[name="bread",aisle=Aisle.bakery,price=2.10],Item[name="croissant",aisle=Aisle.bakery,price=1.35],Item[name="rice",aisle=Aisle.pantry,price=3.80],Item[name="beans",aisle=Aisle.pantry,price=0.95],Item[name="coffee",aisle=Aisle.pantry,price=6.50],Item[name="soap",aisle=Aisle.household,price=1.75],Item[name="candles",aisle=Aisle.household,price=4.20]];}fnopening_stock[]->map[str.utf8,int32]{let'stock':map[str.utf8,int32]=map.empty;map.set[lend.mut'stock',key"apple",value40];map.set[lend.mut'stock',key"banana",value30];map.set[lend.mut'stock',key"milk",value12];map.set[lend.mut'stock',key"bread",value8];map.set[lend.mut'stock',key"croissant",value6];map.set[lend.mut'stock',key"rice",value10];map.set[lend.mut'stock',key"beans",value24];map.set[lend.mut'stock',key"coffee",value5];map.set[lend.mut'stock',key"soap",value9];map.set[lend.mut'stock',key"candles",value4];returngive'stock';}# The catalog entry for a name, or a failure the caller must handle.fnfind_item[lent'items':list[Item],lent'name':str.utf8]->Itemorfail[unknown_item]{for'it'inlend'items'{if'it'.nameequal_to'name'{returncopy'it';}}failunknown_item;}fnaisle_name['a':Aisle]->str.utf8{match'a'{fresh{return"fresh";}bakery{return"bakery";}pantry{return"pantry";}household{return"household";}}}# ---------------------------------------------------------------------------------------------------# Reading orders written as text: "apple 3, bread 1, coffee 2"# ---------------------------------------------------------------------------------------------------# One "name count" piece into a line; a count that is not a number, or not 1 to 99, fails.fnparse_line[lent'piece':str.utf8]->Lineorfail[bad_line: str.utf8]{let'words':=std::str.split[lend'piece',at" "];iflist.length[lend'words']not_equal2{failbad_line[copy'piece'];}let'name':=copy'words'[pos1];let'count':int32[min1,max99]=to.int32[min1,max99]['words'[pos2]]otherwise{not_a_number{failbad_line[copy'piece'];}out_of_range{failbad_line[copy'piece'];}};returnLine[name=give'name',count='count'];}fnparse_order[lent'text':str.utf8]->list[Line]orfail[bad_line: str.utf8]{let'lines':list[Line]=list.empty;let'pieces':=std::str.split[lend'text',at", "];for'piece'inlend'pieces'{let'line':=parse_line[lend'piece']otherwisefail;list.append[lend.mut'lines',valuegive'line'];}returngive'lines';}# ---------------------------------------------------------------------------------------------------# Pricing# ---------------------------------------------------------------------------------------------------# A pricing rule takes a line's price before any rule and gives what the customer pays for it.fnno_rule['line_total':deci64]->deci64{return'line_total';}# Bakery goods are 20% off after six in the evening; the shop runs this rule all day here.fnevening_bakery['line_total':deci64]->deci64{return'line_total'times0.80;}fnrule_for['a':Aisle]->fn[deci64]->deci64{match'a'{bakery{returnevening_bakery;}fresh{returnno_rule;}pantry{returnno_rule;}household{returnno_rule;}}}# A whole-order discount, which the range type keeps between 0 and 50 percent.fnapply_discount['amount':deci64,'percent':int32[min0,max50]]->deci64{let'pct':deci64=to.deci64['percent'];let'off':deci64=('amount'times'pct')div.near100;return'amount'minus'off';}# ---------------------------------------------------------------------------------------------------# Taking one order# ---------------------------------------------------------------------------------------------------fntake_order[lent'items':list[Item],lent.mut'stock':map[str.utf8,int32],lent'order':Order,'ticket':int32]->Saleorfail[out_of_stock: str.utf8, unknown_item: str.utf8]{std::print.useful["--- receipt #",'ticket'," for ",'order'.customer," ---",newln];let'subtotal':deci64=0;let'units':=0;for'line'inlend'order'.lines{let'item':=find_item[lend'items',lend'line'.name]otherwise{unknown_item{failunknown_item[copy'line'.name];}};let'have':=map.get[lend'stock',keycopy'line'.name]otherwise0;if'have'<'line'.count{failout_of_stock[copy'line'.name];}map.set[lend.mut'stock',keycopy'line'.name,value'have'minus'line'.count];let'rule':=rule_for[copy'item'.aisle];let'count':deci64=to.deci64['line'.count];let'line_total':='rule'['item'.pricetimes'count'];change'subtotal'='subtotal'plus'line_total';change'units'='units'plus'line'.count;std::print.format[" {:<10} x{:>2} {:>8.2}\n",'line'.name,'line'.count,'line_total'];}let'total':=apply_discount['subtotal','order'.discount];if'order'.discount>0{std::print.format[" discount {}%\n",'order'.discount];}std::print.format[" {:<14} {:>8.2}\n","total",'total'];std::print.useful[" paid by ",payment_text[lend'order'.payment,'total'],newln];returnSale[ticket='ticket',customer=copy'order'.customer,units='units',total='total'];}fnpayment_text[lent'p':Payment,'total':deci64]->str.utf8{match'p'{cash{return"cash";}card'last4'{return"card ending "++'last4';}voucher'worth'{if'worth'at_least'total'{return"voucher, nothing left to pay";}returnstd::format["voucher, then {:.2} in cash",'total'minus'worth'];}}}# ---------------------------------------------------------------------------------------------------# The day's orders# ---------------------------------------------------------------------------------------------------fntodays_orders[]->list[Order]{let'orders':list[Order]=list.empty;let'texts':=["apple 6, bread 1, milk 2","coffee 2, croissant 4","rice 1, beans 3, soap 2, candles 1","banana 12, milk 1","coffee 4","pineapple 1","apple two","candles 2, bread 2, croissant 2"];let'customers':=["Ann","Bo","Cy","Dee","Eli","Fay","Gus","Hal"];for'i'from1tolist.length[lend'texts']by1{let'lines':=parse_order[lend'texts'[pos'i']]otherwise{bad_line'what'{std::print.useful["(order for ",'customers'[pos'i']," skipped: cannot read \"",'what',"\")",newln];continue;}};let'payment':=choose_payment['i'];let'discount':int32[min0,max50]=0;if'i'equal_to3{change'discount'=10;}if'i'equal_to8{change'discount'=25;}list.append[lend.mut'orders',valueOrder[customer=copy'customers'[pos'i'],lines=give'lines',payment=give'payment',discount='discount']];}returngive'orders';}fnchoose_payment['i':int32]->Payment{if('i'mod.floor3)equal_to0{returnPayment.voucher[5.00];}if('i'mod.floor2)equal_to0{returnPayment.card["4242"];}returnPayment.cash;}# ---------------------------------------------------------------------------------------------------# Loyalty points# ---------------------------------------------------------------------------------------------------fnadd_points[lent.mut'points':map[str.utf8,int32],lent'sale':Sale]{let'earned':='sale'.unitstimes'points_per_item';let'before':=map.get[lend'points',keycopy'sale'.customer]otherwise0;map.set[lend.mut'points',keycopy'sale'.customer,value'before'plus'earned'];}# ---------------------------------------------------------------------------------------------------# The end-of-day report# ---------------------------------------------------------------------------------------------------fnreorder_list[lent'items':list[Item],lent'stock':map[str.utf8,int32]]->list[str.utf8]{let'out':list[str.utf8]=list.empty;for'it'inlend'items'{let'left':=map.get[lend'stock',keycopy'it'.name]otherwise0;if'left'at_most'low_stock'{list.append[lend.mut'out',valuecopy'it'.name++" ("++to.stored.str.utf8['left']++" left)"];}}returngive'out';}fntakings_by_aisle[lent'items':list[Item],lent'opening':map[str.utf8,int32],lent'closing':map[str.utf8,int32],'a':Aisle]->deci64{let'sum':deci64=0;for'it'inlend'items'{if'it'.aisleequal_to'a'{let'had':=map.get[lend'opening',keycopy'it'.name]otherwise0;let'has':=map.get[lend'closing',keycopy'it'.name]otherwise0;let'sold':='had'minus'has';change'sum'='sum'plus'it'.pricetimesto.deci64['sold'];}}return'sum';}fnreport[lent'sales':list[Sale],lent'items':list[Item],lent'opening':map[str.utf8,int32],lent'stock':map[str.utf8,int32],lent'points':map[str.utf8,int32]]{std::print.useful[newln,"=== ",'shop_name',", end of day ===",newln];# Orders and unitslet'units':list[int32]=list.empty;let'takings':deci64=0;for's'inlend'sales'{list.append[lend.mut'units',value's'.units];change'takings'='takings'plus's'.total;}std::print.useful["orders served: ",list.length[lend'sales'],", units sold: ",total_of[lend'units'],newln];std::print.format["takings: {:.2}\n",'takings'];# The biggest baskets, by units: the first three of the list sorted largest firstlet'ranked':=sorted_down[lend'units'];let'shown':=larger[1,list.length[lend'ranked']];if'shown'at_least3{let'top':=list.slice[lend'ranked',frompos1,topos3];std::print.useful["biggest baskets (units): ",'top',newln];}# The best sale of the daylet'best_total':deci64=0;let'best_ticket':=0;let'best_customer':="nobody";for's'inlend'sales'{if's'.total>'best_total'{change'best_total'='s'.total;change'best_ticket'='s'.ticket;change'best_customer'=copy's'.customer;}}std::print.format["best sale: #{}, {}, {:.2}\n",'best_ticket','best_customer','best_total'];# Takings by aisle, counted from what left the shelves (at list price, before discounts)std::print.useful["by aisle, at list price:",newln];let'aisles':=[Aisle.fresh,Aisle.bakery,Aisle.pantry,Aisle.household];for'a'inlend'aisles'{std::print.format[" {:<10} {:>8.2}\n",aisle_name[copy'a'],takings_by_aisle[lend'items',lend'opening',lend'stock',copy'a']];}# Reorderslet'reorder':=reorder_list[lend'items',lend'stock'];iflist.length[lend'reorder']equal_to0{std::print.useful["nothing to reorder",newln];}else{std::print.useful["reorder: ",std::str.join[lend'reorder',between", "],newln];}# Loyalty points, for the customers who were servedstd::print.useful["loyalty points:",newln];for's'inlend'sales'{let'p':=map.get[lend'points',keycopy's'.customer]otherwise0;std::print.format[" {:<5} {:>3}\n",'s'.customer,'p'];}}# ---------------------------------------------------------------------------------------------------# Counting at the same time: each aisle's shelf count is tallied by its own task, which reports back# over a channel. The tasks run alongside each other; MAIN waits for every answer.# ---------------------------------------------------------------------------------------------------fnshelf_count[lent'items':list[Item],lent'stock':map[str.utf8,int32],'a':Aisle]->int32{let'n':=0;for'it'inlend'items'{if'it'.aisleequal_to'a'{let'on_shelf':=map.get[lend'stock',keycopy'it'.name]otherwise0;change'n'='n'plus'on_shelf';}}return'n';}fncount_shelves[lent'items':list[Item],lent'stock':map[str.utf8,int32]]{let'fresh_ch':=channel[int32];let'bakery_ch':=channel[int32];let'pantry_ch':=channel[int32];let'household_ch':=channel[int32];concurwith[lend'fresh_ch',lend'items',lend'stock']{channel.send[lend'fresh_ch',valueshelf_count[lend'items',lend'stock',Aisle.fresh]];}concurwith[lend'bakery_ch',lend'items',lend'stock']{channel.send[lend'bakery_ch',valueshelf_count[lend'items',lend'stock',Aisle.bakery]];}concurwith[lend'pantry_ch',lend'items',lend'stock']{channel.send[lend'pantry_ch',valueshelf_count[lend'items',lend'stock',Aisle.pantry]];}concurwith[lend'household_ch',lend'items',lend'stock']{channel.send[lend'household_ch',valueshelf_count[lend'items',lend'stock',Aisle.household]];}let'f':=channel.take[lend'fresh_ch']otherwise0;let'b':=channel.take[lend'bakery_ch']otherwise0;let'p':=channel.take[lend'pantry_ch']otherwise0;let'h':=channel.take[lend'household_ch']otherwise0;std::print.useful["on the shelves tonight: fresh ",'f',", bakery ",'b',", pantry ",'p',", household ",'h',newln];}# ---------------------------------------------------------------------------------------------------# Looking things up: finding catalog items by what their name starts with or contains# ---------------------------------------------------------------------------------------------------fnnames_starting[lent'items':list[Item],lent'start':str.utf8]->list[str.utf8]{let'out':list[str.utf8]=list.empty;for'it'inlend'items'{ifstd::str.starts_with[lend'it'.name,partcopy'start']{list.append[lend.mut'out',valuecopy'it'.name];}}returngive'out';}fnnames_containing[lent'items':list[Item],lent'part':str.utf8]->list[str.utf8]{let'out':list[str.utf8]=list.empty;for'it'inlend'items'{ifstd::str.contains[lend'it'.name,partcopy'part']{list.append[lend.mut'out',valuecopy'it'.name];}}returngive'out';}fnshow_search[lent'items':list[Item]]{std::print.useful[newln,"searching the catalog:",newln];let'starts':=["b","c","z"];for's'inlend'starts'{let'found':=names_starting[lend'items',lend's'];iflist.length[lend'found']equal_to0{std::print.useful[" nothing starts with \"",'s',"\"",newln];}else{std::print.useful[" starting with \"",'s',"\": ",std::str.join[lend'found',between", "],newln];}}let'has_an':=names_containing[lend'items',"an"];std::print.useful[" containing \"an\": ",std::str.join[lend'has_an',between", "],newln];let'sign':='shop_name';# a const is a fresh value each use, so it is named to lendlet'loud':=std::str.upper[lend'sign'];std::print.useful[" the sign over the door says ",'loud',newln];}# ---------------------------------------------------------------------------------------------------# Restocking: what to order so every shelf is back to its target, in whole cases# ---------------------------------------------------------------------------------------------------structRestock{name:str.utf8,cases:int32,cost:deci64}# How many items one case holds, by aisle.fncase_size['a':Aisle]->int32{let'size':=match'a'{fresh{is10;}bakery{is6;}pantry{is12;}household{is4;}};return'size';}# The supplier charges 60% of the shop's price for each item in a case.fncase_cost['price':deci64,'size':int32]->deci64{let'each':deci64='price'times0.60;let'n':deci64=to.deci64['size'];return'each'times'n';}fnplan_restock[lent'items':list[Item],lent'opening':map[str.utf8,int32],lent'stock':map[str.utf8,int32]]->list[Restock]{let'plan':list[Restock]=list.empty;for'it'inlend'items'{let'target':=map.get[lend'opening',keycopy'it'.name]otherwise0;let'left':=map.get[lend'stock',keycopy'it'.name]otherwise0;let'missing':='target'minus'left';if'missing'>0{let'size':=case_size[copy'it'.aisle];# whole cases only: round the count of cases uplet'cases':=('missing'plus'size'minus1)div.floor'size';let'one_case':=case_cost['it'.price,'size'];let'n':deci64=to.deci64['cases'];list.append[lend.mut'plan',valueRestock[name=copy'it'.name,cases='cases',cost='one_case'times'n']];}}returngive'plan';}fnshow_restock[lent'plan':list[Restock]]{std::print.useful[newln,"tomorrow's delivery:",newln];let'bill':deci64=0;for'r'inlend'plan'{std::print.format[" {:<10} {:>2} case(s) {:>7.2}\n",'r'.name,'r'.cases,'r'.cost];change'bill'='bill'plus'r'.cost;}std::print.format[" {:<22} {:>7.2}\n","to the supplier",'bill'];}# ---------------------------------------------------------------------------------------------------# A week of trade: seven days of made-up but repeatable numbers, averaged and compared# ---------------------------------------------------------------------------------------------------# Customers on day 'd' (1 is Monday): busier towards the weekend, quieter on Sunday.fncustomers_on['d':int32[min1,max7]]->int32{if'd'equal_to7{return18;}return20plus'd'times4;}fnshow_week[]{const'days':=7;std::print.useful[newln,"this week:",newln];let'counts':list[int32]=list.empty;for'd'from1to'days'by1{let'n':=customers_on['d'];list.append[lend.mut'counts',value'n'];}let'names':=["Mon","Tue","Wed","Thu","Fri","Sat","Sun"];let'busiest':=0;for'i','n'inlend'counts'{std::print.format[" {} {:>3} ",'names'[pos'i'],'n'];for'k'from0before'n'by4{std::print.useful["#"];}std::print.useful[newln];change'busiest'=larger['busiest','n'];}let'sum':=total_of[lend'counts'];let'average':bin64=to.bin64['sum']div.nearto.bin64['days'];std::print.format[" {} customers, {:.1} a day, {} on the busiest\n",'sum','average','busiest'];let'weekend':=list.slice[lend'counts',frompos6,topos7];let'weekdays':=list.slice[lend'counts',frompos1,beforepos6];std::print.useful[" weekdays ",total_of[lend'weekdays'],", weekend ",total_of[lend'weekend'],newln];}# ---------------------------------------------------------------------------------------------------# Sales by the hour: a bar for each opening hour, counted with `before`# ---------------------------------------------------------------------------------------------------fnshow_hours['open':int32,'close':int32]{std::print.useful[newln,"customers by the hour:",newln];let'peak':=0;let'peak_hour':='open';for'h'from'open'before'close'by1{# a lunch rush at noon and an evening rush at five, quiet otherwiselet'n':=2;if'h'equal_to12or'h'equal_to17{change'n'=9;}if'h'equal_to13or'h'equal_to18{change'n'=6;}std::print.format[" {:>2}:00 ",'h'];let'bar':=list.fill[value"*",count'n'];std::print.useful[std::str.join[lend'bar',betweenstr.empty],newln];if'n'>'peak'{change'peak'='n';change'peak_hour'='h';}}std::print.useful[" busiest from ",'peak_hour',":00",newln];}# ---------------------------------------------------------------------------------------------------# The day# ---------------------------------------------------------------------------------------------------MAIN{const'opening_hour':=8;const'closing_hour':=20;std::print.useful['shop_name',", open ",'opening_hour',":00 to ",'closing_hour',":00",newln];let'items':=catalog[];let'stock':=opening_stock[];let'opening':=copy'stock';let'points':map[str.utf8,int32]=map.empty;let'sales':list[Sale]=list.empty;# The price list, three columns of a tablestd::print.useful[newln,"today's prices:",newln];for'it'inlend'items'{std::print.format[" {:<10} {:<10} {:>6.2}\n",'it'.name,aisle_name[copy'it'.aisle],'it'.price];}std::print.useful[newln];# Hours the shop is open, counted the new way: from opening, stopping before closinglet'hours':=0;for'h'from'opening_hour'before'closing_hour'by1{change'hours'='hours'plus1;}std::print.useful["open for ",'hours'," hours",newln,newln];# Take every order, each with the next receipt numberlet'orders':=todays_orders[];std::print.useful[newln];let'tickets':=Ticket[now=100,last=199];for'order'inlend'orders'{let'n':=Ticket.next[lend.mut'tickets'];let'ticket':=match'n'{value'number'{is'number';}none{is0;}};let'sale':=take_order[lend'items',lend.mut'stock',lend'order','ticket']otherwise{out_of_stock'what'{std::print.useful[" sorry, ",'order'.customer,": not enough ",'what',newln,newln];continue;}unknown_item'what'{std::print.useful[" sorry, ",'order'.customer,": we do not sell ",'what',newln,newln];continue;}};add_points[lend.mut'points',lend'sale'];list.append[lend.mut'sales',valuegive'sale'];std::print.useful[newln];}report[lend'sales',lend'items',lend'opening',lend'stock',lend'points'];count_shelves[lend'items',lend'stock'];let'plan':=plan_restock[lend'items',lend'opening',lend'stock'];show_restock[lend'plan'];show_hours['opening_hour','closing_hour'];show_week[];show_search[lend'items'];# Exact decimals: the reason money is `deci64` and not `bin64`let'dime':deci64=0.10;let'cents_sum':deci64='dime'plus0.20;let'float_sum':bin64=0.1plus0.2;std::print.useful[newln,"0.10 plus 0.20, stored exactly as money: "];std::print.stored['cents_sum',newln];std::print.useful["0.1 plus 0.2, stored as a binary float: "];std::print.stored['float_sum',newln];std::print.useful["good night from the ",'shop_name',newln];}
Output
Corner Shop, open 8:00 to 20:00
today's prices:
apple fresh 0.45
banana fresh 0.30
milk fresh 1.20
bread bakery 2.10
croissant bakery 1.35
rice pantry 3.80
beans pantry 0.95
coffee pantry 6.50
soap household 1.75
candles household 4.20
open for 12 hours
(order for Gus skipped: cannot read "apple two")
--- receipt #101 for Ann ---
apple x 6 2.70
bread x 1 1.68
milk x 2 2.40
total 6.78
paid by cash
--- receipt #102 for Bo ---
coffee x 2 13.00
croissant x 4 4.32
total 17.32
paid by card ending 4242
--- receipt #103 for Cy ---
rice x 1 3.80
beans x 3 2.85
soap x 2 3.50
candles x 1 4.20
discount 10%
total 12.92
paid by voucher, then 7.92 in cash
--- receipt #104 for Dee ---
banana x12 3.60
milk x 1 1.20
total 4.80
paid by card ending 4242
--- receipt #105 for Eli ---
sorry, Eli: not enough coffee
--- receipt #106 for Fay ---
sorry, Fay: we do not sell pineapple
--- receipt #107 for Hal ---
candles x 2 8.40
bread x 2 3.36
croissant x 2 2.16
discount 25%
total 10.44
paid by card ending 4242
=== Corner Shop, end of day ===
orders served: 5, units sold: 41
takings: 52.26
biggest baskets (units): [13, 9, 7]
best sale: #102, Bo, 17.32
by aisle, at list price:
fresh 9.90
bakery 14.40
pantry 19.65
household 16.10
reorder: croissant (0 left), coffee (3 left), candles (1 left)
loyalty points:
Ann 18
Bo 12
Cy 14
Dee 26
Hal 12
on the shelves tonight: fresh 61, bakery 5, pantry 33, household 8
tomorrow's delivery:
apple 1 case(s) 2.70
banana 2 case(s) 3.60
milk 1 case(s) 7.20
bread 1 case(s) 7.56
croissant 1 case(s) 4.86
rice 1 case(s) 27.36
beans 1 case(s) 6.84
coffee 1 case(s) 46.80
soap 1 case(s) 4.20
candles 1 case(s) 10.08
to the supplier 121.20
customers by the hour:
8:00 **
9:00 **
10:00 **
11:00 **
12:00 *********
13:00 ******
14:00 **
15:00 **
16:00 **
17:00 *********
18:00 ******
19:00 **
busiest from 12:00
this week:
Mon 24 ######
Tue 28 #######
Wed 32 ########
Thu 36 #########
Fri 40 ##########
Sat 44 ###########
Sun 18 #####
222 customers, 31.7 a day, 44 on the busiest
weekdays 160, weekend 62
searching the catalog:
starting with "b": banana, bread, beans
starting with "c": croissant, coffee, candles
nothing starts with "z"
containing "an": banana, croissant, beans, candles
the sign over the door says CORNER SHOP
0.10 plus 0.20, stored exactly as money: 0.30
0.1 plus 0.2, stored as a binary float: 0.3000000000000000444089209850062616169452667236328125
good night from the Corner Shop