Pagination
A feed read one page at a time while items are added and deleted between the requests: offset and keyset pagination side by side, and which rows each delivered twice or skipped - the skip hidden behind a full page.
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 feed, newest first, read one page at a time while items are added and deleted between the requests. Offset pagination and keyset (cursor) pagination run side by side, and the program reports what each one delivered.
main.eml- the menu, the side-by-side walk and its verdict, the events, the page size and the feed sizepages.eml- the feed, the walk under both kinds of pagination, the tally of what was delivered, and reading the events
How each part works:
- Offset pagination asks for "the next 5 after the first 10". Keyset pagination asks for "the next 5 older than the last one I got".
- Events run in order: - page asks both for the next page; - new adds an item at the top; - del N deletes item N.
- As in the corpus case
pagination-shifting-data: - an item added before the window makes offset pagination deliver a row a second time; - an item deleted before the window makes it skip one, and the page that goes past the skipped row still comes back full, so nothing in it shows that a row is missing; - keyset pagination does neither. - The verdict judges only the items that were there for the whole walk, not added and not deleted. An item older than everything delivered so far has not been reached yet, rather than skipped.
What is checked: menu choices 1 to 5; up to 30 events, made of page, new and del followed by a number of up to three digits, with at least one page; 2 to 10 rows to a page; a feed of 5 to 60 items. An empty answer cancels.
Sessions: sessions/basic.in gives: - the starting walk: an item added after page 1 makes offset repeat 16, and 17 deleted after page 2 makes it skip 11 on a full page; - four pages and nothing else: both clean; - two items added after page 1: offset repeats 17 and 16; - an item deleted after page 2, ahead of both; - 4 to a page, with 20 and 19 deleted after pages 1 and 2: offset skips 16 and 11, each on a full page; keyset delivers every one.
sessions/bad-input.in gives: - menu choices 0 and x; - events: a cancel, an unknown word, del with no number, del with a letter, no page at all, a four-digit number; then a script that deletes 21 twice, and its walk; - page sizes: a cancel, 1 and 11; feed sizes: a cancel, 4 and 61, then 7, and the walk again.
Built on the verified corpus case pagination-shifting-data (offset pagination repeats a row on an insert and skips one on a delete, the skip invisible behind a full page).
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== Pagination ==
A feed read a page at a time while items are added and deleted: by offset and by key, side by side.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 0
Pick a number from 1 to 5.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> x
Pick a number from 1 to 5.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 2
Events: page (ask for the next page), new (an item added at the top), del N (item N deleted); up to 30.
events>
Cancelled.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 2
Events: page (ask for the next page), new (an item added at the top), del N (item N deleted); up to 30.
events> jump
Type words page, new and del followed by a number, separated by spaces.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 2
Events: page (ask for the next page), new (an item added at the top), del N (item N deleted); up to 30.
events> page del
Type words page, new and del followed by a number, separated by spaces.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 2
Events: page (ask for the next page), new (an item added at the top), del N (item N deleted); up to 30.
events> page del x
Type words page, new and del followed by a number, separated by spaces.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 2
Events: page (ask for the next page), new (an item added at the top), del N (item N deleted); up to 30.
events> del 3 new
Ask for at least one page.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 2
Events: page (ask for the next page), new (an item added at the top), del N (item N deleted); up to 30.
events> page del 1234
Type words page, new and del followed by a number, separated by spaces.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 2
Events: page (ask for the next page), new (an item added at the top), del N (item N deleted); up to 30.
events> new del 21 del 21 page
Events: new del 21 del 21 page.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 1
Feed: 20 items, newest first; 5 to a page; events: new del 21 del 21 page.
by offset by key
new: item 21 added at the top
del 21: item 21 deleted
del 21: there is no item 21 now
page 1 20 19 18 17 16 20 19 18 17 16
Judged on the items that were there for the whole walk, not added and not deleted:
by offset: every one reached was delivered exactly once; 15 older not reached yet
by key: every one reached was delivered exactly once; 15 older not reached yet
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 3
rows to a page (2 to 10)>
Cancelled.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 3
rows to a page (2 to 10)> 1
Type a whole number from 2 to 10.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 3
rows to a page (2 to 10)> 11
Type a whole number from 2 to 10.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 4
items in the feed (5 to 60)>
Cancelled.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 4
items in the feed (5 to 60)> 4
Type a whole number from 5 to 60.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 4
items in the feed (5 to 60)> 61
Type a whole number from 5 to 60.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 4
items in the feed (5 to 60)> 7
A feed of 7 items, 7 down to 1.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 1
Feed: 7 items, newest first; 5 to a page; events: new del 21 del 21 page.
by offset by key
new: item 8 added at the top
del 21: there is no item 21 now
del 21: there is no item 21 now
page 1 8 7 6 5 4 8 7 6 5 4
Judged on the items that were there for the whole walk, not added and not deleted:
by offset: every one reached was delivered exactly once; 3 older not reached yet
by key: every one reached was delivered exactly once; 3 older not reached yet
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 5
Bye.
What was typed (33 lines)
0
x
2
2
jump
2
page del
2
page del x
2
del 3 new
2
page del 1234
2
new del 21 del 21 page
1
3
3
1
3
11
4
4
4
4
61
4
7
1
5
basic
interpreter: byte-equal== Pagination ==
A feed read a page at a time while items are added and deleted: by offset and by key, side by side.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 1
Feed: 20 items, newest first; 5 to a page; events: page new page del 17 page page page.
by offset by key
page 1 20 19 18 17 16 20 19 18 17 16
new: item 21 added at the top
page 2 16 15 14 13 12 15 14 13 12 11
del 17: item 17 deleted
page 3 10 9 8 7 6 10 9 8 7 6
page 4 5 4 3 2 1 5 4 3 2 1
page 5 (empty) (empty)
Judged on the items that were there for the whole walk, not added and not deleted:
by offset: 16 delivered twice; 11 never delivered
page 3 went past 11 and still came back with all 5 rows: nothing in it shows a row is missing
by key: every one reached was delivered exactly once
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 2
Events: page (ask for the next page), new (an item added at the top), del N (item N deleted); up to 30.
events> page page page page
Events: page page page page.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 1
Feed: 20 items, newest first; 5 to a page; events: page page page page.
by offset by key
page 1 20 19 18 17 16 20 19 18 17 16
page 2 15 14 13 12 11 15 14 13 12 11
page 3 10 9 8 7 6 10 9 8 7 6
page 4 5 4 3 2 1 5 4 3 2 1
Judged on the items that were there for the whole walk, not added and not deleted:
by offset: every one reached was delivered exactly once
by key: every one reached was delivered exactly once
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 2
Events: page (ask for the next page), new (an item added at the top), del N (item N deleted); up to 30.
events> page new new page page
Events: page new new page page.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 1
Feed: 20 items, newest first; 5 to a page; events: page new new page page.
by offset by key
page 1 20 19 18 17 16 20 19 18 17 16
new: item 21 added at the top
new: item 22 added at the top
page 2 17 16 15 14 13 15 14 13 12 11
page 3 12 11 10 9 8 10 9 8 7 6
Judged on the items that were there for the whole walk, not added and not deleted:
by offset: 17, 16 delivered twice; 7 older not reached yet
by key: every one reached was delivered exactly once; 5 older not reached yet
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 2
Events: page (ask for the next page), new (an item added at the top), del N (item N deleted); up to 30.
events> page page del 3 page
Events: page page del 3 page.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 1
Feed: 20 items, newest first; 5 to a page; events: page page del 3 page.
by offset by key
page 1 20 19 18 17 16 20 19 18 17 16
page 2 15 14 13 12 11 15 14 13 12 11
del 3: item 3 deleted
page 3 10 9 8 7 6 10 9 8 7 6
Judged on the items that were there for the whole walk, not added and not deleted:
by offset: every one reached was delivered exactly once; 4 older not reached yet
by key: every one reached was delivered exactly once; 4 older not reached yet
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 3
rows to a page (2 to 10)> 4
4 rows to a page.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 2
Events: page (ask for the next page), new (an item added at the top), del N (item N deleted); up to 30.
events> page del 20 page del 19 page page page page
Events: page del 20 page del 19 page page page page.
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 1
Feed: 20 items, newest first; 4 to a page; events: page del 20 page del 19 page page page page.
by offset by key
page 1 20 19 18 17 20 19 18 17
del 20: item 20 deleted
page 2 15 14 13 12 16 15 14 13
del 19: item 19 deleted
page 3 10 9 8 7 12 11 10 9
page 4 6 5 4 3 8 7 6 5
page 5 2 1 4 3 2 1
page 6 (empty) (empty)
Judged on the items that were there for the whole walk, not added and not deleted:
by offset: 16, 11 never delivered
page 2 went past 16 and still came back with all 4 rows: nothing in it shows a row is missing
page 3 went past 11 and still came back with all 4 rows: nothing in it shows a row is missing
by key: every one reached was delivered exactly once
1) walk the feed 2) events 3) page size 4) feed size 5) quit
choice> 5
Bye.
What was typed (16 lines)
1
2
page page page page
1
2
page new new page page
1
2
page page del 3 page
1
3
4
2
page del 20 page del 19 page page page page
1
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# P110 pagination: a feed read page by page while items come and go,
# by offset and by key side by side, and what each one delivered.
import pages
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 number(s, most):
if len(s) == 0 or len(s) > most:
return -1
0 => n
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n * 10 + int(c) => n
return n
def pad(s, n):
while len(s) < n:
s + " " => s
return s
def ids(xs):
if len(xs) == 0:
return "(empty)"
"" => out
for x in xs:
if out != "":
out + " " => out
out + str(x) => out
return out
def listed(xs):
"" => out
for x in xs:
if out != "":
out + ", " => out
out + str(x) => out
return out
def events_text(events):
"" => out
for e in events:
if out != "":
out + " " => out
if e[0] == "del":
out + "del " + str(e[1]) => out
else:
out + e[0] => out
return out
def show_walk(n, events, size):
pages.feed(n) => start
pages.walk(start, events, size) => w
("Feed: " + str(n) + " items, newest first; " + str(size) + " to a page; events: " + events_text(events) + ".") ^0
(" " + pad("", 9) + pad("by offset", 3 * size + 4) + "by key") ^0
0 => k
n => top
start => present
for e in events:
if e[0] == "page":
(" " + pad("page " + str(k + 1), 9) + pad(ids(w[0][k]), 3 * size + 4) + ids(w[1][k])) ^0
k + 1 => k
elif e[0] == "new":
top + 1 => top
[top] + present => present
(" new: item " + str(top) + " added at the top") ^0
elif pages.has(present, e[1]):
pages.without(present, e[1]) => present
(" del " + str(e[1]) + ": item " + str(e[1]) + " deleted") ^0
else:
(" del " + str(e[1]) + ": there is no item " + str(e[1]) + " now") ^0
"Judged on the items that were there for the whole walk, not added and not deleted:" ^0
["by offset", "by key"] => names
for m in [0:1]:
pages.tally(start, w[m], w[2], w[3]) => t
"" => tail
if t[2] > 0:
"; " + str(t[2]) + " older not reached yet" => tail
if len(t[0]) == 0 and len(t[1]) == 0:
(" " + names[m] + ": every one reached was delivered exactly once" + tail) ^0
else:
"" => line
if len(t[1]) > 0:
listed(t[1]) + " delivered twice" => line
if len(t[0]) > 0:
if line != "":
line + "; " => line
line + listed(t[0]) + " never delivered" => line
(" " + names[m] + ": " + line + tail) ^0
for r in t[0]:
# the page that should have held r: the first with an older item
-1 => at
for i in [0:len(w[m]) - 1]:
if at == -1:
for x in w[m][i]:
if x < r:
i => at
if at >= 0:
if len(w[m][at]) == size:
(" page " + str(at + 1) + " went past " + str(r) + " and still came back with all " + str(size) + " rows: nothing in it shows a row is missing") ^0
else:
(" page " + str(at + 1) + " went past " + str(r)) ^0
def set_events():
"Events: page (ask for the next page), new (an item added at the top), del N (item N deleted); up to 30." ^0
plain(input("events> ")) => t
if t == "":
"Cancelled." ^0
return []
pages.parse(t) => e
if len(e) == 0:
"Type words page, new and del followed by a number, separated by spaces." ^0
return []
0 => p
for x in e:
if x[0] == "page":
p + 1 => p
if p == 0:
"Ask for at least one page." ^0
return []
("Events: " + events_text(e) + ".") ^0
return e
[["page", 0], ["new", 0], ["page", 0], ["del", 17], ["page", 0], ["page", 0], ["page", 0]] => events
20 => n
5 => size
"== Pagination ==" ^0
"A feed read a page at a time while items are added and deleted: by offset and by key, side by side." ^0
True => running
while running:
"" ^0
"1) walk the feed 2) events 3) page size 4) feed size 5) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
show_walk(n, events, size)
elif choice == "2":
set_events() => e
if len(e) > 0:
e => events
elif choice == "3":
plain(input("rows to a page (2 to 10)> ")) => t
number(t, 2) => v
if t == "":
"Cancelled." ^0
elif v < 2 or v > 10:
"Type a whole number from 2 to 10." ^0
else:
v => size
(str(v) + " rows to a page.") ^0
elif choice == "4":
plain(input("items in the feed (5 to 60)> ")) => t
number(t, 2) => v
if t == "":
"Cancelled." ^0
elif v < 5 or v > 60:
"Type a whole number from 5 to 60." ^0
else:
v => n
("A feed of " + str(v) + " items, " + str(v) + " down to 1.") ^0
elif choice == "5":
False => running
else:
"Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import pages
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 number(s, most):
if len(s) == 0 or len(s) > most:
return -1
n = 0
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n = n * 10 + int(c)
return n
def pad(s, n):
while len(s) < n:
s = s + " "
return s
def ids(xs):
if len(xs) == 0:
return "(empty)"
out = ""
for x in xs:
if out != "":
out = out + " "
out = out + str(x)
return out
def listed(xs):
out = ""
for x in xs:
if out != "":
out = out + ", "
out = out + str(x)
return out
def events_text(events):
out = ""
for e in events:
if out != "":
out = out + " "
if e[0] == "del":
out = out + "del " + str(e[1])
else:
out = out + e[0]
return out
def show_walk(n, events, size):
start = pages.feed(n)
w = pages.walk(start, events, size)
print("Feed: " + str(n) + " items, newest first; " + str(size) + " to a page; events: " + events_text(events) + ".")
print(" " + pad("", 9) + pad("by offset", 3 * size + 4) + "by key")
k = 0
top = n
present = start
for e in events:
if e[0] == "page":
print(" " + pad("page " + str(k + 1), 9) + pad(ids(w[0][k]), 3 * size + 4) + ids(w[1][k]))
k = k + 1
elif e[0] == "new":
top = top + 1
present = [top] + present
print(" new: item " + str(top) + " added at the top")
elif pages.has(present, e[1]):
present = pages.without(present, e[1])
print(" del " + str(e[1]) + ": item " + str(e[1]) + " deleted")
else:
print(" del " + str(e[1]) + ": there is no item " + str(e[1]) + " now")
print("Judged on the items that were there for the whole walk, not added and not deleted:")
names = ["by offset", "by key"]
for m in range(0, 2):
t = pages.tally(start, w[m], w[2], w[3])
tail = ""
if t[2] > 0:
tail = "; " + str(t[2]) + " older not reached yet"
if len(t[0]) == 0 and len(t[1]) == 0:
print(" " + names[m] + ": every one reached was delivered exactly once" + tail)
else:
line = ""
if len(t[1]) > 0:
line = listed(t[1]) + " delivered twice"
if len(t[0]) > 0:
if line != "":
line = line + "; "
line = line + listed(t[0]) + " never delivered"
print(" " + names[m] + ": " + line + tail)
for r in t[0]:
at = -1
for i in range(0, len(w[m])):
if at == -1:
for x in w[m][i]:
if x < r:
at = i
if at >= 0:
if len(w[m][at]) == size:
print(" page " + str(at + 1) + " went past " + str(r) + " and still came back with all " + str(size) + " rows: nothing in it shows a row is missing")
else:
print(" page " + str(at + 1) + " went past " + str(r))
def set_events():
print("Events: page (ask for the next page), new (an item added at the top), del N (item N deleted); up to 30.")
t = plain(input("events> "))
if t == "":
print("Cancelled.")
return []
e = pages.parse(t)
if len(e) == 0:
print("Type words page, new and del followed by a number, separated by spaces.")
return []
p = 0
for x in e:
if x[0] == "page":
p = p + 1
if p == 0:
print("Ask for at least one page.")
return []
print("Events: " + events_text(e) + ".")
return e
events = [["page", 0], ["new", 0], ["page", 0], ["del", 17], ["page", 0], ["page", 0], ["page", 0]]
n = 20
size = 5
print("== Pagination ==")
print("A feed read a page at a time while items are added and deleted: by offset and by key, side by side.")
running = True
while running:
print("")
print("1) walk the feed 2) events 3) page size 4) feed size 5) quit")
choice = plain(input("choice> "))
if choice == "1":
show_walk(n, events, size)
elif choice == "2":
e = set_events()
if len(e) > 0:
events = e
elif choice == "3":
t = plain(input("rows to a page (2 to 10)> "))
v = number(t, 2)
if t == "":
print("Cancelled.")
elif v < 2 or v > 10:
print("Type a whole number from 2 to 10.")
else:
size = v
print(str(v) + " rows to a page.")
elif choice == "4":
t = plain(input("items in the feed (5 to 60)> "))
v = number(t, 2)
if t == "":
print("Cancelled.")
elif v < 5 or v > 60:
print("Type a whole number from 5 to 60.")
else:
n = v
print("A feed of " + str(v) + " items, " + str(v) + " down to 1.")
elif choice == "5":
running = False
else:
print("Pick a number from 1 to 5.")
print("Bye.")
pages.eml
eml# P110 pagination - a feed, newest first, read page by page while items
# are added and deleted between the requests. Offset pagination asks for
# "the next 5 after the first 10"; keyset (cursor) pagination asks for "the
# next 5 older than the last one I got". As in the corpus case
# pagination-shifting-data, an item added before the window makes offset
# pagination deliver a row twice, and an item deleted before the window
# makes it skip one - while every page still comes back full. Keyset
# pagination does neither.
#
# Events are "page", "new" (an item added at the top) and "del N".
def feed(n):
# ids n down to 1, newest first
[] => out
for i in [1:n]:
out + [n + 1 - i] => out
return out
def has(xs, x):
for y in xs:
if y == x:
return True
return False
def without(xs, x):
[] => out
for y in xs:
if y != x:
out + [y] => out
return out
def walk(start, events, size):
# [pages by offset, pages by key, ids added, ids deleted]; each page
# is a list of ids. A walk's requests stop at the end of the events.
start => items
0 => offset
-1 => cursor
[] => by_offset
[] => by_key
[] => added
[] => deleted
0 => top
for x in start:
if x > top:
x => top
for e in events:
if e[0] == "new":
top + 1 => top
[top] + items => items
added + [top] => added
elif e[0] == "del":
if has(items, e[1]):
without(items, e[1]) => items
deleted + [e[1]] => deleted
else:
[] => page
for k in [offset:offset + size - 1]:
if k < len(items):
page + [items[k]] => page
offset + size => offset
by_offset + [page] => by_offset
[] => kpage
for x in items:
if len(kpage) < size and (cursor == -1 or x < cursor):
kpage + [x] => kpage
if len(kpage) > 0:
kpage[len(kpage) - 1] => cursor
by_key + [kpage] => by_key
return [by_offset, by_key, added, deleted]
def tally(start, pages, added, deleted):
# [rows never delivered, rows delivered twice or more, rows not reached
# yet]: only rows that were there for the whole walk - not added, not
# deleted - are judged, and a row older than every row delivered is
# one the pages have not reached yet, not one they skipped
{} => seen
-1 => oldest
for p in pages:
for x in p:
if x in seen:
seen[x] + 1 => seen[x]
else:
1 => seen[x]
if oldest == -1 or x < oldest:
x => oldest
[] => missing
[] => twice
0 => later
for x in start:
if not has(deleted, x):
if not (x in seen):
if oldest != -1 and x > oldest:
missing + [x] => missing
else:
later + 1 => later
elif seen[x] > 1:
twice + [x] => twice
return [missing, twice, later]
def parse(text):
# "page new page del 17 page" -> [["page", 0], ["new", 0], ...] or []
[] => words
"" => w
for c in text + " ":
if c == " ":
if w != "":
words + [w] => words
"" => w
else:
w + c => w
[] => out
0 => i
while i < len(words):
if words[i] == "page" or words[i] == "new":
out + [[words[i], 0]] => out
i + 1 => i
elif words[i] == "del" and i + 1 < len(words):
0 => n
True => ok
for c in words[i + 1]:
if c >= "0" and c <= "9":
n * 10 + int(c) => n
else:
False => ok
if not ok or len(words[i + 1]) > 3:
return []
out + [["del", n]] => out
i + 2 => i
else:
return []
if len(out) == 0 or len(out) > 30:
return []
return out
Python projection (pages.py)
def feed(n):
out = []
for i in range(1, n+1):
out = out + [n + 1 - i]
return out
def has(xs, x):
for y in xs:
if y == x:
return True
return False
def without(xs, x):
out = []
for y in xs:
if y != x:
out = out + [y]
return out
def walk(start, events, size):
items = start
offset = 0
cursor = -1
by_offset = []
by_key = []
added = []
deleted = []
top = 0
for x in start:
if x > top:
top = x
for e in events:
if e[0] == "new":
top = top + 1
items = [top] + items
added = added + [top]
elif e[0] == "del":
if has(items, e[1]):
items = without(items, e[1])
deleted = deleted + [e[1]]
else:
page = []
for k in range(offset, offset + size):
if k < len(items):
page = page + [items[k]]
offset = offset + size
by_offset = by_offset + [page]
kpage = []
for x in items:
if len(kpage) < size and (cursor == -1 or x < cursor):
kpage = kpage + [x]
if len(kpage) > 0:
cursor = kpage[len(kpage) - 1]
by_key = by_key + [kpage]
return [by_offset, by_key, added, deleted]
def tally(start, pages, added, deleted):
seen = {}
oldest = -1
for p in pages:
for x in p:
if x in seen:
seen[x] = seen[x] + 1
else:
seen[x] = 1
if oldest == -1 or x < oldest:
oldest = x
missing = []
twice = []
later = 0
for x in start:
if not has(deleted, x):
if not x in seen:
if oldest != -1 and x > oldest:
missing = missing + [x]
else:
later = later + 1
elif seen[x] > 1:
twice = twice + [x]
return [missing, twice, later]
def parse(text):
words = []
w = ""
for c in text + " ":
if c == " ":
if w != "":
words = words + [w]
w = ""
else:
w = w + c
out = []
i = 0
while i < len(words):
if words[i] == "page" or words[i] == "new":
out = out + [[words[i], 0]]
i = i + 1
elif words[i] == "del" and i + 1 < len(words):
n = 0
ok = True
for c in words[i + 1]:
if c >= "0" and c <= "9":
n = n * 10 + int(c)
else:
ok = False
if not ok or len(words[i + 1]) > 3:
return []
out = out + [["del", n]]
i = i + 2
else:
return []
if len(out) == 0 or len(out) > 30:
return []
return out