Locker rental
A gym's lockers rented through a year, each freed number handed to the next tenant: notes written against a locker number now name another tenant or nobody, and random card ids of four hex characters collide among the first thousand members.
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 gym's 40 lockers rented month by month through 2027, the notes written against locker numbers, and member card ids of four hex characters.
main.eml- the menu, the lockers, the notes and who they name today, the member cards, and a member count of your ownlockers.eml- the year of rentals and notes, drawn from a fixed seed; card ids; and counting the members who share one
How each part works:
- Each month some tenants give their locker up; it goes back to the free list, and a new tenant gets the lowest number free. The front desk writes notes - a deposit, a key, a complaint, an item found - naming the locker.
- As in the corpus case
the-id-was-reused-after-the-thing-it-identified-was-gone, every note was right when it was written and every one still finds a locker. Of the year's 21 notes, 12 still name the tenant they were written about, 6 name another tenant who has the locker now, and 3 a locker that is free. Written against the tenancy, the locker and its tenant, each would keep its tenant. - As in
the-id-space-was-smaller-than-the-records, a card id of four hex characters has 65,536 values. Among the first 1,000 members, 10 pairs share an id, where 7.6 are expected; at 100,000 members at least 34,464 get an id another member already has, however the ids are drawn.
What is checked: menu choices 1 to 5; a member count from 2 to 100000. An empty answer cancels.
Sessions: sessions/basic.in gives: - the lockers, the notes and the member cards; - member counts of 1,000 and 70,000.
sessions/bad-input.in gives: - menu choices 0 and x; - member counts: a cancel, 1, 100001 and x; - 2 members, and 65,537, one more than there are ids.
Built on the verified corpus cases the-id-was-reused-after-the-thing-it-identified-was-gone (an audit log of ids that were recycled) and the-id-space-was-smaller-than-the-records (random four-character hex identifiers for a growing set of records).
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== Locker rental ==
Locker numbers handed out again, and card ids drawn from too few.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 0
Pick a number from 1 to 5.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> x
Pick a number from 1 to 5.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 4
members (2 to 100000)>
Cancelled.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 4
members (2 to 100000)> 1
Type a whole number from 2 to 100000.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 4
members (2 to 100000)> 100001
Type a whole number from 2 to 100000.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 4
members (2 to 100000)> x
Type a whole number from 2 to 100000.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 4
members (2 to 100000)> 2
2 members: 0 share their id with another, 0 pairs drawn here, 0.0 pairs expected.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 4
members (2 to 100000)> 65537
65,537 members: 41,485 share their id with another, 33,136 pairs drawn here, 32,768.5 pairs expected.
At least 1 of them gets an id another member already has, however the ids are drawn.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 5
Bye.
What was typed (15 lines)
0
x
4
4
1
4
100001
4
x
4
2
4
65537
5
basic
interpreter: byte-equal== Locker rental ==
Locker numbers handed out again, and card ids drawn from too few.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 1
40 lockers, rented a month at a time through 2027. A locker given up goes back to the free list, and a new tenant gets the lowest number free.
At the end of the year 20 are taken. The first ten:
locker 1 T154
locker 2 T102
locker 3 T146
locker 4 T104
locker 5 T105
locker 6 T155
locker 7 T156
locker 8 T147
locker 9 T157
locker 10 T151
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 2
Notes the front desk wrote in 2027, each naming a locker - 21 of them. The first ten, and who the locker names today:
month locker written about note names today
Jan 17 T117 deposit paid T145, another tenant
Jan 5 T105 complaint about the noise the same tenant
Feb 17 T117 deposit paid T145, another tenant
Feb 29 T129 complaint about the noise nobody: it is free
Mar 17 T117 deposit paid T145, another tenant
Apr 27 T127 item found inside nobody: it is free
May 18 T138 deposit paid the same tenant
May 18 T138 deposit paid the same tenant
Jun 6 T135 complaint about the noise T155, another tenant
Jul 6 T135 complaint about the noise T155, another tenant
Of the 21 notes, 12 still name the tenant they were written about, 6 name another tenant who has the locker now, and 3 a locker that is free.
Every note was right when it was written, and every one still finds a locker. Written against the tenancy - the locker and its tenant - each would keep its tenant.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 3
Every member card gets a random id of four hex characters, such as 8d35 - 65,536 possible ids.
Among the first 1,000 members, 20 share their id with another member: 10 pairs, where 7.6 are expected.
At 100,000 members, at least 34,464 get an id another member already has, however the ids are drawn: there are only 65,536 of them.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 4
members (2 to 100000)> 1000
1,000 members: 20 share their id with another, 10 pairs drawn here, 7.6 pairs expected.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 4
members (2 to 100000)> 70000
70,000 members: 46,017 share their id with another, 37,761 pairs drawn here, 37,383.5 pairs expected.
At least 4,464 of them get an id another member already has, however the ids are drawn.
1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit
choice> 5
Bye.
What was typed (8 lines)
1
2
3
4
1000
4
70000
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# P176 locker rental: a gym's lockers through a year - the lockers, the
# notes written against locker numbers, member card ids of four hex
# characters, and a member count of your own.
import lockers
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 tenths(t):
return commas(int((t - t % 10) / 10)) + "." + str(t % 10)
def months():
return ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
def show_lockers():
lockers.year() => y
0 => taken
for h in y[0]:
if h != "":
taken + 1 => taken
("40 lockers, rented a month at a time through 2027. A locker given up goes back to the free list, and a new tenant gets the lowest number free.") ^0
("At the end of the year " + str(taken) + " are taken. The first ten:") ^0
0 => k
while k < 10:
"free" => h
if y[0][k] != "":
y[0][k] => h
(" locker " + lpad(str(k + 1), 2) + " " + h) ^0
k + 1 => k
def verdict(note, holder):
holder[note[1] - 1] => now
if now == note[2]:
return "the same tenant"
if now == "":
return "nobody: it is free"
return now + ", another tenant"
def show_notes():
lockers.year() => y
y[1] => notes
("Notes the front desk wrote in 2027, each naming a locker - " + str(len(notes)) + " of them. The first ten, and who the locker names today:") ^0
(" " + pad("month", 7) + pad("locker", 8) + pad("written about", 15) + pad("note", 27) + "names today") ^0
0 => i
while i < 10:
notes[i] => n
(" " + pad(months()[n[0] - 1], 7) + pad(str(n[1]), 8) + pad(n[2], 15) + pad(n[3], 27) + verdict(n, y[0])) ^0
i + 1 => i
0 => same
0 => other
0 => free
for n in notes:
y[0][n[1] - 1] => now
if now == n[2]:
same + 1 => same
elif now == "":
free + 1 => free
else:
other + 1 => other
("Of the " + str(len(notes)) + " notes, " + str(same) + " still name the tenant they were written about, " + str(other) + " name another tenant who has the locker now, and " + str(free) + " a locker that is free.") ^0
"Every note was right when it was written, and every one still finds a locker. Written against the tenancy - the locker and its tenant - each would keep its tenant." ^0
def show_cards():
lockers.cards(1000) => ids
lockers.shared(ids) => s
"Every member card gets a random id of four hex characters, such as " + lockers.hex4(ids[0]) + " - 65,536 possible ids." => line
line ^0
("Among the first 1,000 members, " + str(s[0]) + " share their id with another member: " + str(s[1]) + " pairs, where " + tenths(int((1000 * 999 * 10 + 65536) / 131072)) + " are expected.") ^0
"At 100,000 members, at least 34,464 get an id another member already has, however the ids are drawn: there are only 65,536 of them." ^0
def own():
plain(input("members (2 to 100000)> ")) => t
if t == "":
"Cancelled." ^0
return
if len(t) > 6 or not digits(t) or int(t) < 2 or int(t) > 100000:
"Type a whole number from 2 to 100000." ^0
return
int(t) => n
lockers.shared(lockers.cards(n)) => s
int((n * (n - 1) * 10 + 65536) / 131072) => expect
(commas(n) + " members: " + commas(s[0]) + " share their id with another, " + commas(s[1]) + " pairs drawn here, " + tenths(expect) + " pairs expected.") ^0
if n > 65536:
" get" => verb
if n - 65536 == 1:
" gets" => verb
("At least " + commas(n - 65536) + " of them" + verb + " an id another member already has, however the ids are drawn.") ^0
"== Locker rental ==" ^0
"Locker numbers handed out again, and card ids drawn from too few." ^0
True => running
while running:
"" ^0
"1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
show_lockers()
elif choice == "2":
show_notes()
elif choice == "3":
show_cards()
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 lockers
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 tenths(t):
return commas(int((t - t % 10) / 10)) + "." + str(t % 10)
def months():
return ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
def show_lockers():
y = lockers.year()
taken = 0
for h in y[0]:
if h != "":
taken = taken + 1
print("40 lockers, rented a month at a time through 2027. A locker given up goes back to the free list, and a new tenant gets the lowest number free.")
print("At the end of the year " + str(taken) + " are taken. The first ten:")
k = 0
while k < 10:
h = "free"
if y[0][k] != "":
h = y[0][k]
print(" locker " + lpad(str(k + 1), 2) + " " + h)
k = k + 1
def verdict(note, holder):
now = holder[note[1] - 1]
if now == note[2]:
return "the same tenant"
if now == "":
return "nobody: it is free"
return now + ", another tenant"
def show_notes():
y = lockers.year()
notes = y[1]
print("Notes the front desk wrote in 2027, each naming a locker - " + str(len(notes)) + " of them. The first ten, and who the locker names today:")
print(" " + pad("month", 7) + pad("locker", 8) + pad("written about", 15) + pad("note", 27) + "names today")
i = 0
while i < 10:
n = notes[i]
print(" " + pad(months()[n[0] - 1], 7) + pad(str(n[1]), 8) + pad(n[2], 15) + pad(n[3], 27) + verdict(n, y[0]))
i = i + 1
same = 0
other = 0
free = 0
for n in notes:
now = y[0][n[1] - 1]
if now == n[2]:
same = same + 1
elif now == "":
free = free + 1
else:
other = other + 1
print("Of the " + str(len(notes)) + " notes, " + str(same) + " still name the tenant they were written about, " + str(other) + " name another tenant who has the locker now, and " + str(free) + " a locker that is free.")
print("Every note was right when it was written, and every one still finds a locker. Written against the tenancy - the locker and its tenant - each would keep its tenant.")
def show_cards():
ids = lockers.cards(1000)
s = lockers.shared(ids)
line = "Every member card gets a random id of four hex characters, such as " + lockers.hex4(ids[0]) + " - 65,536 possible ids."
print(line)
print("Among the first 1,000 members, " + str(s[0]) + " share their id with another member: " + str(s[1]) + " pairs, where " + tenths(int((1000 * 999 * 10 + 65536) / 131072)) + " are expected.")
print("At 100,000 members, at least 34,464 get an id another member already has, however the ids are drawn: there are only 65,536 of them.")
def own():
t = plain(input("members (2 to 100000)> "))
if t == "":
print("Cancelled.")
return
if len(t) > 6 or not digits(t) or int(t) < 2 or int(t) > 100000:
print("Type a whole number from 2 to 100000.")
return
n = int(t)
s = lockers.shared(lockers.cards(n))
expect = int((n * (n - 1) * 10 + 65536) / 131072)
print(commas(n) + " members: " + commas(s[0]) + " share their id with another, " + commas(s[1]) + " pairs drawn here, " + tenths(expect) + " pairs expected.")
if n > 65536:
verb = " get"
if n - 65536 == 1:
verb = " gets"
print("At least " + commas(n - 65536) + " of them" + verb + " an id another member already has, however the ids are drawn.")
print("== Locker rental ==")
print("Locker numbers handed out again, and card ids drawn from too few.")
running = True
while running:
print("")
print("1) the lockers 2) the notes 3) member cards 4) a member count of your own 5) quit")
choice = plain(input("choice> "))
if choice == "1":
show_lockers()
elif choice == "2":
show_notes()
elif choice == "3":
show_cards()
elif choice == "4":
own()
elif choice == "5":
running = False
else:
print("Pick a number from 1 to 5.")
print("Bye.")
lockers.eml
eml# P176 locker rental - a gym's 40 lockers rented month by month for a year.
# As in the corpus case the-id-was-reused-after-the-thing-it-identified-was-
# gone, a locker number freed by one tenant is the next tenant's, and a note
# that names "locker 12" resolves today to whoever has it now. And as in
# the-id-space-was-smaller-than-the-records, member cards with a random id of
# four hex characters can tell apart 65536 members at most, and collide long
# before that.
def lockers():
return 40
def step(x):
return (x * 48271) % 2147483647
def year():
# [the tenant of each locker at the end ("" if free), the notes]
# A note is [month, locker, tenant when written, what it says].
[""] * lockers() => holder
[] => notes
0 => made
20271 => x
1 => m
while m <= 12:
0 => k
while k < lockers():
if holder[k] != "":
step(x) => x
if x % 100 < 15:
"" => holder[k]
k + 1 => k
step(x) => x
1 + x % 4 => joining
if m == 1:
32 => joining
0 => k
while k < lockers() and joining > 0:
if holder[k] == "":
made + 1 => made
"T" + str(100 + made) => holder[k]
joining - 1 => joining
k + 1 => k
0 => n
while n < 3:
step(x) => x
x % lockers() => k
if holder[k] != "":
step(x) => x
["deposit paid", "key replaced", "complaint about the noise", "item found inside"][x % 4] => what
notes + [[m, k + 1, holder[k], what]] => notes
n + 1 => n
m + 1 => m
return [holder, notes]
def hex4(n):
"0123456789abcdef" => h
"" => out
0 => i
while i < 4:
h[n % 16] + out => out
int((n - n % 16) / 16) => n
i + 1 => i
return out
def cards(n):
# n members' card ids, each drawn at random from the 65536 four-hex values
[0] * n => out
1019 => x
0 => i
while i < n:
step(x) => x
x % 65536 => out[i]
i + 1 => i
return out
def shared(ids):
# [members whose id another member also has, pairs of members sharing one]
{} => count
for v in ids:
if v in count:
count[v] + 1 => count[v]
else:
1 => count[v]
0 => members
0 => pairs
for v in count:
if count[v] > 1:
members + count[v] => members
pairs + int(count[v] * (count[v] - 1) / 2) => pairs
return [members, pairs]
Python projection (lockers.py)
def lockers():
return 40
def step(x):
return x * 48271 % 2147483647
def year():
holder = [""] * lockers()
notes = []
made = 0
x = 20271
m = 1
while m <= 12:
k = 0
while k < lockers():
if holder[k] != "":
x = step(x)
if x % 100 < 15:
holder[k] = ""
k = k + 1
x = step(x)
joining = 1 + x % 4
if m == 1:
joining = 32
k = 0
while k < lockers() and joining > 0:
if holder[k] == "":
made = made + 1
holder[k] = "T" + str(100 + made)
joining = joining - 1
k = k + 1
n = 0
while n < 3:
x = step(x)
k = x % lockers()
if holder[k] != "":
x = step(x)
what = ["deposit paid", "key replaced", "complaint about the noise", "item found inside"][x % 4]
notes = notes + [[m, k + 1, holder[k], what]]
n = n + 1
m = m + 1
return [holder, notes]
def hex4(n):
h = "0123456789abcdef"
out = ""
i = 0
while i < 4:
out = h[n % 16] + out
n = int((n - n % 16) / 16)
i = i + 1
return out
def cards(n):
out = [0] * n
x = 1019
i = 0
while i < n:
x = step(x)
out[i] = x % 65536
i = i + 1
return out
def shared(ids):
count = {}
for v in ids:
if v in count:
count[v] = count[v] + 1
else:
count[v] = 1
members = 0
pairs = 0
for v in count:
if count[v] > 1:
members = members + count[v]
pairs = pairs + int(count[v] * (count[v] - 1) / 2)
return [members, pairs]