Recipe scaler
A roast for four scaled to more or fewer servings: the ingredients scale with them, the time with the 2/3 power of the weight, and a teaspoon of fine salt holds twice a teaspoon of flakes.
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 roast for four, scaled to more or fewer servings: the ingredients by the servings, and the time by something else.
main.eml- the menu, the recipe, the time for bigger joints, the salt, and servings of your ownrecipe.eml- the ingredients, the time for the 1 kg joint, what a teaspoon of each salt holds, a cube root by Newton's method, and the scaled time
How each part works:
- The ingredients scale with the servings, rounded half-up to the gram.
- As in the corpus case
the-roast-eight-times-the-size-took-four-times-the-time, the time goes with the thickness squared, the 2/3 power of the weight: the 1 kg joint takes 30 minutes, 2 kg 48, 4 kg 76 and 8 kg 120 - four times as long for eight times the size, not 240 minutes. - As in
the-same-spoon-held-twice-the-seasoning, a teaspoon is a volume: the recipe's 2 teaspoons of flake salt are 6 g, and the same spoon of fine table salt holds 12 g, twice as salty. Scaled, the salt is given in grams.
What is checked: menu choices 1 to 5; 1 to 32 servings. An empty answer cancels.
Sessions: sessions/basic.in gives: - the recipe, the bigger joints and the salt; - 10, 1 and 4 servings.
sessions/bad-input.in gives: - menu choices 0 and x; - servings: a cancel, 0, 33, x and 2.5; - 32 and 3 servings.
Built on the verified corpus cases the-roast-eight-times-the-size-took-four-times-the-time (a joint scaled to eight times the weight and cooked eight times as long) and the-same-spoon-held-twice-the-seasoning (two teaspoons of fine salt measured for a recipe written for flake salt).
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== Recipe scaler ==
A roast for four, scaled - the ingredients by the servings, and the time by something else.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 0
Pick a number from 1 to 5.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> x
Pick a number from 1 to 5.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 4
servings (1 to 32)>
Cancelled.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 4
servings (1 to 32)> 0
Type a whole number from 1 to 32.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 4
servings (1 to 32)> 33
Type a whole number from 1 to 32.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 4
servings (1 to 32)> x
Type a whole number from 1 to 32.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 4
servings (1 to 32)> 2.5
Type a whole number from 1 to 32.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 4
servings (1 to 32)> 32
For 32:
beef joint 8,000 g
onions 2,400 g
carrots 3,200 g
stock 2,000 g
salt 48 g weighed; by the spoon that is flake or fine, not the same
Roast the 8,000 g joint 120 minutes.
Scaled like the ingredients, the time would be off: it grows more slowly than the weight when the joint is bigger, and falls more slowly when it is smaller.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 4
servings (1 to 32)> 3
For 3:
beef joint 750 g
onions 225 g
carrots 300 g
stock 188 g
salt 5 g weighed; by the spoon that is flake or fine, not the same
Roast the 750 g joint 25 minutes.
Scaled like the ingredients, the time would be off: it grows more slowly than the weight when the joint is bigger, and falls more slowly when it is smaller.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 5
Bye.
What was typed (17 lines)
0
x
4
4
0
4
33
4
x
4
2.5
4
32
4
3
5
basic
interpreter: byte-equal== Recipe scaler ==
A roast for four, scaled - the ingredients by the servings, and the time by something else.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 1
A roast for 4:
beef joint 1,000 g
onions 300 g
carrots 400 g
stock 250 g
salt 2 teaspoons of flake salt
Roast the 1.0 kg joint 30 minutes at 200 C.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 2
Bigger joints of the same shape, and their roasting time:
servings joint times the time, scaled needed
4 1.0 kg 30 min 30 min
8 2.0 kg 60 min 48 min
16 4.0 kg 120 min 76 min
32 8.0 kg 240 min 120 min
A joint eight times the size takes 120 minutes, four times as long, not 240: the heat has to reach the middle, and that time goes with the thickness squared, the 2/3 power of the weight.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 3
The recipe's 2 teaspoons are written for flake salt: 6 g. Measured with the same spoon, fine table salt is 12 g, twice as salty: a teaspoon is a volume, and fine grains pack it.
The same 6 g of fine salt is 1 teaspoon. Better: weigh the salt.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 4
servings (1 to 32)> 10
For 10:
beef joint 2,500 g
onions 750 g
carrots 1,000 g
stock 625 g
salt 15 g weighed; by the spoon that is flake or fine, not the same
Roast the 2,500 g joint 55 minutes.
Scaled like the ingredients, the time would be off: it grows more slowly than the weight when the joint is bigger, and falls more slowly when it is smaller.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 4
servings (1 to 32)> 1
For 1:
beef joint 250 g
onions 75 g
carrots 100 g
stock 63 g
salt 2 g weighed; by the spoon that is flake or fine, not the same
Roast the 250 g joint 12 minutes.
Scaled like the ingredients, the time would be off: it grows more slowly than the weight when the joint is bigger, and falls more slowly when it is smaller.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 4
servings (1 to 32)> 4
For 4:
beef joint 1,000 g
onions 300 g
carrots 400 g
stock 250 g
salt 6 g weighed; by the spoon that is flake or fine, not the same
Roast the 1,000 g joint 30 minutes.
1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit
choice> 5
Bye.
What was typed (10 lines)
1
2
3
4
10
4
1
4
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# P179 recipe scaler: a roast for four - the recipe, the time for bigger
# joints, the salt by the spoon, and the recipe scaled to servings of your
# own.
import recipe
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 digits(t):
for c in t:
if c < "0" or c > "9":
return False
return len(t) > 0
def commas(n):
str(n) => s
"" => out
0 => k
len(s) => i
while i > 0:
if k == 3:
"," + out => out
0 => k
s[i - 1] + out => out
k + 1 => k
i - 1 => i
return out
def scaled(grams, n):
# grams for four, scaled to n servings, rounded half-up
grams * n * 2 + recipe.serves() => y
return int((y - y % (2 * recipe.serves())) / (2 * recipe.serves()))
def kg(grams):
# grams as kilograms to one place, half-up
grams + 50 => y
int((y - y % 100) / 100) => t
return str(int((t - t % 10) / 10)) + "." + str(t % 10) + " kg"
def show_recipe():
"A roast for 4:" ^0
for i in recipe.ingredients():
(" " + pad(i[0], 12) + lpad(commas(i[1]) + " g", 8)) ^0
(" " + pad("salt", 12) + str(recipe.salt_tsp()) + " teaspoons of flake salt") ^0
("Roast the " + kg(recipe.ingredients()[0][1]) + " joint " + str(recipe.minutes()) + " minutes at 200 C.") ^0
def show_joints():
"Bigger joints of the same shape, and their roasting time:" ^0
(" " + pad("servings", 10) + pad("joint", 9) + lpad("times the time, scaled", 24) + lpad("needed", 9)) ^0
for n in [4, 8, 16, 32]:
(" " + pad(str(n), 10) + pad(kg(scaled(1000, n)), 9) + lpad(str(int(recipe.minutes() * n / recipe.serves())) + " min", 24) + lpad(str(recipe.time_for(n)) + " min", 9)) ^0
("A joint eight times the size takes " + str(recipe.time_for(32)) + " minutes, four times as long, not " + str(8 * recipe.minutes()) + ": the heat has to reach the middle, and that time goes with the thickness squared, the 2/3 power of the weight.") ^0
def show_salt():
recipe.salt_tsp() => t
t * recipe.grams_per_tsp("flake") => g
t * recipe.grams_per_tsp("fine") => f
("The recipe's " + str(t) + " teaspoons are written for flake salt: " + str(g) + " g. Measured with the same spoon, fine table salt is " + str(f) + " g, twice as salty: a teaspoon is a volume, and fine grains pack it.") ^0
("The same " + str(g) + " g of fine salt is " + str(int(g / recipe.grams_per_tsp("fine"))) + " teaspoon. Better: weigh the salt.") ^0
def own():
plain(input("servings (1 to 32)> ")) => t
if t == "":
"Cancelled." ^0
return
if len(t) > 2 or not digits(t) or int(t) < 1 or int(t) > 32:
"Type a whole number from 1 to 32." ^0
return
int(t) => n
("For " + str(n) + ":") ^0
for i in recipe.ingredients():
(" " + pad(i[0], 12) + lpad(commas(scaled(i[1], n)) + " g", 8)) ^0
scaled(recipe.salt_tsp() * recipe.grams_per_tsp("flake"), n) => salt
(" " + pad("salt", 12) + lpad(str(salt) + " g", 8) + " weighed; by the spoon that is flake or fine, not the same") ^0
recipe.time_for(n) => m
("Roast the " + commas(scaled(1000, n)) + " g joint " + str(m) + " minutes.") ^0
if n != recipe.serves():
"Scaled like the ingredients, the time would be off: it grows more slowly than the weight when the joint is bigger, and falls more slowly when it is smaller." ^0
"== Recipe scaler ==" ^0
"A roast for four, scaled - the ingredients by the servings, and the time by something else." ^0
True => running
while running:
"" ^0
"1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
show_recipe()
elif choice == "2":
show_joints()
elif choice == "3":
show_salt()
elif choice == "4":
own()
elif choice == "5":
False => running
else:
"Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import recipe
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 digits(t):
for c in t:
if c < "0" or c > "9":
return False
return len(t) > 0
def commas(n):
s = str(n)
out = ""
k = 0
i = len(s)
while i > 0:
if k == 3:
out = "," + out
k = 0
out = s[i - 1] + out
k = k + 1
i = i - 1
return out
def scaled(grams, n):
y = grams * n * 2 + recipe.serves()
return int((y - y % (2 * recipe.serves())) / (2 * recipe.serves()))
def kg(grams):
y = grams + 50
t = int((y - y % 100) / 100)
return str(int((t - t % 10) / 10)) + "." + str(t % 10) + " kg"
def show_recipe():
print("A roast for 4:")
for i in recipe.ingredients():
print(" " + pad(i[0], 12) + lpad(commas(i[1]) + " g", 8))
print(" " + pad("salt", 12) + str(recipe.salt_tsp()) + " teaspoons of flake salt")
print("Roast the " + kg(recipe.ingredients()[0][1]) + " joint " + str(recipe.minutes()) + " minutes at 200 C.")
def show_joints():
print("Bigger joints of the same shape, and their roasting time:")
print(" " + pad("servings", 10) + pad("joint", 9) + lpad("times the time, scaled", 24) + lpad("needed", 9))
for n in [4, 8, 16, 32]:
print(" " + pad(str(n), 10) + pad(kg(scaled(1000, n)), 9) + lpad(str(int(recipe.minutes() * n / recipe.serves())) + " min", 24) + lpad(str(recipe.time_for(n)) + " min", 9))
print("A joint eight times the size takes " + str(recipe.time_for(32)) + " minutes, four times as long, not " + str(8 * recipe.minutes()) + ": the heat has to reach the middle, and that time goes with the thickness squared, the 2/3 power of the weight.")
def show_salt():
t = recipe.salt_tsp()
g = t * recipe.grams_per_tsp("flake")
f = t * recipe.grams_per_tsp("fine")
print("The recipe's " + str(t) + " teaspoons are written for flake salt: " + str(g) + " g. Measured with the same spoon, fine table salt is " + str(f) + " g, twice as salty: a teaspoon is a volume, and fine grains pack it.")
print("The same " + str(g) + " g of fine salt is " + str(int(g / recipe.grams_per_tsp("fine"))) + " teaspoon. Better: weigh the salt.")
def own():
t = plain(input("servings (1 to 32)> "))
if t == "":
print("Cancelled.")
return
if len(t) > 2 or not digits(t) or int(t) < 1 or int(t) > 32:
print("Type a whole number from 1 to 32.")
return
n = int(t)
print("For " + str(n) + ":")
for i in recipe.ingredients():
print(" " + pad(i[0], 12) + lpad(commas(scaled(i[1], n)) + " g", 8))
salt = scaled(recipe.salt_tsp() * recipe.grams_per_tsp("flake"), n)
print(" " + pad("salt", 12) + lpad(str(salt) + " g", 8) + " weighed; by the spoon that is flake or fine, not the same")
m = recipe.time_for(n)
print("Roast the " + commas(scaled(1000, n)) + " g joint " + str(m) + " minutes.")
if n != recipe.serves():
print("Scaled like the ingredients, the time would be off: it grows more slowly than the weight when the joint is bigger, and falls more slowly when it is smaller.")
print("== Recipe scaler ==")
print("A roast for four, scaled - the ingredients by the servings, and the time by something else.")
running = True
while running:
print("")
print("1) the recipe 2) bigger joints 3) the salt 4) servings of your own 5) quit")
choice = plain(input("choice> "))
if choice == "1":
show_recipe()
elif choice == "2":
show_joints()
elif choice == "3":
show_salt()
elif choice == "4":
own()
elif choice == "5":
running = False
else:
print("Pick a number from 1 to 5.")
print("Bye.")
recipe.eml
eml# P179 recipe scaler - a roast for four, scaled to more or fewer servings.
# The ingredients scale with the servings. As in the corpus case
# the-roast-eight-times-the-size-took-four-times-the-time, the cooking time
# does not: heat has to reach the middle, and the time goes with the
# thickness squared, the 2/3 power of the weight, so a joint eight times
# the size takes four times as long. And as in
# the-same-spoon-held-twice-the-seasoning, a teaspoon is a volume, and a
# teaspoon of fine salt holds twice the salt of a teaspoon of flakes.
def serves():
return 4
def ingredients():
# [what, grams for four]
return [["beef joint", 1000], ["onions", 300], ["carrots", 400], ["stock", 250]]
def minutes():
# cooking time for the 1 kg joint
return 30
def grams_per_tsp(salt):
# how much a level teaspoon holds
if salt == "flake":
return 3
return 6
def salt_tsp():
# the recipe's salt for four: 2 teaspoons, written for flake salt
return 2
def cube_root(x):
# x > 0, by Newton's method from a start above the root
1.0 => r
while r * r * r < x:
r * 2.0 => r
0 => k
while k < 60:
(2.0 * r + x / (r * r)) / 3.0 => r
k + 1 => k
return r
def time_for(n):
# whole minutes for the joint scaled to n servings: 30 x (n / 4)^(2/3)
n / serves() => ratio
cube_root(ratio * ratio) => f
return int(minutes() * f + 0.5)
Python projection (recipe.py)
def serves():
return 4
def ingredients():
return [["beef joint", 1000], ["onions", 300], ["carrots", 400], ["stock", 250]]
def minutes():
return 30
def grams_per_tsp(salt):
if salt == "flake":
return 3
return 6
def salt_tsp():
return 2
def cube_root(x):
r = 1.0
while r * r * r < x:
r = r * 2.0
k = 0
while k < 60:
r = (2.0 * r + x / (r * r)) / 3.0
k = k + 1
return r
def time_for(n):
ratio = n / serves()
f = cube_root(ratio * ratio)
return int(minutes() * f + 0.5)