Stretch 1

Parse a receipt

A receipt is one string with one line per item, each line being <item> x <quantity> @ <price>. Write receipt_total(text) that returns the total cost. Lines may have extra spaces at the ends; blank lines are skipped.

receipt_total("bread x 3 @ 25\nmilk x 2 @ 2.5")  ->  80.0
receipt_total("")                                  ->  0
def receipt_total(text):
    ...

Top sellers

sales is a list of item names, one per sale. Write top_sellers(sales, k) that returns the k most sold items as (item, count) tuples, most sold first. Items with the same count are ordered alphabetically. k may exceed the number of distinct items.

top_sellers(["milk", "bread", "milk", "apple", "bread", "milk"], 2)  ->  [("milk", 3), ("bread", 2)]
def top_sellers(sales, k):
    ...

A day at the bakery

All of the day's orders are waiting when the bakery opens, as (priority, name) tuples with distinct priorities, where a smaller number means more urgent. The bakery makes one order per hour, always the most urgent one still waiting. Write schedule(orders, hours) that returns the names made in the first hours hours, in order. If the bakery runs out of orders early, the list is shorter.

schedule([(3, "bread"), (1, "cake"), (2, "rolls")], 2)  ->  ["cake", "rolls"]
schedule([(1, "cake")], 5)                              ->  ["cake"]
import heapq

def schedule(orders, hours):
    ...

Anagram groups

Two words are anagrams if they use exactly the same letters. Write anagram_groups(words) that groups the words into lists of anagrams, with each group in original order, and the groups sorted by their first word.

anagram_groups(["tea", "eat", "tan", "ate", "nat", "bat"])  ->  [["bat"], ["tan", "nat"], ["tea", "eat", "ate"]]
def anagram_groups(words):
    ...

Longest streak

daily is a list of how many loaves were sold each day. Write longest_streak(daily, target) that returns the length of the longest run of consecutive days with sales of at least target.

longest_streak([5, 12, 14, 3, 10, 11, 15], 10)  ->  3
longest_streak([1, 2], 10)                      ->  0
def longest_streak(daily, target):
    ...

Expand shelf ranges

Shelf labels are given as a string like "3-5, 8, 10-11": comma-separated, each part either a single number or a range a-b inclusive. Write expand(text) that returns the full list of numbers in order.

expand("3-5, 8, 10-11")  ->  [3, 4, 5, 8, 10, 11]
expand("7")              ->  [7]
def expand(text):
    ...

Pairs that make a price

Write pairs_summing(prices, target) that returns how many pairs of positions (i, j) with i < j have prices[i] + prices[j] == target.

pairs_summing([10, 30, 20, 30], 40)  ->  2     (10+30 at positions 0,1 and 0,3)
pairs_summing([5], 10)               ->  0
def pairs_summing(prices, target):
    ...

A queue with VIPs

Customers arrive as (name, is_vip) tuples. VIPs go to the front of the line, behind any VIPs already there; everyone else joins the back. Write serving_order(arrivals) that returns the order in which everyone is served.

serving_order([("Ada", False), ("Grace", True), ("Linus", False), ("Ken", True)])  ->  ["Grace", "Ken", "Ada", "Linus"]
from collections import deque

def serving_order(arrivals):
    ...

Compress repeated letters

Write compress(text) that replaces each run of the same character with the character followed by the run length, leaving single characters alone.

compress("aaabccdddd")  ->  "a3bc2d4"
compress("abc")         ->  "abc"
compress("")            ->  ""
def compress(text):
    ...

Undo and redo

actions is a list of strings: "undo" cancels the most recent action in effect, "redo" restores the most recently undone action, and anything else is a new action. A new action clears the redo history. Write final(actions) that returns the actions in effect, in order. Undo or redo with nothing to act on is ignored.

final(["a", "b", "undo", "redo"])        ->  ["a", "b"]
final(["a", "b", "undo", "c", "redo"])   ->  ["a", "c"]
final(["undo", "redo", "a"])             ->  ["a"]
def final(actions):
    ...

Next: Stretch 2.