Username registry
Names folded into keys four ways, from the name as typed to lower case without marks or trailing digits, set against who is who: the people each fold merges and the people it splits.
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
Names folded into keys that decide when two names are one account, set against who is who, written apart from every fold.
main.eml- the menu, the sign-ups, the keys each fold makes, a new sign-up seen by every fold, and each fold's merges and splitsfolds.eml- the four folds, the opening sign-ups, lower case written as a table, the owner of a key, a person's first key, and the counts
How each part works:
- Sixteen sign-ups by ten people: Alice, alice and ALICE are one person; al.ice and al-ice another; alice1 and alice2 two more; carol7 and carol_7 one person, and carol another.
- As in the corpus case
case-fold-collisions, the folds go from the name as typed, to lower case, to lower case without dots, dashes and underscores, to that without trailing digits. Each is a function of the one before, so it only ever joins keys. - Two failures, counted apart against who is who. Merged: a person's name lands on another person's key, so the sign-up is refused or an account is lost. Split: one person's names land on two keys, so one spelling no longer reaches the account. Merged goes 0, 0, 2, 7 from fold to fold and split 6, 3, 0, 0: no fold is right on both for these names.
- A new sign-up is shown under every fold: a new account, one account of the same person, a second account for someone who already has a key, or a key that is another person's.
What is checked: menu choices 1 to 5; a name of 1 to 16 letters, digits, dots, dashes or underscores; a person's number that exists, or n for someone new. An empty answer cancels.
Sessions: sessions/basic.in gives: - the sign-ups, the keys and the counts; - Bob.Smith as person 6, caro.l as someone new and ALICE2 as person 4; the counts again.
sessions/bad-input.in gives: - menu choices 0 and x; - names: a cancel, one with a space and one of 17 characters; - Zed with a cancel, 0, 11, x and 1234 for the person; zed as someone new; the counts.
Built on the verified corpus case case-fold-collisions (three folds of increasing reach, measured against a separately stated list of who is who).
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== Username registry ==
Names folded into keys, set against who is who: people merged, and people split.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 0
Pick a number from 1 to 5.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> x
Pick a number from 1 to 5.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 3
name>
Cancelled.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 3
name> bad name!
Type a name of 1 to 16 letters, digits, dots, dashes or underscores.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 3
name> seventeen_chars_x
Type a name of 1 to 16 letters, digits, dots, dashes or underscores.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 3
name> Zed
who is this (1 to 10, or n for someone new)>
Cancelled.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 3
name> Zed
who is this (1 to 10, or n for someone new)> 0
Type a person's number from 1 to 10, or n.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 3
name> Zed
who is this (1 to 10, or n for someone new)> 11
Type a person's number from 1 to 10, or n.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 3
name> Zed
who is this (1 to 10, or n for someone new)> x
Type a person's number from 1 to 10, or n.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 3
name> Zed
who is this (1 to 10, or n for someone new)> 1234
Type a person's number from 1 to 10, or n.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 3
name> zed
who is this (1 to 10, or n for someone new)> n
zed, person 11:
exact new account 'zed'
lower new account 'zed'
strip new account 'zed'
strip+digits new account 'zed'
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 4
Over 17 sign-ups by 11 people:
fold keys merged split
exact 17 0 6
lower 14 0 3
strip 10 2 0
strip+digits 6 7 0
Merged: a person's name lands on another person's key. Split: one person's names land on two keys.
strip: al.ice (person 2) lands on 'alice', person 1's key
strip: al-ice (person 2) lands on 'alice', person 1's key
strip+digits: al.ice (person 2) lands on 'alice', person 1's key
strip+digits: al-ice (person 2) lands on 'alice', person 1's key
strip+digits: alice1 (person 3) lands on 'alice', person 1's key
strip+digits: alice2 (person 4) lands on 'alice', person 1's key
strip+digits: carol7 (person 8) lands on 'carol', person 7's key
strip+digits: carol_7 (person 8) lands on 'carol', person 7's key
strip+digits: Dave2024 (person 10) lands on 'dave', person 9's key
exact splits, first alice (person 1): 'alice', where the same person has 'Alice'
lower splits, first al-ice (person 2): 'al-ice', where the same person has 'al.ice'
No fold keeps every person apart and every person's names together, for these names.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 5
Bye.
What was typed (28 lines)
0
x
3
3
bad name!
3
seventeen_chars_x
3
Zed
3
Zed
0
3
Zed
11
3
Zed
x
3
Zed
1234
3
zed
n
4
5
basic
interpreter: byte-equal== Username registry ==
Names folded into keys, set against who is who: people merged, and people split.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 1
The sign-ups, in order, with who is who:
Alice person 1
alice person 1
ALICE person 1
al.ice person 2
al-ice person 2
alice1 person 3
alice2 person 4
Bob person 5
bob person 5
bob.smith person 6
bobsmith person 6
carol person 7
carol7 person 8
carol_7 person 8
dave person 9
Dave2024 person 10
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 2
name lower strip strip+digits
Alice alice alice alice
alice alice alice alice
ALICE alice alice alice
al.ice al.ice alice alice
al-ice al-ice alice alice
alice1 alice1 alice1 alice
alice2 alice2 alice2 alice
Bob bob bob bob
bob bob bob bob
bob.smith bob.smith bobsmith bobsmith
bobsmith bobsmith bobsmith bobsmith
carol carol carol carol
carol7 carol7 carol7 carol
carol_7 carol_7 carol7 carol
dave dave dave dave
Dave2024 dave2024 dave2024 dave
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 4
Over 16 sign-ups by 10 people:
fold keys merged split
exact 16 0 6
lower 13 0 3
strip 9 2 0
strip+digits 5 7 0
Merged: a person's name lands on another person's key. Split: one person's names land on two keys.
strip: al.ice (person 2) lands on 'alice', person 1's key
strip: al-ice (person 2) lands on 'alice', person 1's key
strip+digits: al.ice (person 2) lands on 'alice', person 1's key
strip+digits: al-ice (person 2) lands on 'alice', person 1's key
strip+digits: alice1 (person 3) lands on 'alice', person 1's key
strip+digits: alice2 (person 4) lands on 'alice', person 1's key
strip+digits: carol7 (person 8) lands on 'carol', person 7's key
strip+digits: carol_7 (person 8) lands on 'carol', person 7's key
strip+digits: Dave2024 (person 10) lands on 'dave', person 9's key
exact splits, first alice (person 1): 'alice', where the same person has 'Alice'
lower splits, first al-ice (person 2): 'al-ice', where the same person has 'al.ice'
No fold keeps every person apart and every person's names together, for these names.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 3
name> Bob.Smith
who is this (1 to 10, or n for someone new)> 6
Bob.Smith, person 6:
exact new key 'Bob.Smith', though person 6 already has 'bob.smith': a second account (split)
lower the account 'bob.smith' of the same person: one account, right
strip the account 'bobsmith' of the same person: one account, right
strip+digits the account 'bobsmith' of the same person: one account, right
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 3
name> caro.l
who is this (1 to 10, or n for someone new)> n
caro.l, person 11:
exact new account 'caro.l'
lower new account 'caro.l'
strip key 'carol' is person 7's: a different person refused (merged)
strip+digits key 'carol' is person 7's: a different person refused (merged)
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 3
name> ALICE2
who is this (1 to 11, or n for someone new)> 4
ALICE2, person 4:
exact new key 'ALICE2', though person 4 already has 'alice2': a second account (split)
lower the account 'alice2' of the same person: one account, right
strip the account 'alice2' of the same person: one account, right
strip+digits key 'alice' is person 1's: a different person refused (merged)
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 4
Over 19 sign-ups by 11 people:
fold keys merged split
exact 19 0 8
lower 14 0 3
strip 9 3 0
strip+digits 5 9 0
Merged: a person's name lands on another person's key. Split: one person's names land on two keys.
strip: al.ice (person 2) lands on 'alice', person 1's key
strip: al-ice (person 2) lands on 'alice', person 1's key
strip: caro.l (person 11) lands on 'carol', person 7's key
strip+digits: al.ice (person 2) lands on 'alice', person 1's key
strip+digits: al-ice (person 2) lands on 'alice', person 1's key
strip+digits: alice1 (person 3) lands on 'alice', person 1's key
strip+digits: alice2 (person 4) lands on 'alice', person 1's key
strip+digits: carol7 (person 8) lands on 'carol', person 7's key
strip+digits: carol_7 (person 8) lands on 'carol', person 7's key
strip+digits: Dave2024 (person 10) lands on 'dave', person 9's key
strip+digits: caro.l (person 11) lands on 'carol', person 7's key
strip+digits: ALICE2 (person 4) lands on 'alice', person 1's key
exact splits, first alice (person 1): 'alice', where the same person has 'Alice'
lower splits, first al-ice (person 2): 'al-ice', where the same person has 'al.ice'
No fold keeps every person apart and every person's names together, for these names.
1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit
choice> 5
Bye.
What was typed (14 lines)
1
2
4
3
Bob.Smith
6
3
caro.l
n
3
ALICE2
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# P145 username registry: sign-ups with who is who written beside them,
# the key each fold makes of every name, a new sign-up seen by every fold,
# and each fold's merges and splits.
import folds
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 q(s):
return "'" + s + "'"
def name_ok(t):
if len(t) == 0 or len(t) > 16:
return False
for c in t:
if not ((c >= "a" and c <= "z") or (c >= "A" and c <= "Z") or (c >= "0" and c <= "9") or c == "." or c == "-" or c == "_"):
return False
return True
def show_signups(rows):
"The sign-ups, in order, with who is who:" ^0
for r in rows:
(" " + pad(r[0], 18) + "person " + str(r[1])) ^0
def show_keys(rows):
(" " + pad("name", 18) + pad("lower", 18) + pad("strip", 18) + "strip+digits") ^0
for r in rows:
(" " + pad(r[0], 18) + pad(folds.fold("lower", r[0]), 18) + pad(folds.fold("strip", r[0]), 18) + folds.fold("strip+digits", r[0])) ^0
def outcome(which, rows, name, who):
folds.fold(which, name) => k
folds.owner(which, rows, k) => o
folds.first_key(which, rows, who) => fk
if o == 0 and fk == "":
return "new account " + q(k)
if o == 0:
return "new key " + q(k) + ", though person " + str(who) + " already has " + q(fk) + ": a second account (split)"
if o == who:
return "the account " + q(k) + " of the same person: one account, right"
return "key " + q(k) + " is person " + str(o) + "'s: a different person refused (merged)"
def signup(rows):
plain(input("name> ")) => name
if name == "":
"Cancelled." ^0
return rows
if not name_ok(name):
"Type a name of 1 to 16 letters, digits, dots, dashes or underscores." ^0
return rows
folds.people(rows) => n
plain(input("who is this (1 to " + str(n) + ", or n for someone new)> ")) => t
if t == "":
"Cancelled." ^0
return rows
if t == "n":
n + 1 => who
else:
if len(t) > 3:
("Type a person's number from 1 to " + str(n) + ", or n.") ^0
return rows
for c in t:
if c < "0" or c > "9":
("Type a person's number from 1 to " + str(n) + ", or n.") ^0
return rows
int(t) => who
if who < 1 or who > n:
("Type a person's number from 1 to " + str(n) + ", or n.") ^0
return rows
(name + ", person " + str(who) + ":") ^0
for f in folds.folds():
(" " + pad(f, 14) + outcome(f, rows, name, who)) ^0
return rows + [[name, who]]
def results(rows):
("Over " + str(len(rows)) + " sign-ups by " + str(folds.people(rows)) + " people:") ^0
(" " + pad("fold", 14) + "keys merged split") ^0
[] => right
for f in folds.folds():
folds.measure(f, rows) => m
(" " + pad(f, 14) + lpad(str(m[2]), 4) + lpad(str(m[0]), 8) + lpad(str(m[1]), 7)) ^0
if m[0] == 0 and m[1] == 0:
right + [f] => right
"Merged: a person's name lands on another person's key. Split: one person's names land on two keys." ^0
for f in folds.folds():
for r in rows:
folds.fold(f, r[0]) => k
folds.owner(f, rows, k) => o
if o != r[1]:
(" " + f + ": " + r[0] + " (person " + str(r[1]) + ") lands on " + q(k) + ", person " + str(o) + "'s key") ^0
for f in folds.folds():
"" => ex
for r in rows:
folds.fold(f, r[0]) => k
folds.first_key(f, rows, r[1]) => fk
if fk != k and ex == "":
" " + f + " splits, first " + r[0] + " (person " + str(r[1]) + "): " + q(k) + ", where the same person has " + q(fk) => ex
if ex != "":
ex ^0
if len(right) == 0:
"No fold keeps every person apart and every person's names together, for these names." ^0
folds.opening() => rows
"== Username registry ==" ^0
"Names folded into keys, set against who is who: people merged, and people split." ^0
True => running
while running:
"" ^0
"1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
show_signups(rows)
elif choice == "2":
show_keys(rows)
elif choice == "3":
signup(rows) => rows
elif choice == "4":
results(rows)
elif choice == "5":
False => running
else:
"Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import folds
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 q(s):
return "'" + s + "'"
def name_ok(t):
if len(t) == 0 or len(t) > 16:
return False
for c in t:
if not (c >= "a" and c <= "z" or c >= "A" and c <= "Z" or c >= "0" and c <= "9" or c == "." or c == "-" or c == "_"):
return False
return True
def show_signups(rows):
print("The sign-ups, in order, with who is who:")
for r in rows:
print(" " + pad(r[0], 18) + "person " + str(r[1]))
def show_keys(rows):
print(" " + pad("name", 18) + pad("lower", 18) + pad("strip", 18) + "strip+digits")
for r in rows:
print(" " + pad(r[0], 18) + pad(folds.fold("lower", r[0]), 18) + pad(folds.fold("strip", r[0]), 18) + folds.fold("strip+digits", r[0]))
def outcome(which, rows, name, who):
k = folds.fold(which, name)
o = folds.owner(which, rows, k)
fk = folds.first_key(which, rows, who)
if o == 0 and fk == "":
return "new account " + q(k)
if o == 0:
return "new key " + q(k) + ", though person " + str(who) + " already has " + q(fk) + ": a second account (split)"
if o == who:
return "the account " + q(k) + " of the same person: one account, right"
return "key " + q(k) + " is person " + str(o) + "'s: a different person refused (merged)"
def signup(rows):
name = plain(input("name> "))
if name == "":
print("Cancelled.")
return rows
if not name_ok(name):
print("Type a name of 1 to 16 letters, digits, dots, dashes or underscores.")
return rows
n = folds.people(rows)
t = plain(input("who is this (1 to " + str(n) + ", or n for someone new)> "))
if t == "":
print("Cancelled.")
return rows
if t == "n":
who = n + 1
else:
if len(t) > 3:
print("Type a person's number from 1 to " + str(n) + ", or n.")
return rows
for c in t:
if c < "0" or c > "9":
print("Type a person's number from 1 to " + str(n) + ", or n.")
return rows
who = int(t)
if who < 1 or who > n:
print("Type a person's number from 1 to " + str(n) + ", or n.")
return rows
print(name + ", person " + str(who) + ":")
for f in folds.folds():
print(" " + pad(f, 14) + outcome(f, rows, name, who))
return rows + [[name, who]]
def results(rows):
print("Over " + str(len(rows)) + " sign-ups by " + str(folds.people(rows)) + " people:")
print(" " + pad("fold", 14) + "keys merged split")
right = []
for f in folds.folds():
m = folds.measure(f, rows)
print(" " + pad(f, 14) + lpad(str(m[2]), 4) + lpad(str(m[0]), 8) + lpad(str(m[1]), 7))
if m[0] == 0 and m[1] == 0:
right = right + [f]
print("Merged: a person's name lands on another person's key. Split: one person's names land on two keys.")
for f in folds.folds():
for r in rows:
k = folds.fold(f, r[0])
o = folds.owner(f, rows, k)
if o != r[1]:
print(" " + f + ": " + r[0] + " (person " + str(r[1]) + ") lands on " + q(k) + ", person " + str(o) + "'s key")
for f in folds.folds():
ex = ""
for r in rows:
k = folds.fold(f, r[0])
fk = folds.first_key(f, rows, r[1])
if fk != k and ex == "":
ex = " " + f + " splits, first " + r[0] + " (person " + str(r[1]) + "): " + q(k) + ", where the same person has " + q(fk)
if ex != "":
print(ex)
if len(right) == 0:
print("No fold keeps every person apart and every person's names together, for these names.")
rows = folds.opening()
print("== Username registry ==")
print("Names folded into keys, set against who is who: people merged, and people split.")
running = True
while running:
print("")
print("1) sign-ups 2) keys 3) sign up 4) merged and split 5) quit")
choice = plain(input("choice> "))
if choice == "1":
show_signups(rows)
elif choice == "2":
show_keys(rows)
elif choice == "3":
rows = signup(rows)
elif choice == "4":
results(rows)
elif choice == "5":
running = False
else:
print("Pick a number from 1 to 5.")
print("Bye.")
folds.eml
eml# P145 username registry - names registered under a fold that decides when
# two names are the same account. As in the corpus case
# case-fold-collisions, a fold is many-to-one, and the property that makes
# it a uniqueness key - fold(a) == fold(b) only when a and b are the same
# person - can only be checked against who is who, written apart from every
# fold. Two failures, counted apart: MERGED, a person whose name lands on
# another person's key (refused, or their account lost), and SPLIT, a
# person whose names land on two keys (one spelling no longer reaches the
# account). A sign-up is [name as typed, person].
def folds():
return ["exact", "lower", "strip", "strip+digits"]
def what():
return ["the name as typed", "lower case", "lower case, without . - _", "lower case, without . - _ and trailing digits"]
def opening():
return [["Alice", 1], ["alice", 1], ["ALICE", 1], ["al.ice", 2], ["al-ice", 2], ["alice1", 3], ["alice2", 4], ["Bob", 5], ["bob", 5], ["bob.smith", 6], ["bobsmith", 6], ["carol", 7], ["carol7", 8], ["carol_7", 8], ["dave", 9], ["Dave2024", 10]]
def to_lower(s):
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" => up
"abcdefghijklmnopqrstuvwxyz" => low
"" => out
for c in s:
c => m
0 => i
while i < 26:
if up[i] == c:
low[i] => m
i + 1 => i
out + m => out
return out
def fold(which, s):
if which == "exact":
return s
to_lower(s) => t
if which == "lower":
return t
"" => u
for c in t:
if c != "." and c != "-" and c != "_":
u + c => u
if which == "strip":
return u
len(u) => i
while i > 0 and u[i - 1] >= "0" and u[i - 1] <= "9":
i - 1 => i
return u[0:i]
def owner(which, rows, k):
# the person whose name first took key k, or 0
for r in rows:
if fold(which, r[0]) == k:
return r[1]
return 0
def first_key(which, rows, who):
# the first key person who has, or ""
for r in rows:
if r[1] == who:
return fold(which, r[0])
return ""
def measure(which, rows):
# [merged, split, keys] over the sign-ups in order
{} => key_to
{} => person_to
0 => merged
0 => split
for r in rows:
fold(which, r[0]) => k
if k in key_to:
if key_to[k] != r[1]:
merged + 1 => merged
else:
r[1] => key_to[k]
if r[1] in person_to:
if person_to[r[1]] != k:
split + 1 => split
else:
k => person_to[r[1]]
return [merged, split, len(key_to)]
def people(rows):
0 => n
for r in rows:
if r[1] > n:
r[1] => n
return n
Python projection (folds.py)
def folds():
return ["exact", "lower", "strip", "strip+digits"]
def what():
return ["the name as typed", "lower case", "lower case, without . - _", "lower case, without . - _ and trailing digits"]
def opening():
return [["Alice", 1], ["alice", 1], ["ALICE", 1], ["al.ice", 2], ["al-ice", 2], ["alice1", 3], ["alice2", 4], ["Bob", 5], ["bob", 5], ["bob.smith", 6], ["bobsmith", 6], ["carol", 7], ["carol7", 8], ["carol_7", 8], ["dave", 9], ["Dave2024", 10]]
def to_lower(s):
up = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
low = "abcdefghijklmnopqrstuvwxyz"
out = ""
for c in s:
m = c
i = 0
while i < 26:
if up[i] == c:
m = low[i]
i = i + 1
out = out + m
return out
def fold(which, s):
if which == "exact":
return s
t = to_lower(s)
if which == "lower":
return t
u = ""
for c in t:
if c != "." and c != "-" and c != "_":
u = u + c
if which == "strip":
return u
i = len(u)
while i > 0 and u[i - 1] >= "0" and u[i - 1] <= "9":
i = i - 1
return u[0:i]
def owner(which, rows, k):
for r in rows:
if fold(which, r[0]) == k:
return r[1]
return 0
def first_key(which, rows, who):
for r in rows:
if r[1] == who:
return fold(which, r[0])
return ""
def measure(which, rows):
key_to = {}
person_to = {}
merged = 0
split = 0
for r in rows:
k = fold(which, r[0])
if k in key_to:
if key_to[k] != r[1]:
merged = merged + 1
else:
key_to[k] = r[1]
if r[1] in person_to:
if person_to[r[1]] != k:
split = split + 1
else:
person_to[r[1]] = k
return [merged, split, len(key_to)]
def people(rows):
n = 0
for r in rows:
if r[1] > n:
n = r[1]
return n