Generic BigList Utilities (#15)(an instance of Generic Utilities Package made by Hacker)     Generic BigList Utilities      ----------------------------      This is a package for maintaining huge persistent (sorted) lists in a format that is less likely to spam the server (which runs into a certain amount of trouble dealing with long ordinary lists --- btw we use `biglist' to refer to the huge data structure we're about to describe and `list' to refer to ordinary MOO lists {...}). The biglist in question lives on a particular object, to which we will refer in the discussion below as the `home' object, and its various elements appear as leaves of a tree whose nodes are kept in properties of the home object. It should be noted that the home object does not need to be (and in fact should *not* be) a descendant of this one; this object merely provides utilities for manipulating the properties on the home object that are used in a particular biglist manipulation.            All of the utilities below refer to `caller' to locate the home object. Thus verbs to manipulate a given biglist must be located on or inherited by its home object itself. The home object needs to define the following verbs             :_make(@args) => new property on home object with value args       :_kill(prop) delete a given property that was created by :_make       :_get(prop) => home.prop       :_put(prop,@args) set home.prop = args       :_ord(element) given something that is of the form of a biglist element       return the corresponding ordinal (for sorting purposes).       If you never intend to use :find_ord, then this can be a       routine that always returns 0 or some other random value.            See #5546 (Generic Biglist Resident) or $big_mail_recipient      for examples.            Those of the following routines that take a biglist argument are expecting      either {} (empty biglist) or some biglist returned by one of the other routines             :length(biglist) => length(biglist) (i.e., number of elements)       :find_nth(biglist,n) => biglist[n]       :find_ord(biglist,k,comp) => n where n is       the largest such that home:(comp)(k,home:_ord(biglist[n])) is false, or       the smallest such that home:(comp)(k,home:_ord(biglist[n+1])) is true.       Always returns a value between 0 and length(biglist) inclusive.       This assumes biglist to be sorted in order of increasing :_ord values       with respect to home:(comp)().       Standard situation is :_ord returns a number and comp is a < verb.             :start(biglist,s,e) => {biglist[s..?],@handle} or {}       :next(@handle) => {biglist[?+1..??],@newhandle} or {}       These two are used for iterating over a range of elements of a biglist       The canonical incantation for doing       for elt in (biglist[first..last])       ...       endfor       is       handle = :start(biglist,first,last);       while(handle)       for elt in (handle[1])       ...       endfor       handle = :next(@listdelete(handle,1));       endwhile            The following all destructively modify their biglist argument(s) L (and M).             :set_nth(L,n,value) => L[n] = value       replaces the indicated element             :insert_before(L,M,n) => {@L[1..n-1],@M,@L[n..length(L)]}       :insert_after (L,M,n) => {@L[1..n], @M,@L[n+1..length(L)]}       takes two distinct biglists, inserts one into the other at the given point       returns the resulting consolidated biglist             :extract_range(L,m,n) => {{@L[1..m-1],@L[n+1..]}, L[m..n]}       breaks the given biglist into two distinct biglists.             :delete_range(L,m,n[,leafkiller]) => {@L[1..m-1],@L[n+1..]}       :keep_range (L,m,n[,leafkiller]) => L[m..n]       like extract_range only we destroy what we don't want.             :insertlast(L,value) => {@L,value}       inserts a new element at the end of biglist.       If find_ord is to continue to work properly, it is assumed that the       home:_ord(elt) is greater (comp-wise) than all of the :_ord values       of elements currently in the biglist.             :kill(L[,leafkiller])       destroys all nodes used by biglist.       Calls home:leafkiller on each element. VERB SOURCE CODE: length: ":length(tree) => number of leaves in tree."; return args[1] ? args[1][2] | 0; . find_nth: ":find_nth(tree,n) => nth leaf of tree. Assumes n in [1..tree[2]]"; return this:_find_nth(caller, @args); . find_ord: ":_find_ord(tree,n,comp) "; " => index of rightmost leaf for which :(comp)(n,:_ord(leaf)) is false."; "returns 0 if true for all leaves."; return args[1] ? this:_find_ord(caller, @args) | 0; . set_nth:
":set_nth(tree,n,value) => tree";
"modifies tree so that nth leaf == value";
if (((n = args[2]) < 1) || ((!(tree = args[1])) || (tree[2] < n)))
return E_RANGE;
else
this:_set_nth(caller, @args);
return (n != 1) ? tree | listset(tree, caller:_ord(args[3]), 3);
endif
.
kill:
":kill(tree[,leafverb]) deletes tree and _kills all of the nodes that it uses.";
"if leafverb is given, caller:leafverb is called on all leaves in tree.";
if (tree = args[1])
lverb = {@args, ""}[2];
this:_skill(caller, (typeof(tree) == LIST) ? tree[1] | tree, lverb);
endif
"... otherwise nothing to do...";
.
insert_after insert_before:
":insert_after(tree,subtree,n)";
":insert_before(tree,subtree,n)";
" inserts subtree after (before) the nth leaf of tree,";
" returning the resulting tree.";
subtree = args[2];
if (tree = args[1])
if (subtree)
where = args[3] - (verb == "insert_before");
if (where <= 0)
return this:_merge(caller, subtree, tree);
elseif (where >= tree[2])
return this:_merge(caller, tree, subtree);
else
s = this:_split(caller, caller:_get(tree[1])[1], where, tree);
return this:_merge(caller, this:_merge(caller, s[1], subtree), s[2]);
endif
else
return tree;
endif
else
return subtree;
endif
.
extract_range:
":extract_range(tree,first,last) => {newtree,extraction}";
return this:_extract(caller, @args);
.
delete_range:
":delete_range(tree,first,last[,leafkill]) => newtree";
extract = this:_extract(caller, @args);
if (die = extract[2])
this:_skill(caller, die[1], {@args, ""}[4]);
endif
return extract[1];
.
keep_range:
":keep_range(tree,first,last[,leafkill]) => range";
extract = this:_extract(caller, @args);
if (die = extract[1])
this:_skill(caller, die[1], {@args, ""}[4]);
endif
return extract[2];
.
insert_last:
":insert_last(tree,insert) => newtree";
"insert a new leaf to be inserted at the righthand end of the tree";
tree = args[1];
insert = args[2];
if (!tree)
return {caller:_make(0, {insert}), 1, caller:_ord(insert)};
endif
hgt = caller:_get(tree[1]);
rspine = {{tree, plen = length(kids = hgt[2])}};
for i in [1..hgt[1]]
parent = kids[plen];
kids = caller:_get(parent[1])[2];
plen = length(kids);
rspine = {{parent, plen}, @rspine};
endfor
iord = caller:_ord(insert);
for h in [1..length(rspine)]
"... tree is the plen'th (rightmost) child of parent...";
if (rspine[h][2] < this.maxfanout)
parent = rspine[h][1];
hgp = caller:_get(parent[1]);
caller:_put(parent[1], @listset(hgp, {@hgp[2], insert}, 2));
for p in (rspine[h + 1..length(rspine)])
rkid = listset(parent, parent[2] + 1, 2);
parent = p[1];
hgp = caller:_get(parent[1]);
caller:_put(parent[1], @listset(hgp, listset(hgp[2], rkid, p[2]), 2));
endfor
return listset(tree, tree[2] + 1, 2);
endif
insert = {caller:_make(h - 1, {insert}), 1, iord};
endfor
return {caller:_make(length(rspine), {tree, insert}), tree[2] + 1, tree[3]};
.
start:
":start(tree,first,last) => {list of leaf nodes, @handle}";
"handle is of the form {{node,next,size}...}";
if (tree = args[1])
before = max(0, args[2] - 1);
howmany = min(args[3], tree[2]) - before;
if (howmany <= 0)
return {};
else
spine = {};
for h in [1..caller:_get(tree[1])[1]]
ik = this:_listfind_nth(kids = caller:_get(tree[1])[2], before);
newh = kids[ik[1]][2] - ik[2];
if (newh < howmany)
spine = {{tree[1], ik[1] + 1, howmany - newh}, @spine};
howmany = newh;
endif
tree = kids[ik[1]];
before = ik[2];
endfor
return {caller:_get(tree[1])[2][before + 1..before + howmany], @spine};
endif
else
return {};
endif
.
next:
":next(@handle) => {list of more leaf nodes, @newhandle}";
if (args)
spine = listdelete(args, 1);
node = args[1][1];
n = args[1][2];
size = args[1][3];
for h in [1..caller:_get(node)[1]]
nnode = caller:_get(node)[2][n];
if (size > nnode[2])
spine = {{node, n + 1, size - nnode[2]}, @spine};
size = nnode[2];
endif
n = 1;
node = nnode[1];
endfor
return {caller:_get(node)[2][n..size], @spine};
else
return {};
endif
.
_find_nth:
":_find_nth(home,tree,n) => nth leaf of tree.";
"...Assumes n in [1..tree[2]]";
if (caller != this)
return E_PERM;
endif
home = args[1];
tree = args[2];
n = args[3];
if ((p = home:_get(tree[1]))[1])
for k in (p[2])
if (n > k[2])
n = n - k[2];
else
return this:_find_nth(home, k, n);
endif
endfor
return E_RANGE;
else
return p[2][n];
endif
.
_find_ord:
":_find_ord(home,tree,n,comp) ";
" => index of rightmost leaf for which :(comp)(n,:_ord(leaf)) is false.";
"returns 0 if true for all leaves.";
if (caller != this)
return E_PERM;
endif
home = args[1];
tree = args[2];
n = args[3];
comp = args[4];
if ((p = home:_get(tree[1]))[1])
sz = tree[2];
for i in [-length(p[2])..-1]
k = p[2][-i];
sz = sz - k[2];
if (!home:(comp)(n, k[3]))
return sz + this:_find_ord(home, k, n, comp);
endif
endfor
return 0;
else
for i in [1..r = length(p[2])]
if (home:(comp)(n, home:_ord(p[2][i])))
return i - 1;
endif
endfor
return r;
endif
.
_set_nth:
":_set_nth(home,tree,n,value) => tree[n] = value";
"Assumes n in [1..tree[2]]";
if (caller != this)
return E_PERM;
endif
home = args[1];
tree = args[2];
n = args[3];
value = args[4];
if ((p = home:_get(tree[1]))[1])
ik = this:_listfind_nth(p[2], n - 1);
this:_set_nth(home, p[2][ik[1]], ik[2] + 1, value);
if (!ik[2])
p[2][ik[1]][3] = home:_ord(value);
home:_put(tree[1], @p);
endif
else
p[2][n] = value;
home:_put(tree[1], @p);
endif
.
_skill:
":_skill(home,node,leafverb)";
"home:_kill's node and all descendants, home:(leafverb)'s all leaves";
if (caller != this)
return E_PERM;
endif
home = args[1];
hgn = home:_get(node = args[2]) || {0, {}};
lverb = args[3];
if (hgn[1])
for kid in (hgn[2])
this:_skill(home, kid[1], lverb);
endfor
elseif (lverb)
for kid in (hgn[2])
home:(lverb)(kid);
endfor
endif
home:_kill(node);
.
_extract:
":_extract(home,tree,first,last) => {newtree,extraction}";
if (caller != this)
return E_PERM;
endif
home = args[1];
if (!(tree = args[2]))
return {{}, {}};
endif
before = max(0, args[3] - 1);
end = min(tree[2], args[4]);
if ((end <= 0) || (before >= end))
return {tree, {}};
endif
height = home:_get(tree[1])[1];
if (end < tree[2])
r = this:_split(home, height, end, tree);
if (before)
l = this:_split(home, height, before, r[1]);
extract = l[2];
newtree = this:_merge(home, l[1], r[2]);
else
extract = r[1];
newtree = r[2];
endif
elseif (before)
l = this:_split(home, height, before, tree);
extract = l[2];
newtree = l[1];
else
return {{}, tree};
endif
return {this:_scrunch(home, newtree), this:_scrunch(home, extract)};
.
_merge:
"_merge(home,ltree,rtree) => newtree";
"assumes ltree and rtree to be nonempty.";
if (caller != this)
return E_PERM;
endif
home = args[1];
lnode = args[2];
rnode = args[3];
lh = home:_get(lnode[1])[1];
rh = home:_get(rnode[1])[1];
if (lh > rh)
return this:_rmerge(home, lnode, rnode);
endif
for h in [lh + 1..rh]
lnode[1] = home:_make(h, {lnode});
endfor
m = this:_smerge(home, rh, lnode, rnode);
return (length(m) <= 1) ? m[1] | {home:_make(rh + 1, m), m[1][2] + m[2][2], m[1][3]};
.
_smerge:
"_smerge(home, height, ltree, rtree) =>{ltree[,rtree]}";
"assumes ltree and rtree are at the given height.";
"merges the trees if the combined number of children is <= maxfanout";
"otherwise returns two trees where ltree is guaranteed minfanout children and rtree
is guaranteed the minimum of minfanout and however many children it started with.";
if (caller != this)
return E_PERM;
endif
home = args[1];
height = args[2];
ltree = args[3];
rtree = args[4];
llen = length(lkids = home:_get(ltree[1])[2]);
rlen = length(rkids = home:_get(rtree[1])[2]);
if (height)
m = this:_smerge(home, height - 1, lkids[llen], rkids[1]);
mlen = length(mkids = {@listdelete(lkids, llen), @m, @listdelete(rkids, 1)});
if (mlen <= this.maxfanout)
home:_put(ltree[1], height, mkids);
home:_kill(rtree[1]);
ltree[2] = ltree[2] + rtree[2];
return {ltree};
else
S = max(llen - 1, (mlen + 1) / 2);
home:_put(ltree[1], height, mkids[1..S]);
home:_put(rtree[1], height, mkids[S + 1..length(mkids)]);
xfer = -lkids[llen][2];
for k in (mkids[llen..S])
xfer = xfer + k[2];
endfor
ltree[2] = ltree[2] + xfer;
rtree[2] = rtree[2] - xfer;
rtree[3] = mkids[S + 1][3];
return {ltree, rtree};
endif
elseif ((llen * 2) >= this.maxfanout)
return {ltree, rtree};
elseif (this.maxfanout < (llen + rlen))
T = ((rlen - llen) + 1) / 2;
home:_put(ltree[1], 0, {@lkids, @rkids[1..T]});
home:_put(rtree[1], 0, rkids[T + 1..rlen]);
ltree[2] = ltree[2] + T;
rtree[2] = rtree[2] - T;
rtree[3] = home:_ord(rkids[T + 1]);
return {ltree, rtree};
else
home:_put(ltree[1], 0, {@lkids, @rkids});
home:_kill(rtree[1]);
ltree[2] = ltree[2] + rtree[2];
return {ltree};
endif
.
_split:
"_split(home, height,lmax,ltree[,@rtrees]}) => {ltree,[mtree,]@rtrees}";
"ltree is split after the lmax'th leaf, the righthand portion grafted onto the leftmost
of the rtrees, if possible. Otherwise we create a new tree mtree, stealing from
rtrees[1] if necessary.";
"Assumes 1<=lmax
_rmerge:
":_rmerge(home, tree, insertree) => newtree ";
"(newtree is tree with insertree appended to the right)";
"insertree is assumed to be of height < tree";
if (caller != this)
return E_PERM;
endif
home = args[1];
tree = args[2];
insert = args[3];
if (!tree)
return insert;
elseif (!insert)
return tree;
endif
iheight = home:_get(insert[1])[1];
rspine = {};
for i in [iheight + 1..home:_get(tree[1])[1]]
kids = home:_get(tree[1])[2];
tlen = length(kids);
rspine = {{tree, tlen}, @rspine};
tree = kids[tlen];
endfor
isize = insert[2];
m = this:_smerge(home, iheight, tree, insert);
for h in [1..length(rspine)]
plen = rspine[h][2];
parent = rspine[h][1];
hgp = home:_get(parent[1]);
if (((length(m) - 1) + plen) > this.maxfanout)
home:_put(parent[1], @listset(hgp, listset(hgp[2], m[1], plen), 2));
parent[2] = (parent[2] + isize) - m[2][2];
m = {parent, listset(m[2], home:_make(h + iheight, {m[2]}), 1)};
else
home:_put(parent[1], @listset(hgp, {@hgp[2][1..plen - 1], @m}, 2));
for p in (rspine[h + 1..length(rspine)])
parent[2] = parent[2] + isize;
tree = parent;
parent = p[1];
hgp = home:_get(parent[1]);
home:_put(parent[1], @listset(hgp, listset(hgp[2], tree, p[2]), 2));
endfor
return listset(parent, parent[2] + isize, 2);
endif
endfor
return {home:_make((length(rspine) + iheight) + 1, m), m[1][2] + m[2][2], m[1][3]};
.
_scrunch:
":_scrunch(home,tree) => newtree";
"decapitates single-child nodes from the top of the tree, returns new root.";
if (caller != this)
return E_PERM;
endif
if (tree = args[2])
home = args[1];
while ((n = home:_get(tree[1]))[1] && (length(n[2]) == 1))
home:_kill(tree[1]);
tree = n[2][1];
endwhile
endif
return tree;
.
_listfind_nth:
"_listfind_nth(nodelist,key) => {i,k} where i is the smallest i such that the sum
of the first i elements of intlist is > key, and k==key - sum(first i-1 elements).";
"1 <= i <= length(intlist)+1";
lst = args[1];
key = args[2];
for i in [1..length(lst)]
key = key - lst[i][2];
if (0 > key)
return {i, key + lst[i][2]};
endif
endfor
return {length(lst) + 1, key};
.
_insertfirst:
if (caller != this)
return E_PERM;
endif
.
debug: return $perm_utils:controls(caller_perms(), this) ? this:(args[1])(@listdelete(args, 1)) | E_PERM; . PROPERTY DATA:       about       maxfanout |