Member accounts
Sign-ups and soft deletes kept four ways - plain, partial, nulled and compound uniqueness - each decision set against the rule meant, one active account per address.
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
Sign-ups and soft deletes kept four ways, each decision set against the rule meant: at most one active account per address.
main.eml- the menu, the accounts in each scheme, signing up, deleting, the clock, and the checksaccounts.eml- the four schemes, the key each unique index compares, signing up, deleting, and counting the deleted rows and the addresses they keep
How each part works:
- A soft delete marks the row with the second it was deleted instead of removing it. Ana, Bob and Cy have accounts; the clock is at second 2 and moves on only when asked.
- As in the corpus case
soft-delete-and-uniqueness, the four schemes: plain is unique on the address over every row; partial is unique among active rows; nulled takes the address off a deleted row; compound is unique on the address and the second it was deleted. - Plain refuses every sign-up of an address that only deleted accounts have: its index still counts them. Partial makes every decision the rule meant. Nulled decides the same but keeps no deleted address, so the history no longer says whose the accounts were.
- Compound decides right until one address is deleted twice in the same second: the second deletion has the key of the first and fails, the account stays active there, and the next sign-up of that address is wrongly refused. A second later the deletion goes through.
- No scheme ever lets two accounts with one address be active at once; with no deletions all four decide the same, which is why a test that never deletes and signs up again cannot tell them apart.
What is checked: menu choices 1 to 6; an address of 3 to 12 characters, letters a to z with one @ not at either end; a deletion only of an address with an active account. An empty answer cancels.
Sessions: sessions/basic.in gives: - the accounts; Bob deleted and signing up again; the checks; - in second 2: Ana's sign-up refused while active, then Ana deleted, signing up, deleted again (compound fails) and signing up again; - in second 3: Ana deleted and signing up; the checks and the accounts.
sessions/bad-input.in gives: - menu choices 0, x and 7; - addresses: a cancel, Ana@x, ab, a@b@c, @ab and one of 16 characters; - deleting: a cancel, zed@x (no active account) and x; - the next second, Dan signing up, and the checks with no deletions.
Built on the verified corpus case soft-delete-and-uniqueness (four uniqueness schemes over sign-ups, deletions and re-sign-ups, wrong refusals and duplicates counted apart).
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== Member accounts ==
Sign-ups and soft deletes kept four ways, against the rule meant: one active account per address.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 0
Pick a number from 1 to 6.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> x
Pick a number from 1 to 6.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 7
Pick a number from 1 to 6.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 2
address>
Cancelled.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 2
address> Ana@x
Type an address like ana@x: letters a to z with one @, 3 to 12 characters.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 2
address> ab
Type an address like ana@x: letters a to z with one @, 3 to 12 characters.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 2
address> a@b@c
Type an address like ana@x: letters a to z with one @, 3 to 12 characters.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 2
address> @ab
Type an address like ana@x: letters a to z with one @, 3 to 12 characters.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 2
address> toolongaddress@x
Type an address like ana@x: letters a to z with one @, 3 to 12 characters.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 3
address>
Cancelled.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 3
address> zed@x
No account with the address zed@x is active.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 3
address> x
Type an address like ana@x: letters a to z with one @, 3 to 12 characters.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 4
It is second 3.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 2
address> dan@x
Sign-up for dan@x in second 3 - the rule meant: accept, no active account has it.
plain accepted
partial accepted
nulled accepted
compound accepted
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 5
wrongly two active deletions deleted rows
refused allowed failed keeping the address
plain 0 0 0 0 of 0
partial 0 0 0 0 of 0
nulled 0 0 0 0 of 0
compound 0 0 0 0 of 0
The rule meant: at most one active account per address.
Right on every count so far: plain, partial, nulled, compound.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 6
Bye.
What was typed (26 lines)
0
x
7
2
2
Ana@x
2
ab
2
a@b@c
2
@ab
2
toolongaddress@x
3
3
zed@x
3
x
4
2
dan@x
5
6
basic
interpreter: byte-equal== Member accounts ==
Sign-ups and soft deletes kept four ways, against the rule meant: one active account per address.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 1
It is second 2.
plain (unique on the address over every row):
ana@x active
bob@x active
cy@x active
partial (unique on the address among active rows):
ana@x active
bob@x active
cy@x active
nulled (a deleted row loses its address):
ana@x active
bob@x active
cy@x active
compound (unique on the address and the second it was deleted):
ana@x active
bob@x active
cy@x active
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 3
address> bob@x
Deleting bob@x in second 2:
plain deleted
partial deleted
nulled deleted
compound deleted
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 2
address> bob@x
Sign-up for bob@x in second 2 - the rule meant: accept, no active account has it.
plain refused: a deleted account still has the address <- wrong
partial accepted
nulled accepted
compound accepted
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 5
wrongly two active deletions deleted rows
refused allowed failed keeping the address
plain 1 0 0 1 of 1
partial 0 0 0 1 of 1
nulled 0 0 0 0 of 1
compound 0 0 0 1 of 1
The rule meant: at most one active account per address.
Right on every count so far: partial, compound.
plain refused 1 sign-up the rule allows: its index still counts the deleted rows.
nulled decides right, and kept the address of 0 of 1 deleted account: the history no longer says whose they were.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 2
address> ana@x
Sign-up for ana@x in second 2 - the rule meant: refuse, an active account has it.
plain refused: an active account has the address
partial refused: an active account has the address
nulled refused: an active account has the address
compound refused: an active account has the address
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 3
address> ana@x
Deleting ana@x in second 2:
plain deleted
partial deleted
nulled deleted
compound deleted
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 2
address> ana@x
Sign-up for ana@x in second 2 - the rule meant: accept, no active account has it.
plain refused: a deleted account still has the address <- wrong
partial accepted
nulled accepted
compound accepted
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 3
address> ana@x
Deleting ana@x in second 2:
plain nothing to delete: no active account has the address here
partial deleted
nulled deleted
compound the deletion fails: a row deleted in second 2 already has the key ana@x@2
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 2
address> ana@x
Sign-up for ana@x in second 2 - the rule meant: accept, no active account has it.
plain refused: a deleted account still has the address <- wrong
partial accepted
nulled accepted
compound refused: an active account has the address <- wrong
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 4
It is second 3.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 3
address> ana@x
Deleting ana@x in second 3:
plain nothing to delete: no active account has the address here
partial deleted
nulled deleted
compound deleted
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 2
address> ana@x
Sign-up for ana@x in second 3 - the rule meant: accept, no active account has it.
plain refused: a deleted account still has the address <- wrong
partial accepted
nulled accepted
compound accepted
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 5
wrongly two active deletions deleted rows
refused allowed failed keeping the address
plain 4 0 0 2 of 2
partial 0 0 0 4 of 4
nulled 0 0 0 0 of 4
compound 1 0 1 3 of 3
The rule meant: at most one active account per address.
Right on every count so far: partial.
plain refused 4 sign-ups the rule allows: its index still counts the deleted rows.
nulled decides right, and kept the address of 0 of 4 deleted accounts: the history no longer says whose they were.
compound failed 1 deletion: two deletions of one address in the same second have the same key.
compound refused 1 sign-up the rule allows: an account whose deletion failed is still active there.
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 1
It is second 3.
plain (unique on the address over every row):
ana@x deleted in second 2
bob@x deleted in second 2
cy@x active
partial (unique on the address among active rows):
ana@x deleted in second 2
bob@x deleted in second 2
cy@x active
bob@x active
ana@x deleted in second 2
ana@x deleted in second 3
ana@x active
nulled (a deleted row loses its address):
(no address) deleted in second 2
(no address) deleted in second 2
cy@x active
bob@x active
(no address) deleted in second 2
(no address) deleted in second 3
ana@x active
compound (unique on the address and the second it was deleted):
ana@x deleted in second 2
bob@x deleted in second 2
cy@x active
bob@x active
ana@x deleted in second 3
ana@x active
1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit
choice> 6
Bye.
What was typed (24 lines)
1
3
bob@x
2
bob@x
5
2
ana@x
3
ana@x
2
ana@x
3
ana@x
2
ana@x
4
3
ana@x
2
ana@x
5
1
6
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# P143 member accounts: the same sign-ups and deletions kept four ways,
# each decision set against the rule meant - at most one active account per
# address - and the counts of what each way gets wrong.
import accounts
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 address_ok(t):
# a to z with one @, not at either end, 3 to 12 characters
if len(t) < 3 or len(t) > 12:
return False
0 => ats
for c in t:
if c == "@":
ats + 1 => ats
elif c < "a" or c > "z":
return False
return ats == 1 and t[0] != "@" and t[len(t) - 1] != "@"
def times(n, one, many):
if n == 1:
return "1 " + one
return str(n) + " " + many
def show(rows, truth, t):
("It is second " + str(t) + ".") ^0
0 => i
for s in accounts.schemes():
(s + " (" + accounts.what()[i] + "):") ^0
for r in rows[s]:
if r[0] == "":
"(no address)" => a
else:
r[0] => a
if r[1] == 0:
(" " + pad(a, 14) + "active") ^0
else:
(" " + pad(a, 14) + "deleted in second " + str(r[1])) ^0
i + 1 => i
def signup(rows, truth, counts, t):
plain(input("address> ")) => a
if a == "":
"Cancelled." ^0
return [rows, truth, counts]
if not address_ok(a):
"Type an address like ana@x: letters a to z with one @, 3 to 12 characters." ^0
return [rows, truth, counts]
accounts.active(truth, a) < 0 => meant
if meant:
("Sign-up for " + a + " in second " + str(t) + " - the rule meant: accept, no active account has it.") ^0
else:
("Sign-up for " + a + " in second " + str(t) + " - the rule meant: refuse, an active account has it.") ^0
{} => out
for s in accounts.schemes():
accounts.signup(s, rows[s], a) => r
r[0] => out[s]
if r[1] == "ok":
"accepted" => said
elif r[1] == "active":
"refused: an active account has the address" => said
else:
"refused: a deleted account still has the address" => said
if meant and r[1] != "ok":
said + " <- wrong" => said
counts[s + " wrong"] + 1 => counts[s + " wrong"]
if not meant and r[1] == "ok":
said + " <- wrong: two active accounts" => said
counts[s + " dup"] + 1 => counts[s + " dup"]
(" " + pad(s, 10) + said) ^0
if meant:
truth + [[a, 0]] => truth
return [out, truth, counts]
def delete(rows, truth, counts, t):
plain(input("address> ")) => a
if a == "":
"Cancelled." ^0
return [rows, truth, counts]
if not address_ok(a):
"Type an address like ana@x: letters a to z with one @, 3 to 12 characters." ^0
return [rows, truth, counts]
if accounts.active(truth, a) < 0:
("No account with the address " + a + " is active.") ^0
return [rows, truth, counts]
("Deleting " + a + " in second " + str(t) + ":") ^0
{} => out
for s in accounts.schemes():
accounts.delete(s, rows[s], a, t) => r
r[0] => out[s]
if r[1] == "ok":
"deleted" => said
elif r[1] == "none":
"nothing to delete: no active account has the address here" => said
else:
"the deletion fails: a row deleted in second " + str(t) + " already has the key " + accounts.key(s, [a, t]) => said
counts[s + " failed"] + 1 => counts[s + " failed"]
(" " + pad(s, 10) + said) ^0
accounts.delete("partial", truth, a, t)[0] => truth
return [out, truth, counts]
def checks(rows, counts):
" wrongly two active deletions deleted rows" ^0
" refused allowed failed keeping the address" ^0
[] => right
for s in accounts.schemes():
[counts[s + " wrong"], counts[s + " dup"], counts[s + " failed"]] => c
accounts.kept(rows[s]) => k
accounts.deleted(rows[s]) => d
(" " + pad(s, 12) + lpad(str(c[0]), 6) + lpad(str(c[1]), 11) + lpad(str(c[2]), 13) + " " + str(k) + " of " + str(d)) ^0
if c[0] == 0 and c[1] == 0 and c[2] == 0 and k == d:
right + [s] => right
"The rule meant: at most one active account per address." ^0
if len(right) > 0:
"" => names
for s in right:
if names == "":
s => names
else:
names + ", " + s => names
("Right on every count so far: " + names + ".") ^0
counts["plain wrong"] => c
if c > 0:
("plain refused " + times(c, "sign-up", "sign-ups") + " the rule allows: its index still counts the deleted rows.") ^0
accounts.kept(rows["nulled"]) => k
accounts.deleted(rows["nulled"]) => d
if k < d:
("nulled decides right, and kept the address of " + str(k) + " of " + times(d, "deleted account", "deleted accounts") + ": the history no longer says whose they were.") ^0
if counts["compound failed"] > 0:
("compound failed " + times(counts["compound failed"], "deletion", "deletions") + ": two deletions of one address in the same second have the same key.") ^0
if counts["compound wrong"] > 0:
("compound refused " + times(counts["compound wrong"], "sign-up", "sign-ups") + " the rule allows: an account whose deletion failed is still active there.") ^0
{} => rows
{} => counts
[] => truth
for s in accounts.schemes():
[] => rows[s]
0 => counts[s + " wrong"]
0 => counts[s + " dup"]
0 => counts[s + " failed"]
for a in accounts.opening():
for s in accounts.schemes():
accounts.signup(s, rows[s], a)[0] => rows[s]
truth + [[a, 0]] => truth
2 => t
"== Member accounts ==" ^0
"Sign-ups and soft deletes kept four ways, against the rule meant: one active account per address." ^0
True => running
while running:
"" ^0
"1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
show(rows, truth, t)
elif choice == "2":
signup(rows, truth, counts, t) => r
r[0] => rows
r[1] => truth
r[2] => counts
elif choice == "3":
delete(rows, truth, counts, t) => r
r[0] => rows
r[1] => truth
r[2] => counts
elif choice == "4":
t + 1 => t
("It is second " + str(t) + ".") ^0
elif choice == "5":
checks(rows, counts)
elif choice == "6":
False => running
else:
"Pick a number from 1 to 6." ^0
"Bye." ^0
Python projection (main.py)
import accounts
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 address_ok(t):
if len(t) < 3 or len(t) > 12:
return False
ats = 0
for c in t:
if c == "@":
ats = ats + 1
elif c < "a" or c > "z":
return False
return ats == 1 and t[0] != "@" and t[len(t) - 1] != "@"
def times(n, one, many):
if n == 1:
return "1 " + one
return str(n) + " " + many
def show(rows, truth, t):
print("It is second " + str(t) + ".")
i = 0
for s in accounts.schemes():
print(s + " (" + accounts.what()[i] + "):")
for r in rows[s]:
if r[0] == "":
a = "(no address)"
else:
a = r[0]
if r[1] == 0:
print(" " + pad(a, 14) + "active")
else:
print(" " + pad(a, 14) + "deleted in second " + str(r[1]))
i = i + 1
def signup(rows, truth, counts, t):
a = plain(input("address> "))
if a == "":
print("Cancelled.")
return [rows, truth, counts]
if not address_ok(a):
print("Type an address like ana@x: letters a to z with one @, 3 to 12 characters.")
return [rows, truth, counts]
meant = accounts.active(truth, a) < 0
if meant:
print("Sign-up for " + a + " in second " + str(t) + " - the rule meant: accept, no active account has it.")
else:
print("Sign-up for " + a + " in second " + str(t) + " - the rule meant: refuse, an active account has it.")
out = {}
for s in accounts.schemes():
r = accounts.signup(s, rows[s], a)
out[s] = r[0]
if r[1] == "ok":
said = "accepted"
elif r[1] == "active":
said = "refused: an active account has the address"
else:
said = "refused: a deleted account still has the address"
if meant and r[1] != "ok":
said = said + " <- wrong"
counts[s + " wrong"] = counts[s + " wrong"] + 1
if not meant and r[1] == "ok":
said = said + " <- wrong: two active accounts"
counts[s + " dup"] = counts[s + " dup"] + 1
print(" " + pad(s, 10) + said)
if meant:
truth = truth + [[a, 0]]
return [out, truth, counts]
def delete(rows, truth, counts, t):
a = plain(input("address> "))
if a == "":
print("Cancelled.")
return [rows, truth, counts]
if not address_ok(a):
print("Type an address like ana@x: letters a to z with one @, 3 to 12 characters.")
return [rows, truth, counts]
if accounts.active(truth, a) < 0:
print("No account with the address " + a + " is active.")
return [rows, truth, counts]
print("Deleting " + a + " in second " + str(t) + ":")
out = {}
for s in accounts.schemes():
r = accounts.delete(s, rows[s], a, t)
out[s] = r[0]
if r[1] == "ok":
said = "deleted"
elif r[1] == "none":
said = "nothing to delete: no active account has the address here"
else:
said = "the deletion fails: a row deleted in second " + str(t) + " already has the key " + accounts.key(s, [a, t])
counts[s + " failed"] = counts[s + " failed"] + 1
print(" " + pad(s, 10) + said)
truth = accounts.delete("partial", truth, a, t)[0]
return [out, truth, counts]
def checks(rows, counts):
print(" wrongly two active deletions deleted rows")
print(" refused allowed failed keeping the address")
right = []
for s in accounts.schemes():
c = [counts[s + " wrong"], counts[s + " dup"], counts[s + " failed"]]
k = accounts.kept(rows[s])
d = accounts.deleted(rows[s])
print(" " + pad(s, 12) + lpad(str(c[0]), 6) + lpad(str(c[1]), 11) + lpad(str(c[2]), 13) + " " + str(k) + " of " + str(d))
if c[0] == 0 and c[1] == 0 and c[2] == 0 and k == d:
right = right + [s]
print("The rule meant: at most one active account per address.")
if len(right) > 0:
names = ""
for s in right:
if names == "":
names = s
else:
names = names + ", " + s
print("Right on every count so far: " + names + ".")
c = counts["plain wrong"]
if c > 0:
print("plain refused " + times(c, "sign-up", "sign-ups") + " the rule allows: its index still counts the deleted rows.")
k = accounts.kept(rows["nulled"])
d = accounts.deleted(rows["nulled"])
if k < d:
print("nulled decides right, and kept the address of " + str(k) + " of " + times(d, "deleted account", "deleted accounts") + ": the history no longer says whose they were.")
if counts["compound failed"] > 0:
print("compound failed " + times(counts["compound failed"], "deletion", "deletions") + ": two deletions of one address in the same second have the same key.")
if counts["compound wrong"] > 0:
print("compound refused " + times(counts["compound wrong"], "sign-up", "sign-ups") + " the rule allows: an account whose deletion failed is still active there.")
rows = {}
counts = {}
truth = []
for s in accounts.schemes():
rows[s] = []
counts[s + " wrong"] = 0
counts[s + " dup"] = 0
counts[s + " failed"] = 0
for a in accounts.opening():
for s in accounts.schemes():
rows[s] = accounts.signup(s, rows[s], a)[0]
truth = truth + [[a, 0]]
t = 2
print("== Member accounts ==")
print("Sign-ups and soft deletes kept four ways, against the rule meant: one active account per address.")
running = True
while running:
print("")
print("1) accounts 2) sign up 3) delete 4) next second 5) checks 6) quit")
choice = plain(input("choice> "))
if choice == "1":
show(rows, truth, t)
elif choice == "2":
r = signup(rows, truth, counts, t)
rows = r[0]
truth = r[1]
counts = r[2]
elif choice == "3":
r = delete(rows, truth, counts, t)
rows = r[0]
truth = r[1]
counts = r[2]
elif choice == "4":
t = t + 1
print("It is second " + str(t) + ".")
elif choice == "5":
checks(rows, counts)
elif choice == "6":
running = False
else:
print("Pick a number from 1 to 6.")
print("Bye.")
accounts.eml
eml# P143 member accounts - one table of accounts kept four ways. As in the
# corpus case soft-delete-and-uniqueness, a soft delete marks a row instead
# of removing it, and a unique index on the address still counts the
# deleted rows: the rule meant is "at most one ACTIVE account per address".
# A row is [address, second it was deleted]; 0 means active.
# plain unique on the address over every row
# partial unique on the address among active rows
# nulled a deleted row loses its address; unique over the rest
# compound unique on the address and the second it was deleted
def schemes():
return ["plain", "partial", "nulled", "compound"]
def what():
return ["unique on the address over every row", "unique on the address among active rows", "a deleted row loses its address", "unique on the address and the second it was deleted"]
def key(scheme, row):
# what the unique index compares; "" is not compared at all
if scheme == "partial":
if row[1] > 0:
return ""
return row[0]
if scheme == "compound":
return row[0] + "@" + str(row[1])
return row[0]
def clash(scheme, rows, k, skip):
# another row, not the one at position skip, with the same key
if k == "":
return False
0 => i
for r in rows:
if i != skip and key(scheme, r) == k:
return True
i + 1 => i
return False
def active(rows, address):
0 => i
for r in rows:
if r[0] == address and r[1] == 0:
return i
i + 1 => i
return 0 - 1
def signup(scheme, rows, address):
# [rows, "ok"], or the rows unchanged with "active" or "taken"
[address, 0] => row
if clash(scheme, rows, key(scheme, row), 0 - 1):
if active(rows, address) >= 0:
return [rows, "active"]
return [rows, "taken"]
return [rows + [row], "ok"]
def delete(scheme, rows, address, t):
# [rows, "ok"], or the rows unchanged with "none" or "clash"
active(rows, address) => i
if i < 0:
return [rows, "none"]
if scheme == "nulled":
["", t] => row
else:
[address, t] => row
if clash(scheme, rows, key(scheme, row), i):
return [rows, "clash"]
[] => out
0 => j
for r in rows:
if j == i:
out + [row] => out
else:
out + [r] => out
j + 1 => j
return [out, "ok"]
def kept(rows):
# deleted rows that still record their address
0 => n
for r in rows:
if r[1] > 0 and r[0] != "":
n + 1 => n
return n
def deleted(rows):
0 => n
for r in rows:
if r[1] > 0:
n + 1 => n
return n
def opening():
return ["ana@x", "bob@x", "cy@x"]
Python projection (accounts.py)
def schemes():
return ["plain", "partial", "nulled", "compound"]
def what():
return ["unique on the address over every row", "unique on the address among active rows", "a deleted row loses its address", "unique on the address and the second it was deleted"]
def key(scheme, row):
if scheme == "partial":
if row[1] > 0:
return ""
return row[0]
if scheme == "compound":
return row[0] + "@" + str(row[1])
return row[0]
def clash(scheme, rows, k, skip):
if k == "":
return False
i = 0
for r in rows:
if i != skip and key(scheme, r) == k:
return True
i = i + 1
return False
def active(rows, address):
i = 0
for r in rows:
if r[0] == address and r[1] == 0:
return i
i = i + 1
return 0 - 1
def signup(scheme, rows, address):
row = [address, 0]
if clash(scheme, rows, key(scheme, row), 0 - 1):
if active(rows, address) >= 0:
return [rows, "active"]
return [rows, "taken"]
return [rows + [row], "ok"]
def delete(scheme, rows, address, t):
i = active(rows, address)
if i < 0:
return [rows, "none"]
if scheme == "nulled":
row = ["", t]
else:
row = [address, t]
if clash(scheme, rows, key(scheme, row), i):
return [rows, "clash"]
out = []
j = 0
for r in rows:
if j == i:
out = out + [row]
else:
out = out + [r]
j = j + 1
return [out, "ok"]
def kept(rows):
n = 0
for r in rows:
if r[1] > 0 and r[0] != "":
n = n + 1
return n
def deleted(rows):
n = 0
for r in rows:
if r[1] > 0:
n = n + 1
return n
def opening():
return ["ana@x", "bob@x", "cy@x"]