Price table
A parcel rate card keyed on the weights as typed and on whole grams: computed weights that find no entry, and 1, 1.0 and True landing on one key so a misaligned checkbox sets the 1 kg price.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
A parcel rate card looked up by weight, keyed two ways: on the weights as they were typed, and on whole grams.
main.eml- the menu, the rate card, the two tables built from it, a parcel priced both ways, and every parcel of two itemsrates.eml- the card, the default price, the kind of each weight cell, grams rounded half up, the two tables, and the two lookups
How each part works:
- The card has a row for every 0.1 kg up to 2 kg, the whole kilos typed as 1 and 2, then two more rows: the 1 kg price updated, typed 1.0, and a True that a misaligned checkbox column put in the weight cell.
- As in the corpus case
dict-key-identity-collapse, 1, 1.0 and True are one key: all three rows write it, the key stays the int 1 it was first written as, and the last row wins - 1 kg costs 0.00. Keyed on grams, the True is refused as not a number and 1000 g costs 9.00, the update. - As in the corpus case
float-key-instability, a parcel's weight arrives by adding its items: 0.1 + 0.2 is 0.30000000000000004, which the card keyed as typed has no entry for, so the default - the 2 kg rate - is charged. Keyed on grams, rounded once where each weight comes in, every weight on the card is found. - Of the 45 parcels of two items from 0.1 to 0.9 kg, keyed as typed 8 find no entry and 5 find the 1 kg price the checkbox wrote; the weight in all is 45.000 kg both ways, to the gram - a check on the total passes while 13 prices are wrong.
What is checked: menu choices 1 to 5; 1 to 6 weights from 0.001 to 2 kg, each up to 5 characters, and a parcel of at most 2 kg. An empty answer cancels. A weight that is not on the card is not found keyed either way, and the default is charged.
Sessions: sessions/basic.in gives: - the rate card and the two tables; - parcels of 0.1 and 0.2, of 0.4 and 0.6, of 0.3 alone, and of 0.7, 0.1 and 0.2; every two-item parcel.
sessions/bad-input.in gives: - menu choices 0 and x; - weights: a cancel, abc, 0.1.2, 0, 2.5, seven weights and 123456; a parcel of 3 kg; - a parcel of 1 and 1, the 2 kg rate both ways.
Built on the verified corpus cases float-key-instability (a table keyed on decimal amounts, looked up by computed sums, against integer cents) and dict-key-identity-collapse (1, 1.0 and True as one key, the first key kept and the last value).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Price table ==
A parcel rate card looked up by weight: keyed on the weights as typed, and on whole grams.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 0
Pick a number from 1 to 5.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> x
Pick a number from 1 to 5.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 3
item weights in kg (e.g. 0.1 0.2)>
Cancelled.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 3
item weights in kg (e.g. 0.1 0.2)> abc
Type 1 to 6 weights from 0.001 to 2 kg, separated by spaces.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 3
item weights in kg (e.g. 0.1 0.2)> 0.1.2
Type 1 to 6 weights from 0.001 to 2 kg, separated by spaces.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 3
item weights in kg (e.g. 0.1 0.2)> 0
Type 1 to 6 weights from 0.001 to 2 kg, separated by spaces.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 3
item weights in kg (e.g. 0.1 0.2)> 2.5
Type 1 to 6 weights from 0.001 to 2 kg, separated by spaces.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 3
item weights in kg (e.g. 0.1 0.2)> 1 1 1
The parcel weighs over 2 kg; the card goes up to 2.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 3
item weights in kg (e.g. 0.1 0.2)> 0.1 0.1 0.1 0.1 0.1 0.1 0.1
Type 1 to 6 weights from 0.001 to 2 kg, separated by spaces.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 3
item weights in kg (e.g. 0.1 0.2)> 123456
Type 1 to 6 weights from 0.001 to 2 kg, separated by spaces.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 3
item weights in kg (e.g. 0.1 0.2)> 1 1
Added as floats the weight is 2.0 kg; added as grams, 2000 g.
keyed as typed: 12.50
keyed on grams: 12.50
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 5
Bye.
What was typed (21 lines)
0
x
3
3
abc
3
0.1.2
3
0
3
2.5
3
1 1 1
3
0.1 0.1 0.1 0.1 0.1 0.1 0.1
3
123456
3
1 1
5
basic
interpreter: byte-equal== Price table ==
A parcel rate card looked up by weight: keyed on the weights as typed, and on whole grams.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 1
The rate card, row by row, the weight as it was entered:
1 0.1 (float) 3.00
2 0.2 (float) 3.50
3 0.3 (float) 4.00
4 0.4 (float) 4.50
5 0.5 (float) 5.00
6 0.6 (float) 5.50
7 0.7 (float) 6.00
8 0.8 (float) 6.50
9 0.9 (float) 7.00
10 1 (int) 7.50
11 1.1 (float) 8.00
12 1.2 (float) 8.50
13 1.3 (float) 9.00
14 1.4 (float) 9.50
15 1.5 (float) 10.00
16 1.6 (float) 10.50
17 1.7 (float) 11.00
18 1.8 (float) 11.50
19 1.9 (float) 12.00
20 2 (int) 12.50
21 1.0 (float) 9.00
22 True (bool) 0.00
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 2
Keyed on the weights as typed: 20 entries from 22 rows.
1, 1.0 and True are one key; rows 10 (1, int), 21 (1.0, float), 22 (True, bool) all wrote it, and the last one wins: 1 kg costs 0.00.
Keyed on grams, the True refused as not a number: 20 entries; 1000 g costs 9.00, the update typed 1.0.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 3
item weights in kg (e.g. 0.1 0.2)> 0.1 0.2
Added as floats the weight is 0.30000000000000004 kg; added as grams, 300 g.
keyed as typed: no entry for 0.30000000000000004, so the default 12.50 is charged
keyed on grams: 4.00
The two tables charge 12.50 and 4.00 for the same parcel.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 3
item weights in kg (e.g. 0.1 0.2)> 0.4 0.6
Added as floats the weight is 1.0 kg; added as grams, 1000 g.
keyed as typed: 0.00
keyed on grams: 9.00
The two tables charge 0.00 and 9.00 for the same parcel.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 3
item weights in kg (e.g. 0.1 0.2)> 0.3
Added as floats the weight is 0.3 kg; added as grams, 300 g.
keyed as typed: 4.00
keyed on grams: 4.00
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 3
item weights in kg (e.g. 0.1 0.2)> 0.7 0.1 0.2
Added as floats the weight is 1.0 kg; added as grams, 1000 g.
keyed as typed: 0.00
keyed on grams: 9.00
The two tables charge 0.00 and 9.00 for the same parcel.
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 4
Every parcel of two of the items 0.1 to 0.9 kg: 45 parcels.
keyed as typed: 8 find no entry and pay the default, as 0.1 + 0.2 = 0.30000000000000004; 5 find the 1 kg entry the checkbox wrote
keyed on grams: every one finds its entry
charged in all: 341.50 keyed as typed, 345.00 keyed on grams
the weight in all is 45.000 kg both ways, to the gram: a check on the total passes while 13 of the 45 prices are wrong
1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit
choice> 5
Bye.
What was typed (12 lines)
1
2
3
0.1 0.2
3
0.4 0.6
3
0.3
3
0.7 0.1 0.2
4
5
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P148 price table: a parcel rate card, the two tables built from it - keyed
# on the weights as typed and on whole grams - a parcel priced both ways,
# and every parcel of two items.
import rates
def plain(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def pad(s, n):
s => out
while len(out) < n:
out + " " => out
return out
def lpad(s, n):
s => out
while len(out) < n:
" " + out => out
return out
def money(c):
c % 100 => p
if p < 10:
return str(int((c - p) / 100)) + ".0" + str(p)
return str(int((c - p) / 100)) + "." + str(p)
def kg(g):
# whole grams as kg with three places
g % 1000 => r
if r < 10:
return str(int((g - r) / 1000)) + ".00" + str(r)
if r < 100:
return str(int((g - r) / 1000)) + ".0" + str(r)
return str(int((g - r) / 1000)) + "." + str(r)
def show_card():
"The rate card, row by row, the weight as it was entered:" ^0
1 => k
for r in rates.card():
(" " + lpad(str(k), 2) + " " + pad(str(r[0]) + " (" + rates.kind(r[0]) + ")", 16) + lpad(money(r[1]), 6)) ^0
k + 1 => k
def show_tables():
rates.float_table() => ft
rates.gram_table() => gt
len(rates.card()) => n
("Keyed on the weights as typed: " + str(len(ft)) + " entries from " + str(n) + " rows.") ^0
"" => who
1 => k
for r in rates.card():
if r[0] == 1:
if who == "":
str(k) + " (" + str(r[0]) + ", " + rates.kind(r[0]) + ")" => who
else:
who + ", " + str(k) + " (" + str(r[0]) + ", " + rates.kind(r[0]) + ")" => who
k + 1 => k
(" 1, 1.0 and True are one key; rows " + who + " all wrote it, and the last one wins: 1 kg costs " + money(ft[1]) + ".") ^0
("Keyed on grams, the True refused as not a number: " + str(len(gt)) + " entries; 1000 g costs " + money(gt[1000]) + ", the update typed 1.0.") ^0
def parse(t):
# the weights in kg, or [] when one is not a weight from 0.001 to 2
[] => out
"" => cur
for c in t + " ":
if c == " ":
if cur != "":
0 => dots
for d in cur:
if d == ".":
dots + 1 => dots
elif d < "0" or d > "9":
return []
if dots > 1 or cur == "." or len(cur) > 5:
return []
float(cur) => w
if w < 0.001 or w > 2:
return []
out + [w] => out
"" => cur
else:
cur + c => cur
return out
def price(ws):
0 => total
0 => g
for w in ws:
total + w => total
g + rates.grams(w) => g
rates.by_float(rates.float_table(), total) => pf
rates.by_grams(rates.gram_table(), g) => pg
("Added as floats the weight is " + str(total) + " kg; added as grams, " + str(g) + " g.") ^0
if pf < 0:
(" keyed as typed: no entry for " + str(total) + ", so the default " + money(rates.default_price()) + " is charged") ^0
rates.default_price() => pf
else:
(" keyed as typed: " + money(pf)) ^0
if pg < 0:
(" keyed on grams: no entry for " + str(g) + " g, so the default " + money(rates.default_price()) + " is charged") ^0
rates.default_price() => pg
else:
(" keyed on grams: " + money(pg)) ^0
return [pf, pg]
def one_parcel():
plain(input("item weights in kg (e.g. 0.1 0.2)> ")) => t
if t == "":
"Cancelled." ^0
return
parse(t) => ws
if len(ws) == 0 or len(ws) > 6:
"Type 1 to 6 weights from 0.001 to 2 kg, separated by spaces." ^0
return
0 => g
for w in ws:
g + rates.grams(w) => g
if g > 2000:
"The parcel weighs over 2 kg; the card goes up to 2." ^0
return
price(ws) => p
if p[0] != p[1]:
("The two tables charge " + money(p[0]) + " and " + money(p[1]) + " for the same parcel.") ^0
def pairs():
rates.float_table() => ft
rates.gram_table() => gt
rates.items() => its
0 => n
0 => miss
0 => other
0 => fsum
0 => gsum
0 => cf
0 => cg
"" => ex
0 => i
while i < len(its):
i => j
while j < len(its):
its[i] + its[j] => total
rates.grams(its[i]) + rates.grams(its[j]) => g
n + 1 => n
fsum + total => fsum
gsum + g => gsum
rates.by_float(ft, total) => pf
rates.by_grams(gt, g) => pg
if pf < 0:
miss + 1 => miss
rates.default_price() => pf
if ex == "":
str(its[i]) + " + " + str(its[j]) + " = " + str(total) => ex
elif pf != pg:
other + 1 => other
cf + pf => cf
cg + pg => cg
j + 1 => j
i + 1 => i
("Every parcel of two of the items 0.1 to 0.9 kg: " + str(n) + " parcels.") ^0
(" keyed as typed: " + str(miss) + " find no entry and pay the default, as " + ex + "; " + str(other) + " find the 1 kg entry the checkbox wrote") ^0
(" keyed on grams: every one finds its entry") ^0
(" charged in all: " + money(cf) + " keyed as typed, " + money(cg) + " keyed on grams") ^0
if rates.grams(fsum) == gsum:
(" the weight in all is " + kg(gsum) + " kg both ways, to the gram: a check on the total passes while " + str(miss + other) + " of the " + str(n) + " prices are wrong") ^0
else:
(" the weight in all: " + str(fsum) + " kg added as floats, " + kg(gsum) + " kg as grams") ^0
"== Price table ==" ^0
"A parcel rate card looked up by weight: keyed on the weights as typed, and on whole grams." ^0
True => running
while running:
"" ^0
"1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
show_card()
elif choice == "2":
show_tables()
elif choice == "3":
one_parcel()
elif choice == "4":
pairs()
elif choice == "5":
False => running
else:
"Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import rates
def plain(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def pad(s, n):
out = s
while len(out) < n:
out = out + " "
return out
def lpad(s, n):
out = s
while len(out) < n:
out = " " + out
return out
def money(c):
p = c % 100
if p < 10:
return str(int((c - p) / 100)) + ".0" + str(p)
return str(int((c - p) / 100)) + "." + str(p)
def kg(g):
r = g % 1000
if r < 10:
return str(int((g - r) / 1000)) + ".00" + str(r)
if r < 100:
return str(int((g - r) / 1000)) + ".0" + str(r)
return str(int((g - r) / 1000)) + "." + str(r)
def show_card():
print("The rate card, row by row, the weight as it was entered:")
k = 1
for r in rates.card():
print(" " + lpad(str(k), 2) + " " + pad(str(r[0]) + " (" + rates.kind(r[0]) + ")", 16) + lpad(money(r[1]), 6))
k = k + 1
def show_tables():
ft = rates.float_table()
gt = rates.gram_table()
n = len(rates.card())
print("Keyed on the weights as typed: " + str(len(ft)) + " entries from " + str(n) + " rows.")
who = ""
k = 1
for r in rates.card():
if r[0] == 1:
if who == "":
who = str(k) + " (" + str(r[0]) + ", " + rates.kind(r[0]) + ")"
else:
who = who + ", " + str(k) + " (" + str(r[0]) + ", " + rates.kind(r[0]) + ")"
k = k + 1
print(" 1, 1.0 and True are one key; rows " + who + " all wrote it, and the last one wins: 1 kg costs " + money(ft[1]) + ".")
print("Keyed on grams, the True refused as not a number: " + str(len(gt)) + " entries; 1000 g costs " + money(gt[1000]) + ", the update typed 1.0.")
def parse(t):
out = []
cur = ""
for c in t + " ":
if c == " ":
if cur != "":
dots = 0
for d in cur:
if d == ".":
dots = dots + 1
elif d < "0" or d > "9":
return []
if dots > 1 or cur == "." or len(cur) > 5:
return []
w = float(cur)
if w < 0.001 or w > 2:
return []
out = out + [w]
cur = ""
else:
cur = cur + c
return out
def price(ws):
total = 0
g = 0
for w in ws:
total = total + w
g = g + rates.grams(w)
pf = rates.by_float(rates.float_table(), total)
pg = rates.by_grams(rates.gram_table(), g)
print("Added as floats the weight is " + str(total) + " kg; added as grams, " + str(g) + " g.")
if pf < 0:
print(" keyed as typed: no entry for " + str(total) + ", so the default " + money(rates.default_price()) + " is charged")
pf = rates.default_price()
else:
print(" keyed as typed: " + money(pf))
if pg < 0:
print(" keyed on grams: no entry for " + str(g) + " g, so the default " + money(rates.default_price()) + " is charged")
pg = rates.default_price()
else:
print(" keyed on grams: " + money(pg))
return [pf, pg]
def one_parcel():
t = plain(input("item weights in kg (e.g. 0.1 0.2)> "))
if t == "":
print("Cancelled.")
return
ws = parse(t)
if len(ws) == 0 or len(ws) > 6:
print("Type 1 to 6 weights from 0.001 to 2 kg, separated by spaces.")
return
g = 0
for w in ws:
g = g + rates.grams(w)
if g > 2000:
print("The parcel weighs over 2 kg; the card goes up to 2.")
return
p = price(ws)
if p[0] != p[1]:
print("The two tables charge " + money(p[0]) + " and " + money(p[1]) + " for the same parcel.")
def pairs():
ft = rates.float_table()
gt = rates.gram_table()
its = rates.items()
n = 0
miss = 0
other = 0
fsum = 0
gsum = 0
cf = 0
cg = 0
ex = ""
i = 0
while i < len(its):
j = i
while j < len(its):
total = its[i] + its[j]
g = rates.grams(its[i]) + rates.grams(its[j])
n = n + 1
fsum = fsum + total
gsum = gsum + g
pf = rates.by_float(ft, total)
pg = rates.by_grams(gt, g)
if pf < 0:
miss = miss + 1
pf = rates.default_price()
if ex == "":
ex = str(its[i]) + " + " + str(its[j]) + " = " + str(total)
elif pf != pg:
other = other + 1
cf = cf + pf
cg = cg + pg
j = j + 1
i = i + 1
print("Every parcel of two of the items 0.1 to 0.9 kg: " + str(n) + " parcels.")
print(" keyed as typed: " + str(miss) + " find no entry and pay the default, as " + ex + "; " + str(other) + " find the 1 kg entry the checkbox wrote")
print(" keyed on grams: every one finds its entry")
print(" charged in all: " + money(cf) + " keyed as typed, " + money(cg) + " keyed on grams")
if rates.grams(fsum) == gsum:
print(" the weight in all is " + kg(gsum) + " kg both ways, to the gram: a check on the total passes while " + str(miss + other) + " of the " + str(n) + " prices are wrong")
else:
print(" the weight in all: " + str(fsum) + " kg added as floats, " + kg(gsum) + " kg as grams")
print("== Price table ==")
print("A parcel rate card looked up by weight: keyed on the weights as typed, and on whole grams.")
running = True
while running:
print("")
print("1) rate card 2) the two tables 3) price a parcel 4) every two-item parcel 5) quit")
choice = plain(input("choice> "))
if choice == "1":
show_card()
elif choice == "2":
show_tables()
elif choice == "3":
one_parcel()
elif choice == "4":
pairs()
elif choice == "5":
running = False
else:
print("Pick a number from 1 to 5.")
print("Bye.")
rates.eml
eml# P148 price table - a parcel rate card looked up by weight, keyed two
# ways. As in the corpus case float-key-instability, a table keyed on the
# weights a person typed has no entry for a weight that arrived by adding,
# 0.1 + 0.2 being 0.30000000000000004; keyed on whole grams, rounded once
# where a weight comes in, it finds every weight the card has. As in the
# corpus case dict-key-identity-collapse, 1, 1.0 and True are one key: the
# row for 1 kg, its later update typed 1.0, and a True a misaligned
# checkbox column put in the weight cell all land on it, and the last one
# written is the price.
def card():
# [weight in kg as typed, price in cents], in the order the rows come
[] => out
1 => k
while k <= 20:
if k == 10:
out + [[1, 300 + 50 * (k - 1)]] => out
elif k == 20:
out + [[2, 300 + 50 * (k - 1)]] => out
else:
out + [[k / 10, 300 + 50 * (k - 1)]] => out
k + 1 => k
return out + [[1.0, 900], [True, 0]]
def default_price():
# charged when a weight has no entry: the 2 kg rate
return 1250
def kind(w):
str(w) => s
if s == "True" or s == "False":
return "bool"
for c in s:
if c == ".":
return "float"
return "int"
def grams(w):
# a weight in kg to whole grams, rounded half up, once
return int(w * 1000 + 0.5)
def float_table():
{} => t
for r in card():
r[1] => t[r[0]]
return t
def gram_table():
# the same rows keyed on grams; a cell that is not a number is refused
{} => t
for r in card():
if kind(r[0]) != "bool":
r[1] => t[grams(r[0])]
return t
def by_float(t, total):
if total in t:
return t[total]
return 0 - 1
def by_grams(t, g):
if g in t:
return t[g]
return 0 - 1
def items():
return [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
Python projection (rates.py)
def card():
out = []
k = 1
while k <= 20:
if k == 10:
out = out + [[1, 300 + 50 * (k - 1)]]
elif k == 20:
out = out + [[2, 300 + 50 * (k - 1)]]
else:
out = out + [[k / 10, 300 + 50 * (k - 1)]]
k = k + 1
return out + [[1.0, 900], [True, 0]]
def default_price():
return 1250
def kind(w):
s = str(w)
if s == "True" or s == "False":
return "bool"
for c in s:
if c == ".":
return "float"
return "int"
def grams(w):
return int(w * 1000 + 0.5)
def float_table():
t = {}
for r in card():
t[r[0]] = r[1]
return t
def gram_table():
t = {}
for r in card():
if kind(r[0]) != "bool":
t[grams(r[0])] = r[1]
return t
def by_float(t, total):
if total in t:
return t[total]
return 0 - 1
def by_grams(t, g):
if g in t:
return t[g]
return 0 - 1
def items():
return [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]