-- forward declarations
local archive -- table to store texts of all files in the archive
local onLoad -- optionally defineable function for
local autoloader = {}
-- main code for this package
--[[
Sano Library, 1/14/2006 Release
Copyright 2005-2006 Paul Chiusano
Copyright 1994-2005 Lua.org, PUC-Rio.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
]]
local documentation = {}
local autoloader = {}
local makersMap = {
heap = "PairingHeap",
queue = "QueueVector",
list = "SkipVector",
skipvector = "SkipVector",
llist = "LinkedList",
map = "HashMap",
hmap= "HashMap",
hashmap = "HashMap",
omap = "SkipMap",
orderedmap = "SkipMap",
lmap = "LinkedMap",
set = "HashSet",
oset = "SkipSet",
orderedset = "SkipSet",
mset = "Multiset",
mmap = "Multimap",
lset = "LinkedSet",
linkedlist = "LinkedList",
linkedmap = "LinkedMap",
linkedset = "LinkedSet",
__index = function(t,k) return string.upper(string.sub(k,1,1))..string.lower(string.sub(k,2,-1)) end
}
setmetatable(makersMap, makersMap)
local makers = {
__index = function(t,k)
--print(makersMap[k])
local class = autoloader[makersMap[k]]
assert(class, "No class by the name "..k)
t[k] = function(...) return class:make(unpack(arg)) end
local name = makersMap[k]
documentation[t[k] ] = "\tmakers."..k.."(...) - Returns "..name..":make(...).\n"
return t[k]
end
}
setmetatable(makers, makers)
onLoad = function(name, module)
documentation[module] = module.documentation and module.documentation.general
if module.documentation then
for k,v in pairs(module.documentation) do
if k=="make" then
makers[k] = function(...) return module:make(unpack(arg)) end
end
if not documentation[k] then
documentation[k] = v
end
end
end
end
autoloader.makers = makers
autoloader.documentation = {}
local apidoc = autoloader.documentation
documentation[autoloader] = [[
sano - Sano is a library of Lua data structures and algorithms.
Among the highlights are: Several sequence types, including the humble
Tuple (a lightweight, hashable table wrapper), Vector (slightly more
industrial strength table wrapper, including negative indexing),
QueueVector (random access + efficient inserts at head OR tail),
a basic LinkedList, and SkipVector (a skip-list based random access
sequence with efficient inserts or removals at ANY position).
The library also includes several map implementations: among them the
basic HashMap (a table-based map with plenty of convenience methods
and support for user-defined hashing functions), an ordered,
mergeable, spliceable SkipMap, and a LinkedMap (iteration order same as
insertion order). For each of these map types, Sano also has
the parallel set type - HashSet, SkipSet, LinkedSet.
In addition, the library contains a PairingHeap (constant-time
mergeable heap), Multimap (implements one to many mappings), Multiset
(ordered or unordered), and numerous algorithms and utility functions
for working with all the data structures in the library.
To get help on any object, function, or module,
sano.summary(obj)
prints a brief one sentance
description of obj, and, if obj is an object or module, a brief one
sentance description of each of the methods of the object or functions
of the module. sano.doc(obj)
prints full
documentation for obj.
]]
apidoc.general = documentation[autoloader]
documentation[makers] = [[
sano.makers - Table of convenience functions for accessing the value
constructors of Sano data structures.
These function exist only to make creating data structures less
verbose. Each function in sano.makers returns class:make(...)
for some class.
The mapping between function name and class is as follows:
vector = Vector,
list, skipvector = SkipVector,
queue, queuevector = QueueVector,
llist, linkedlist = LinkedList,
heap, pairingheap = PairingHeap,
set, hset, hashset = HashSet,
oset, orderedset = SkipSet,
lset, linkedset = LinkedSet,
map, hmap, hashmap = HashMap,
omap, orderedmap = SkipMap,
lmap, linkedmap = LinkedMap,
phonebook = Phonebook,
multiset, mset = Multiset,
multimap, mmap = Multimap,
tuple = Tuple
Examples:
v = vector(1,2,3,4) <--> v = Vector:make(1,2,3,4)
sl = list(1,2,3,4) <--> sl = SkipVector:make(1,2,3,4)
q = queue(1,2,3,4) <--> q = QueueVector:make(1,2,3,4)
ll = llist(1,2,3,4) <--> ll = LinkedList:make(1,2,3,4)
s = set(1,2,3,4) <--> s = HashSet:make(1,2,3,4)
]]
-- TODO: this documentation parsing code is an ugly hack; clean up
local function getFirstSentance(str)
if not str then return '\t'..default end
return string.gfind(str, "(.-%.)%s")() or ""
end
local function getShortDescription(obj, default, localDoc)
return getFirstSentance(autoloader.description(obj,localDoc))
end
local function prepSummary(obj, skipGeneral) -- ugly hack alert
local ordering = autoloader.ordering
local methodPriorities = ordering.ranked{{""},{"new","make"}}
local name = string.gfind(obj.documentation.general, '%a+')()
local origObj = obj
local localDoc = origObj.documentation or {}
local localMethodsFirst = function(x,y)
local objX, objY = origObj[x], origObj[y]
local parentX = string.gfind(string.sub(localDoc[objX] or documentation[origObj[x] ] or "", 1, 30) or "", '(%a+):')()
local parentY = string.gfind(string.sub(localDoc[objY] or documentation[origObj[y] ] or "", 1, 30) or "", '(%a+):')()
--printCall(parentX, parentY)
if parentX == name and parentY ~= name then return x
elseif parentX ~= name and parentY == name then return y
else return x end
end
local order = ordering.chained(methodPriorities, localMethodsFirst, ordering.INCREASING)
local nameToObj = autoloader.SkipMap:new(order)
if not skipGeneral then nameToObj:add("", obj) end
while obj do
for k,v in pairs(obj) do
if k~="documentation" and k~="__index" and k~="source" and k~="loadAll" then
nameToObj:add(k,v) end
end
obj = getmetatable(obj) ~= obj and getmetatable(obj)
end
return nameToObj
end
local function summary(map, localDoc)
local buf = autoloader.makers.vector()
for name, obj in map:iter() do
buf:add(getShortDescription(obj, name, localDoc))
buf:add("\n\n")
end
return string.gsub(buf:asString(),"\n\n\n","\n")
end
--print(string.gfind(str,'(%a+):')())
local nilMsg = [[
Cannot print documentation for nil object!
]]
local function missingMsg(obj)
return "No documentation found for: "..tostring(obj)
end
function autoloader.doc(obj, printCall)
printCall = printCall or print
if obj == nil then
printCall(nilMsg)
return nilMsg
end
local doc = documentation[obj]
if not string.find(doc,"%-") then doc = doc.." - ... " end
if obj==makers then
printCall(doc); return doc
elseif type(obj)=="table" then
local buf = autoloader.makers.vector()
buf:add(doc)
buf:add("\n\nSummary \n")
buf:add(autoloader.summary(obj, function() end, true))
printCall(buf:asString())
return buf:asString()
elseif type(obj)=="function" then
printCall(doc); return doc
else
printCall(missingMsg(obj)); return missingMsg(obj)
end
end
documentation[autoloader.doc] = [[
sano.doc(obj) - Prints the full documentation for obj.
]]
function autoloader.description(obj, localDoc)
local doc = localDoc and localDoc[obj] or documentation[obj]
if not doc then return "" end
if not string.find(doc,"%-") then doc = doc.."\t - ... " end
return doc
end
--local function
function autoloader.summary(obj, printCall, skipGeneral) -- to disable printing to console
printCall = printCall or print
if obj == nil then
printCall(nilMsg)
return t
end
if type(obj) ~= "table" then
local t = getShortDescription(obj, missingMsg(obj))
printCall(t)
return t
elseif obj == autoloader then
autoloader.loadAll()
local t = summary(prepSummary(obj, true), obj.documentation)
printCall(t)
return t
elseif obj == autoloader.makers then
local t = getShortDescription(obj, missingMsg(obj))
printCall(t)
return t
end
local t = summary(prepSummary(obj, skipGeneral), obj.documentation)
printCall(t)
return t
end
documentation[autoloader.summary] = [[
sano.summary(obj) - Prints a brief, one sentance description of obj,
and, if obj is an object or module, a brief one sentance description
of each of the methods of the object or functions of the module.
]]
local modules = {'Vector','QueueVector','LinkedList','Tuple',
'SkipVector','SkipSet','SkipMap','Phonebook',
'HashMap','HashSet','LinkedSet','LinkedMap',
'Multiset','Multimap','PairingHeap',
'oop','ordering','iter','collections',
'sequences','sets','maps','heaps','utils'}
function autoloader.testAll()
print("Testing module set:\n"..tostring(autoloader.makers.vector(modules)))
for _,moduleName in ipairs(modules) do
local m = autoloader[moduleName]
if m.test then
io.write("Testing "..moduleName.."... ")
io.write(m:test() and "passed\n" or "passed\n")
end
end
print("... all tests passed.")
end
documentation[autoloader.testAll] = [[
sano.testAll() - Runs unit tests on all modules in the Sano library.
]]
-- File text archive table
archive = {collections=[[
local sano = require 'sano'
local utils = sano.utils
local iter = sano.iter
local oop = sano.oop
--- Collections
local collections = {}
collections.documentation = {}
local apidoc = collections.documentation
local doc
apidoc.general = beginlongstringliteral
collections - Module containing methods shared by multiple collection
implementations.
endlongstringliteral
collections.mixins = {}
doc = beginlongstringliteral
collections.make(self, ...) - Creates and returns a new instance of self
containing the elements passed in as vararg params.
If #args is 1, the argument is assumed to be iterable and its elements
are added to the Collection.
Example, assuming Collection is a sequence type:
> print( Collection:make(1,2,3,4) )
[1, 2, 3, 4]
> print( Collection:make(iter.count(4) )
[1, 2, 3, 4]
> v = Collection:make(1,2,3,4); print( Collection:make(v) )
[1, 2, 3, 4]
This method is used as the make method for LinkedList, SkipVector,
HashSet, SkipSet, QueueVector, and PairingHeap. It assumes that self
has new() and addAll() methods.
endlongstringliteral
function collections.make(self, ...)
local obj = self:new()
obj:addAll(table.getn(arg)==1 and arg[1] or arg)
return obj
end
apidoc[collections.make] = doc
doc = beginlongstringliteral
collections.addAll(collection, iterable) - Calls collection:add(e) for
each e in iter(iterable).
endlongstringliteral
function collections.addAll(collection, elements)
for i in iter.from(elements) do collection:add(i) end
return collection
end
apidoc[collections.addAll] = doc
doc = beginlongstringliteral
collections.contains(collection, element) - Returns true if element
exists in collection.
This involves iterating through the entire collection and takes
expected linear time.
endlongstringliteral
function collections.contains(collection, element)
for e in collection:iter() do
if e == element then return true end
end
return false
end
apidoc[collections.contains] = doc
doc = beginlongstringliteral
collections.containsAll(collection, elements) - Returns true iff
collection:contains(e) for e in iter(iterable).
endlongstringliteral
function collections.containsAll(collection, iterable)
for i in iter.from(iterable) do
if not collection:contains(i) then return false end
end
return true
end
apidoc[collections.containsAll] = doc
doc = beginlongstringliteral
collections.containsAny(collection, iterable) - Returns true if for
collection:contains(e) for some e in iter(iterable).
endlongstringliteral
function collections.containsAny(collection, elements)
for i in iter.from(elements) do
if collection:contains(i) then return true end
end
return false
end
apidoc[collections.containsAny] = doc
collections.mixins.plauralizeHelper = {
addAll = collections.addAll,
containsAll = collections.containsAll,
containsAny = collections.containsAny
}
collections.mixins.convenienceMethods = {
contains = collections.contains,
enum = iter.enum
}
return collections
]],
LinkedSet=[[
local sano = require 'sano'
local LinkedMap = sano.LinkedMap
local iter = sano.iter
local LinkedSet = LinkedMap:new()
sano.maps.makeSet(LinkedSet)
local apidoc = {}
local doc
LinkedSet.documentation = apidoc
apidoc.general = beginlongstringliteral
LinkedSet - Set implementation in which iteration order is same as
insertion order.
The LinkedSet class was created by a call to
sano.maps.makeSet(LinkedMap:new()). The set is implemented
by storing the elements of the set as the keys in LinkedMap, where all
keys simply map to the value 'true'.
Example usage:
> linkedset = function(...) return LinkedSet:make(unpack(arg)) end
> -- above is equivalent to: LinkedSet = sano.makers.linkedset
> s = linkedset(1,2,3,3,4); print(s)
{1, 2, 3, 4}
> print(s == linkedset(4,3,2,1))
true
> print(s == linkedset(1,3,5,7))
false
> print(s:remove(1))
1
> s:addAll(iter.count(10,15)); print(s)
{2, 3, 4, 10, 11, 12, 13, 14, 15}
> s:removeAll(iter.count(2,4)); print(s)
{10, 11, 12, 13, 14, 15}
> print(s:size())
6
endlongstringliteral
doc = beginlongstringliteral
LinkedSet:test() - Unit test.
endlongstringliteral
function LinkedSet:test()
local linkedset = function(...) return self:make(unpack(arg)) end
local s = linkedset(1,2,3,3,4)
assert(s == linkedset(4,3,2,1))
assert(s:size()==4)
assert(s:remove(3)==3)
assert(s:remove(7)==nil)
assert(s:contains(2))
assert(s:containsAll(sano.iter.count(2)))
end
apidoc[LinkedSet.test] = doc
return LinkedSet
]],
sequences=[[local sano = require 'sano'
local collections = sano.collections
local oop = sano.oop
local iter = sano.iter
local apidoc = {}
local doc
local sequences = {documentation=apidoc}
sequences.mixins = {}
apidoc.general = beginlongstringliteral
sequences - Module containing methods shared across sequence
implementations.
endlongstringliteral
doc = beginlongstringliteral
sequences.removeElement(sequence, element) - Removes the first instance
of element from sequence and returns it, or if not found, nil.
endlongstringliteral
function sequences.removeElement(seq, element)
local toRemove = nil
for ind,v in iter.enum(seq:iter(),seq:size()) do
if v == element then toRemove = ind end
end
if toRemove ~= nil then return seq:remove(toRemove) end
return nil
end
apidoc[sequences.removeElement] = doc
doc = beginlongstringliteral
sequences.removeAll(sequence, elements) - Calls
sequence:removeElement(e) for e in iter(elements).
endlongstringliteral
function sequences.removeAll(collection, elements)
for i in iter.from(elements) do collection:removeElement(i) end
end
apidoc[sequences.removeAll] = doc
doc = beginlongstringliteral
sequences.first(sequence) - Returns the first element of the sequence,
or nil if the sequence is empty.
endlongstringliteral
function sequences.first(seq) return seq:size()>0 and seq:get(1) end
apidoc[sequences.first] = doc
doc = beginlongstringliteral
sequences.last(sequence) - Returns the last element of the sequence, or
nil if the sequence is emtpty.
endlongstringliteral
function sequences.last(seq) return seq:size()>0 and seq:get(seq:size()) end
apidoc[sequences.last] = doc
doc = beginlongstringliteral
sequences.toString(sequence) - Returns a string representation of
sequence.
This method is used for the __tostring metamethod of Vector,
SkipVector, QueueVector, and LinkedList. Example:
> print(SkipVector:make(1,2,3,4,5))
[1, 2, 3, 4, 5]
endlongstringliteral
function sequences.toString(seq) return "["..iter.toString(seq:iter()).."]" end
apidoc[sequences.toString] = doc
doc = beginlongstringliteral
sequences.swap(sequence, index1, index2) - Exchanges the elements
stored in sequence at the two supplied indices.
endlongstringliteral
function sequences.swap(seq, index1, index2)
local t = seq:get(index1)
seq:set(index1, seq:get(index2))
seq:set(index2, t)
end
apidoc[sequences.swap] = doc
doc = beginlongstringliteral
sequences.shuffle(sequence [,rng]) - Permutes the input sequence in
place, using the optionally supplied rng (by default math.random).
rng can be any function which can takes two integer arguments a,b and
returns a random integer in the range [a..b] (inclusive on both sides).
The algorithm runs in linear time and generates all permutations with
equal probability, assuming the rng is perfectly uniform. It is
implemented using an algorithm proposed by Knuth, see
http://en.wikipedia.org/wiki/Shuffle#Shuffling_algorithms
endlongstringliteral
function sequences.shuffle(seq, rng)
rng = rng or math.random
local n = seq:size()
for i=n,2,-1 do
local rand = rng(1,i)
seq:swap(i, rand)
end
return seq
end
apidoc[sequences.shuffle] = doc
doc = beginlongstringliteral
Sequence:sort([orderFn]) - Sorts the sequence in place using the
built-in table.sort function and the ordering specified (default
is increasing order).
If seq is a Vector or Tuple, no auxilliary table is created for the
sort. If seq is any other collection, a copy of seq is created,
sorted, and the results copied back to seq.
endlongstringliteral
function sequences.sort(seq, orderFn)
local ordering = sano.ordering
orderFn = orderFn or ordering.INCREASING
local lt = ordering.lessThan
if oop.isInstance(seq, sano.Vector) or oop.isInstance(seq, sano.Tuple) then
local order = function(x,y) return lt(x,y,orderFn) end
table.sort(seq, order)
else
for ind,v in ordering.sorted(seq,orderFn):enum() do
seq:set(ind,v)
end
end
return seq
end
apidoc[sequences.sort] = doc
sequences.mixins.plauralizeHelper = {
addAll = collections.addAll,
containsAll = collections.containsAll,
containsAny = collections.containsAny,
removeAll = sequences.removeAll
}
sequences.mixins.convenienceMethods = {
enum = iter.enum,
make = collections.make,
removeElement = sequences.removeElement,
contains = collections.contains,
first = sequences.first,
last = sequences.last,
__tostring = sequences.toString
}
sequences.mixins.randomAccessHelper = {
swap = sequences.swap,
shuffle = sequences.shuffle,
sort = sequences.sort
}
return sequences]],
heaps=[[local sano = require 'sano'
local collections = sano.collections
local iter = sano.iter
local apidoc = {}
local heaps = {documentation = apidoc}
heaps.mixins = {}
local doc
apidoc.general = beginlongstringliteral
heaps - Module containing methods shared across heap implementations.
Currently, the Sano library contains only one heap implementation
(PairingHeap).
endlongstringliteral
doc = beginlongstringliteral
heaps.toString(heap) - Returns a string representation of heap.
The order in which elements are displayed is unspecified, except that
the top element of the heap will always appear first.
Example:
> h = heap(1,32,2,5,2,0)
> print(h) -- 0 is top of heap, appears first in string repr.
/0, 1, 2, 5, 2, 32\
endlongstringliteral
function heaps.toString(heap)
return "/"..iter.toString(heap:iter()).."\\"
end
apidoc[heaps.toString] = doc
doc = beginlongstringliteral
heaps.extractFirstK(heap [, k]) - Returns an iterator which
destructively removes and returns the first k elements from this
heap.
If k is unspecified, the entire heap is drained by the returned
iterator. If k is greater than heap:size(), an error is thrown.
The typical use for this method would be in a heapsort or a partial
sort, for example:
> v = vector(iter.count(15)); v:shuffle(); print(v)
[4, 9, 3, 8, 12, 2, 14, 1, 6, 11, 13, 10, 7, 15, 5]
> h = heap(v); print(h)
/1, 5, 15, 7, 10, 13, 11, 6, 2, 14, 3, 12, 8, 4, 9\
> print(vector(h:extractFirstK(10)))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
endlongstringliteral
function heaps.extractFirstK(heap, k)
local c = 0
k = k or heap:size()
assert(k <= heap:size(), "k is greater than heap size.")
return function()
if c < k then
c = c + 1; return heap:remove()
end
end
end
apidoc[heaps.extractFirstK] = doc
heaps.mixins.convenienceMethods = {
make = collections.make,
addAll = collections.addAll,
__tostring = heaps.toString,
extractFirstK = heaps.extractFirstK
}
return heaps]],
ordering=[[
local sano = require("sano")
local utils = sano.utils
local iter = sano.iter
local ordering = {documentation={}}
local doc
local apidoc = ordering.documentation
apidoc.general = beginlongstringliteral
ordering - A collection of utility functions and algorithms for working
with ordered data.
This module contains several utilities for working with ordering
functions. An ordering function is a binary function which returns the
first argument if it is ordered before the second argument or if the
two arguments are equal.
For example, the natural increasing order, ordering.INCREASING, is
defined as:
function ordering.INCREASING(x,y)
return x <= y and x or y
end
Instances of ordered collections in the Sano library (SkipMap,
SkipSet, PairingHeap) can be constructed using any user-defined
ordering function (by default they use ordering.INCREASING). For each
of these collections, the ordering function is interpreted in the
obvious way: the 'retrieval' order of the collection will be consistent
with the order in which elements are returned by the ordering function:
> p = PairingHeap:new(ordering.DECREASING)
> p:addAll{3,2,1,6,5}; print(p:get())
6
> s = SkipSet:new(ordering.DECREASING)
> s:addAll{1,2,3,4,5,6}; print(s)
{6, 5, 4, 3, 2, 1}
ordering.DECREASING returns larger elements first, so the largest
element is at the top of the heap, and at the start of the iteration
order of the SkipSet (an ordered set implementation).
In addition, this module contains several algorithms for operating on
ordered data. See for example: ordering.first, ordering.kth,
ordering.sorted, and ordering.lexicographical
endlongstringliteral
doc = beginlongstringliteral
ordering.INCREASING(x,y) - Returns x if x <= y and y otherwise.
This ordering is the default ordering typically used in functions
where an (optional) ordering function is left unspecified.
endlongstringliteral
function ordering.INCREASING(x,y)
return x <= y and x or y
end
apidoc[ordering.INCREASING] = doc
doc = beginlongstringliteral
ordering.reverse(orderFn) - Returns a new ordering which is the reverse
of orderFn.
endlongstringliteral
function ordering.reverse(orderFn)
return function(x,y)
local first = orderFn(x,y)
return rawequal(first, x) and y or x
end
end
apidoc[ordering.reverse] = doc
doc = beginlongstringliteral
ordering.DECREASING(x,y) - Returns x if x >= y and y otherwise.
endlongstringliteral
function ordering.DECREASING(x,y)
return x >= y and x or y
end
apidoc[ordering.DECREASING] = doc
doc = beginlongstringliteral
ordering.byKey(key [,orderFn]) - Returns a new ordering, obk, in which
obk(x,y) == x iff x[key] <= y[key].
For example, to sort a vector of tables, t, using the second element of
each table as the sort criteria:
> v = vector({1,1},{1,2},{1,1.3},{1,1.4})
> sorted = ordering.sorted(v, ordering.byKey(2))
> for t in sorted:iter() do print(t[1],t[2]) end
1 1
1 1.3
1 1.4
1 2
endlongstringliteral
function ordering.byKey(key, orderFn)
local natural = orderFn or ordering.INCREASING
return function(x,y)
local vx, vy = x[key], y[key]
local first = natural(vx, vy)
return rawequal(first, vx) and x or y
end
end
apidoc[ordering.byKey] = doc
doc = beginlongstringliteral
ordering.byTransform(transform) - Returns a new ordering obt, in which
obt(x,y) == x iff transform(x) <= transform(y).
endlongstringliteral
function ordering.byTransform(transformer, rev)
local natural = not rev and ordering.INCREASING or ordering.DECREASING
return function(x,y)
local tx, ty = transformer(x), transformer(y)
local first = natural(tx, ty)
return rawequal(first, tx) and x or y
end
end
apidoc[ordering.byTransform] = doc
doc = beginlongstringliteral
ordering.equals(x,y,orderFn)
endlongstringliteral
function ordering.equals(x,y,orderFn)
return rawequal(orderFn(x,y), x) and rawequal(orderFn(y,x), y)
end
apidoc[ordering.equals] = doc
local eq = ordering.equals
doc = beginlongstringliteral
ordering.chained(...) - Returns a new ordering based on a chain of
ordering functions.
If the first ordering function in the chain determines the two input
are equal, the second ordering function is consulted, and so on, until
one of the orderings reveals a preference or the chain is exhausted.
In effect, orderings later in the chain are used to 'break ties'
between orderings earlier in the chain.
This is useful for sorting some data based on multiple criteria. For
example, we might sort a list of Tuple objects storing names in the
form (first, last) using the following code:
> -- names = [('J','Jones'),('A','Jones'),('A','Smith'),('B','Smith')]
> -- sort by last name, then use first name to break ties
> names:sort(ordering.chained(ordering.byKey(2), ordering.byKey(1)))
> print(names)
[("A", "Jones"), ("J", "Jones"), ("A", "Smith"), ("B", "Smith")]
endlongstringliteral
function ordering.chained(...)
return function(x,y)
for orderFn in iter(arg) do
if not eq(x,y,orderFn) then
return orderFn(x,y)
end
end
return arg[table.getn(arg)](x,y)
end
end
apidoc[ordering.chained] = doc
doc = beginlongstringliteral
ordering.ranked(rankGroups)
endlongstringliteral
function ordering.ranked(rankGroups)
local rankMap = {}
local rank = 1
for group in iter(rankGroups) do
for e in iter(group) do
rankMap[e]=rank
end
rank = rank+1
end
return function(x,y)
--for k,v in pairs(rankMap) do print(k,v) end
local rankX, rankY = rankMap[x] or rank, rankMap[y] or rank
--print(rankX, rankY)
return rankY < rankX and y or x
end
end
apidoc[ordering.ranked] = doc
doc = beginlongstringliteral
ordering.lessThan(x,y,orderFn)
endlongstringliteral
function ordering.lessThan(x,y,orderFn)
return rawequal(orderFn(x,y), x) and not rawequal(orderFn(y,x), y)
end
apidoc[ordering.lessThan] = doc
doc = beginlongstringliteral
ordering.lessThanOrEq(x,y,orderFn)
endlongstringliteral
function ordering.lessThanOrEq(x,y,orderFn)
return rawequal(orderFn(x,y), x)
end
apidoc[ordering.lessThanOrEq] = doc
doc = beginlongstringliteral
ordering.greaterThan(x,y,orderFn)
endlongstringliteral
function ordering.greaterThan(x,y,orderFn)
return rawequal(orderFn(y,x), y) and not rawequal(orderFn(x,y),x)
end
apidoc[ordering.greaterThan] = doc
doc = beginlongstringliteral
ordering.greaterThanOrEq(x,y,orderFn)
endlongstringliteral
function ordering.greaterThanOrEq(x,y,orderFn)
return rawequal(orderFn(y,x), y)
end
apidoc[ordering.greaterThanOrEq] = doc
doc = beginlongstringliteral
ordering.isSorted(iterable [,orderFn]) - Testing function which returns
true if the iterable is sorted according to the specified ordering
function.
endlongstringliteral
function ordering.isSorted(iterable, orderFn)
orderFn = orderFn or ordering.INCREASING
for i,j in iter.windows(iter(iterable), 2) do
if orderFn(i,j) ~= i then return false end
end
return true
end
apidoc[ordering.isSorted] = doc
local lt = ordering.lessThan
doc = beginlongstringliteral
ordering.sorted(iterable [,orderFn]) - Returns a Sano Vector sorted
using the built in Lua sort function and the ordering specified
(default ordering.INCREASING).
endlongstringliteral
function ordering.sorted(iterable, orderFn)
orderFn = orderFn or ordering.INCREASING
local order = function(x,y) return lt(x,y,orderFn) end
local tab = iter.toList(iter.from(iterable))
table.sort(tab, order)
setmetatable(tab, sano.Vector)
tab.mSize = table.getn(tab)
return tab
end
apidoc[ordering.sorted] = doc
doc = beginlongstringliteral
ordering.rsorted(iterable [,orderFn]) - Returns a Sano Vector reverse
sorted using the built-in Lua sort function and the ordering function
specified (default ordering.INCREASING).
If orderFn is left unspecified, the order of the returned Vector will
be max first.
endlongstringliteral
function ordering.rsorted(iterable, orderFn)
orderFn = ordering.reverse(orderFn or ordering.INCREASING)
return ordering.sorted(iterable, orderFn)
end
apidoc[ordering.rsorted] = doc
doc = beginlongstringliteral
ordering.first(iterable [,orderFn]) - Returns the first element that
would appear if the elements of iterable were sorted by orderFn,
which defaults to ordering.INCREASING.
endlongstringliteral
function ordering.first(iterable, orderFn)
return iter.fold(iterable, orderFn or ordering.INCREASING)
end
apidoc[ordering.first] = doc
doc = beginlongstringliteral
ordering.first(iterable [,orderFn]) - Returns the last element that
would appear if the elements of iterable were sorted by orderFn,
which defaults to ordering.INCREASING.
endlongstringliteral
function ordering.last(iterable, orderFn)
return iter.fold(iterable, ordering.reverse(orderFn or ordering.INCREASING))
end
apidoc[ordering.last] = doc
local le = ordering.lessThanOrEq
local function partition(a, pivotInd, left, right, orderFn)
local pivot = a:get(pivotInd)
local storeInd = left
a:swap(pivotInd, right)
for i=left,right-1 do
if le(a[i], pivot, orderFn) then
a:swap(storeInd, i)
storeInd = storeInd+1
end
end
a:swap(right, storeInd)
return storeInd
end
local function kth(a, k, left, right, orderFn)
local pivotInd = math.random(left,right)
local newPivotInd = partition(a, pivotInd, left, right, orderFn)
if newPivotInd == k then
return a:get(k), a
elseif k < newPivotInd then
return kth(a,k,left,newPivotInd-1,orderFn)
else
return kth(a,k,newPivotInd+1,right,orderFn)
end
end
doc = beginlongstringliteral
ordering.kth(iterable, k, orderFn) - Returns the element that would
appear at the kth index if the elements of iterable were sorted.
This method also returns as a second value a sano.Vector, v in which:
+) v[k] == v:sort()[k],
+) set(v:iter(1,k)) == set(v:sort():iter(1,k)),
+) set(v:iter(k+1,-1)) == set(v:sort():iter(k+1,-1))
For example:
> v = vector(iter.count(1,10)); v:shuffle(); print(v)
[4, 3, 9, 2, 6, 8, 5, 1, 10, 7]
> print(ordering.kth(v,6))
6 [1, 2, 3, 5, 4, 6, 7, 9, 10, 8]
The element 6 is in the 6th position and is the first value returned.
The second value returned is the Vector used for partitioning. Notice
that the set of elements which appear before 6 in the Vector are the
same that would appear if the entire Vector were sorted; likewise for
the set of elements which appear after 6.
The time complexity is expected O(size(iterable)). See
http://en.wikipedia.org/wiki/Median_algorithm for more information on
the algorithm used.
If iterable is a Vector, the operation is done IN PLACE; otherwise, the
elements of iterable are added to a new Vector. In either case, the
Vector (new or otherwise) is returned as the second value.
param k: must be positive and <= the number of elements in iterable
endlongstringliteral
function ordering.kth(iterable, k, orderFn)
local oop = sano.oop
local Vector = sano.Vector
if not oop.isInstance(iterable, Vector) then
iterable = Vector:make(iterable)
assert(k <= iterable:size(), "k must be less than the number of elements in iterable")
end
local size = iterable:size()
return kth(iterable, k>=1 and k or math.ceil(k*size), 1, size, orderFn or ordering.INCREASING)
end
apidoc[ordering.kth] = doc
doc = beginlongstringliteral
ordering.keyOrdered(mapIterable, orderFn) - Returns an iterator over
the key-value pairs of mapIterable, ordered by key.
mapIterable may be a Lua table or any two element iterable. See
iter.mapIter for more information on the map-iterable contract.
Examples:
> keys, vals = "edcba", iter.count(5,1,-1)
> for k,v in ordering.keyOrdered(iter.zip(keys,vals)) do print(k,v) end
a 1
b 2
c 3
d 4
e 5
> -- equivalently:
> m = {a=1,b=2,c=3,d=4,e=5}
> for k,v in ordering.keyOrdered(m) do print(k,v)
a 1
b 2
c 3
d 4
e 5
endlongstringliteral
function ordering.keyOrdered(mapIter, orderFn)
local orderedKeys = sano.SkipMap:new(orderFn)
orderedKeys:addMappings(mapIter)
return orderedKeys:iter()
end
apidoc[ordering.keyOrdered] = doc
doc = beginlongstringliteral
ordering.heapsorted(iterable, orderFn) - Sorts the elements of iterable
using a heap along with the specified ordering function.
endlongstringliteral
function ordering.heapsorted(iterable, orderFn)
local h = sano.PairingHeap:new(orderFn)
h:addAll(iterable)
return h:extractFirstK()
end
apidoc[ordering.heapsorted] = doc
local function lexicographical(i1, i2, orderFn)
local gt = ordering.greaterThan
while true do
local a, b = i1(), i2()
if a~=nil and b~=nil then
if gt(a,b,orderFn) then return 1
elseif gt(b,a,orderFn) then return -1
--else return lexicographical(i1, i2, orderFn)
end
elseif a==nil then
if b == nil then return 0 -- they're equal
elseif b ~= nil then return -1 -- i2 has more elements and is therefore greater
end
elseif a~=nil and b==nil then -- i1 has more elements and is greater
return 1
end
end
end
doc = beginlongstringliteral
ordering.lexicographical(iterable1, iterable2 [,orderFn]) - Returns
-1, 0, or 1 indicating whether iterable1 is ordered lexicographically
before, is equal to, or is ordered after iterable2.
The ordering on strings is a lexicographical ordering based on the
ordering of individual letters - if the two strings have a different
first letter, the string which starts with the alphabetically first
letter is ordered first. If the two strings have the same first letter,
then second letter is examined, and so on, until one or both of the
strings run out of letters or pairwise comparisons reveal an ordering.
More generally, in a lexicographical ordering:
If a = a1, a2, ..., aN and b = b1, b2, ..., bN, then a < b iff:
b1 > a1
OR
b1 == a1, b2 == a2, ..., bK == aK, bK+1 > aK+1 (b is greater than a)
If a and b have different sizes, the smaller will be ordered before the
larger if pairwise comparisons do not reveal an ordering.
Examples: (1,1) <= (1,1), () <= (1), (1,2,1) <= (1,3)
The evaluation is 'short-circuit' and halts as soon as there is enough
information to determine the ordering.
returns -1 if iterable1 is ordered before iterable2,
0 if the two iterables are equal,
1 if iterable2 is ordered after iterable2
endlongstringliteral
function ordering.lexicographical(iterable1, iterable2, orderFn)
orderFn = orderFn or ordering.INCREASING
local result = lexicographical(iter(iterable1), iter(iterable2), orderFn)
if result <= 0 then
return iterable1, iterable2, result==0, result
else
return iterable1, iterable2, nil, result
end
end
apidoc[ordering.lexicographical] = doc
doc = beginlongstringliteral
ordering.test - unit test
endlongstringliteral
function ordering.test()
assert( ordering.equals(1,1,ordering.INCREASING) )
assert( ordering.equals(1,1,ordering.DECREASING) )
assert( not ordering.equals(1,2,ordering.INCREASING) )
assert( ordering.lessThanOrEq(1,1,ordering.INCREASING) )
assert( ordering.lessThanOrEq(1,6,ordering.INCREASING) )
assert( ordering.greaterThanOrEq(1,1,ordering.DECREASING) )
assert( ordering.greaterThanOrEq(6,1,ordering.INCREASING) )
assert( ordering.lessThan(1,6,ordering.INCREASING) )
assert( ordering.greaterThan(6,1,ordering.INCREASING) )
assert( ordering.first(iter.count(25,1,-1)) == 1 )
assert( ordering.first(iter.count(25,1,-1), ordering.DECREASING) == 25 )
assert( ordering.last(iter.count(25,1,-1)) == 25 )
local vector = sano.makers.vector
local N = 100
local v = vector(iter.count(N))
for i=1,10 do
v:shuffle()
assert( ordering.kth(v, 10) == 10 )
assert( ordering.kth(v, 1) == 1 )
assert( ordering.kth(v, .9999) == N )
end
assert(ordering.isSorted{1,2,3,4,5,6})
assert(not ordering.isSorted{1,2,1,4,24})
v:shuffle()
--print(vector(ordering.sorted(v)))
assert( ordering.isSorted(ordering.sorted(v)) )
assert( ordering.isSorted(ordering.rsorted(v, ordering.DECREASING)) )
--assert( ordering.isSorted(ordering.heapsorted(v)) )
end
apidoc[ordering.test] = doc
ordering.test()
return ordering
]],
SkipMap=[[
local sano = require 'sano'
local iter = sano.iter
local oop = sano.oop
local ordering = sano.ordering
local maps = sano.maps
local utils = sano.utils
local function SNode(key,val,numSkips)
return {key=key, val=val, numSkips=numSkips, skips={}}
end
local function default0(t,ind) return 0 end
local function SINode(val,numSkips)
local t = {val=val, numSkips=numSkips, skips={}, distance={__index=default0}}
setmetatable(t.distance,t.distance); return t
end
local E = 2.718
local MAX_LEVELS = 24
local function exponential(survivalp, intmax)
local toReturn = 1
while math.random() > survivalp and toReturn <= intmax do
toReturn = toReturn + 1
end
return toReturn
end
local SkipMap = {documentation={},cDefaultOrdering=ordering.INCREASING}
local apidoc = SkipMap.documentation
local doc
apidoc.general = beginlongstringliteral
SkipMap - An ordered map implementation with performance characteristics
similar to a balanced binary tree.
The name 'SkipMap' comes from the implementation, which is based on
skip-lists.
Example of usage:
> local s1 = SkipMap:make{a=1,b=2,c=3,d=4,e=5}
> assert( s1:size()==5 )
> assert( ordering.isSorted(s1) )
> assert( s1:get("a")==1 )
> assert( s1:get("e")==5 )
> assert( s1:first()=="a" )
> assert( s1:last()=="e" )
> assert( s1:remove("b")==2 ) -- can also remove a key range
> -- iterate just from keys=="c" or after
> for k,v in s1:iter("c") do print(k,v) end
c 3
d 4
e 5
SkipSet is just a SkipMap where the keys map to true for all elements
that exist in the set. Phonebook is a SkipMap which allows duplicate
key-value pairs.
endlongstringliteral
doc = beginlongstringliteral
SkipMap:new([orderFn]) - Returns a newly constructed SkipMap using the
specified ordering function, or self.cDefaultOrdering (initially
natural increasing order) if none is specified.
The newly created map can be used to create other maps with the same
default ordering, for instance:
> MaxFirstSkipMap = SkipMap:new(ordering.DECREASING)
> maxFirstMap = MaxFirstSkipMap:make{a=1,b=2,c=3,d=4}
> print(maxFirstMap:first())
d
endlongstringliteral
function SkipMap:new(orderFn)
local obj = {}
setmetatable(obj, self)
self.__index = oop.loadOnFirstUse(self)
obj.mOrderFn = orderFn or self.cDefaultOrdering
obj.cDefaultOrdering = obj.mOrderFn
obj.mHead = SNode(nil, nil, MAX_LEVELS)
obj.mMaxLevel = 1
obj.mFingers = {}
obj.mSize = 0
return obj
end
apidoc[SkipMap.new] = doc
-- private methods: search, fingerSearch
local function search(node, key)
for i=maxLevel,1,-1 do
local t = node.skips[i]
while t and t.key < key do node = t; t = t.skips[i] end
end
local prev,atOrGt = node, node.skips[1]
if atOrGt and atOrGt.key == key then return atOrGt
else return prev, atOrGt end -- bracket where it would be
end
local lessThan = ordering.lessThan
local greaterThanOrEq = ordering.greaterThanOrEq
local lessThanOrEq = ordering.lessThanOrEq
local equals = ordering.equals
--beginlongstringliteral same as search, but store largestNode at each level traversed
also updates fingers endlongstringliteral
local function trackedSearch(self, node, key, startingLevel, largestPerLevel)
local mOrderFn = self.mOrderFn
for i=startingLevel,1,-1 do
local t = node.skips[i]
while t and lessThan(t.key, key, mOrderFn) do node = t; t = t.skips[i] end
largestPerLevel[i] = node
end
self.mFingers = largestPerLevel
local prev,atOrGt = node, node.skips[1]
if atOrGt and equals(atOrGt.key,key,mOrderFn) then return atOrGt
else return prev, atOrGt end -- bracket where it would be
end
local function fingerSearch(self, key)
if table.getn(self.mFingers)==0 then
return trackedSearch(self, self.mHead, key, self.mMaxLevel, self.mFingers)
end
local fingers, mMaxLevel, mOrderFn = self.mFingers, self.mMaxLevel, self.mOrderFn
local v, startNode = 2, nil
if fingers[1].key and lessThan(fingers[1].key, key, mOrderFn) then -- move forward on highest level possible
while v <= mMaxLevel and fingers[v].skips[v] and lessThan(fingers[v].skips[v].key, key, mOrderFn)
do v = v + 1 end
v = v - 1
startNode = fingers[v]
else -- move backward
while v <= mMaxLevel and fingers[v].key and greaterThanOrEq(fingers[v].key, key, mOrderFn)
do v = v + 1 end
if v > mMaxLevel then v = mMaxLevel; startNode = self.mHead -- back to start
else startNode = fingers[v] end
end
return trackedSearch(self, startNode, key, v, fingers)
end
local function insert(self, startNode, key, val, allowDups)
local maxLevel = self.mMaxLevel; local update = {}; local node = startNode
local mOrderFn = self.mOrderFn
if not allowDups then
local at = trackedSearch(self, startNode, key, maxLevel, update)
if at.key and equals(at.key, key, mOrderFn) then -- already exists
local old = at.val; at.val = val; return old
end
else
for i=maxLevel,1,-1 do
local t = node.skips[i]
while t and lessThanOrEq(t.key,key,mOrderFn) do node = t; t = t.skips[i] end
update[i] = node
end
end
local newLevel = exponential(1/E, MAX_LEVELS)
if newLevel > maxLevel then
maxLevel, newLevel = maxLevel+1, maxLevel+1
update[maxLevel] = self.mHead
--for i=maxLevel+1,newLevel do update[i] = mHead end
--maxLevel = newLevel -- technically correct
end
local newNode = SNode(key, val, newLevel)
for i=1,newLevel do
newNode.skips[i] = update[i].skips[i]
update[i].skips[i] = newNode
end
self.mFingers = {}
self.mMaxLevel = maxLevel
self.mSize = self.mSize + 1
end
local function delete(self, node, key)
local update = {}
local maxLevel, mHead = self.mMaxLevel, self.mHead
local toRemove = trackedSearch(self, node, key, maxLevel, update)
if toRemove.key ~= key then return nil end
for i=1,maxLevel do
if update[i].skips[i] == toRemove then
update[i].skips[i] = toRemove.skips[i]
end
end
while maxLevel > 1 and not mHead.skips[maxLevel] do
maxLevel = maxLevel - 1
end
self.mSize = self.mSize - 1
self.mMaxLevel = maxLevel
return toRemove
end
doc = beginlongstringliteral
SkipMap:first() - Returns the first key-value pair in this map, or nil
if the map is empty.
time complexity: O(1)
endlongstringliteral
function SkipMap:first()
local n = self.mHead.skips[1]
if n then return n.key, n.val end
end
apidoc[SkipMap.first] = doc
doc = beginlongstringliteral
SkipMap:last() - Returns the last key-value pair in this map, or nil if
the map is empty.
time complexity: O(lg(size))
endlongstringliteral
function SkipMap:last()
local n = self.mHead
for i=self.mMaxLevel,1,-1 do while n.skips[i] do n = n.skips[i] end end
return n.key,n.val
end
apidoc[SkipMap.last] = doc
doc = beginlongstringliteral
SkipMap:split(splitKey) - Removes all elements ordered at or after
splitKey and returns them in a new SkipMap.
Example, given the map m = {a=1,b=2,c=3,d=4}, m:split("b") would
return {b=2,c=3,d=4}.
The same result can be acheived by calling SkipMap:splice(splitKey).
splice() is a more general method in that it also allows an end key.
time complexity: O(lg(size))
endlongstringliteral
function SkipMap:split(splitKey)
local mOrderFn = self.mOrderFn
local toReturn = getmetatable(self):new()
toReturn.mMaxLevel = self.mMaxLevel
local mMaxLevel, smMaxLevel = self.mMaxLevel, toReturn.mMaxLevel
local node, shead = self.mHead, toReturn.mHead
for i=mMaxLevel,1,-1 do
while node.skips[i] and lessThan(node.skips[i].key, splitKey, mOrderFn)
do node = node.skips[i] end
shead.skips[i] = node.skips[i]
node.skips[i] = nil
end
local mHead = self.mHead
while mHead.skips[mMaxLevel] == nil and mMaxLevel > 1 do
mMaxLevel = mMaxLevel - 1 end
self.mMaxLevel = mMaxLevel
while shead.skips[smMaxLevel] == nil and smMaxLevel > 1 do
smMaxLevel = smMaxLevel - 1 end
local smSize = -1; while shead do smSize=smSize+1; shead=shead.skips[1] end
toReturn.mMaxLevel = smMaxLevel
toReturn.mSize = smSize; self.mSize = self.mSize - smSize
return toReturn
end
apidoc[SkipMap.split] = doc
doc = beginlongstringliteral
SkipMap:concatenate(skipmap) - Concatenates skipmap onto the end of this
SkipMap.
This method throws an error if the two maps overlap. If the two maps
are expected to have overlapping keysets according to the ordering, then
use SkipMap:merge().
skiplist is destroyed as a result of this call.
time complexity: O(lg(size))
endlongstringliteral
function SkipMap:concatenate(skiplist)
if self:size()>0 and skiplist:size()>0 then
assert(lessThan(self:last(), skiplist:first(), self.mOrderFn),
"Cannot concatenate overlapping list; use merge() instead.")
end
self.mMaxLevel = math.max(self.mMaxLevel, skiplist.mMaxLevel)
local node = self.mHead
for i=self.mMaxLevel,1,-1 do
while node.skips[i] do node = node.skips[i] end
if i <= skiplist.mMaxLevel then node.skips[i] = skiplist.mHead.skips[i] end
end
self.mSize = self.mSize + skiplist.mSize
oop.invalidate(skiplist, SkipMap, "SkipMap has been concatenated onto another.")
return self
end
apidoc[SkipMap.concatenate] = doc
doc = beginlongstringliteral
SkipMap:splice(start [, stop]) - Removes all elements ordered between
start and stop (both inclusive) and returns them as a new SkipMap.
param stop: the last key (inclusive) to remove, default SkipMap:last()
time complexity: O(lg(size) + |stop-start|) (Unfortunately, the linear
term is needed to recompute sizes - todo is to recompute these lazily)
endlongstringliteral
function SkipMap:splice(start, stop)
assert(start)
stop = stop or self:last()
local startToEnd = self:split(start)
--if startToEnd:size()==0 then return
local stopToEnd = startToEnd:split(stop)
local last = stopToEnd:first()
if last then startToEnd:add(last, stopToEnd:remove(last)) end
self:concatenate(stopToEnd)
return startToEnd
end
apidoc[SkipMap.splice] = doc
doc = beginlongstringliteral
SkipMap:merge(skipmap) - Merges another SkipMap into this SkipMap,
ensuring that the merged map remains ordered.
The other SkipMap is destroyed as result of this call.
The time complexity of this operation depends on the amount of overlap.
The worst case, when entries need to be perfectly interlaced, takes
O(size(self)+size(skipmap)). The best case, when entries are completely
disjoint, takes only O(lg(size)).
endlongstringliteral
function SkipMap:merge(slist2, saveDuplicates)
local update, unflipped = {}, true
local dupCount = 0
local merged = self:new()
local mOrderFn = self.mOrderFn
merged.mMaxLevel = math.max(self.mMaxLevel, slist2.mMaxLevel)
local mergedMaxLevel= merged.mMaxLevel
for i=1,mergedMaxLevel do update[i] = merged.mHead end
local head1,head2 = self.mHead,slist2.mHead
while head1.skips[1] and head2.skips[1] do
local key1,key2 = head1.skips[1].key, head2.skips[1].key
if lessThan(key2, key1, mOrderFn) then
head1,head2,key1,key2,unflipped
= head2,head1,key2,key1,not unflipped
end
local v = 1
repeat update[v].skips[v] = head1.skips[v]; v = v+1
until v > mergedMaxLevel or not head1.skips[v] or lessThan(key2, head1.skips[v].key, mOrderFn)
v = v - 1
local node = head1.skips[v]
for i=v,1,-1 do
while node.skips[i] and lessThanOrEq(node.skips[i].key, key2, mOrderFn)
do node = node.skips[i] end
update[i] = node
head1.skips[i] = node.skips[i]
end
if not saveDuplicates and equals(key2,node.key,mOrderFn) then
dupCount = dupCount + 1
if unflipped then node.val = head2.skips[1].val end
local node2 = head2.skips[1]
for i=1,node2.numSkips do head2.skips[i] = node2.skips[i] end
-- node2 is deleted
end
end -- one or both lists have been exhausted
local leftOver = (not head2.skips[1]) and head1 or head2
local leftOverML = unflipped and self.mMaxLevel or slist2.mMaxLevel
for i=1,leftOverML do update[i].skips[i] = leftOver.skips[i] end
-- we may have deleted some elements, readjust
if not saveDuplicates then
for i=leftOverML+1,mergedMaxLevel do update[i].skips[i] = nil end
while merged.mHead.skips[merged.mMaxLevel] == nil and merged.mMaxLevel > 1
do merged.mMaxLevel = merged.mMaxLevel - 1 end
end
self.mMaxLevel = merged.mMaxLevel
self.mHead = merged.mHead
self.mSize = self.mSize + slist2.mSize + (saveDuplicates and 0 or -dupCount)
self.mFingers = {}
return self
end
apidoc[SkipMap.merge] = doc
doc = beginlongstringliteral
SkipMap:add(key, val) - Adds a new key-value pair to this SkipMap and
returns the old value stored against key (or nil if the pair is new).
endlongstringliteral
function SkipMap:add(key, val, allowDuplicates)
return insert(self, self.mHead, key, val, allowDuplicates)
end
apidoc[SkipMap.add] = doc
doc = beginlongstringliteral
SkipMap:get(key) - Returns the value associated with the supplied key,
or nil if none exists.
synonyms: map:get(key) == map(key)
endlongstringliteral
function SkipMap:get(key)
local node = fingerSearch(self, key)
return (node.key and equals(node.key, key, self.mOrderFn)) and node.val or nil
end
apidoc[SkipMap.get] = doc
SkipMap.__call = SkipMap.get
doc = beginlongstringliteral
SkipMap:contains(key) - Returns true if this SkipMap contains a value
for the supplied key.
endlongstringliteral
function SkipMap:contains(key)
return self:get(key) ~= nil
end
apidoc[SkipMap.contains] = doc
doc = beginlongstringliteral
SkipMap:remove(key [, last]) - Removes the mapping associated with the
supplied key and returns the value stored against key.
Supplying a second parameter makes this equalivalent to calling
SkipMap:splice().
Examples:
{a=1,b=2,c=3}:remove("a") == 1
{a=1,b=2,c=3}:remove("a","b") == {a=1,b=2}
endlongstringliteral
function SkipMap:remove(key, last)
if last then return self:splice(key, last) end
local node = delete(self, self.mHead, key)
return node and node.val or nil
end
apidoc[SkipMap.remove] = doc
doc = beginlongstringliteral
SkipMap:iter([start [,stop] ]) - Returns an iterator over all key-value
pairs in this map, or optionally over the range [start..stop]
(inclusive both ends).
If stop is unspecified, the iteration is from start to the last mapping
(inclusive).
Each step in the iteration returns two elements, k,v where k is a key
in the key sequence and v is the value associated with k.
endlongstringliteral
function SkipMap:iter(start, stop)
if self.mSize == 0 then return iter.EMPTY end
local mOrderFn = self.mOrderFn
start = start ~= nil and start or self:first()
stop = stop ~= nil and stop or self:last()
local startNode, failed = fingerSearch(self, start)
if failed then
startNode = failed
end -- just past where start would be
return coroutine.wrap(function()
local curNode = startNode
while curNode and lessThanOrEq(curNode.key, stop, mOrderFn) do
coroutine.yield(curNode.key, curNode.val)
curNode = curNode.skips[1]
end
end)
end
doc = beginlongstringliteral
SkipMap:size() - Returns the number of mappings in this SkipMap.
endlongstringliteral
function SkipMap:size() return self.mSize end
apidoc[SkipMap.size] = doc
doc = beginlongstringliteral
SkipMap:__eq(other) - Returns true if both SkipMaps have the same size
and contain the same elements in the same order.
endlongstringliteral
function SkipMap:__eq(other)
return self:size()==other:size() and iter.multiEquals(self:iter(), other:iter())
end
apidoc[SkipMap.__eq] = doc
function SkipMap:debug()
local curNode = self.mHead
local function strNode(node)
local k,v = node.key and node.key or "nil", node.val and node.val or "nil"
local links = ""
for _,l in pairs(node.skips) do links = links..l.key..", " end
return k.."="..v.."->"..links
end
print("maxLevel="..self.mMaxLevel, "size="..self.mSize)
while curNode do
print (strNode(curNode))
curNode = curNode.skips[1]
end
end
oop.mixin(SkipMap, maps.mixins.plauralizeHelper)
oop.mixin(SkipMap, maps.mixins.convenienceMethods)
doc = beginlongstringliteral
SkipMap:test() - Unit test.
endlongstringliteral
function SkipMap:test()
local skipmap = function(map) return self:make(map) end
-- exercise basic functionality
local s1 = skipmap{a=1,b=2,c=3,d=4,e=5}
--print(s1, s1:size())
--s1:add("b",2)
--print(s1)
assert(s1:size()==5)
assert(ordering.isSorted(s1))
assert(s1:get("a")==1)
assert(s1:get("e")==5)
assert(s1:first()=="a")
assert(s1:last()=="e")
assert(s1:remove("b")==2)
local s2 = skipmap{d=4,c=3,a=1,e=5}
assert(s1==s2)
local s3 = skipmap{e=5,f=6,g=7,h=8}
s1:merge(s3)
assert(ordering.isSorted(s1))
assert(s1:size()==7, "Duplicate elements were retained, size should be 7, was: "..s1:size())
local empty = s1:splice("z")
assert(empty:size()==0, tostring(empty))
assert(empty:first()==nil)
assert(empty:last()==nil)
s1:merge(empty)
local s1Copy = skipmap(s1)
local nonempty = s1:split("b")
s1:concatenate(nonempty)
assert(s1==s1Copy)
local s1Copy2 = skipmap(s1)
assert(s1Copy2:splice('b','d') == skipmap(s1:iter('b','d')))
local queue = sano.makers.queue()
local N=500
for i=1,N do
queue:add(skipmap{[math.random()]=math.random()})
end
while queue:size()>1 do
local slist1 = queue:remove()
local slist2 = queue:remove()
queue:add(slist1:merge(slist2))
end
local mergesorted = queue:remove()
assert(mergesorted:size()==N)
assert(ordering.isSorted(mergesorted))
end
apidoc[SkipMap.test] = doc
return SkipMap
]],
Vector=[[
local sano = require 'sano'
local sequences = sano.sequences
local oop = sano.oop
local iter = sano.iter
local utils = sano.utils
local Vector = {}
Vector.documentation = {}
local apidoc = Vector.documentation
apidoc.general = beginlongstringliteral
Vector - Table implementation of a general purpose vector. Provides
constant time random access and worst-case linear time insertions and
deletions in the middle of the list.
Indices in the vector start at 1 go to size(), and can be negative.
Negative indices count backward from the end of the vector: get(-1)
returns the last element, get(-2) returns the second to last element,
get(-size()) returns the first element, etc. Any method that accepts
index arguments can use negative indexing. Indexing modes can be mixed
in the same call, i.e. iter(1,-2) will iterate from the first element to
the second to last element.
nil values can be stored in a Vector, although nil values will cause
the iterators returned by this class to halt prematurely, since Lua
interprets nil being returned as signaling the end of the iteration.
Note that the elements of a Vector are stored directly in the Vector
object itself, so a Vector can be treated like a vanilla Lua table.
For example, the following code is all valid:
> v = Vector:make(1,2,3,4,5,6); v:shuffle(); print(v)
[1, 2, 6, 4, 3, 5]
> table.sort(v); print(v)
[1, 2, 3, 4, 5, 6]
> for _,e in ipairs(v) do print(e) end
1
2
3
4
5
6
> -- raw indexing - no range checking or negative index translation
> print(v[1], v[-1])
1 nil
endlongstringliteral
local doc -- tmp var to store documentation strings
doc = beginlongstringliteral
Vector:new(array) - Creates a new Vector from array or returns an empty
Vector if array is nil.
endlongstringliteral
function Vector:new(obj)
obj = obj or {}
obj.mSize = table.getn(obj) or 0
if obj.mSize == 0 then for i in ipairs(obj) do obj.mSize = obj.mSize + 1 end end
setmetatable(obj, self)
self.__index = oop.loadOnFirstUse(self)
return obj
end
apidoc[Vector.new] = doc
doc = beginlongstringliteral
Vector:make(...) - Creates and returns a new Vector containing the
elements passed as varargs to this method.
If only one argument is specified, it is assumed to be iterable and the
elements of the iteration are added to a newly constructed Vector.
Otherwise, if there is more than one argument, each argument is added
to a newly constructed Vector.
Thus, Vector:make(1,2,3) == Vector:make{1,2,3}.
endlongstringliteral
function Vector:make(...)
if table.getn(arg) == 1 then
local v = self:new(); v:addAll(arg[1]); return v
else
return self:new(arg)
end
end
apidoc[Vector.make] = doc
local function checkRange(self, ind)
if not ind then error("Index is null",3) end
if ind < 1 or ind > self.mSize then error("Index: "..ind.." out of range,"
.." list size is: "..self.mSize,3) end
end
local function translate(self, index)
assert(self.mSize)
index = index or self.mSize
if index < 0 then return self.mSize+1+index else return index end
end
doc = beginlongstringliteral
Vector:get(index) - Returns the element at the 1-based position index in
the Vector.
Indices may be negative, with -1 retrieving the last element, -2 the
second-to-last element, and -size() retrieving the first element.
param index: in +/-[1..size()]
error: if index not in valid range
endlongstringliteral
function Vector:get(ind)
ind = translate(self,ind);
return self[ind]
end
apidoc[Vector.get] = doc
doc = beginlongstringliteral
Vector:set(index, value) - Sets the element at the 1-based position
index equal to value and returns the previous value stored there.
Indices may be negative, with -1 setting the last element, -2 the
second-to-last element, and -size() the first element.
param index: in +/-[1..size()]
Example:
> v = Vector:make(1,2,3,4)
> print(v:set(2, "hello world!"))
2
> print(v)
[1, hello world!, 3, 4]
endlongstringliteral
function Vector:set(ind, val)
ind = translate(self, ind); checkRange(self, ind)
local old = self[ind]; self[ind] = val; return old
end
apidoc[Vector.set] = doc
doc = beginlongstringliteral
Vector:add(value, [index]) - Inserts value AFTER position index, or
appends it onto the end of the Vector if index is not specified.
param index: in +/-[0..size()]
Example:
> v = Vector:make(1,2,3,4)
> v:add(-1, 0); v:add(5, -1); print(v)
[-1, 1, 2, 3, 4, 5]
> v:add(2.5, 3); print(v)
[-1, 1, 2, 2.5, 3, 4, 5]
endlongstringliteral
function Vector:add(val, ind)
ind = translate(self,ind)
if ind < 0 or ind > self.mSize then error("Index: "..ind.." out of range,"
.." list size is: "..self.mSize,2) end
self.mSize = self.mSize + 1
table.insert(self, ind+1, val)
return true
end
apidoc[Vector.add] = doc
doc = beginlongstringliteral
Vector:remove([index]) - Removes and returns the element at index or
the last element of the Vector if no index is specified.
endlongstringliteral
function Vector:remove(ind)
ind = translate(self,ind); checkRange(self,ind)
self.mSize = self.mSize - 1
return table.remove(self, ind)
end
apidoc[Vector.remove] = doc
doc = beginlongstringliteral
Vector:iter([start, [stop, [step] ]) - Returns an iterator over the
range [start..stop] (inclusive on both ends) with the step size
defaulting to +1.
This method uses iter.count(start, stop, step) to count off the indices
being iterated over. Thus:
If no parameters are specified, the iteration will be from 1..size(),+1
If one parameter, p, is specified, the iteration will be from 1..p,+1
If two parameters p1 and p2 are specified, the iteration will be over
p1..p2,+1
If three parameters, p1, p2, p3 are specified, the iteration will be
over p1..p2,+p3
Examples:
> v = Vector:make(1,2,3,4,5)
> print( Vector:make(v:iter()) )
[1, 2, 3, 4, 5]
> print( Vector:make(v:iter(-1,1,-1)) )
[5, 4, 3, 2, 1]
> print( Vector:make(v:iter(2,4) )
[2, 3, 4]
> print( Vector:make(v:iter(-1, 2, -2) )
[5, 3]
> print( Vector:make(v:iter(4, 3)) ) -- empty ranges are allowed
[]
endlongstringliteral
function Vector:iter(start, stop, step)
if self:size() == 0 then return iter.EMPTY end
if not stop then stop = translate(self,start); start = 1
else start = translate(self,start); stop = translate(self,stop) end
checkRange(self,start); checkRange(self,stop)
return coroutine.wrap(function()
for ind in iter.count(start, stop, step) do
coroutine.yield(self[ind])
end
end)
end
apidoc[Vector.iter] = doc
doc = beginlongstringliteral
Vector:asString(separator) - Concatenates all elements of the Vector
into a string and returns it.
Vectors can thus be used as string buffers. This method just wraps
table.concat().
endlongstringliteral
function Vector:asString()
return table.concat(self, separator)
end
apidoc[Vector.asString] = doc
doc = beginlongstringliteral
Vector:unpack() - Returns all of the elements of this Vector.
This method just wraps the built-in function unpack.
endlongstringliteral
function Vector:unpack() return unpack(self) end
apidoc[Vector.unpack] = doc
doc = beginlongstringliteral
Vector:size() - Returns the number of elements in this Vector.
This is also the largest valid index for calls to get, set.
endlongstringliteral
function Vector:size() return self.mSize end
apidoc[Vector.size] = doc
doc = beginlongstringliteral
Vector:__eq(other) - Returns true if self and other are the same size
and both contain the same elements in the same order.
endlongstringliteral
function Vector:__eq(other)
return self:size() == other:size() and iter.equals(self:iter(),other:iter())
end
apidoc[Vector.__eq] = doc
Vector.__call = Vector.get
doc = beginlongstringliteral
Vector:test() - Unit test.
endlongstringliteral
function Vector:test()
local vector = oop.methodCaller(Vector, "make")
local N = 10
local v = vector(iter.count(N))
assert( v:size() == N )
assert( v(1) == 1 )
assert( v(-1) == N )
assert( v:remove() == N )
assert( v:set(1,0) == 1 )
assert( v:get(1) == 0 )
assert( v:size() == N-1 )
assert( v:remove(1) == 0 )
assert( v:remove(1) == 2 )
assert( v:size() == N-3 )
assert( v:add(2,0) )
assert( v:add(1,0) )
assert( v:add(N) )
assert( v:size() == N )
--print(v)
assert( v == vector(v:iter(1,-1)))
assert( v ~= vector(v:iter(1,-2)))
assert( vector(v:iter(1,3)) == vector(v:iter(1,3)))
--print(vector(v:iter(-1,1,-1)))
end
apidoc[Vector.test] = doc
-- add some convenience methods
oop.fallback(Vector, sequences.mixins.plauralizeHelper)
oop.fallback(Vector, sequences.mixins.convenienceMethods)
oop.fallback(Vector, sequences.mixins.randomAccessHelper)
return Vector
]],
iter=[[local sano = require 'sano'
local utils = sano.utils
local oop = sano.oop
---
local iter = {}
setmetatable(iter,iter)
local doc
iter.documentation = {}
local apidoc = iter.documentation
apidoc.general = beginlongstringliteral
iter - A collection of useful functions for working with iterators and
iterable objects.
There are two contracts which are used extensively throughout the Sano
library: the *iterable* contract and the *map-iterable* contract.
An object, a, satisfies the *iterable* contract if:
1) It is an iterator function, for instance: a = iter.count(10)
2) It is an object with an 'iter' method: a = Vector:make{1,2,3,4,5}
3) It is a vanilla Lua table, for example: a = {1,2,3,4,5}
4) It is a string, i.e. a = "hello world!"
Nearly all library functions which operate on sequences of elements
will work for any object which satisfies the iterable contract. For
example, the function ordering.first takes as input an iterable object
and returns the element that would appear first if the iterable object
were to be sorted:
> a1 = iter.count(5,1,-1)
> a2 = {4,3,5,2,1}
> a3 = Vector:make(4,3,5,2,1)
> a4 = "fhazqe"
> print(ordering.first(a1), ordering.first(a2),
>> ordering.first(a3), ordering.first(a4))
1 1 1 a
As another example, the SkipSet value constructor can take any iterable
as an argument:
> a = SkipSet:make(iter.count(5,1,-1))
> print(a)
{1, 2, 3, 4, 5}
> print(SkipSet:make(a))
{1, 2, 3, 4, 5}
> print(SkipSet:make{1,2,3,4,5})
{1, 2, 3, 4, 5}
> print(SkipSet:make("fhaayejvbad"))
{"a", "b", "d", "e", "f", "h", "j", "v", "y"}
An object, a, satisfies the *map-iterable* contract if:
1) It is an iterator function which returns two values per
step: iter.zip("abc",{1,2,3})
2) It is an object whose 'iter' method returns a function satisfying 1.
3) It is a vanilla Lua table, for instance a = {a=1,b=2,c=3}
Any function which expects a map-iterable object will work for any
object which satisfies the map-iterable contract. For example, the
HashMap value constructor can take any map-iterable as an argument:
> a = HashMap:make {a=1,b=2,c=3}; print(a)
{a=1, c=3, b=2}
> print( HashMap:make(a) ) -- make a copy of a
{a=1, c=3, b=2}
> print( HashMap:make(iter.zip("abc",{1,2,3})) )
{a=1, c=3, b=2}
endlongstringliteral
doc = beginlongstringliteral
iter(obj) - Returns an iterator extracted from the supplied object.
There are four cases, each of which is handled differently:
1) If obj is a function, this function assumes it is an iterator
and returns it.
2) If obj is a string, this function returns an
iterator over the characters of the string.
3) If obj is a table with an 'iter' field, this function returns
obj:iter().
4) If obj is a table without an 'iter' field, this function returns an
iterator over the values of the table, using ipairs()
So, the loop, for e in iter(a) do ... end, works so long as a is one
of the types handled by the above cases.
synonyms: iter.from
endlongstringliteral
function iter.from(obj)
if type(obj) == "function" then return obj
elseif type(obj) == "table" then
if obj.iter then return obj:iter()
else return utils.tableVals(obj) end
elseif type(obj) == "string" then
return utils.charIter(obj)
else
error("Unrecognized type: "..type(obj)..", cannot extract iterator")
end
end
function iter.__call(_, obj) return iter.from(obj) end
apidoc[iter.__call] = doc
apidoc[iter.from] = doc
doc = beginlongstringliteral
iter.mapIter(obj) - Extracts a 2-value iterator from obj.
obj may be:
1) a function (iter.zip("abc",{123}), in which case obj is returned
directly
2) an object with an 'iter' method, in which case obj:iter() is
returned
3) a Lua table {a=1, b=2, c=3}, in which case an iteration over the
key-value pairs of the table is returned.
endlongstringliteral
function iter.mapIter(obj)
if type(obj) == "function" then return obj
elseif obj.iter then return obj:iter()
else return coroutine.wrap(function()
for k,v in pairs(obj) do coroutine.yield(k,v) end
end)
end
end
apidoc[iter.mapIter] = doc
doc = beginlongstringliteral
iter.make(func) - Creates an iterable from the supplied function, or
simply returns func if it is already a object with an iter method.
endlongstringliteral
function iter.make(func)
if type(func)=="table" and func.iter then return func
else return {iter=function() return func() end} end
end
apidoc[iter.make] = doc
doc = beginlongstringliteral
iter.EMPTY - An empty iteration.
endlongstringliteral
iter.EMPTY = function() return nil end
apidoc[iter.EMPTY] = doc
doc = beginlongstringliteral
iter.resume(iterator)
endlongstringliteral
function iter.resume(iterator)
local first
if pcall(function() for i in iterator do first = i; break; end end) then
if first ~= nil then return iter.chain({first}, iter)
else return iter.EMPTY end
else
return iter.EMPTY
end
end
apidoc[iter.resume] = doc
doc = beginlongstringliteral
iter.noCycle(iterator)
endlongstringliteral
function iter.noCycle(iterator)
local done = false
return function()
if not done then
local e = iterator()
done = e == nil
return e
end
end
end
apidoc[iter.noCycle] = doc
doc = beginlongstringliteral
iter.chain(...) - Returns an iterator that is the chaining together of
each iterable passed as an argument to this function.
If only one argument is passed to this function, it is assumed to be an
iterable over iterables.
Examples:
chain{ {1,1,1},{2},{3} } --> 1, 1, 1, 2, 3
chain( {1,1,1}, {2}, {3} ) --> 1, 1, 1, 2, 3
chain(iter.count(1,3), iter.count(3,6)) --> 1, 2, 3, 3, 4, 5, 6
endlongstringliteral
function iter.chain(...)
local iterables = table.getn(arg)==1 and iter(arg[1]) or iter(arg)
return coroutine.wrap(function()
for i in iterables do
for e in iter(i) do coroutine.yield(e) end
end
end)
end
apidoc[iter.chain] = doc
-- iter.flatten
doc = beginlongstringliteral
iter.unary(element) - Returns an 'iterator' over a single element.
The returned function will, when first called, return the element, and
on subsequent calls return nil.
endlongstringliteral
function iter.unary(element)
local returned = false
return function()
if not returned then
returned = true; return element
end
end
end
apidoc[iter.unary] = doc
doc = beginlongstringliteral
iter.map(iterable, transform) - Returns an iteration in which each
element e, in iterable, is replaced by transform(e).
The mapping is 'lazy', so the transform of an element is not applied
until that element is requested by the iteration.
Example:
iter.map({1,2,3,4}, math.sqrt) --> sqrt(1), sqrt(2), sqrt(3), sqrt(4)
endlongstringliteral
function iter.map(iterable, transformer)
local i = iter.from(iterable)
return function()
local e = i()
if e ~= nil then return transformer(e) end
end
end
apidoc[iter.map] = doc
doc = beginlongstringliteral
iter.mapN(n, transform) - Returns the result of
iter.map(iter.count(n), transform).
Example:
iter.mapN(5, function(x) return x*x end) --> 1, 4, 9, 16, 25
endlongstringliteral
function iter.mapN(n, transformer)
return iter.map(iter.count(n), transformer)
end
apidoc[iter.mapN] = doc
doc = beginlongstringliteral
iter.filter(iterable, filter [,removed]) - Returns an iteration
containing only the elements of iterable for which filter returns
true.
For each element, e which is removed from the returned iteration,
removed(e) is called if removed is specified. The filtering is 'lazy',
so the filtered iteration is not determined until the returned
iteration is exhausted.
Example:
function isEven(n) return math.mod(n,2)==0 end
iter.filter(count(10), isEven) --> 2, 4, 6, 8, 10
endlongstringliteral
function iter.filter(iterable, filter, removed)
local i = iter.from(iterable)
return coroutine.wrap(function()
for e in i do
if e == nil or filter(e) then coroutine.yield(e)
elseif removed then removed.add(e) end
end
end)
end
apidoc[iter.filter] = doc
doc = beginlongstringliteral
iter.openFilter(filter)
endlongstringliteral
function iter.openFilter(filter)
return function(iterable)
return iter.filter(iterable, filter)
end
end
apidoc[iter.openFilter] = doc
doc = beginlongstringliteral
iter.mapFilter(iterable, transform)
endlongstringliteral
function iter.mapFilter(iterable, map)
local i = iter(iterable)
return coroutine.wrap(function()
for e in i do
local t = map(e)
if t == true then coroutine.yield(e)
elseif t then coroutine.yield(t) end
end
end)
end
apidoc[iter.mapFilter] = doc
doc = beginlongstringliteral
iter.pipe(...)
endlongstringliteral
function iter.pipe(...)
-- beautiful!
return function(source)
return iter.fold(arg, iter.mapFilter, source)
end
end
apidoc[iter.pipe] = doc
doc = beginlongstringliteral
iter.pipeIter(source, ...)
endlongstringliteral
function iter.pipeIter(source, ...)
return iter.pipe(arg)(source)
end
apidoc[iter.pipeIter] = doc
doc = beginlongstringliteral
iter.fold(iterable, fn [, seed, thirdArg]) - Also sometimes called
'reduce' in functional programming contexts.
fn is a binary function. Label the elements of iterable i1, i2...iN.
fold calls fn(i1, i2) and passes the result to fn(_, i3), then passes
the result of that to fn(_, i4) and so on. The result of the last call
to fn(_, iN) is this function's return value.
Some examples:
> function sum(t) return iter.fold(t, function(a,b) return a+b end) end
> print(sum{1,2,3,4,5})
15
> -- compute the smallest element in an iterable
> function first(iterable, orderFn)
>> return iter.fold(iterable, orderFn or ordering.INCREASING)
>> end
> v = vector(iter.count(100)):shuffle()
> print(first(v), first(v,ordering.DECREASING))
1 100
param func: a binary function
param seed: the first element supplied to the folding process; if nil
defaults to the first element in iterable
param thirdArg: passed as the third argument to func at each stage of
the fold
endlongstringliteral
function iter.fold(iterable, func, seed, thirdArg)
iterable = iter(iterable)
seed = seed or iterable()
for i in iterable do
seed = func(seed, i, thirdArg)
end
return seed
end
apidoc[iter.fold] = doc
doc = beginlongstringliteral
iter.foldIter(iterable, func [, seed, thirdArg]) - Same as fold, except
that intermediate values are returned as an iterator.
Example:
add = function(a,b) return a+b end
sums = iter.toList(iter.foldSeq({1,2,3,4,5}, add)) --> [3,6,10,15]
endlongstringliteral
function iter.foldIter(iterable, func, seed, thirdArg)
iterable = iter(iterable)
seed = seed or iterable()
return coroutine.wrap(function()
for i in iterable do seed = func(seed, i, thirdArg); coroutine.yield(seed) end
end)
end
apidoc[iter.foldIter] = doc
doc = beginlongstringliteral
iter.autofolding(func, seed, thirdArg) - Turn any binary function into
a function which operates on a variable number of arguments, by
cascading the results.
Example:
function add(x,y) return x + y end
sum = autofolding(add)
print(sum(1,2,3,4)) --> 10
print(sum(iter.count(4)) --> 10
endlongstringliteral
function iter.autofolding(func, seed, thirdArg)
return function(...)
return iter.fold((table.getn(arg)>1 and arg or arg[1]), func, seed, thirdArg)
end
end
apidoc[iter.autofolding] = doc
doc = beginlongstringliteral
iter.groupEq(iterable, equalsFn) - Returns an iterator over lists of
adjacent elements that are equal according to the supplied equals
function (default uses '==').
Example:
iter.groupEq({1,1,2,2,3,3,3,4,5}) --> {1,1}, {2,2}, {3,3,3}, {4}, {5}
endlongstringliteral
function iter.groupEq(iterable, equalsFn)
local i = iter.from(iterable)
local equals = equalsFn or function(x,y) return x==y end
if type(equals) == "number" then equals = negation(everyNth(equals)) end
return coroutine.wrap(function()
local buf = {}
for e in i do
local size = table.getn(buf)
if size == 0 or equals(e, buf[size]) then
table.insert(buf, e)
else
coroutine.yield(buf)
buf = {e,n=1}
end
end
-- flush anything left in the buffer
if table.getn(buf) ~= 0 then coroutine.yield(buf) end
end)
end
apidoc[iter.groupEq] = doc
doc = beginlongstringliteral
iter.collapseEq(iterable, equalsFn) - Returns an iterator in which
adjacent duplicate elements are removed.
endlongstringliteral
function iter.collapseEq(iterable, equalsFn)
return iter.map(iter.groupEq(iterable, equalsFn), function(t) return t[1] end)
end
apidoc[iter.collapseEq] = doc
doc = beginlongstringliteral
iter.interleave(iter1, iter2) - Returns an iteration which alternates
between returning elements from iter1 and iter2.
The iteration halts when either iterator runs out of elements.
example: interleave({'a','b','c'}, {1,2,3}) --> a, 1, b, 2, c, 3
endlongstringliteral
function iter.interleave(iter1, iter2)
return coroutine.wrap(function()
local z = iter.zip(iter1, iter2)
for i,j in z do
coroutine.yield(i); coroutine.yield(j)
end
end)
end
apidoc[iter.interleave] = doc
doc = beginlongstringliteral
iter.loop(iterable [,times]) - Returns an iterator which cycles the
iterable through times repetitions, or forever, if times is nil.
endlongstringliteral
function iter.loop(iterable, times)
return coroutine.wrap(function()
local elements = {}
for i in iter.from(iterable) do
coroutine.yield(i); table.insert(elements,i)
end
if times then
for k=1,times-1 do
for i in iter.from(elements) do coroutine.yield(i) end
end
else
while true do
for i in iter.from(elements) do coroutine.yield(i) end
end
end
end)
end
apidoc[iter.loop] = doc
doc = beginlongstringliteral
iter.repeatCall(n, fn, ...) - Returns an iterator which calls the
function fn n times with the arguments (...).
Example:
> -- generate a vector of random ints, all in [1..5]
> print( vector(iter.repeatCall(10, math.random, 1, 5)) )
[3, 3, 1, 3, 5, 3, 4, 1, 4, 3]
endlongstringliteral
function iter.repeatCall(n, fn, ...)
local s = 0
return function()
if s < n then
s = s+1
return fn(unpack(arg))
end
end
end
apidoc[iter.repeatCall] = doc
doc = beginlongstringliteral
iter.restartable(iterable)
endlongstringliteral
function iter.restartable(iterable)
return coroutine.wrap(function()
local elements = {}
for i in iter(iterable) do
coroutine.yield(i); table.insert(elements,i)
end
repeat
coroutine.yield(nil)
for _,i in ipairs(elements) do coroutine.yield(i) end
until false
end)
end
apidoc[iter.restartable] = doc
doc = beginlongstringliteral
iter.reverse(iterable) - Returns an iterator that yields elements in
the reverse order of iterable.
This requires allocating a stack to store all the elements in the
iteration.
endlongstringliteral
function iter.reverse(iterable)
local t = {}
for i in iter.from(iterable) do table.insert(t,i) end
return function() if table.getn(t) > 0 then return table.remove(t) end end
end
apidoc[iter.reverse] = doc
doc = beginlongstringliteral
iter.buffer()
endlongstringliteral
function iter.buffer()
local buf, first, size = {}, 1, 0
function buf.__call()
if size > 0 then
local t = buf[first]; first = first + 1; size = size - 1; return t
end
end
function buf.iter() return function() return buf() end end
function buf.add(element) table.insert(buf, element); size = size + 1 end
function buf.addAll(elements) for i in iter.from(elements) do buf.add(i) end end
function buf.size() return size end
function buf.unpack()
local t = {}; for i=first,first+size-1 do table.insert(t,buf[i]) end
return unpack(t)
end
function buf.clear() while size > 0 do buf() end end
setmetatable(buf, buf)
return buf
end
apidoc[iter.buffer] = doc
doc = beginlongstringliteral
iter.partialWindows(iterable, size)
endlongstringliteral
local function partialWindows(iterable, size)
return coroutine.wrap(function()
local buf = iter.buffer()
for i in iter.from(iterable) do
buf.add(i)
if buf.size() > size then buf() end
coroutine.yield(buf.unpack())
end
end)
end
doc = beginlongstringliteral
iter.windows(iterable, size) - Returns an iteration in which each step
returns size elements in a 'moving window' of the iteration.
Example:
> for lead,follow in iter.windows(vector(1,2,3,4,5), 2) do
>> print(lead, follow) end
1 2
2 3
3 4
4 5
endlongstringliteral
function iter.windows(iterable, size)
return iter.trim(partialWindows(iterable, size), size-1)
end
apidoc[iter.windows] = doc
doc = beginlongstringliteral
iter.everyNth(iterable, n)
endlongstringliteral
function iter.everyNth(iterable, n)
return coroutine.wrap(function()
for ind, e in iter.enum(iterable) do
if math.mod(ind-1, n)==0 then
coroutine.yield(e)
end
end
end)
end
apidoc[iter.everyNth] = doc
doc = beginlongstringliteral
iter.cross(...)
endlongstringliteral
function iter.cross(iterable1, iterable2)
iterable1 = type(iterable1)=="number" and iter.count(iterable1) or iterable1
iterable2 = type(iterable2)=="number" and iter.count(iterable2) or iterable2
local iter2 = iter.restartable(iterable2)
return coroutine.wrap(function()
while true do
for i in iter.pack(iter(iterable1)) do
for j in iter2 do
local c = {}
utils.tableInsertAll(c, i)
table.insert(c,j)
coroutine.yield(unpack(c))
end
end
coroutine.yield(nil)
end
end)
end
iter.cross = iter.autofolding(iter.cross)
apidoc[iter.cross] = doc
doc = beginlongstringliteral
iter.pairs(iterable)
endlongstringliteral
function iter.pairs(iterable)
local elements = {}
for e in iter(iterable) do table.insert(elements,e) end
local count = table.getn(elements)
return coroutine.wrap(function()
for i=1,count do
for j=i+1,count do
coroutine.yield({elements[i], elements[j]})
end
end
end)
end
apidoc[iter.pairs] = doc
--function iter.pairs(iterable) return iter.subsets(iterable, 2) end
--function iter.trios(iterable) return iter.subsets(iterable, 3) end
doc = beginlongstringliteral
iter.preorder(roots, nodeExtractor, filter, queue)
endlongstringliteral
function iter.preorder(roots, nodeExtractor, filter, queue)
filter = filter or function(unused) return true end
if not queue then
queue = require("sano").Vector:new()
end
return coroutine.wrap(function()
local cur
for n in iter(roots) do queue:add(n) end
while queue:size() > 0 do
cur = queue:remove()
if filter(cur) then
coroutine.yield(cur)
for c in iter(nodeExtractor(cur)) do queue:add(c) end
end
end
end)
end
apidoc[iter.preorder] = doc
doc = beginlongstringliteral
iter.trim(iterable, count) - Returns an iterator which ignores the
first count elements of iterable.
endlongstringliteral
function iter.trim(iterable, headJunk)
iterable = iter.from(iterable)
for i=1,headJunk do iterable() end
return iterable
end
apidoc[iter.trim] = doc
doc = beginlongstringliteral
iter.last(iterable) - Returns the last element in the iteration over
iterable.
The iterator is exhausted by this call.
endlongstringliteral
function iter.last(iterable)
local e
for i in iter.from(iterable) do e = i end
return e
end
apidoc[iter.last] = doc
local function firstK(iterable, k)
local i = iter(iterable)
return coroutine.wrap(function()
for i=1,k do coroutine.yield(i()) end
end)
end
doc = beginlongstringliteral
iter.first(iterable [,k]) - Returns the first element of iterable if k
is unspecified, or an iterator over the first k elements.
endlongstringliteral
function iter.first(iterable, k)
return not k and iter.from(iterable)() or firstK(iterable, k)
end
apidoc[iter.first] = doc
doc = beginlongstringliteral
iter.equals(iter1, iter2, equalsFn) - Returns true if both iterations
return the same number of elements and each pair of corresponding
elements are equal according to equalsFn (default '==').
endlongstringliteral
function iter.equals(iter1, iter2, equalityFn)
equalityFn = equalityFn or function(x,y) return x==y end
local iter1, iter2 = iter.from(iter1), iter.from(iter2)
for i,j in iter.zip(iter1, iter2) do
if not equalityFn(i,j) then return false end
end
-- now make sure that both iterators have been exhausted
local i1, i2 = iter.resume(iter1), iter.resume(iter2)
local a,b = i1(), i2()
if a==nil and b==nil then return true
elseif not equalityFn(a, b) then return false
else return true end
end
apidoc[iter.equals] = doc
doc = beginlongstringliteral
iter.multiEquals(iter1, iter2) - Compares two iterators that return
multiple values for equality.
The two iterators are equal if both halt after the same number of steps
and if, for each iteration step, both return the same values in the same
order.
endlongstringliteral
function iter.multiEquals(iter1, iter2)
return iter.equals(iter.pack(iter1), iter.pack(iter2), utils.tableEquals)
end
apidoc[iter.multiEquals] = doc
doc = beginlongstringliteral
iter.toList(iterable [, collector]) - Converts an iterable to a Lua
table.
If collector is specified, collector:add(i) is called for each i in
iter(iterable) and collector is returned.
endlongstringliteral
function iter.toList(iterator, collector)
if collector then
for i in iter(iterator) do collector:add(i) end
return collector
else
local t = {}
for i in iter(iterator) do table.insert(t,i) end
return t
end
end
apidoc[iter.toList] = doc
doc = beginlongstringliteral
iter.zip(...) - 'Zips' multiple iterations up into one, similar to the
built-in python 'zip' function.
When any one of the iterations returns nil, that iteration is removed
from the zip and the zip halts. If resumed, the zip then returns
elements from the remaining live iterators.
Example:
a,b = count(5), count(10)
z = zip(a,b)
for i,j in z do print(i,j) end --> 1,1 2,2 3,3 4,4 5,5
for i in z do print(i) end --> 6 7 8 9 10
endlongstringliteral
function iter.zip(...)
if table.getn(arg) == 1 then
arg = iter.toList(arg[1])
end
for k,v in ipairs(arg) do arg[k] = iter.from(v) end
local n = table.getn(arg)
return coroutine.wrap(function()
while true do
local tuple = {}
for i=1,n do
if arg[i] then -- iterable will be nil if it has been exhausted
local e = arg[i]() -- generate next element
if e == nil then coroutine.yield(nil, arg[i]); arg[i] = nil;
else table.insert(tuple, e) end
end
end
coroutine.yield(unpack(tuple))
end
end)
end
apidoc[iter.zip] = doc
doc = beginlongstringliteral
iter.pack(iterable) - Returns an iterator which packs the result(s) of
each iteration step of iter(iterable) into a table.
Example:
iter.pack(iter.mapIter{a=1,b=2,c=3}) will yield {a,1}, {b,2}, {c,3}
endlongstringliteral
function iter.pack(iter)
return function()
local e = {iter()}
if e[1] ~= nil then return e end
end
end
apidoc[iter.pack] = doc
doc = beginlongstringliteral
iter.unpack(iterable) - Equivalent to iter.map(iterable, unpack)
endlongstringliteral
function iter.unpack(iter)
return iter.map(iter, unpack)
end
apidoc[iter.unpack] = doc
doc = beginlongstringliteral
iter.count(start, stop, step) - Returns an iterator which counts off
successive integers in the range [start..stop] with stepsize step.
This function has several defaults:
+) If no parameters are provided, this iteration will return 1,2,3,etc up
to infinity and beyond.
+) If one param p is provided, start defaults to 1 and stop becomes p,
and the iteration returned is [1..p],+1.
(The notation [a..b],+1 indicates the iteration a, a+1, a+2,..., b.)
+) If two params p1, p2 are provided, start becomes p1, stop becomes p2,
and the iteration returned is [p1..p2],+1.
+) If three params p1, p2, p3 are provided, the iteration returned is
[p1..p2],+p3. (p3 may be negative)
If start > stop and step is positive, the iteration returned will be
EMPTY. Likewise if start < stop and step is negative. Thus there is no
way to accidentally construct a count iterator which does not
terminate (these semantics are much like the Lua numeric for loop).
Examples:
count(4) --> 1 2 3 4
count(-4) --> empty
count(1,-4) --> empty
count(2,4) --> 2 3 4
count(4,2) --> empty
count(1,10,2) --> 1 3 5 7 9
count(10,1,-2) --> 10 8 6 4 2
endlongstringliteral
function iter.count(start, stop, step)
if not start then -- count from 1 to infinity
local i = 0; return function() i = i+1; return i end
elseif not stop then -- count from 1 to first param
start, stop, step = 1, start, 1
else start, step = start or 1, step or 1 end -- count from start to stop
local delta = math.abs(stop-start)
if step > 0 and stop < start or
step < 0 and stop > start then
return iter.EMPTY
end
return coroutine.wrap(function()
while math.abs(start-stop+step) < delta do
coroutine.yield(start)
start = start + step;
delta = math.abs(stop-start);
end
coroutine.yield(start)
end)
end
apidoc[iter.count] = doc
doc = beginlongstringliteral
iter.enum(iterable [, size, stop, step]) - Decorates another iteration
to return the index of each element returned from the decorated
iteration.
If iterable is an object with a size() method, or if size is explicitly
specified, the iteration will halt after iterable:size() steps.
Otherwise, the iteration halts as soon as iter(iterable) halts (this
may be undesireable if the iteration is expected to contain nil values)
If 2 args are given the indices will range from 1 to arg[2].
If 3 args are given the second and third arguments are interpreted as a
counting range for the enumeration;
If a 4th argument is specified, args 2 and 3 specify
the counting range, and arg 4 specifies the step increment.
Examples:
> for ind,v in vector("abcd"):enum() do print(ind,v) end
1 a
2 b
3 c
4 d
> vec = vector("abc"); vec:add(nil)
> for ind,v in vec:enum() do print(ind, v) end
1 a
2 b
3 c
4 nil
endlongstringliteral
function iter.enum(iteration, size, stop, step)
if type(iteration)=="table" and iteration.size then
size = iteration:size()
end
if not size then return iter.zip(iter.count(),iter.from(iteration)) end
iteration = iter.from(iteration)
local counter
if not stop then counter = iter.count(size)
else counter = iter.count(size,stop,step) end
return function()
local ind = counter()
return ind, ind and iteration() -- don't destroy an element from iteration
-- if indexing has been exhausted
end
end
apidoc[iter.enum] = doc
doc = beginlongstringliteral
iter.exchange(iterable) - Swaps the order that multiple values are
returned by two-element iterator.
Example:
-- i = 1,a 2,b 3,c
iter.exchange(i) --> a,1 b,2 c,3
endlongstringliteral
function iter.exchange(iteration)
return function() local i,j = iteration(); return j,i end
end
apidoc[iter.exchange] = doc
doc = beginlongstringliteral
iter.find(iterable, predicate) - Returns the first object, e from
iterable for which predicate(e) returns true, or nil if there are no
matches.
endlongstringliteral
function iter.find(iterable, predicate)
for i in iter.from(iterable) do
if predicate(i) then return i end
end
end
apidoc[iter.find] = doc
doc = beginlongstringliteral
iter.toString(iterable [,delimiter]) - Returns a comma+space-delimited
string of the elements in iterable, or uses the delimiter if one is
provided.
Example:
> print(iter.toString(iter.count(3)))
1, 2, 3
endlongstringliteral
function iter.toString(iterable, delimiter)
local tab = {}
for i in iter.from(iterable) do
if type(i)=="string" then
i = '\"'..i..'\"'
end
table.insert(tab, tostring(i))
end
return table.concat(tab, delimiter or ", ")
end
apidoc[iter.toString] = doc
doc = beginlongstringliteral
iter.serialize(iterable, deserializeName)
endlongstringliteral
function iter.serialize(iterable, deserializeName)
return deserializeName..'('..iter.toString(iterable)..')'
end
apidoc[iter.serialize] = doc
doc = beginlongstringliteral
iter.transpose(iterable)
endlongstringliteral
function iter.transpose(iterable)
local rows = {}
local cols
for r in iter(iterable) do
table.insert(rows, r)
cols = r:size()
end
return coroutine.wrap(function()
for i=1,cols do
coroutine.yield(coroutine.wrap(function()
for r in iter(rows) do
coroutine.yield(r:get(i))
end
end))
end
end)
end
apidoc[iter.transpose] = doc
doc = beginlongstringliteral
iter.pairsToString(iterable) - Returns a comma-delimted string of the
key-value pairs in iterable; each pair of items (a,b) returned by
iterable will be denoted by a=b in the returned string.
Example:
> print(iter.pairsToString(iter.zip("abc",{1,2,3})))
a=1, b=2, c=3
endlongstringliteral
function iter.pairsToString(iterable)
local tab = {}
for a,b in iterable do
if type(b)=="string" then
b = '\"'..b..'\"'
end
table.insert(tab, tostring(a).."="..tostring(b))
end
return table.concat(tab, ", ")
end
apidoc[iter.pairsToString] = doc
return iter
]],
Multiset=[[
local sano = require 'sano'
local sets = sano.sets
local iter = sano.iter
local collections = sano.collections
local oop = sano.oop
local SkipMap = sano.SkipMap
local Multiset = {documentation={}}
local apidoc = Multiset.documentation
local doc
apidoc.general = beginlongstringliteral
Multiset - Collection in which duplicate elements are not explicitly
stored.
A Multiset is implemented as a map in which the keys are the elements
of the Multiset and the value stored against key is a number denoting
the quantity (or multiplicity) of that element. When an element is
added to a multiset, its quantity is incremented by 1.
Multisets are handy for efficiently processing collections of objects
which are expected to contain a large number of duplicates. For
instance, to sort a list of one million integers in the range 1..5:
> m = Multiset:new(SkipMap)
> m:addAll(iter.repeatCall(1e6, math.random, 1, 5))
> print(m)
{1^199981, 2^199613, 3^200520, 4^200379, 5^199507}
> sorted = vector(m:iter())
The constructor takes an optional map implementation, which defaults to
SkipMap (thus it wasn't neccessary to supply SkipMap to the constructor
above).
Computing the sum of the numbers in a Multiset is also efficient:
> sum = 0
> for e, quantity in m:uniqueIter() do
>> sum = sum + (e*quantity)
>> end
Other operations, such as union and intersection, can be computed more
efficiently by Multisets by taking advantage of implicit storage of
duplicates.
endlongstringliteral
doc = beginlongstringliteral
Multiset.cDefaultMapType - The default backing map for this Multiset,
initially SkipMap.
endlongstringliteral
Multiset.cDefaultMapType = SkipMap
apidoc[Multiset.cDefaultMapType] = doc
doc = beginlongstringliteral
Multiset:new([map]) - Returns a new Multiset, using map as the backing
map, or Multiset.cDefaultMapType (initially SkipMap) if map is
unspecified.
There are not separate 'classes' for each different backing map type;
instead, the newly returned multiset can be used to create other
multisets with the same default backing map, for instance:
> HashMultiset = Multiset:new(HashMap)
> multi = HashMultiset:make(1,2,2,3,4)
endlongstringliteral
function Multiset:new(map)
local obj = {}
map = map or self.cDefaultMapType
obj.cDefaultMapType = map
setmetatable(obj, self)
self.__index = oop.loadOnFirstUse(self)
obj.mQuantities = map:new()
obj.mSize = 0
return obj
end
apidoc[Multiset.new] = doc
doc = beginlongstringliteral
Multiset:__tostring() - Returns a string representation of this
Multiset.
{a^3, b^1, e^12} signifies a multiset with 3 copies of element a, 1 copy
of element b, and 12 copies of element e.
endlongstringliteral
function Multiset:__tostring()
local buf = sano.makers.vector()
for e, quantity in self:uniqueIter() do
e = type(e)==string and '"'..e..'"' or tostring(e)
buf:add(e); buf:add("^"..quantity); buf:add(", ")
end
if buf:size() > 0 then buf:remove() end -- remove dangling comma
return "{"..table.concat(buf).."}"
end
apidoc[Multiset.__tostring] = doc
doc = beginlongstringliteral
Multiset:add(element [,quantity]) - Adds quantity copies of element to
this Multiset, default 1.
param element: the element to add
param quantity: the number of copies to add, defaults to 1
returns: the updated total number of copies of element in this multiset
endlongstringliteral
function Multiset:add(element, quantity)
quantity = quantity or 1
if quantity < 0 then return self:remove(element, -quantity) end
local newQuantity = (self.mQuantities:get(element) or 0) + quantity
self.mQuantities:add(element, newQuantity)
self.mSize = self.mSize + quantity
return newQuantity
end
apidoc[Multiset.add] = doc
doc = beginlongstringliteral
Multiset:addAll(iterable) - Adds all elements in collection to this
Multiset.
If iterable is also a Multiset, this takes time O(u), where u is the
number of unique elements in iterable. Otherwise, this takes O(n),
where n is the total number of elements in iterable.
endlongstringliteral
function Multiset:addAll(iterable)
if oop.isInstance(iterable, Multiset) then
for e, quantity in iterable:uniqueIter() do
self:add(e, quantity)
end
else
collections.addAll(self, iterable)
end
return self
end
apidoc[Multiset.addAll] = doc
doc = beginlongstringliteral
Multiset:remove(element, quantity) - Remove quantity copies of element
from this Multiset, default 1.
param element: the element to remove
param quantity: the number of copies to remove, defaults to 1
returns: element and the actual number of copies that were removed
example: {a^3, b^6}:remove(b, 32) will return b, 6
endlongstringliteral
function Multiset:remove(element, quantity)
local curQuantity = (self.mQuantities:get(element) or 0)
local numRemoved = math.min(quantity or 1, curQuantity)
if numRemoved ~= curQuantity then
self.mQuantities:add(element, curQuantity - numRemoved)
else
self.mQuantities:remove(element)
end
self.mSize = self.mSize - numRemoved
return element, numRemoved
end
apidoc[Multiset.remove] = doc
doc = beginlongstringliteral
Multiset:removeEntirely(element) - Removes all copies of element from
this Multiset.
This function is equivalent to calling
remove(element, getQuantity(element))
endlongstringliteral
function Multiset:removeEntirely(element)
return self:remove(element, self:getQuantity(element))
end
doc = beginlongstringliteral
Multiset:removeAll(iterable) - Removes all elements in iterable from
this Multiset, respecting quantity.
Thus, if self={a^4, b^5, e^11} and iterable={a^2, b^3}, after a call to
self:removeAll(iterable), self would be {a^2, b^2, e^11}. To remove all
elements entirely, ignoring multiplicity, use removeAllEntirely().
If collection is also a Multiset, this runs in O(u), where u is the
number of unique elements in iterable. Otherwise, this takes time O(n),
where n is the total number of elements in iterable.
endlongstringliteral
function Multiset:removeAll(iterable)
if oop.isInstance(iterable, Multiset) then
for e, quantity in iterable:uniqueIter() do
self:remove(e, quantity)
end
else
sets.removeAll(self, iterable)
end
end
apidoc[Multiset.removeAll] = doc
doc = beginlongstringliteral
Multiset:removeAllEntirely(iterable) - Remove all copies of elements in
iterable that exist in this Multiset.
endlongstringliteral
function Multiset:removeAllEntirely(iterable)
if oop.isInstance(iterable, Multiset) then
for e in iterable:uniqueIter() do self:removeEntirely(e) end
else
for e in iter(iterable) do self:removeEntirely(e) end
end
end
apidoc[Multiset.removeAllEntirely] = doc
doc = beginlongstringliteral
Multiset:retainOnly(iterable) - Modifies this Multiset so that it is
the intersection (respecting quantity) of itself and iterable.
If iterable is also a Multiset, this takes time O(u*c), where u is the
number of unique items in this Multiset and c is the time for a lookup
in the backing map. If not, then it takes O(n + u*c), where n is the
number of elements in iterable.
endlongstringliteral
function Multiset:retainOnly(iterable)
if oop.isInstance(iterable, Multiset) then
for e, quantity in self:uniqueIter() do
self:setQuantity(e, math.min(quantity, iterable:getQuantity(e)))
end
else
iterable = Multiset:make(iterable)
self:retainOnly(iterable)
end
end
apidoc[Multiset.retainOnly] = doc
doc = beginlongstringliteral
Multiset:intersectionCount(otherMultiset) - Efficiently computes the
number of overlapping elements between this Multiset and
otherMultiset.
endlongstringliteral
function Multiset:intersectionCount(otherMultiset)
local total = 0
for e, quantity in self:uniqueIter() do
total = total + math.min(quantity, otherMultiset:getQuantity(e))
end
return total
end
apidoc[Multiset.intersectionCount] = doc
doc = beginlongstringliteral
Multiset:xorCount(other) - Returns the total number of unshared elements
between this Multiset and otherMultiset.
endlongstringliteral
function Multiset:xorCount(other)
return self:size()+other:size()-self:intersectionCount(other)
end
apidoc[Multiset.xorCount] = doc
doc = beginlongstringliteral
Multiset:setQuantity(element, quantity) - Sets the number of copies of
element that are stored in this Multiset and returns the old quantity.
endlongstringliteral
function Multiset:setQuantity(element, quantity)
local oldQuantity = self:getQuantity(element)
self.mSize = self.mSize + (quantity-oldQuantity)
if quantity == 0 then self.mQuantities:remove(element)
else self.mQuantities:add(element, quantity) end
return oldQuantity
end
apidoc[Multiset.setQuantity] = doc
doc = beginlongstringliteral
Multiset:getQuantity(element) - Retrieves the number of copies of
element in this Multiset, or 0 if the element does not exist.
endlongstringliteral
function Multiset:getQuantity(element)
return self.mQuantities:get(element) or 0
end
apidoc[Multiset.getQuantity] = doc
doc = beginlongstringliteral
Multiset:contains(element) - Returns whether there are > 0 copies of
element in this Multiset.
endlongstringliteral
function Multiset:contains(element)
return self.mQuantities:get(element) ~= nil
end
apidoc[Multiset.contains] = doc
doc = beginlongstringliteral
Multiset:size() - Returns the total number of elements in this Multiset.
This is also the total number of elements that would be returned by
a call to iter().
Example: {a^3, b^7, c^8} has size 3+7+8=18 and uniqueSize=3
endlongstringliteral
function Multiset:size()
return self.mSize
end
apidoc[Multiset.size] = doc
doc = beginlongstringliteral
Multiset:uniqueSize() - Returns the total number of unique elements in
this Multiset.
This is also the total number of elements that would be returned by a
call to uniqueIter().
Example: {a^3, b^7, c^8} has uniqueSize of 3 (and size of 3+7+8=18)
endlongstringliteral
function Multiset:uniqueSize()
return self.mQuantities:size()
end
apidoc[Multiset.uniqueSize] = doc
doc = beginlongstringliteral
Multiset:uniqueIter([start, stop]) - Returns an iterator over the unique
set of elements in this Multiset.
The iteration order is determined by the iteration order of the map used
to implement this Multiset. Each call to the iterator return a pair
e,quantity, where e is a unique element in this Multiset, and quantity
is the number of copies of e in the Multiset.
param start, stop: these are passed as params to the iterator of the
underlying map
endlongstringliteral
function Multiset:uniqueIter(start, stop)
return self.mQuantities:iter(start, stop)
end
apidoc[Multiset.uniqueIter] = doc
doc = beginlongstringliteral
Multiset:iter([start, stop]) - Returns an 'uncompressed' iterator over
the elements in this Multiset, taking quanitity into account.
The optional start and stop parameters are passed to the backing map.
endlongstringliteral
function Multiset:iter(start, stop)
return coroutine.wrap(function()
for element, n in self.mQuantities:iter(start, stop) do
for i=1,n do coroutine.yield(element) end
end
end)
end
apidoc[Multiset.iter] = doc
doc = beginlongstringliteral
Multiset:__eq(other) - Returns true if both Multisets have the same
unique set of elements and the same quantities for each of these
elements.
endlongstringliteral
function Multiset:__eq(other)
return self:size()==other:size() and self.mQuantities == other.mQuantities
end
apidoc[Multiset.__eq] = doc
oop.fallback(Multiset, sets.mixins.convenienceMethods)
oop.fallback(Multiset, sets.mixins.plauralizeHelper)
doc = beginlongstringliteral
Multiset:test() - Unit test.
endlongstringliteral
function Multiset:test()
local multiset = function(...) return self:make(arg) end
local m1 = multiset(5,4,3,2,1)
assert(m1==multiset(1,2,3,4,5))
assert(m1:getQuantity(5)==1)
assert(m1:size()==5)
assert(m1:remove(1)==1)
assert(m1:size()==4)
m1:add(1,100)
assert(m1:size()==104)
local _, onesQuantity = m1:remove(1, 1000)
assert(onesQuantity==100)
local m2 = multiset()
m2:addAll(iter.repeatCall(500, math.random, 1, 10))
m2:removeAll(iter.repeatCall(100, math.random, 1, 10))
m2:retainOnly(iter.repeatCall(100, math.random, 1, 10))
end
apidoc[Multiset.test] = doc
return Multiset
]],
utils=[[
local apidoc = {}
local utils = {documentation=apidoc}
local doc
apidoc.general = beginlongstringliteral
utils - A collection of miscellaneous utility functions.
endlongstringliteral
doc = beginlongstringliteral
utils.key(obj) - Extracts a hash key from obj.
If obj is not a table, it is simply returned.
If obj is a table whose metatable, mt, has a __hash metamethod,
mt.__hash(obj) is returned.
This is method is used to generate hash keys for HashMap and HashSet.
endlongstringliteral-- return the key to be used for this type of object in a table
function utils.key(obj)
if type(obj)~="table" then
return obj
else
local t = getmetatable(obj)
if t and t.__hash then return t.__hash(obj) end
return obj
end
end
apidoc[utils.key] = doc
doc = beginlongstringliteral
utils.hash(obj) - Returns a numeric hash of obj.
If obj is a number, it is simply returned.
If obj is a table with a __hash metamethod, that method is called with
obj as an argument and the result is returned.
If obj is a table with no __hash metamethod, this function returns
utils.hashString(tostring(obj)).
For all other cases, this function returns
utils.hashString(tostring(obj)).
endlongstringliteral
function utils.hash(obj)
if type(obj)=="number" then
return obj
elseif type(obj)=="table" then
local t = getmetatable(obj)
if t and t.__hash then return t.__hash(obj)
else return utils.hashString(tostring(obj)) end
else
return utils.hashString(tostring(obj))
end
end
apidoc[utils.hash] = doc
doc = beginlongstringliteral
utils.hashString(str) - Returns a numeric hash of the input string.
This method accumulates the hash value by iterating, for each index
[1..string.len(str)], hash = hash*prime + string.byte(str, i).
If hash exceeds some large prime, it is reduced to
math.mod(hash, largePrime) and accumulation continues.
endlongstringliteral
function utils.hashString(str)
local hash = string.len(str)
-- these primes are arbitrary
local prime, largePrime = 7, 8000000269
for i=1,string.len(str) do
hash = hash*prime + string.byte(str, i)
hash = hash < largePrime and hash or math.mod(hash, largePrime)
end
return hash
end
apidoc[utils.hashString] = doc
doc = beginlongstringliteral
utils.hashIterable(iterable) - Returns a numeric hash of the input
iterable.
This method accumulates the hash value by iterating, for each
e in iter(iterable), hash = hash*prime + utils.hash(e).
If hash exceeds some large prime, it is reduced to
math.mod(hash, largePrime) and accumulation continues.
The primes chosen for this function were selected arbitrarily.
endlongstringliteral
function utils.hashIterable(iterable)
local prime, largePrime = 7, 8000000269
local hash, hashFn = 0, utils.hash
local iter = require('sano').iter
for e in iter(iterable) do
hash = hash*prime + hashFn(e)
hash = hash < largePrime and hash or math.mod(hash, largePrime)
end
return hash
end
apidoc[utils.hashIterable] = doc
utils.Nil = {}
local Nil = utils.Nil
function utils.size(obj)
if type(obj) == "table" then
if obj.size then return obj:size()
else return table.getn(obj) end
elseif type(obj) == "string" then
return string.len(obj)
else
error("Cannot extract size from unrecognized type: "..tostring(obj)
..".\nThis function only works for tables, collections, and strings")
end
end
function utils.tableVals(atable)
return coroutine.wrap(function()
while true do
for _,v in ipairs(atable) do coroutine.yield(v,_) end
coroutine.yield(nil) -- allows restarting
end
end)
end
function utils.tableEquals(t1,t2)
for k,v in pairs(t1) do
if t2[k] ~= v then return false end
end
for k,v in pairs(t2) do
if t1[k] ~= v then return false end
end
return true
end
function utils.tableCopy(t)
local copy = {}
for k,v in pairs(t) do copy[k] = v end
if getmetatable(t) then setmetatable(copy,getmetatable(t)) end
return copy
end
function utils.tableInsertAll(t,t2)
for e in utils.tableVals(t2) do table.insert(t,e) end
end
local function maskNil(maybeNil) return maybeNil == nil and Nil or maybeNil end
local function unmaskNil(maybeNil) return maybeNil ~= Nil and maybeNil or nil end
utils.maskNil = maskNil
utils.unmaskNil = unmaskNil
-- returns a comma-delimited string of the elements in iterable
local function iterToString(iterable)
local tab = {}
for i in iterable do table.insert(tab, tostring(i)) end
return table.concat(tab, ", ")
end
local function memoizedKnownArgs(func, numArgs)
local results = {}
if numArgs == 1 then
return function(...)
local t = results[arg[1endlongstringliteral
if t == nil then
t = func(arg[1])
results[arg[1endlongstringliteral = t
end
return t
end
else
error("only works for memoizing 1 param functions")
end
end
-- returns a memoized version of the function; the memoized values are
-- contained in the upvalues of the returned function
function utils.memoized(func, numArgs)
if numArgs then return memoizedKnownArgs(func, numArgs) end
local results = {}
setmetatable(results, {__mode="v"}) -- weak values
return function(...)
local s,t = iterToString(utils.tableVals(arg))
t = results[s]
if t == nil then
t = func(unpack(arg))
results[s] = t
end
return t
end
end
function utils.charIter(str)
local pos = 0
return function()
pos = pos + 1
if pos <= string.len(str) then
return string.sub(str,pos,pos)
end
end
end
return utils
]],
LinkedList=[[
local sano = require 'sano'
local oop = sano.oop
local sequences = sano.sequences
local iter = sano.iter
----
local apidoc = {}
local doc
local LinkedList = {documentation=apidoc}
local function LNode(val, prev, next)
return {val=val, prev=prev, next=next}
end
apidoc.general = beginlongstringliteral
LinkedList - Basic doubly linked list implementation.
LinkedList can be used as a FIFO queue: add() by default appends to the
end of the list (enqueue) and remove() removes by default from the
front of the list (dequeue). (get() by default returns the element
that would be removed next via a call to remove() and operates like a
'peek' method)
The nodes of the list are tables with fields "val", "next", and "prev".
It IS safe to modify a LinkedList while iterating, for example:
> a = LinkedList:make(iter.count(8))
> for node in a:nodeIter() do -- filter out even integers
>> if math.mod(node.val, 2)==0 then
>> a:remove(node)
>> end
>> end
> print(a)
[1, 3, 5, 7]
Elements that are added while iterating are never seen by the iteration,
even when they are added after the current node via a call to addAfter,
for example:
> a = LinkedList:make(iter.count(5))
> for n in a:nodeIter() do
>> print(n.val)
>> a:addAfter("between", n)
>> end
1
2
3
4
5
> print(a)
[1, between, 2, between, 3, between, 4, between, 5, between]
Also see QueueVector, SkipVector.
endlongstringliteral
doc = beginlongstringliteral
LinkedList:new() - Returns a new empty LinkedList.
endlongstringliteral
function LinkedList:new()
local obj = {}
setmetatable(obj, self)
obj.mHead = LNode()
obj.mTail = LNode()
obj.mHead.next, obj.mTail.prev = obj.mTail, obj.mHead
assert(obj.mTail.prev)
obj.mSize = 0
self.__index = oop.loadOnFirstUse(self)
return obj
end
apidoc[LinkedList.new] = doc
local function computeSize(self)
local count = 0
for e in self:nodeIter() do count = count + 1 end
self.mSize = count
return count
end
local function insertAfter(before, at)
local after = before.next
before.next, at.next, at.prev, after.prev = at, after, before, at
return at
end
local function insertBefore(after, at)
assert(after.prev)
return insertAfter(after.prev, at)
end
local function remove(at)
local before, after = at.prev, at.next
before.next, after.prev = after, before
return at
end
doc = beginlongstringliteral
LinkedList:add(val [,beforeNode]) - Adds val before the supplied node,
or at the end of the LinkedList if no second parameter is specified.
returns: the added node, which can be passed to a call to remove
synonyms: LinkedList.addBefore
endlongstringliteral
function LinkedList:add(val, beforeNode)
self.mSize = self.mSize + 1
return insertBefore(beforeNode or self.mTail, LNode(val))
end
apidoc[LinkedList.add] = doc
doc = beginlongstringliteral
LinkedList:addAfter(val [,afterNode]) - Adds val AFTER the given node,
or at the end of the LinkedList if no second parameter is specified.
endlongstringliteral
function LinkedList:addAfter(val, afterNode)
self.mSize = self.mSize + 1
return insertAfter(afterNode or self.mTail.prev, LNode(val))
end
apidoc[LinkedList.addAfter] = doc
LinkedList.addBefore = LinkedList.add
doc = beginlongstringliteral
LinkedList:remove(node) - Removes node from this LinkedList.
Nodes can be accessed via a call to nodeIter, firstNode,
lastNode, or add:
> -- assuming a == [1,2,3,4,5,6,7,8]
> for node in a:nodeIter() do -- filter out even ints
>> if math.mod(node.val, 2) == 0 then a:remove(node) end
>> end
> print(a)
[1, 3, 5, 7]
endlongstringliteral
function LinkedList:remove(node)
local t
if self.mSize > 0 then
t = remove(node or self.mHead.next)
self.mSize = self.mSize - 1
return t.val
end
end
apidoc[LinkedList.remove] = doc
doc = beginlongstringliteral
LinkedList:removeElement(val) - Removes the first-encountered instance
of val from this LinkedList.
Returns val if it was removed successfully, otherwise nil.
endlongstringliteral
function LinkedList:removeElement(val)
for n in self:nodeIter() do
if n.val == val then return self:remove(n) end
end
end
apidoc[LinkedList.removeElement] = doc
function LinkedList:prepend(val) return self:addAfter(val, self.mHead) end
function LinkedList:append(val) return self:add(val) end
doc = beginlongstringliteral
LinkedList:removeFirst() - Removes the first element of this list and
returns it.
If this list is empty then an error is thrown.
endlongstringliteral
function LinkedList:removeFirst()
return self:size() > 0 and self:remove(self.mHead.next) or error("Cannot remove from empty LinkedList")
end
apidoc[LinkedList.removeFirst] = doc
doc = beginlongstringliteral
LinkedList:removeLast() - Removes the last element of this list and
returns it.
If this list is empty then this method throws an error.
endlongstringliteral
function LinkedList:removeLast()
return self:size() > 0 and self:remove(self.mTail.prev) or error("Cannot remove from empty LinkedList")
end
apidoc[LinkedList.removeLast] = doc
doc = beginlongstringliteral
LinkedList:get([node]) - Gets the value being stored at node, or the
first element in this LinkedList.
endlongstringliteral
function LinkedList:get(node) return (node or self.mHead.next).val end
apidoc[LinkedList.get] = doc
function LinkedList:firstNode() if self.mSize > 0 then return self.mHead.next end end
function LinkedList:lastNode() if self.mSize > 0 then return self.mTail.prev end end
doc = beginlongstringliteral
LinkedList:first() - Returns the first element in this LinkedList.
If this list is empty, this method returns nil.
Example:
> a = LinkedList:make(4,3,2,1); print(a:first())
4
endlongstringliteral
function LinkedList:first() if self.mSize > 0 then return self.mHead.next.val end end
apidoc[LinkedList.first] = doc
doc = beginlongstringliteral
LinkedList:last() - Returns the last element of this LinkedList.
If this list is empty, this method returns nil.
Example:
> a = LinkedList:make(4,3,2,1); print(a:last())
1
endlongstringliteral
function LinkedList:last() if self.mSize > 0 then return self.mTail.prev.val end end
apidoc[LinkedList.last] = doc
doc = beginlongstringliteral
LinkedList:set(node, val) - Equivalent to performing node.val = val.
val is returned by this call.
endlongstringliteral
function LinkedList:set(node, val) node.val = val; return val end
apidoc[LinkedList.set] = doc
doc = beginlongstringliteral
LinkedList:size() - Returns the number of elements in this LinkedList.
The first size()-call after a call to LinkedList:splice() will take
O(size()) to compute. In all other cases it takes constant time.
endlongstringliteral
function LinkedList:size() return self.mSize or computeSize(self) end
apidoc[LinkedList.size] = doc
doc = beginlongstringliteral
LinkedList:iter() - Returns an iterator over the elements of this
LinkedList.
The number of elements in the iteration will be equal to self:size().
endlongstringliteral
function LinkedList:iter()
local n = self.mHead
return function()
n = n.next
return n and n.val or nil
end
end
apidoc[LinkedList.iter] = doc
doc = beginlongstringliteral
LinkedList:reverseIter() - Returns an iterator over the elements of this
LinkedList in reverse order.
endlongstringliteral
function LinkedList:reverseIter()
local n = self.mTail
return function()
n = n.prev
return n and n.val
end
end
apidoc[LinkedList.reverseIter] = doc
doc = beginlongstringliteral
LinkedList:nodeIter([start [, stop] ]) - Returns an iterator over the
nodes of this LinkedList.
The nodes returned by this iteration can be passed to a call to remove()
addBefore(), or addAfter().
endlongstringliteral
function LinkedList:nodeIter(start, stop)
start, stop = start or self.mHead.next, stop or self.mTail
return coroutine.wrap(function()
repeat
local t = start.next
coroutine.yield(start)
start = t
until start == stop
end)
end
apidoc[LinkedList.nodeIter] = doc
doc = beginlongstringliteral
LinkedList:splice(startNode, endNode) - Removes the nodeRange
[startNode..endNode] (inclusive both sides) and returns it as a list.
The returned list will have as its first element startNode.val and as
its last element endNode.val.
endlongstringliteral
function LinkedList:splice(startNode, endNode)
local before, after = startNode.prev, endNode.next
before.next, after.prev = after, before
local l = LinkedList:new()
l.mHead.next, startNode.prev = startNode, l.mHead
l.mTail.prev, endNode.next = endNode, l.mTail
self.mSize = nil
l.mSize = nil
return l
end
apidoc[LinkedList.splice] = doc
doc = beginlongstringliteral
LinkedList:join(otherList [,afterNode]) - Inserts the LinkedList
otherList after the supplied node or concatenates it onto the end if
no insert position is specified.
otherList is invalidated as a result of this call.
returns: self (to allow chaining -- a:join(b):join(c):join(d))
endlongstringliteral
function LinkedList:join(otherList, afterNode)
afterNode = afterNode or self.mTail.prev
local before = afterNode
local after = afterNode.next
local newsize = self:size()+otherList:size()
local jfirst, jlast = otherList.mHead.next, otherList.mTail.prev
before.next, after.prev, jfirst.prev, jlast.next =
otherList.mHead.next, otherList.mTail.prev, before, after
self.mSize = newsize
oop.invalidate(otherList, LinkedList)
return self
end
apidoc[LinkedList.join] = doc
doc = beginlongstringliteral
LinkedList:__eq(other) - Returns true if both LinkedLists have the same
size and contain the same elements in the same order.
endlongstringliteral
function LinkedList:__eq(other)
return self:size()==other:size() and iter.equals(self, other)
end
apidoc[LinkedList.__eq] = doc
doc = beginlongstringliteral
LinkedList:test() - Unit test.
endlongstringliteral
function LinkedList:test()
local link = function(...) return self:make(unpack(arg)) end
local a = link(1,2,3,4)
local b = link(5,6,7,8)
assert(a:join(b) == link(iter.count(8))); assert(a:size()==8)
assert(a:remove()==1); assert(a:removeLast()==8)
assert(a:size()==6)
assert(a:prepend(1)); assert(a:append(8))
assert(a:size()==8)
local c = link(2.1,2.2,2.3,2.4)
a:join(c, a.mHead.next.next)
assert(a == link(1, 2, 2.1, 2.2, 2.3, 2.4, 3, 4, 5, 6, 7, 8))
local innerList = a:splice(a.mHead.next.next, a.mTail.prev.prev)
assert(innerList == link(2,2.1,2.2,2.3,2.4,3,4,5,6,7))
assert(a:size()==2); assert(a:first()==1); assert(a:last()==8)
end
apidoc[LinkedList.test] = doc
oop.fallback(LinkedList, sequences.mixins.convenienceMethods)
oop.fallback(LinkedList, sequences.mixins.plauralizeHelper)
return LinkedList
]],
HashMap=[[
local sano = require "sano"
local utils = sano.utils
local oop = sano.oop
local maps = sano.maps
local ordering = sano.ordering
local iter = sano.iter
local Vector = sano.Vector
local key = utils.key
local HashMap = {documentation = {}}
local apidoc = HashMap.documentation
local doc
apidoc.general = beginlongstringliteral
HashMap - Table-based implementation of a map with support for
user-defined hash functions and nil values.
On a call to HashMap:add(key,val), HashMap checks for the existence of
getmetatable(key).__hash. If it exists, key:__hash() is called and the
result is used to select a bucket for the entry (key=val). If it does
not exist, the default Lua hashing function is used to determine the
bucket for the entry.
For instance, The Tuple class overrides __hash and __eq, so HashMap can
be used as a multi-key map by making the keys tuples:
> h = HashMap:new()
> tuple = sano.makers.tuple
> h:add(tuple(1,1,2), tuple(3,4))
...
> print( h:get(tuple(1,1,2)) )
(3, 4)
If h were a regular Lua table, the two instances of tuple(1,1,2) would
not be recognized as being equivalent:
> h = {}
> h[tuple(1,1,2)] = tuple(3,4)
...
> print( h[tuple(1,1,2)] )
nil
If the __hash metamethod hashes distinct keys to the same bucket, then
all keys which are different according to '==' are retained in the
HashMap. If Tuple used a bad hash function and by chance tuple(1,2,3)
and tuple(3,2,1) hashed to the same value, both would still be retained
as keys since tuple(1,2,3) ~= tuple(3,2,1).
Mutable objects should not be changed when they are being used as keys
in a HashMap, as HashMap has no way of knowing when keys change and will
not know to rehash the changed key.
More examples of usage:
> h = HashMap:new(); h:add("Jones","Phil"); print(h)
{Jones="Phil"}
> print(h:get("Jones"))
Phil
> h:add("Smith","Andrea"); h:add("Doe","John")
> for k,v in h:iter() do print(k,v) end
Smith Andrea
Jones Phil
Doe John
> for k,v in h:orderedIter() do print(k,v) end -- entries sorted by key
Doe John
Jones Phil
Smith Andrea
> print(h:add("Jones","Frankie")) -- override previous value
Phil
> print(h)
{Smith="Andrea", Jones="Frankie", Doe="John"}
> print(h:size()) -- prints the number of entries in the map
3
endlongstringliteral
local emptyVector = Vector:new()
local mt = {__index = function(t,k) return emptyVector end}
doc = beginlongstringliteral
HashMap:new() - Creates and returns a new, empty HashMap.
endlongstringliteral
function HashMap:new()
obj = {}
obj.mMap = {}
obj.mNativeMap = {}
setmetatable(obj, self)
setmetatable(obj.mMap, mt)
self.__index = oop.loadOnFirstUse(self)
obj.mSize = 0
return obj
end
apidoc[HashMap.new] = doc
local function isNative(obj)
local mt = getmetatable(obj)
return not mt or not mt.__hash
end
local Nil = {}
local function maskNil(obj) return obj~=nil and obj or Nil end
local function unmaskNil(obj) return obj~=Nil and obj or nil end
local function get(self, keyobj)
if isNative(keyobj) then
return self.mNativeMap[keyobj]
else
local k = key(keyobj)
for e in self.mMap[k]:iter() do
if e[1] == keyobj then return e end
end
end
end
doc = beginlongstringliteral
HashMap:add(key,value) - Adds the key-value pair to this HashMap and
returns the previous value stored against key (possibly nil)
value may be nil, but key must be non-nil.
Example:
> h = hashmap {a=1,b=2}; print(h:add("a",19))
1
endlongstringliteral
function HashMap:add(keyobj, value)
assert(key~=nil, "HashMap cannot use nil keys (nil values are okay)")
--print(keyobj,value,isNative(keyobj),getmetatable(keyobj) and getmetatable(keyobj).__hash)
if isNative(keyobj) then
local nativemap = self.mNativeMap
local old = nativemap[keyobj]
nativemap[keyobj] = maskNil(value)
if old==nil then self.mSize = self.mSize+1 end
return old
else
local entry = get(self, keyobj)
local old
if entry then
old, entry[2] = entry[2], value
else
local k = key(keyobj)
if rawequal(self.mMap[k],emptyVector) then
self.mMap[k] = Vector:new{{keyobj,value}}
else
self.mMap[k]:add({keyobj,value})
end
end
if not entry then
self.mSize = self.mSize + 1
end
return old
end
end
apidoc[HashMap.add] = doc
doc = beginlongstringliteral
HashMap:get(key) - Returns the value stored against key, or nil if no
no such value exists.
If the value of nil is explicitly stored against key, then get(key) will
return nil and contains(key) will return true.
synonym: __call
So, m:get("h") == m("h")
endlongstringliteral
function HashMap:get(keyobj)
if isNative(keyobj) then return unmaskNil(get(self, keyobj))
else
local entry = get(self, keyobj)
return entry and entry[2] or nil
end
end
apidoc[HashMap.get] = doc
HashMap.__call = HashMap.get
doc = beginlongstringliteral
HashMap:contains(key) - Returns true if this map contains a mapping
(possibly nil) for the given key.
endlongstringliteral
function HashMap:contains(keyobj)
return get(self, keyobj) ~= nil
end
apidoc[HashMap.contains] = doc
doc = beginlongstringliteral
HashMap:remove(key) - Removes the entry with key == key from this
HashMap and returns the value associated with key.
Example:
> h = HashMap:make {a=1,b=2}; print(h:remove("a"))
1
> print(h)
{b=2}
endlongstringliteral
function HashMap:remove(keyobj)
if isNative(keyobj) then
local old = self.mNativeMap[keyobj]
if old~=nil then self.mSize = self.mSize-1 end
self.mNativeMap[keyobj] = nil
return unmaskNil(old)
else
local k = key(keyobj)
local vec = self.mMap[k]
local toremove
for i=1,vec:size() do
if vec[i][1]==keyobj then
toremove=i; break
end
end
if toremove then
self.mSize = self.mSize - 1
return vec:remove(toremove)[2]
end
end
end
apidoc[HashMap.remove] = doc
doc = beginlongstringliteral
HashMap:iter() - Returns an iteration over all key-value pairs in this
map.
Each iteration step returns two values; the first is the key for the
current entry, the second is the value for the entry.
endlongstringliteral
function HashMap:iter()
return coroutine.wrap(function()
for key,val in pairs(self.mNativeMap) do
coroutine.yield(key,unmaskNil(val))
end
for key,val in pairs(self.mMap) do
for j in val:iter() do
coroutine.yield(j[1],j[2])
end
end
end)
end
apidoc[HashMap.iter] = doc
doc = beginlongstringliteral
HashMap:orderedIter(orderFn) - Returns an iteration over the entries in
this HashMap, ordered by key.
orderFn determines how the keys are ordered; default is natural
increasing order. This method internally makes a copy of the key set
of this map and sorts it.
endlongstringliteral
function HashMap:orderedIter(orderFn)
local sortedKeys = ordering.sorted(self:iter(), orderFn or ordering.INCREASING)
return coroutine.wrap(function()
for k in iter.from(sortedKeys) do
coroutine.yield(k, self:get(k))
end
end)
end
apidoc[HashMap.orderedIter] = doc
doc = beginlongstringliteral
HashMap:size() - Returns the number of key-value pairs in this HashMap.
key-value pairs with a nil value are included in the count.
endlongstringliteral
function HashMap:size() return self.mSize end
apidoc[HashMap.size] = doc
doc = beginlongstringliteral
HashMap:__eq(other) - Returns true if both maps have the same size and
contain the same mappings.
endlongstringliteral
function HashMap:__eq(tab2)
if self:size() ~= tab2:size() then return false
else
for k,v in self:iter() do
if tab2:get(k) ~= v then return false end
end
end
return true
end
apidoc[HashMap.__eq] = doc
oop.fallback(HashMap, maps.mixins.convenienceMethods)
oop.fallback(HashMap, maps.mixins.plauralizeHelper)
doc = beginlongstringliteral
HashMap:test() - Unit test.
endlongstringliteral
function HashMap:test()
local hashmap = function(map) return self:make(map) end
local h1 = hashmap{a=1,b=2,c=3,d=4}
assert(h1 == hashmap{a=1,b=2,c=3,d=4})
assert(h1:size()==4)
assert(h1:get('b'))
assert(h1:get("b")==2, h1:get('b'))
assert(h1:add("b",22)==2)
assert(h1:remove("a")==1)
assert(iter.equals(h1:orderedIter(), {'b','c','d'}))
assert(h1:size()==3)
assert(not h1:add("e",nil))
assert(h1:size()==4)
assert(h1:contains("e"))
assert(h1:get("e")==nil)
assert(h1:remove("e")==nil)
-- h1 == {b=22,c=3,d=4}
local tuple = sano.makers.tuple
local tup = tuple("John","Doe")
h1:add(tup,12345)
assert(h1:get(tuple("John","Doe"))==12345)
assert(h1:remove(tup)==12345)
assert(not h1:add(tup,nil))
assert(h1:contains(tup)) -- nil value still counts for contains
assert(h1:get(tup)==nil)
assert(h1:size()==4, h1:size())
assert(h1:remove(tup)==nil)
assert(h1:size()==3)
end
apidoc[HashMap.test] = doc
return HashMap
]],
LinkedMap=[[
local sano = require 'sano'
local HashMap = sano.HashMap
local LinkedList = sano.LinkedList
local oop = sano.oop
local maps = sano.maps
local iter = sano.iter
-----
local apidoc = {}
local doc
local LinkedMap = {documentation=apidoc, cDefaultMap=HashMap}
apidoc.general = beginlongstringliteral
LinkedMap - Map implementation in which iteration order is same as
insertion order.
In a regular HashMap (or a plain Lua table), the iteration order is
dependent on the hash function and can be quite random. This is
sometimes undesireable. In a LinkedMap, the iteration order of entries
is the same as the order in which the entries are added to the map.
For example:
> a = HashMap:make("edcba", iter.count(5,1,-1)); print(a)
{a=1, c=3, b=2, e=5, d=4}
> a = LinkedMap:make("edcba", iter.count(5,1,-1)); print(a)
{e=5, d=4, c=3, b=2, a=1}
Note that the iteration order is determined only by the first point
when an entry is added. If an entry is subsequently modified, its
position in the iteration is unchanged unless it is removed then
reinserted.
endlongstringliteral
doc = beginlongstringliteral
LinkedMap:new(mapImpl) - Returns a new empty LinkedList.
endlongstringliteral
function LinkedMap:new(mapImpl)
local obj = {}
setmetatable(obj, self)
self.__index = oop.loadOnFirstUse(self)
obj.mMap = (mapImpl or self.cDefaultMap):new()
obj.mList = LinkedList:new()
return obj
end
apidoc[LinkedMap.new] = doc
doc = beginlongstringliteral
LinkedMap.cDefaultMap - The default backing map for this LinkedMap
(initially HashMap).
endlongstringliteral
apidoc[LinkedMap.cDefaultMap] = doc
doc = beginlongstringliteral
LinkedMap:add(key, val) - Adds the key-value pair to this map and
returns the previous value stored against key (possibly nil).
If there is already a value stored against key, this function
is equivalent to a 'set' operation, and has no effect on the iteration
order.
endlongstringliteral
function LinkedMap:add(key, val)
local entry = self.mMap:get(key)
if entry then
local old = entry.val[2]
entry.val[2] = val
return old
else
self.mMap:add(key, self.mList:add{key,val})
return nil
end
end
apidoc[LinkedMap.add] = doc
doc = string.gsub(HashMap.documentation[HashMap.remove], 'HashMap', 'LinkedMap')
function LinkedMap:remove(key)
local entry = self.mMap:remove(key)
if entry then
return self.mList:remove(entry)[2]
end
end
apidoc[LinkedMap.remove] = doc
doc = string.gsub(HashMap.documentation[HashMap.get], 'HashMap', 'LinkedMap')
function LinkedMap:get(key)
local t = self.mMap:get(key)
return t and t.val[2]
end
apidoc[LinkedMap.get] = doc
LinkedMap.__call = LinkedMap.get
doc = string.gsub(HashMap.documentation[HashMap.contains], 'HashMap', 'LinkedMap')
function LinkedMap:contains(key)
return self.mMap:get(key) ~= nil
end
apidoc[LinkedMap.contains] = doc
doc = beginlongstringliteral
LinkedMap:iter() - Returns an iterator over the key-value pairs in this
map in the same order in which they were inserted.
Each step in the iteration returns two values.
endlongstringliteral
function LinkedMap:iter()
return coroutine.wrap(function()
for e in self.mList:iter() do
coroutine.yield(e[1], e[2])
end
end)
end
apidoc[LinkedMap.iter] = doc
doc = string.gsub(HashMap.documentation[HashMap.size], 'HashMap', 'LinkedMap')
function LinkedMap:size()
return self.mList:size()
end
apidoc[LinkedMap.size] = doc
doc = beginlongstringliteral
LinkedMap:__eq(other) - Returns true iff maps.orderedEquals(self, other).
endlongstringliteral
function LinkedMap:__eq(other)
return maps.orderedEquals(self, other)
end
apidoc[LinkedMap.__eq] = doc
doc = beginlongstringliteral
LinkedMap:test() - Unit test.
endlongstringliteral
function LinkedMap:test()
local lmap = function(map, vals) return self:make(map, vals) end
local h1 = lmap("abcd", iter.count())
assert(h1 == lmap("abcd", iter.count()))
assert(h1:size()==4)
assert(h1:get("b")==2, h1:get('b'))
assert(h1:add("b",22)==2)
assert(h1:remove("a")==1)
assert(h1:size()==3)
assert(not h1:add("e",nil))
assert(h1:size()==4)
assert(h1:contains("e"))
assert(h1:get("e")==nil)
assert(h1:remove("e")==nil)
-- h1 == {b=22,c=3,d=4}
local tuple = sano.makers.tuple
local tup = tuple("John","Doe")
h1:add(tup,12345)
assert(h1:get(tuple("John","Doe"))==12345)
assert(h1:remove(tup)==12345)
assert(not h1:add(tup,nil))
assert(h1:contains(tup)) -- nil value still counts for contains
assert(h1:get(tup)==nil)
assert(h1:size()==4, h1:size())
assert(h1:remove(tup)==nil)
assert(h1:size()==3)
end
apidoc[LinkedMap.test] = doc
oop.fallback(LinkedMap, maps.mixins.convenienceMethods)
oop.fallback(LinkedMap, maps.mixins.plauralizeHelper)
return LinkedMap]],
HashSet=[[
local sano = require "sano"
local collections = sano.collections
local sets = sano.sets
local oop = sano.oop
local Vector = sano.Vector
local iter = sano.iter
local utils = sano.utils
local key = utils.key
local maskNil = utils.maskNil
local unmaskNil = utils.unmaskNil
local apidoc = {}
local doc
local HashSet = {documentation = apidoc}
apidoc.general = beginlongstringliteral
HashSet - Table-based set implementation with support for user defined
hash functions and nil elements.
Examples:
> set = oop.methodCaller(HashSet, "make")
> s = set(iter.count(10))
> s2 = set(iter.count(5,15))
> print(s + s2) -- synonym for s:union(s2)
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
> print(s - s2)
{1, 2, 3, 4}
> print(s:intersection(s2))
{5, 6, 7, 8, 9, 10}
> print(s:size())
10
> print(s:contains(3), s(3)) -- __call is synonym for contains
true true
> print(set(1,2,3) == set(1,2,3))
true
HashSets are themselves hashable, so they can be stored in HashSets or
used as keys in HashMaps, for example:
> s = set(set(1,2), set(2,1), set(3,4))
> print(s)
{{1, 2}, {3, 4}}
HashSet can also store nil as an element, although when iterating over
a HashSet, the nil element will cause the iteration to halt, as Lua uses
nil to signal the end of an iteration. To work around this, use the enum
method:
> s = set(1,2); s:add(nil)
> for _,e in s:enum() do print(e) end
1
2
nil
See HashMap for more information on how the Sano library handles
hashing of user defined objects.
endlongstringliteral
local emptyVector = Vector:new()
local mt = {__index = function(t,k) return emptyVector end}
doc = beginlongstringliteral
HashSet:new() - Creates and returns a new, empty HashSet
endlongstringliteral
function HashSet:new()
local obj = {}
obj.mSize = 0
obj.mSet = {}
setmetatable(obj.mSet, mt)
setmetatable(obj, self)
self.__index = oop.loadOnFirstUse(self)
return obj
end
apidoc[HashSet.new] = doc
doc = beginlongstringliteral
HashSet:contains(element) - Returns true if element exists in this set.
Since HashSet can be used to store nil elements, this method returns
true if element is nil and there has been a prior call to add(nil)
The __call metamethod is a synonym for contains.
endlongstringliteral
function HashSet:contains(element)
local e = maskNil(element)
return self.mSet[key(e)]:contains(e)
end
apidoc[HashSet.contains] = doc
HashSet.__call = HashSet.contains
doc = beginlongstringliteral
HashSet:add(element) - Adds element to this HashSet and returns true
if it did not already exist in the set.
nil may be added to a HashSet.
endlongstringliteral
function HashSet:add(element)
if self:contains(element) then return false
else
local e = maskNil(element)
local h = key(e)
if rawequal(self.mSet[h], emptyVector) then
self.mSet[h] = Vector:new{e}
else
self.mSet[h]:add(e)
end
self.mSize = self.mSize + 1
return true
end
end
apidoc[HashSet.add] = doc
doc = beginlongstringliteral
HashSet:remove(element) - Removes and returns the element if it existed
in this HashSet; otherwise returns nil.
endlongstringliteral
function HashSet:remove(element)
if not self:contains(element) then return nil
else
local e = maskNil(element)
self.mSet[key(e)]:removeElement(e)
self.mSize = self.mSize - 1
return element
end
end
apidoc[HashSet.remove] = doc
doc = beginlongstringliteral
HashSet:iter() - Returns an iterator over the elements of this HashSet.
endlongstringliteral
function HashSet:iter()
return coroutine.wrap(function()
for key,val in pairs(self.mSet) do
for j in val:iter() do
coroutine.yield(unmaskNil(j)) end end
end)
end
apidoc[HashSet.iter] = doc
doc = beginlongstringliteral
HashSet:size() - Returns the number of elements in this HashSet.
endlongstringliteral
function HashSet:size() return self.mSize end
apidoc[HashSet.size] = doc
doc = beginlongstringliteral
HashSet:__eq(set2) - Returns true if both sets have the same size and
both contain the same elements.
endlongstringliteral
function HashSet:__eq(set2)
if self:size() ~= set2:size() then
return false
else
for i in self:iter() do
if not set2:contains(i) then return false end
end
end
return true
end
apidoc[HashSet.__eq] = doc
oop.fallback(HashSet, sets.mixins.plauralizeHelper)
oop.fallback(HashSet, sets.mixins.convenienceMethods)
doc = beginlongstringliteral
HashSet:test() - Unit test.
endlongstringliteral
function HashSet:test()
local set = oop.methodCaller(self, "make")
local N = 10
local s1 = set(iter.count(N))
assert(s1:contains(N-1))
assert(not s1:add(N-1))
assert(s1:size() == N)
assert(s1 == set(iter.count(N)))
assert(s1:addAll(iter.count(N)):size() == N)
local diff = s1 - set(iter.count(N/2+1,N))
local diffC = set(iter.count(N/2))
assert(diff == diffC)
local tuple = sano.makers.tuple
local s2 = set(tuple(1,2), tuple(3,4), tuple(5,6))
assert(s2:contains(tuple(1,2)))
assert(s2(tuple(1,2)))
local s3 = set(set(1,2), set(2,1), set(3,4))
assert(s3:size()==2)
end
apidoc[HashSet.test] = doc
return HashSet
]],
maps=[[local sano = require 'sano'
local collections = sano.collections
local sets = sano.sets
local oop = sano.oop
local iter = sano.iter
local apidoc = {}
local doc
local maps = {documentation = apidoc}
maps.mixins = {}
apidoc.general = beginlongstringliteral
maps - Module containing methods shared across map implementations.
endlongstringliteral
doc = beginlongstringliteral
maps.toString(map) - Returns a string representation of map.
Example:
> x = HashMap:make{a=1,b=2,c=3,d=4}
> print(x)
{a=1, d=4, c=3, b=2}
endlongstringliteral
function maps.toString(map)
return "{"..iter.pairsToString(map:iter()).."}"
end
apidoc[maps.toString] = doc
local inputTypes = beginlongstringliteral
input can be one of several types:
1) A single Lua table {a=1,b=2,c=3,d=4,e=5}
2) A single iteration which returns two values per iteration step:
iter.zip("abcde",iter.count())
3) Two iterables which both return one value per iteration step - this
is a shorthand for 2.
4) Any table with an iter method which, when called, returns an
iteration satisfying 2.
So, the following are all equivalent:
Map:make{a=1,b=2,c=3,d=4,e=5}
== Map:make(iter.zip("abcde",iter.count()))
== Map:make("abcde",iter.count())
== Map:make(Map:make{a=1,b=2,c=3,d=4,e=5})
endlongstringliteral
doc = beginlongstringliteral
Map:make(mappings [, vals]) - Creates and returns a new Map,
containing the mappings supplied.
endlongstringliteral
function maps.make(self, mappings, vals)
local h = self:new()
return h:addMappings(mappings, vals)
end
apidoc[maps.make] = doc..inputTypes
doc = beginlongstringliteral
Map:addMappings(mappings, vals) - Adds mappings to this Map and returns
Map to allow chaining.
endlongstringliteral
function maps.addMappings(map, toAdd, vals)
if vals then
toAdd = iter.zip(toAdd, vals)
end
for k,v in iter.mapIter(toAdd or iter.EMPTY) do
map:add(k,v)
end
return map
end
apidoc[maps.addMappings] = doc..string.gsub(inputTypes, "make", "addMappings")
doc = beginlongstringliteral
Map:valIter() - Equivalent to Map:iter() except that iteration steps
return elements in the order val,key instead of key,val.
endlongstringliteral
function maps.valIter(self) return iter.exchange(self:iter()) end
apidoc[maps.valIter] = doc
doc = beginlongstringliteral
maps.orderedEquals(map1, map2) - Returns true iff both maps contain the
same mappings AND the same iteration order.
This is typically not the function that would be used for equality
comparisons of map. See maps.unorderedEquals.
endlongstringliteral
function maps.orderedEquals(map1, map2)
return map1:size() == map2:size() and
iter.multiEquals(map1:iter(), map2:iter())
end
apidoc[maps.orderedEquals] = doc
doc = beginlongstringliteral
maps.unorderedEquals(map1, map2) - Returns true if both maps have the
same size and contain the same mappings.
endlongstringliteral
function maps.unorderedEquals(map1, map2)
if map1:size() ~= map2:size() then return false
else
for k,v in map1:iter() do
if map2:get(k) ~= v then
return false
end
end
return true
end
end
apidoc[maps.unorderedEquals] = doc
doc = beginlongstringliteral
maps.makeSet(map) - Modifies the methods of the supplied map in place
to create an object following the set contract.
This function is used to create the SkipSet and LinkedSet 'classes':
SkipSet = maps.makeSet(SkipMap:new())
LinkedSet = maps.makeSet(LinkedMap:new())
endlongstringliteral
function maps.makeSet(map)
local mapadd = map.add
local maprem = map.remove
oop.loadAll(map, getmetatable(map))
map.add = function(self, val)
return mapadd(self, val, true) == nil
end
map.documentation[map.add] = beginlongstringliteral
Set:add(element) - Adds element to this set and returns true on
success, false if element already existed in this set.
endlongstringliteral
map.remove = function(self, val)
return maprem(self, val) and val or nil
end
map.documentation[map.remove] = beginlongstringliteral
Set:remove(element) - Removes and returns element from this set,
or returns nil if element was not a member of this set.
endlongstringliteral
setmetatable(map, nil)
map.__index = oop.loadOnFirstUse(map)
map.addMappings = nil
map.__tostring = sets.toString
map.__eq = function(set1, set2) return sets.equals(set1, set2) end
map.__call = map.contains
oop.fallback(map, sets.mixins.convenienceMethods)
oop.fallback(map, sets.mixins.plauralizeHelper)
map.make = collections.make
return map
end
apidoc[maps.makeSet] = doc
maps.mixins.plauralizeHelper = {
containsAll = collections.containsAll,
removeAll = sets.removeAll,
addMappings = maps.addMappings
}
maps.mixins.convenienceMethods = {
__tostring = maps.toString,
valIter = maps.valIter,
make = maps.make
}
return maps]],
sets=[[local sano = require 'sano'
local collections = sano.collections
local utils = sano.utils
local iter = sano.iter
---
local apidoc = {}
local doc
local sets = {documentation = apidoc}
sets.mixins = {}
apidoc.general = beginlongstringliteral
sets - Module containing methods shared across set implementations.
endlongstringliteral
doc = beginlongstringliteral
sets.removeElement(set, element) - Equivalent to set:remove(element).
In a sequence, the remove() method takes as input an index while the
removeElement method takes as input the element to be removed. This
method has the same semantics across both set and sequence
implementations.
endlongstringliteral
function sets.removeElement(set, element)
return set:remove(element)
end
apidoc[sets.removeElement] = doc
doc = beginlongstringliteral
sets.removeAll(set, elements) - Equivalent to calling set:remove(e)
for e in iter(elements).
endlongstringliteral
function sets.removeAll(collection, elements)
for i in iter.from(elements) do collection:remove(i) end
end
apidoc[sets.removeAll] = doc
doc = beginlongstringliteral
sets.retainOnly(set, elements) - Removes all elements from set which
do not also appear in elements.
If elements is also a set, s, set is modified to be the intersection of
set and s.
endlongstringliteral
function sets.retainOnly(set, elements)
for e in set:iter() do
if not elements:contains(e) then set:remove(e) end
end
end
apidoc[sets.retainOnly] = doc
doc = beginlongstringliteral
sets.toString(set) - Returns a string representation of set.
The order in which elements appear in the representation is the same as
the iteration order of elements of the set.
Example:
> print(HashSet:make(1,1,3,2,5,6,4))
{1, 2, 3, 4, 5, 6}
endlongstringliteral
function sets.toString(set)
return "{"..iter.toString(set:iter()).."}"
end
apidoc[sets.toString] = doc
doc = beginlongstringliteral
sets.intersection(set1, set2 [, setType]) - Computes and returns the
intersection of set1 and set2.
The returned set is created by a call to:
(setType or getmetatable(set1)):new()
endlongstringliteral
function sets.intersection(set1, set2, returnSet)
local toReturn = (returnSet or getmetatable(set1)):new()
if set2:size() < set1:size() then
set1, set2 = set2, set1 -- more efficient to compute in this direction
end
toReturn:addAll(set1);
assert(toReturn.retainOnly, "setType must have a retainOnly method")
toReturn:retainOnly(set2)
return toReturn
end
apidoc[sets.intersection] = doc
doc = beginlongstringliteral
sets.union(set1, set2 [, setType]) - Returns the union of set1 and set2
as a new set.
The returned set is created by a call to:
(setType or getmetatable(set1)):make(...)
synonyms: __add
endlongstringliteral
function sets.union(set1, set2, setType)
return (setType or getmetatable(set1)):make(iter.chain(set1:iter(), set2:iter()))
end
apidoc[sets.union] = doc
--function sets.unionCount(set1, set2)
doc = beginlongstringliteral
sets.difference(set1, set2 [,setType]) - Returns the set that would be
created by removing from set1 all elements in
intersection(set1,set2).
The returned set is created by a call to:
(setType or getmetatable(set1)):new()
synonyms: __sub, minus
endlongstringliteral
function sets.difference(set1, set2, setType)
local s = (setType or getmetatable(set1)):new(); s:addAll(set1); s:removeAll(set2)
return s
end
apidoc[sets.difference] = doc
sets.minus = sets.difference
doc = beginlongstringliteral
sets.xor(set1, set2 [,setType]) - Returns the set of elements which
appear in either set1 or set2, but not in both.
This operation is also often called the symmetric set difference.
endlongstringliteral
function sets.xor(set1, set2, setType)
return sets.difference(set1:union(set2), set1:intersection(set2), setType)
end
apidoc[sets.xor] = doc
doc = beginlongstringliteral
sets.equals(set1, set2) - Returns true if both sets have the same size
and contain the same elements.
This function does not care whether both sets are the same type - a
HashSet and SkipSet may be equal according to this function.
endlongstringliteral
function sets.equals(set1, set2)
return set1:size()==set2:size() and set1:containsAll(set2)
end
apidoc[sets.equals] = doc
doc = beginlongstringliteral
sets.hash(set) - Returns the sum of utils.hash(i) for i in set.
This is the hash function used by all set implementations.
endlongstringliteral
function sets.hash(self)
local total = 0
for i in self:iter() do total = total + utils.hash(i) end
return total
end
apidoc[sets.hash] = doc
doc = beginlongstringliteral
sets.uniqueFilter(set) - Create and return a uniqueness filter based
on set.
The filter is a function which returns true only for objects it has
not yet seen. This can be used in conjuction with iter.filter.
For example:
> v = vector(1,1,1,2,3,3,4,1,5,5)
> print(vector(iter.filter(v, HashSet:uniqueFilter())))
[1, 2, 3, 4, 5]
endlongstringliteral
function sets.uniqueFilter(set)
local seen = set:new()
return function(x)
return seen:add(x)
end
end
apidoc[sets.uniqueFilter] = doc
sets.mixins.convenienceMethods = {
enum = iter.enum,
make = collections.make,
intersection = sets.intersection,
union = sets.union,
difference = sets.difference,
__sub = sets.difference,
minus = sets.difference,
__add = sets.union,
xor = sets.xor,
__tostring = sets.toString,
__hash = sets.hash,
retainOnly = sets.retainOnly,
uniqueFilter = sets.uniqueFilter,
removeElement = sets.removeElement
}
sets.mixins.plauralizeHelper = {
removeAll = sets.removeAll,
addAll = collections.addAll,
containsAll = collections.containsAll,
containsAny = collections.containsAny
}
return sets]],
Tuple=[[local sano = require 'sano'
local utils = sano.utils
local ordering = sano.ordering
local iter = sano.iter
local Tuple = {documentation={}}
Tuple.__index = Tuple
local apidoc = Tuple.documentation
local doc
apidoc.general = beginlongstringliteral
Tuple - A lightweight sequence type mainly for use as keys in a map or
as elements of a set.
Examples of usage:
> tuple = sano.makers.tuple
> t = tuple(1,2,3); print(t)
(1, 2, 3)
> print(t == tuple(1, 2, 3))
true
> print(t == tuple(1, 2))
false
> for e in t:iter() do print(e) end
1
2
3
> a, b, c = t:unpack() -- built-in unpack(t) has same effect
> print(a, b, c)
1 2 3
> hashset = sano.makers.hashset
> tuples = hashset(t, tuple(8,7,6))
> print(tuples:contains(tuple(8, 7, 6)))
true
It's possible to give any table tuple-like powers simply by setting
its metatable to be Tuple, for instance:
> t = {1,2,3,4}
> print(t)
table: 0x82cb810
> setmetatable(t, Tuple)
> print(t)
(1, 2, 3, 4)
> h = hashset{tuple(1,2,3,4)}
> print(h:contains(t))
true
> setmetatable(t, nil) -- restore back to normal
> print(h:contains(t))
false
Tuples are ordered lexicographically in the obvious way, for example:
(1,1) <= (1,1), (1,2,2) < (1,2,3), () < (1), (1,2,1) < (1,3), etc.
More precisely, if a = (a1, a2, ..., aN) and b = (b1, b2, ..., bN),
then a < b if:
b1 > a1
OR
b1 == a1, b2 == a2, ..., bK == aK, bK+1 > aK+1 (b is greater than a)
If a and b have different sizes, the smaller Tuple will be ordered
before the larger if pairwise comparisons do not reveal an ordering.
endlongstringliteral
doc = beginlongstringliteral
Tuple:new(...) - Returns a newly constructed Tuple containing all
elements passed as arguments to this method.
It is perfectly fine to pass zero arguments (this creates an empty
tuple) or one argument (this creates a tuple containing just that
argument)
Examples:
> print( Tuple:new(1,2,3) )
(1, 2, 3)
> print( Tuple:new(1) )
(1)
> print( Tuple:new() )
()
endlongstringliteral
function Tuple:new(...)
local obj = arg
setmetatable(obj, self)
return obj
end
apidoc[Tuple.new] = doc
doc = beginlongstringliteral
Tuple:make(...) - Identical behavior to Tuple:new(), except that if just
one argument is passed, it is assumed to be *iterable* and the
elements of the iteration are added to the Tuple.
Example: Tuple:make(iter.count(3)) == Tuple:make{1,2,3}
== Tuple:make(1,2,3)
endlongstringliteral
function Tuple:make(...)
if table.getn(arg) == 1 then
local t = self:new()
for e in iter(arg[1]) do table.insert(t, e) end
return t
else
return self:new(unpack(arg))
end
end
apidoc[Tuple.make] = doc
doc = beginlongstringliteral
Tuple:unpack() - Wrapper around built-in unpack method.
Given a Tuple, t, t:unpack() and unpack(t) are equivalent, since
t is just a table with a few convenience methods.
endlongstringliteral
function Tuple:unpack()
return unpack(self)
end
apidoc[Tuple.unpack] = doc
doc = beginlongstringliteral
Tuple:iter() - Returns an iterator over the elements in this Tuple.
endlongstringliteral
function Tuple:iter()
local pos = 0
return function()
pos = pos + 1
return self[pos]
end
end
apidoc[Tuple.iter] = doc
doc = beginlongstringliteral
Tuple:size() - Returns the number of elements in this tuple.
If the tuple, t, has no holes, then #t will return the same result as
t:size().
endlongstringliteral
function Tuple:size()
return table.getn(self)
end
apidoc[Tuple.size] = doc
doc = beginlongstringliteral
Tuple:__eq(other) - Returns true if both Tuples have the same size and
contain the same elements in the same order.
endlongstringliteral
function Tuple:__eq(other)
return self:size() == other:size() and iter.equals(self:iter(), other:iter())
end
apidoc[Tuple.__eq] = doc
doc = beginlongstringliteral
Tuple:__hash() - Returns a number that will be used as the hash code
when this Tuple is stored in a HashMap or HashSet.
The value is cached in the field self.hash. Tuples are intended to be
used as immutable objects (otherwise, use Vector). Although there is
nothing stopping a determined soul from modifying a Tuple, if it is
being used as a key in a map, this will result in the Tuple being stored
in the wrong bucket.
See HashMap and SkipMap for more information on using Tuples or other
composite objects as keys.
In keeping with the contract of the __hash metamethod Tuple objects that
are equal return the same hash value.
endlongstringliteral
function Tuple:__hash()
if not self.hash then self.hash = utils.hashIterable(self) end
return self.hash
end
apidoc[Tuple.__hash] = doc
doc = beginlongstringliteral
Tuple:rehash() - Recompute and return the hash value for this Tuple.
Since Tuples are intended mostly to be used as immutable objects, this
method shouldn't normally be called (once the hash value is computed
once, it shouldn't need to be computed again).
endlongstringliteral
function Tuple:rehash()
self.hash = nil; return self:__hash()
end
apidoc[Tuple.rehash] = doc
doc = beginlongstringliteral
Tuple:__tostring() - Returns a string of the form "(e1, e2, ..., eN)"
endlongstringliteral
function Tuple:__tostring()
return "("..sano.iter.toString(self:iter())..")"
end
apidoc[Tuple.__tostring] = doc
doc = beginlongstringliteral
Tuple:__lt(other) - Returns true if this Tuple is lexicographically less
than other.
If a = (a1, a2, ..., aN) and b = (b1, b2, ..., bN), then a < b iff:
b1 > a1
OR
b1 == a1, b2 == a2, ..., bK == aK, bK+1 > aK+1 (b is greater than a)
If a and b have different sizes, the smaller Tuple will be ordered
before the larger if pairwise comparisons do not reveal an ordering.
Examples: (1,1) <= (1,1), () <= (1), (1,2,1) <= (1,3)
The evaluation is 'short-circuit' and halts as soon as there is enough
information to determine the ordering.
See Tuple.documentation.general for more information on how Tuples are
ordered.
Also see ordering.lexicographical.
endlongstringliteral
function Tuple:__lt(other)
local _1,_2,_3,result = ordering.lexicographical(self, other)
return result==-1
end
apidoc[Tuple.__lt] = doc
doc = beginlongstringliteral
Tuple:test() - Unit test.
endlongstringliteral
function Tuple:test()
local tuple = function(...) return Tuple:make(unpack(arg)) end
local t1 = tuple(1,2,3)
-- check comparison operators
assert(t1 == tuple(1,2,3))
assert(t1 < tuple(1,2,4))
assert(t1 <= tuple(1,2,4))
assert(t1 <= tuple(1,2,3))
assert(t1 > tuple(1,2,1,9))
assert(t1 >= tuple(1,2,1,9))
assert(t1 > tuple()) -- corner case: the empty tuple comes before everything
assert(t1 ~= tuple(1,2,3,4))
-- make sure hash and equals methods are consistent
assert(utils.hash(t1)==utils.hash(tuple(1,2,3)))
assert(utils.hash(t1)~=utils.hash(tuple(1,1,1)))
local a,b,c = t1:unpack()
assert(a==1); assert(b==2); assert(c==3)
end
apidoc[Tuple.test] = doc
return Tuple
]],
SkipVector=[[
local sano = require 'sano'
local oop = sano.oop
local iter = sano.iter
local collections = sano.collections
local sequences = sano.sequences
local function default0(t,ind) return 0 end
local function SINode(val,numSkips)
local t = {val=val, numSkips=numSkips, skips={}, distance={__index=default0}}
setmetatable(t.distance,t.distance); return t
end
local E = 2.718
local MAX_LEVELS = 24
local function exponential(survivalp, intmax)
local toReturn = 1
while math.random() > survivalp and toReturn <= intmax do
toReturn = toReturn + 1
end
return toReturn
end
local SkipVector = {documentation={}}
local apidoc = SkipVector.documentation
apidoc.general = beginlongstringliteral
SkipVector - Skip-list implementation of a general purpose vector.
Provides worst-case logarithmic random access, constant time sequential
access, and logarithmic time inserts or removals at ANY position. Also
provides functions for splicing and concatenating, and joining other
SkipVectors.
The basic functions are:
+) add: adds an element to this vector
+) remove: removes an element from the vector
+) get: return the element stored at a position in the vector
+) set: set the value stored at an index to a different value
+) splice: remove and return a range of elements in the vector as a new
vector
+) join: concatenates or inserts in another SkipVector
+) iter: returns an iterator over the elements of the vector
+) size: returns the number of elements in the vector
add, remove, get, set, splice, and join all have worst case O(lg(size))
performance. However, the vector maintains search 'fingers' into the
structure, so that the time complexity for get and set operations is
O(lg(k)), where k is the distance from the most recently accessed
element. E.g., for the access pattern 1,2,3..n, get/set will both run in
O(1).
Indices in the vector start at 1 go to size(), and can be negative.
Negative indices count backward from the end of the vector: get(-1)
returns the last element, get(-2) returns the second to last element,
get(-size()) returns the first element, etc. Any method that accepts
index arguments can use negative indexing. Indexing modes can be mixed
in the same call, i.e. iter(1,-2) will iterate from the first element to
the second to last element.
nil values can be stored in a SkipVector, although nil values will
cause the iterators returned by this class to halt prematurely, since
Lua interprets nil being returned as signaling the end of the iteration.
Implementation is based heavily on: 'A Skip List Cookbook', William Pugh,
1990.
endlongstringliteral
local doc
doc = beginlongstringliteral
SkipVector:new() - Creates and returns a new, empty SkipVector
endlongstringliteral
function SkipVector:new(mHead, mMaxLevel, mSize)
local obj = {}
obj.mHead = mHead or SINode(nil, MAX_LEVELS)
obj.mMaxLevel = mMaxLevel or 1
obj.mFingers = {}
obj.mSize = mSize or 0
setmetatable(obj, self)
self.__index = oop.loadOnFirstUse(self)
return obj
end
apidoc[SkipVector.new] = doc
-- Private methods --
local function checkRange(vec, ind)
if not ind then error("Index is null",3) end
if ind < 1 or ind > vec.mSize then error("Index: "..ind.." out of range,"
.." size is: "..vec.mSize,3) end
end
local function translate(vec, index)
index = index or rawget(vec,"mSize")
if index < 0 then index = vec.mSize+1+index end
return index
end
local function searchByIndex(vec, ind, node, pos, startLevel)
checkRange(vec, ind)
node, pos, startLevel = node or vec.mHead, pos or 0, startLevel or vec.mMaxLevel
--local count = 0
for i=startLevel,1,-1 do
while node.skips[i] and pos + node.distance[i] < ind
do pos = pos + node.distance[i]; node = node.skips[i]; --beginlongstringliteralcount = count + 1endlongstringliteral end
vec.mFingers[i] = {node,pos}
end
--print("steps:"..count)
return node, node.skips[1]
end
local function fingerSearch(vec, ind)
if not rawget(vec,"mFingers") then rawset(vec,"mFingers",{}); return searchByIndex(vec, ind) end
checkRange(vec, ind)
local v,startNode,pos = 2,nil,0
if vec.mFingers[1] and vec.mFingers[1][2] < ind then
-- move up to first level that skips past the target index
while v <= vec.mMaxLevel and vec.mFingers[v][2] + vec.mFingers[v][1].distance[v] < ind
do v = v + 1 end
v = v - 1
startNode,pos = unpack(vec.mFingers[v])
else -- move backward
while v <= vec.mMaxLevel and vec.mFingers[v][2] >= ind do -- TODO: getting attempt to index nil here
v = v + 1
end
if v > vec.mMaxLevel then v = vec.mMaxLevel; startNode,pos = vec.mHead,0
else startNode,pos = unpack(vec.mFingers[v]) end
end
return searchByIndex(vec, ind, startNode, pos, v)
end
local function insertAt(vec, ind, val)
if ind < 0 or ind > vec.mSize then error("Index "..ind.." out of range, "
.."size is: "..vec.mSize) end
local v = exponential(1/E, MAX_LEVELS)
local toInsert = SINode(val, v)
if v > vec.mMaxLevel then
for i=vec.mMaxLevel+1,v do vec.mHead.distance[i] = vec.mSize + 1 end
vec.mMaxLevel = v
end
-- search for the position, adjusting distances as we go
local node, pos = vec.mHead, 0
for i=vec.mMaxLevel,1,-1 do
while node.skips[i] and pos + node.distance[i] <= ind
do pos = pos + node.distance[i]; node = node.skips[i] end
if i > v then node.distance[i] = node.distance[i] + 1
else
local after = node.skips[i]
toInsert.skips[i] = after
node.skips[i] = toInsert
toInsert.distance[i] = pos + node.distance[i] - ind
node.distance[i] = ind + 1 - pos
end
end
vec.mFingers = nil
vec.mSize = vec.mSize + 1
end
local function deleteAt(vec, ind)
rawset(vec,"mFingers",rawget(vec,"mFingers") or {})
local prev, node = searchByIndex(vec, ind)
for i=1,vec.mMaxLevel do
if vec.mFingers[i][1].skips[i] == node then
vec.mFingers[i][1].skips[i] = node.skips[i]
vec.mFingers[i][1].distance[i] = vec.mFingers[i][1].distance[i] + node.distance[i] - 1
else
vec.mFingers[i][1].distance[i] = vec.mFingers[i][1].distance[i] - 1
end
end
vec.mSize = vec.mSize - 1
while vec.mHead.skips[vec.mMaxLevel] == nil and vec.mMaxLevel > 1
do vec.mMaxLevel = vec.mMaxLevel - 1 end
vec.mFingers = nil
return node
end
--beginlongstringliteral
Remove the sublist from [ind,size()]
endlongstringliteral
local function split(vec,ind)
checkRange(vec,ind)
local sHead = SINode(nil,MAX_LEVELS)
local sSize = 0
local sMaxLevel = vec.mMaxLevel
-- do the split
local node, pos = vec.mHead, 0
for i=vec.mMaxLevel,1,-1 do
while node.skips[i] and pos + node.distance[i] < ind
do pos = pos + node.distance[i]; node = node.skips[i] end
sHead.skips[i],sHead.distance[i] = node.skips[i],node.distance[i]-(ind-pos)+1
node.skips[i],node.distance[i] = nil, nil
end
while not vec.mHead.skips[vec.mMaxLevel] and vec.mMaxLevel > 1
do vec.mMaxLevel = vec.mMaxLevel-1 end
while sHead.skips[sMaxLevel] == nil and sMaxLevel > 1
do sMaxLevel = sMaxLevel-1 end
local toReturn = SkipVector:new(sHead, sMaxLevel, vec.mSize-ind+1)
vec.mSize = vec.mSize - toReturn:size()
vec.mFingers = nil
toReturn.mFingers = nil
return toReturn
end
local function concatenate(vec,vec2)
local vec2MaxLevel = vec2.mMaxLevel
local vec2Head = vec2.mHead
if vec.mMaxLevel < vec2MaxLevel then vec.mMaxLevel = vec2MaxLevel end
local node = vec.mHead
local pos = 0
for i=vec.mMaxLevel,1,-1 do
while node.skips[i] do node,pos = node.skips[i],pos+node.distance[i] end
if i <= vec2MaxLevel then
node.skips[i] = vec2Head.skips[i]
node.distance[i] = (vec.mSize-pos) + vec2Head.distance[i]
end
end
vec.mSize = vec.mSize + vec2:size()
vec.mFingers = nil
oop.invalidate(vec2, SkipVector,
"This vector is no longer valid -- it has been joined into another vector.")
end
-- PUBLIC INTERFACE --
--beginlongstringliteral
Prints a human-friendly representation of this SkipVector for debugging.
Each node in the vector is printed out in order, along with the skip links
and the distances associated with each link.
endlongstringliteral
function SkipVector:debug()
local function strNode(node)
local v = node.val and node.val or "nil"
local links = ""
for ind,l in pairs(node.skips) do links = links..l.val.."/d="..node.distance[ind]..", " end
return v.."->"..links
end
print("maxLevel="..self.mMaxLevel..", size="..self.mSize)
local curNode,ind = self.mHead, 0
while curNode do
print ("["..ind.."]"..strNode(curNode))
curNode = curNode.skips[1]
ind = ind + 1
end
end
doc = beginlongstringliteral
SkipVector:get([index]) - Returns the element currently stored at
position index in this SkipVector, defaulting to the last element.
Indices start at 1 and go to size(), and can be negative. Negative
indices count backward from the end of the vector get(-1) returns the
last element, get(-2) returns the second to last element, get(-size())
returns the first element, etc. Any method that accepts index arguments
can use negative indexing.
param index: in +/-[1,size()]
example:
[a,b,c]:get(-1) == c, [a,b,c]:get(1) == a
time complexity: O(lg(k)), where k is the distance from the most recent
get/set
endlongstringliteral
function SkipVector:get(index)
index = translate(self,index)
local prev,at = fingerSearch(self,index);
--local prev,at = searchByIndex(self,index)
return at.val
end
apidoc[SkipVector.get] = doc
doc = beginlongstringliteral
SkipVector:remove(index [,stopIndex]) - Removes the element at the
supplied index, or, the range [index..stopIndex] and returns the
result as a SkipVector.
If no parameters are specified, the last element is removed and
returned. Negative indices can also be used.
param index, stopIndex: in +/-[1,size()]
time complexity: 0(lg(size())), for either operation.
examples:
[1,2,3]:remove(-2) results in [1,3]
[1,2,3,4]:remove(2,3) results in [1,4]
[1,2,3,4]:remove()==4 and results in [1,2,3]
endlongstringliteral
function SkipVector:remove(index, stopIndex)
index = translate(self,index)
if not stopIndex then -- we're just removing a single element
return deleteAt(self,index).val
else
return self:splice(index, translate(self,stopIndex))
end
end
apidoc[SkipVector.remove] = doc
doc = beginlongstringliteral
SkipVector:set(index, val) - Sets the element at index to val and
returns the previous element stored.
param index: in +/-[1,size()]
time complexity: O(lg(k)), where k is the distance from the most recent
get/set
example:
[1,2,3,4]:set(2,"a") results in [1,"a",3,4] and returns 2
endlongstringliteral
function SkipVector:set(index, val)
index = translate(self,index)
local prev,at = fingerSearch(self,index);
local old = at.val; at.val = val; return old
end
apidoc[SkipVector.set] = doc
doc = beginlongstringliteral
SkipVector:add(val [,index]) - Adds val to the end of this vector,
or, if index is specified, inserts val AFTER index.
Negative indices can be used.
param index: in +/-[0,size()] (0 inserts before the first element)
time complexity: O(lg(size())
example:
[1,2,3]:add(a, 1) results in [1,a,2,3]
endlongstringliteral
function SkipVector:add(val, index)
index = translate(self,index)
insertAt(self, index, val)
return index+1
end
apidoc[SkipVector.add] = doc
local kBufferSize = 20
doc = beginlongstringliteral
SkipVector:addAll(iterable [, index]) - Adds all elements of iterable to
this vector AFTER the index'th element, or at the end if no index is
specified.
param index: in +/-[0,size()] (0 inserts before the first element)
param iterable: an iterator, an object with an iter method, or a table,
in which case the values of the table are added
endlongstringliteral
function SkipVector:addAll(iterable, index)
--
local stack = {}; table.insert(stack, SkipVector:new())
local buffer = SkipVector:new()
for i in iter.from(iterable) do
buffer:add(i)
if buffer:size() == kBufferSize then
table.insert(stack, buffer)
buffer = SkipVector:new()
local i = table.getn(stack)
while i > 1 and (stack[i]:size() >= stack[i-1]:size()) do
stack[i-1]:join(table.remove(stack))
i = i - 1
end
end
end
-- print("stack size: "..table.getn(stack))
for i=2,table.getn(stack) do
stack[1]:join(stack[i])
end
stack[1]:join(buffer) -- flush anything still in the buffer
self:join(stack[1], translate(self,index))
end
apidoc[SkipVector.addAll] = doc
doc = beginlongstringliteral
SkipVector:splice(start [,stop]) - Removes the range [start,stop] from
this vector and returns it as a SkipVector.
If omitted, the stop parameter defaults to the end of the vector. The
call splice(s1,s2) is equivalent to the call remove(s1,s2). Negative
indices can also be used.
param start,stop: in +/-[1,size()]
time complexity: O(lg(size()))
example:
['a','b','c',1,2,3]:splice(2,4) returns ['b','c',1], and the
original list becomes ['a',2,3]
endlongstringliteral
function SkipVector:splice(start,stop)
if start then start = translate(self,start) end
stop = translate(self, stop)
checkRange(self,start); checkRange(self,stop);
if stop < start then
error("Stop index, "..stop..", is less than start index, "..start) end
if stop == self.mSize then return split(self, start) end
local newTail = split(self, stop+1)
local removed = split(self, start)
concatenate(self, newTail)
return removed
end
apidoc[SkipVector.addAll] = doc
function SkipVector:divide(pieceCount)
local copy = self:make(self:iter())
local modulo = math.floor(self:size()/pieceCount)
local pieces = {}
for i=pieceCount,1,-1 do
pieces[i] = copy:splice(copy:size()-modulo+1)
end
--for k,v in pairs(pieces) do print(k,v) end
return unpack(pieces)
end
doc = beginlongstringliteral
SkipVector:join(vec2 [, index]) - Splices vec2 into this list AFTER
position index.
vec2 is destroyed as a result of this call. If the index parameter is
omitted, vec2 is concatenated onto the end of this vector.
time complexity: O(lg(size())).
param: index in +/-[0,size()] the index after which to insert vec2
examples:
[1,2,3]:join([a,b,c],2) results in [1,2,a,b,c,3]
[1,2,3,4,5]:join([a,b,c],-2) results in [1,2,3,4,a,b,c,5]
endlongstringliteral
function SkipVector:join(vec2, index)
index = translate(self,index)
if index < 0 or index-1 > self.mSize then
error("Index "..index.." out of range, list size is: "..self.mSize) end
if vec2:size() == 0 then return self end
if index == self.mSize then concatenate(self,vec2)
else
local tmp = split(self,index+1)
concatenate(self, vec2);
concatenate(self, tmp); -- that was easy!
end
return self
end
apidoc[SkipVector.join] = doc
doc = beginlongstringliteral
SkipVector:replace(start,stop,iterable) - Replaces the range
[start,stop] with the elements in iterable.
If the range being replaced has the close to the same number of elements
as the iteration, this function runs in roughly linear time. Otherwise,
the time complexity can be no worse than O(k*lg(size)), where k is the
number of elements in iterable.
param start,stop: in +/-[1,size()]
time complexity: min(k,r) + abs(k-r)*lg(size), where
k is the number of elements in iterable and r is the number of elements
in the range (start, stop)
example:
[a,b,c,d,e]:replace(2,5,{1,2}) results in [a,1,2]
endlongstringliteral
function SkipVector:replace(start, stop, iterable)
assert(start)
start = translate(self,start); stop = translate(self,stop)
iterable = iter.from(iterable)
local indices = iter.count(start,stop)
local z = iter.zip(indices,iterable)
local num = 0
for ind,val in z do
self:set(ind, val)
num = num + 1
end
if num == stop-start+1 then -- iterable had >= elements than [start,stop]
for e in z do self:add(e, stop); stop = stop + 1 end
else -- iterable had fewer elements than [start,stop]
for i in z do self:remove(start+num) end -- remove the remaining elements in [start,stop]
end
end
apidoc[SkipVector.replace] = doc
doc = beginlongstringliteral
SkipVector:size() - Returns the number of elements in this vector.
This is also the largest valid index for a call to get().
endlongstringliteral
function SkipVector:size() return self.mSize end
apidoc[SkipVector.size] = doc
doc = beginlongstringliteral
SkipVector:reverseIter(start, finish) - Returns a reverse iterator
over the elements in this vector, starting from start (inclusive) and
moving backward to finish (inclusive).
param finish: in +/-[1,size()], defaults to 1
param start: in +/-[1,size()], defaults to size()
endlongstringliteral
function SkipVector:reverseIter(start, finish)
if self:size() == 0 then return iter.EMPTY end
start = translate(self,start); finish = finish or 1; finish = translate(self,finish)
local indices = iter.count(start, finish, -1)
return function()
local ind = indices()
if ind then return self:get(ind) end
end
end
apidoc[SkipVector.reverseIter] = doc
doc = beginlongstringliteral
SkipVector:iter([start [,finish] ]) - Returns an iterator over the
elements at indices [start..finish] in this vector.
If no finish is specified, the iteration is from start (inclusive)
to the end of the vector; if no start is specified, the iteration is
over all elements in this vector. Negative indices can be used when
specifying the range. If start > finish, a reverse iteration over the
range [finish,start],-1 is returned.
param start,finish: in +/-[1,size()]
time complexity: advancing to the next element in the iteration is a
constant-time operation.
Examples:
for i in [1,2,3,4,5]:iter(3) do print(i) end --> 3,4,5
for i in [1,2,3,4,5]:iter(1,-2) do print(i) end --> 1,2,3,4
endlongstringliteral
function SkipVector:iter(start, finish)
if self:size() == 0 then return iter.EMPTY end
start = start or 1; start = translate(self,start); finish = translate(self,finish)
if start > finish then return self:reverseIter(start, finish) end
local n,_ = fingerSearch(self,start)
local pos = start - 1
return function()
if n.skips[1] and pos < finish then
pos = pos + 1; n = n.skips[1]; return n.val end
end
end
apidoc[SkipVector.iter] = doc
doc = beginlongstringliteral
SkipVector:__eq(other) - Returns true if both vectors are of the same
size and contain the same elements in the same order.
endlongstringliteral
function SkipVector:__eq(other)
return self:size() == other:size() and iter.equals(self:iter(),other:iter())
end
apidoc[SkipVector.__eq] = doc
SkipVector.__call = SkipVector.get
doc = beginlongstringliteral
SkipVector:asString(sep) - Returns a string created by concatenating
together all the elements in this SkipVector, using sep as the
separator (default empty)
This method is useful when SkipVector is being used as a string
buffer.
Example:
> a = SkipVector:make("a","b","c")
> a:add("d")
> print(a:asString())
abcd
endlongstringliteral
function SkipVector:asString(sep)
local t = {}
for i in self:iter() do table.insert(t,i) end
return table.concat(t, sep)
end
doc = beginlongstringliteral
SkipVector:test() - Unit test.
endlongstringliteral
function SkipVector:test()
local v = self:make(1,2,3,4,5)
for i=1,v:size() do v:get(i) end
assert(self:make(v:reverseIter()) == self:make(5,4,3,2,1))
assert(v:remove(2)==2)
assert(v:remove(-1)==5)
assert(v:add("hello",0))
local count = iter.count
local v2 = self:make(count(1000))
local v2Split = v2:splice(500)
assert(v2Split==self:make(count(500,1000)))
v2:join(v2Split, 1)
assert(v2:splice(2,502)==self:make(count(500,1000)))
end
apidoc[SkipVector.test] = doc
-- add some convenience methods
oop.fallback(SkipVector, sequences.mixins.convenienceMethods)
oop.fallback(SkipVector, sequences.mixins.plauralizeHelper)
oop.fallback(SkipVector, sequences.mixins.randomAccessHelper)
return SkipVector
]],
oop=[[
local sano = require 'sano'
local utils = sano.utils
local apidoc = {}
local doc
local oop = {documentation = apidoc}
apidoc.general = beginlongstringliteral
oop - Internally used module for miscellaneous object-oriented
programming support functions.
endlongstringliteral
--beginlongstringliteral
Returns a function that can be used
endlongstringliteral
local function indexWithCopyFrom(source)
return function(t,k)
t[k] = source[k]
return rawget(t,k)
end
end
oop.loadOnFirstUse = utils.memoized(indexWithCopyFrom,1)
function oop.loadAll(obj, interface)
assert(obj~=interface)
for k,v in pairs(interface) do
rawset(obj,k,v)
end
end
function oop.methodCaller(obj, methodName)
return function(...)
return obj[methodName](obj, unpack(arg))
end
end
-- Any method in interface is replaced with function that throws the error message
-- msg
function oop.invalidate(obj, interface, msg)
for name,method in pairs(interface) do
if type(method) == "function" then
rawset(obj, name, function() error(msg,3) end)
end
end
end
--beginlongstringliteral
Throws an error if any of the mixed-in methods are already defined.
endlongstringliteral
function oop.mixin(class, methods)
for name,fn in pairs(methods) do
if class[name] ~= nil then
error("oop.mixin error: '"..name.."' is already defined by obj/class "..tostring(class),2)
else
class[name] = fn
end
end
end
--beginlongstringliteral
Adds each method in methods to class only if they do not already exist.
endlongstringliteral
function oop.fallback(class, methods)
for name,fn in pairs(methods) do
if class[name] == nil then
class[name] = fn
end
end
end
function oop.decorate(class, methods)
for name,fn in pairs(methods) do
class[name] = fn
end
end
function oop.ancestors(obj)
return coroutine.wrap(function()
coroutine.yield(obj)
local objParent = getmetatable(obj)
if objParent and not rawequal(objParent, obj) then
for p in oop.ancestors(objParent) do
coroutine.yield(p)
end
end
end)
end
function oop.isInstance(obj, class)
for p in oop.ancestors(obj) do
if rawequal(p, class) then return true end
end
return false
end
return oop
]],
SkipSet=[[
local sano = require 'sano'
local SkipMap = sano.SkipMap
local ordering = sano.ordering
local SkipSet = SkipMap:new()
sano.maps.makeSet(SkipSet)
local apidoc = SkipSet.documentation
local doc
--SkipSet.documentation = apidoc
apidoc.general = beginlongstringliteral
SkipSet - Ordered set implementation based on SkipMap.
The SkipSet class was created by a call to
sano.maps.makeSet(SkipMap:new()). The set is implemented
by storing the elements of the set as the keys in SkipMap, where all
keys simply map to the value 'true'.
Example usage:
> oset = function(...) return SkipSet:make(unpack(arg)) end
> -- above is equivalent to: oset = sano.makers.oset
> s = oset(1,2,3,3,4); print(s)
{1, 2, 3, 4}
> print(s == oset(4,3,2,1))
true
> print(s == oset(1,3,5,7))
false
> print(s:remove(1))
1
> s:addAll(iter.count(10,15)); print(s)
{2, 3, 4, 10, 11, 12, 13, 14, 15}
> s:removeAll(iter.count(2,4)); print(s)
{10, 11, 12, 13, 14, 15}
> print(s:size())
6
endlongstringliteral
doc = beginlongstringliteral
SkipSet:test() - Unit test.
endlongstringliteral
function SkipSet:test()
local skipset = function(...) return self:make(unpack(arg)) end
local s = skipset(1,2,3,3,4)
assert(s == skipset(4,3,2,1), "equals failed")
assert(s:size()==4, "size() failed")
assert(s:remove(3)==3, "remove failed")
assert(s:remove(7)==nil, "remove failed")
assert(s:contains(2), "contains failed")
assert(s:containsAll(sano.iter.count(2)))
s:merge(skipset(9,8,7,6,5,4,3,2))
assert(s:add(11))
assert(ordering.isSorted(s))
local t = s:splice(1,4)
assert(t==skipset(1,2,3,4),tostring(t))
end
apidoc[SkipSet.test] = doc
return SkipSet
]],
Phonebook=[[
local sano = require 'sano'
local SkipMap = sano.SkipMap
local oop = sano.oop
local apidoc = {}
local doc
local Phonebook = SkipMap:new()
Phonebook.documentation = apidoc
apidoc.general = beginlongstringliteral
Phonebook - An ordered map based on SkipMap which allows duplicate
keys.
In SkipMap, calling add(key, val) for a key which already exists
overwrites the previous value. In a Phonebook, the new key-value pair is
added AFTER the previous pair. Calling Phonebook:get(key) retrieves the
FIRST value stored agains the key. Phonebook:remove(key) also removes
the first value stored against key.
For more control over the handling of duplicate keys in a map, see
Multimap.
Example usage:
> p = Phonebook:new()
> p:add('Doe', 'John'); p:add('Doe', 'Jane'); print(p)
{Doe="John", Doe="Jane"}
> p:add('Smith','Mike'); p:add('Smith','Mary'); p:add('Jones','Jim')
> print(p)
{Doe="John", Doe="Jane", Jones="Jim", Smith="Mike", Smith="Mary"}
> for first,last in p:iter('Doe','Jones') do print(first,last) end
Doe John
Doe Jane
Jones Jim
> print(p:remove('Doe'))
John
endlongstringliteral
doc = beginlongstringliteral
Phonebook:add(key,val) - Adds the key-value pair to this Phonebook after
any existing pair with the same key.
endlongstringliteral
function Phonebook:add(key, val)
return SkipMap.add(self, key, val, true)
end
apidoc[Phonebook.add] = doc
doc = beginlongstringliteral
Phonebook:merge(orderedMap) - Merges the key-value pairs in the other
orderedMap into this Phonebook.
Duplicate keys are retained in the merged Phonebook. orderedMap is
invalidated as a result of this call.
endlongstringliteral
function Phonebook:merge(orderedMap)
return SkipMap.merge(self, orderedMap, true)
end
apidoc[Phonebook.merge] = doc
doc = beginlongstringliteral
Phonebook:test() - Unit test.
endlongstringliteral
function Phonebook:test()
local p, p2 = self:new(), self:new()
p:add('Smith', 'Joe')
p:add('Smith', 'Betty')
p2:add('Smith', 'Joe')
p2:add('Smith', 'Mike')
p:merge(p2)
assert(p:size()==4)
end
apidoc[Phonebook.test] = doc
return Phonebook
]],
QueueVector=[[
-- random access + efficient adds and removes from both start and end of list
-- imports
local sano = require 'sano'
local oop = sano.oop
local iter = sano.iter
local sequences = sano.sequences
local QueueVector = {}
QueueVector.documentation = {}
local apidoc = QueueVector.documentation
apidoc.general = beginlongstringliteral
QueueVector - Table-based vector implementation with efficient
(amortized O(1)) inserts and removals at both the start and end of the
vector.
Indicies of a QueueVector, q run from 1 to q:size() and can be negative,
where q:get(-1) retrieves the last element, q:get(-2) the second to last
element, and q:get(-q:size()) and q:get(1) both retrieve the first
element.
Elements can be nil, with the caveat that the iter method will halt
prematurely at these elements if the iterator is not wrapped in an
enumeration.
Example uses:
> q = QueueVector:make(1,2,3,4,5)
> print(q)
[1, 2, 3, 4, 5]
> print(q:remove()) -- O(1), remove removes first element by default
1
> q:add("hello", 0) -- O(1), adds "hello" after index 0 (prepends it)
> print(q)
[hello, 2, 3, 4, 5]
> q:add("goodbye"); print(q)
[hello, 2, 3, 4, 5, goodbye]
endlongstringliteral
local function translate(self, index)
index = index or self.mSize
if index < 0 then return self.mSize+1+index else return index end
end
local doc -- temp var we'll reuse to store documentation of each function
doc = beginlongstringliteral
QueueVector:new(obj) - Creates a new, empty QueueVector.
endlongstringliteral
function QueueVector:new(obj)
obj = obj or {}
obj.mStart = 1
obj.mSize = table.getn(obj) or 0
setmetatable(obj, self)
self.__index = oop.loadOnFirstUse(self)
return obj
end
apidoc[QueueVector.new] = doc
doc = beginlongstringliteral
QueueVector:add(val,ind) - Adds the value, val, AFTER position ind in
this QueueVector.
ind defaults to the end of the vector. 0 inserts at the head of the
vector. Values can be nil, with the caveat that the iter method will
halt prematurely at these values if not wrapped in an enumeration.
param ind: in +/-[0..size]
error: if ind not in valid range
returns: true (fulfills general contract of collections.add)
endlongstringliteral
function QueueVector:add(val,ind)
ind = translate(self, ind)
if ind < 0 or ind > self.mSize then
error("Index: "..ind.." out of range, queue size is: "..self.mSize,2)
end
self.mSize = self.mSize+1
if ind == 0 then
--print("eq start", self, "size "..self.mSize, self.mStart)
self.mStart = self.mStart - 1
self[self.mStart] = val
else
--print("appending", self, "size "..self.mSize, self.mStart)
table.insert(self,self.mStart+ind, val)
end
return ind+1
end
apidoc[QueueVector.add] = doc
doc = beginlongstringliteral
QueueVector:remove(ind) - Removes and returns the element at index ind
(default 1) from this vector.
If ind is 1 or the size of the vector, this runs in constant time;
otherwise the elements to the left of the removed element are shifted
over to fill the hole created.
param ind: in +/-[1..size], default=1
error: if ind not in valid range
returns: the removed element
endlongstringliteral
function QueueVector:remove(ind)
ind = ind and translate(self, ind) or 1
if ind < 1 or ind > self.mSize then
error("Index: "..ind.." out of range, queue size is: "..self.mSize,2)
end
self.mSize = self.mSize - 1
if ind == 1 then
local removed = self[self.mStart]
self[self.mStart] = nil
self.mStart = self.mStart + 1
return removed
else
return table.remove(self, ind+self.mStart-1)
end
end
apidoc[QueueVector.remove] = doc
doc = beginlongstringliteral
QueueVector:get(ind) - Returns the ind'th element of this queue.
Indicies are in +/-[1..size]. Negative indices count backward from the
end of the queue: the -1'th element is the last element, the -2'th
element is the second to last element, and the -size'th is the first
element.
param ind: in +/-[1..size] default=1
error: if ind not in valid range
synonym: __call
endlongstringliteral
function QueueVector:get(ind)
ind = ind and translate(self, ind) or self.mStart
if ind < 1 or ind > self.mSize then
error("Index: "..ind.." out of range, queue size is: "..self.mSize,2)
end
return self[self.mStart+ind-1]
end
apidoc[QueueVector.get] = doc
QueueVector.__call = QueueVector.get
doc = beginlongstringliteral
QueueVector:set(ind, val) - Sets the value at index ind equal to val and
returns the previous value.
param ind: in +/-[1..size]
returns: the previous value stored at index ind
endlongstringliteral
function QueueVector:set(ind, val)
assert(ind, "ind is nil, must be in range +/-[1..size]")
ind = translate(self, ind)
if ind < 1 or ind > self.mSize then
error("Index: "..ind.." out of range, queue size is: "..self.mSize,2)
end
local old
old, self[self.mStart+ind-1] = self[self.mStart+ind-1], val
return old
end
apidoc[QueueVector.set] = doc
doc = beginlongstringliteral
QueueVector:size() - Returns the number of elements in this queue.
This is also the largest valid index for a call to get().
endlongstringliteral
function QueueVector:size() return self.mSize end
apidoc[QueueVector.size] = doc
doc = beginlongstringliteral
QueueVector:iter(start, stop) - Returns an iterator over the elements
in the index range [start..stop].
Example uses:
> q = QueueVector:make(1,2,3)
> for e in q:iter() do print(e) end
1
2
3
> q:set(2,nil)
> for ind,e in iter.enum(q:iter(),q:size()) do print(ind,e) end
1 1
1 nil
2 2
> q:addAll{"a","b","c"}; q:set(2,2); print(q)
[1, 2, 3, a, b, c]
> q2 = QueueVector:make(q:iter(2,4)); print(q2)
[2, 3, a]
endlongstringliteral
function QueueVector:iter(start, stop)
start = start and translate(self, start) or 1
stop = stop and translate(self, stop) or self.mSize
start = self.mStart+start-1
stop = self.mStart+stop-1
if start > stop then return iter.EMPTY end
return coroutine.wrap(function()
for i in iter.count(start,stop) do
coroutine.yield(self[i])
end
end)
end
apidoc[QueueVector.iter] = doc
doc = beginlongstringliteral
QueueVector:__eq(other) - Returns true if both QueueVector objects are
the same size and contain the same elements in the same order.
endlongstringliteral
function QueueVector:__eq(other)
return self:size()==other:size() and iter.equals(self, other)
end
apidoc[QueueVector.__eq], apidoc["=="] = doc
doc = beginlongstringliteral
QueueVector:test() - Unit test.
endlongstringliteral
function QueueVector:test()
local queue = function(...) return self:make(unpack(arg)) end
local q1 = queue(1,2,3,4,5)
-- removals
assert(q1:remove(2)==2) -- in the middle
assert(q1:remove(-1)==5) -- last
assert(q1:remove()==1) -- first
assert(q1 == queue(3,4))
-- adds
assert(q1:add("hello",0)) -- at front
assert(q1:add("goodbye",-1)) -- at end
assert(q1:add(3.14159, 2)) -- in middle
assert(q1 == queue("hello", 3, 3.14159, 4, "goodbye"))
local q2 = queue(q1)
-- subiter
assert(queue(q2:iter(2,-2)) == queue(3,3.14159,4))
-- bounds checking
while q1:size() > 0 do q1:remove() end -- empty it
if pcall(function() q1:remove() end) then
error("Trying to remove from an empty queue should throw an error!")
end
if pcall(function() q1:add("no good",-22) end) then
error("Invalid index should throw an error!")
end
-- set
assert(q2:set(3, 3.5) == 3.14159)
assert(q2 == queue("hello",3,3.5,4,"goodbye"))
end
apidoc[QueueVector.test] = doc
oop.mixin(QueueVector, sequences.mixins.plauralizeHelper)
oop.mixin(QueueVector, sequences.mixins.convenienceMethods)
oop.mixin(QueueVector, sequences.mixins.randomAccessHelper)
-- Test before returning; ok if tests don't take long
return QueueVector
]],
PairingHeap=[[
local sano = require "sano"
local ordering = sano.ordering
local queue = sano.makers.queue
local heaps = sano.heaps
local oop = sano.oop
local iter = sano.iter
local utils = sano.utils
local HashSet = sano.HashSet
local PairingHeap = {cDefaultOrdering = ordering.INCREASING}
PairingHeap.documentation = {}
local apidoc = PairingHeap.documentation
local doc
apidoc.general = beginlongstringliteral
PairingHeap - Heap implementation with constant-time merge and get-first
operations.
This implementation has the following complexity guarantees:
+) add, merge, get - O(1)
+) remove - amortized O(lg(size))
+) set - O(1) if new value is ordered before the old value, otherwise
amortized O(lg(size))
By default, a PairingHeap uses the natural increasing order of the data,
but any function which satisfies the ordering contract can be supplied
to the new() method.
Examples of use:
> h1 = PairingHeap:make(1,3,5,2,4,6); print(h1)
/1, 6, 4, 2, 5, 3\
> node = h1:add(-1); print(h1) -- returns the node allocated
/-1, 1, 6, 4, 2, 5, 3\
> h1:set(node, 44); print(h1) -- adjust value, O(lg N) for decrease
/1, 6, 4, 2, 5, 3, 44\
> print(h1:remove()) -- removes and returns top/first element, O(lg N)
1
> print(h1:get()) -- the first element (equivalent to getFirst())
2
> h1:merge(heap(6,2,3,9)) -- merge in another heap, O(1)
> print(vector(h1:extractFirstK())) -- heapsort
[2, 2, 3, 3, 4, 5, 6, 6, 9, 44]
See: http://www.cise.ufl.edu/~sahni/dsaaj/enrich/c13/pairing.htm for
more information on pairing heaps
endlongstringliteral
local function HeapNode(val)
return {val=val} -- child=child, prev=prev, next=next
end
-- private function used to merge/add
local function compareAndLink(orderFn, nodeA, nodeB)
-- we assume both nodes are roots
nodeA.prev, nodeA.next, nodeB.prev, nodeB.next = nil, nil, nil, nil
-- we require that neither node has any common children
local first, second = nodeA, nodeB
if orderFn(nodeA.val, nodeB.val)==nodeB.val then
first, second = nodeB, nodeA
end
first.child, second.prev, second.next = second, first, first.child
if second.next then second.next.prev = second end
return first
end
-- used for delete/set operations
local function detatch(self, node)
if node.prev and node.prev.child==node then -- this is the leftmost child
node.prev.child = node.next
if node.next then node.next.prev = node.prev end
elseif node.prev then -- this is just a standard linked-list delete
node.prev.next = node.next
if node.next then node.next.prev = node.prev end
elseif node == self.mRoot then
self.mRoot = nil
end
node.prev, node.next = nil, nil
local tmpHeap = getmetatable(self):new(self.mOrderFn)
tmpHeap.mRoot = node
return tmpHeap
end
-- core operation for set() if new val is comes before the old val according
-- to the ordering function
local function removeInternal(self, node)
local sizeToBe = self.mSize - 1
local subtree = detatch(self, node)
subtree:remove()
self:merge(subtree)
self.mSize = sizeToBe
return node.val
end
doc = beginlongstringliteral
PairingHeap:new(orderFn) - Constructs a new PairingHeap using the
supplied ordering function (default is natural increasing order).
If no ordering function is specified, the ordering function will become
PairingHeap.cDefaultOrdering, which is by default ordering.INCREASING.
Thus the heap created is a so-called 'min-heap': get() will return the
smallest element. By setting PairingHeap.cDefaultOrdering to
ordering.DECREASING or by explicitly passing ordering.DECREASING
to this method, get() will return the largest element.
param orderFn: function meeting the *ordering* contract
endlongstringliteral
function PairingHeap:new(orderFn)
local obj = {}
obj.mSize = 0
obj.mOrderFn = orderFn or self.cDefaultOrdering
obj.cDefaultOrdering = obj.mOrderFn
setmetatable(obj, self)
self.__index = oop.loadOnFirstUse(self)
self.mSize = 0
return obj
end
apidoc[PairingHeap.new] = doc
doc = beginlongstringliteral
PairingHeap.cDefaultOrdering - The default ordering for elements in a
PairingHeap (initially ordering.INCREASING)
endlongstringliteral
apidoc[PairingHeap.cDefaultOrdering] = doc
local function addNode(self, node)
--self:assertInvariants()
self.mRoot = self.mRoot and compareAndLink(self.mOrderFn, self.mRoot, node) or node
self.mSize = self.mSize + 1
--self:assertInvariants()
return node
end
doc = beginlongstringliteral
PairingHeap:add(val) - Adds val to this PairingHeap.
param val: an element which is totaly ordered wrt all other elements in
the heap
returns: the allocated node in the heap -- this reference can be passed
as the first argument to the remove() or set() methods
time complexity: O(1)
endlongstringliteral
function PairingHeap:add(val)
local node = HeapNode(val)
return addNode(self, node)
end
apidoc[PairingHeap.add] = doc
doc = beginlongstringliteral
PairingHeap:get() - Returns the first/topmost element in the heap.
The heap is not modified by the call; this merely 'peeks' at the top
element.
time complexity: O(1)
synonyms: getFirst
endlongstringliteral
function PairingHeap:get()
return self.mRoot and self.mRoot.val
end
apidoc[PairingHeap.get] = doc
PairingHeap.getFirst = PairingHeap.get -- synonym
doc = beginlongstringliteral
PairingHeap:set(node, newVal) - Adjusts the value stored at node to be
newVal and reestablishes the heap property.
param node: a node returned by a prior call to add()
time complexity: O(1) if newVal comes before the old val according to
the ordering function of the heap; O(lg(size)) if newVal comes after
endlongstringliteral
function PairingHeap:set(node, newVal)
local oldSize = self.mSize
local t = detatch(self, node)
local old = node.val
if self.mOrderFn(node.val, newVal) == newVal then -- constant time
t.mRoot.val = newVal; self:merge(t)
else
-- maintain the node reference for later use
t:remove(); node.prev, node.next, node.child = nil, nil, nil
node.val=newVal; addNode(t, node)
self:merge(t)
end
self.mSize = oldSize -- the detatch/merge musses up the mSize; restore it
return old
end
apidoc[PairingHeap.set] = doc
doc = beginlongstringliteral
PairingHeap:update(node) - Removes and reinserts node.
This method is used for re-establishing the heap property after the
value stored at node has been altered.
endlongstringliteral
function PairingHeap:update(node)
self:remove(node)
node.child, node.prev, node.next = nil, nil, nil
addNode(self, node)
end
apidoc[PairingHeap.update] = doc
doc = beginlongstringliteral
PairingHeap:remove(node) - Removes the node from this PairingHeap and
returns the value that was stored at that node.
param node: a node returned by a prior call to add()
time complexity: amortized O(lg(size))
endlongstringliteral
function PairingHeap:remove(node)
--self:assertInvariants()
if not self.mRoot then error("Cannot remove from empty heap.",2) end
if node and node ~= self.mRoot then return removeInternal(self,node) end
local toReturn = self.mRoot.val
local c, q = self.mRoot.child, queue();
while c do q:add(c); c=c.next end
while q:size() > 1 do -- multipass pairing heap
q:add(compareAndLink(self.mOrderFn, q:remove(), q:remove()))
end
self.mRoot = q:size() > 0 and q:get(1) or nil
self.mSize = self.mSize - 1
--self:assertInvariants()
return toReturn
end
apidoc[PairingHeap.remove] = doc
doc = beginlongstringliteral
PairingHeap:replaceRoot(newRoot) -
endlongstringliteral
function PairingHeap:replaceRoot(newRoot)
newRoot.next, newRoot.prev, newRoot.child = nil, nil, self.mRoot.child
if newRoot.child then newRoot.child.prev = newRoot end
local oldRoot = self.mRoot; self.mRoot = newRoot
oldRoot.prev, oldRoot.next, oldRoot.child = nil, nil, nil
assert(self.mRoot)
return oldRoot
end
apidoc[PairingHeap.replaceRoot] = doc
doc = beginlongstringliteral
PairingHeap:addNode(node) - Adds this node to the heap and returns it.
This method is part of the contract which enables a PairingHeap to be
used in a MinMaxHeap; it should not normally be used by clients.
param node: a node returned by a prior call to add()
returns the added node
endlongstringliteral
PairingHeap.addNode = addNode
apidoc[PairingHeap.addNode] = doc
doc = beginlongstringliteral
PairingHeap:merge(pheap) - Merges another PairingHeap, pheap, in-place
into this heap.
This heap is modified and pheap is invalidated as a result of this call.
returns: this PairingHeap (to allow chaining)
time complexity: O(1)
endlongstringliteral
function PairingHeap:merge(pheap)
--assert(self.mOrderFn==pheap.mOrderFn, "Heaps do not share the same ordering function.")
--self:assertInvariants()
if pheap.mRoot then
self.mRoot = self.mRoot and compareAndLink(self.mOrderFn, self.mRoot, pheap.mRoot) or pheap.mRoot
end
self.mSize = self.mSize + pheap.mSize
oop.invalidate(pheap, PairingHeap, "Heap is no longer valid; it has been merged into another heap.")
--self:assertInvariants()
return self -- to allow chaining
end
apidoc[PairingHeap.merge] = doc
doc = beginlongstringliteral
PairingHeap:iter(node) - Returns an iterator over the values in the
heap, starting from node, which defaults to the root.
The elements of the iteration are not returned in sorted order,
although the first element returned will by default be the topmost
element of the heap.
param node: a node returned by a prior call to add() (default is root
of heap)
endlongstringliteral
function PairingHeap:iter(node)
return coroutine.wrap(function()
for n in self:nodeIter(node) do coroutine.yield(n.val) end
end)
end
apidoc[PairingHeap.iter] = doc
doc = beginlongstringliteral
PairingHeap:nodeIter(node) - Returns an iterator over the NODES in the
heap, starting from node (default is root).
The nodes returned by the iteration could be passed to the set() or
remove() methods, although it is not safe to modify the heap while
iterating.
param node: a node returned by a prior call to add() (default is root)
endlongstringliteral
function PairingHeap:nodeIter(node)
return iter.preorder({node or self.mRoot},
function(n)
local neighbors = {}
if n.child then table.insert(neighbors, n.child) end
if n.next then table.insert(neighbors, n.next) end
return neighbors
end)
end
apidoc[PairingHeap.nodeIter] = doc
doc = beginlongstringliteral
PairingHeap:size() - Returns the number of elements in the PairingHeap.
endlongstringliteral
function PairingHeap:size() return self.mSize end
apidoc[PairingHeap.size] = doc
doc = beginlongstringliteral
PairingHeap:__eq(otherheap) - Returns true if both heaps have the same
size and both self:iter() and otherheap:iter() return the same
sequence of values.
endlongstringliteral
function PairingHeap:__eq(otherheap)
return self:size()==otherheap:size() and iter.equals(self:iter(),otherheap:iter())
end
apidoc[PairingHeap.__eq], apidoc["=="] = doc
function PairingHeap:assertInvariants()
-- Ensure there are no cycles in the tree
local unique = sano.HashSet:uniqueFilter()
local traversal = iter.preorder({self.mRoot},
function(n)
local neighbors = {}
if n.child then table.insert(neighbors, n.child) end
if n.next then table.insert(neighbors, n.next) end
return neighbors
end,
function(e) return unique(e) and true or error("Cycle in tree.",3) end
)
repeat until not traversal()
end
function PairingHeap:debug(node, prefix)
prefix = prefix or ""
node = node or self.mRoot
if not node then return "( )" end
local str = sano.makers.vector()
str:add(prefix..tostring(node.val)..(node.child and "\n" or ""))
local child = node.child
while child~=nil do
str:add(self:debug(child, prefix.." ")..(child.next and "\n" or ""))
child=child.next
end
return str:asString()
end
doc = beginlongstringliteral
PairingHeap:test() - Unit test.
endlongstringliteral
function PairingHeap:test()
local heap = function(...) return self:make(unpack(arg)) end
local h1 = heap(1,3,5,6,4,2)
local node = h1:add(-1)
assert(h1.mRoot == node)
assert(node.val == -1)
assert(h1:size()==7, "Size is: "..h1:size())
assert(h1:set(node,22)==-1) -- test adjust up
assert(h1:size()==7, "Size is: "..h1.mSize)
assert(h1:set(node,-1)==22) -- test adjust down
assert(h1:set(node,5.5)) -- adjust into middle of heap
assert(h1:remove(node)==5.5)
assert(h1:remove()==1)
local h2 = heap(2.5, 3.5, 4.5, 5.5)
h1:merge(h2)
if pcall(function() h2:size() end) then
error("h2 should have been invalidated by the merge")
end
assert(h1:size()==9)
assert(ordering.isSorted(h1:extractFirstK(4)))
-- test of update
local h3 = heap()
for i=1,500 do h3:add(math.random(1,10)) end
h3:assertInvariants()
local h3Nodes = queue(h3:nodeIter())
for node in h3Nodes:iter() do
node.val = math.random(1,4)
h3:update(node)
end
h3:assertInvariants()
assert(ordering.isSorted(h3:extractFirstK()))
end
apidoc[PairingHeap.test] = doc
oop.mixin(PairingHeap, heaps.mixins.convenienceMethods)
return PairingHeap
]],
Multimap=[[
local sano = require 'sano'
local oop = sano.oop
local iter = sano.iter
local maps = sano.maps
local Vector = sano.Vector
local apidoc = {}
local doc
local Multimap = {documentation = apidoc}
Multimap.cDefaultMapType = sano.HashMap
Multimap.cDefaultValuesType = Vector
apidoc.general = beginlongstringliteral
Multimap - Map in which each key maps to a collection of values.
A Multimap is a map in which each key maps to a collection of values.
Multimaps merely provide a few convenience methods for behavior that
is achievable with a regular Map.
When add(key, val) is called, Multimap first checks to see if there is
already a collection stored against key. If yes, val is added to that
collection. If no (val is the very first value to be stored against
key), then a new collection is allocated and val is added to that
collection. For example:
> m = Multimap:new()
> m:add("R. White","'Tater Salad")
> m:add("R. White", "The 'Tater")
> m:add("G. Pitcher", "The Ace")
> m:addValues("G. Pitcher", {"Ace", "The Kid"})
> print(m)
{G. Pitcher=["The Ace", "Ace", "The Kid"],
R. White =["'Tater Salad", "The 'Tater"]}
Both the map implementation and values collection type can be specified
in the Multimap constructor. By default, the map implementation is a
HashMap and the values collection is a Vector. But, for instance:
> m = Multimap:new(HashMap, SkipSet)
...
> m:add("R. White", "The 'Tater")
> m:add("R. White", "The 'Tater")
> print(m:get("R. White")) -- The 'Tater only appears once, since the
{"'Tater Salad", "The 'Tater"} -- values collection is a set
Multimaps provide several other convenience methods, see the method
documentation.
endlongstringliteral
doc = beginlongstringliteral
Multimap:new(mapType, valuesCollection) - Creates and returns a new
Multimap, using the supplied map implementation and values
collection.
mapType defaults to self.cDefaultMapType, which is initially HashMap,
and valuesCollection defaults to self.cDefaultValuesType, which is
initally Vector.
endlongstringliteral
function Multimap:new(map, valuesCollection)
local obj = {}
setmetatable(obj, self)
self.__index = oop.loadOnFirstUse(self)
obj.mMap = map or self.cDefaultMapType:new()
obj.mValuesPrototype = valuesCollection or self.cDefaultValuesType
obj.mSize = 0
return obj
end
apidoc[Multimap.new] = doc
doc = beginlongstringliteral
Multimap:decorate(map, valuesType) - Decorates the supplied map's add
method to have multimap behavior.
After this method is called, map.add(key,val) will first check to see
if there is a collection stored against key. If yes, val is added to
that collection. If not, val is added to a newly allocated collection
of type valuesType and the new collection is stored against key.
It also adds a method, valSize, which returns the total number of
values in the map.
endlongstringliteral
function Multimap:decorate(map, valuesType)
local oldAdd = map.add
local mmap = self
assert(map:size()==0, "Cannot decorate a non-empty map.")
map.mValSize = 0
map.add = function(self, key, val)
local t = self:get(key) or (valuesType or mmap.cDefaultValuesType):new()
local success = t:add(val)
oldAdd(self, key, t)
self.mValSize = self.mValSize + (success and 1 or 0)
return success
end
map.valSize = function(self) return self.mValSize end
end
apidoc[Multimap.decorate] = doc
doc = beginlongstringliteral
Multimap:add(key, val) - Adds val to the collection stored against key
or to a new collection if no values are yet stored against key.
Examples:
{a=[1,2]}:add(a,3) results in {a=[1,2,3]}
{}:add(a,3) results in {a=[3]}
endlongstringliteral
function Multimap:add(key, val)
local t = self:get(key) or self.mValuesPrototype:new()
local success = t:add(val)
self.mMap:add(key, t)
self.mSize = success and self.mSize + 1 or self.mSize
return success
end
apidoc[Multimap.add] = doc
doc = beginlongstringliteral
Multimap:addValues(key, vals) - Equivalent to the loop:
for v in iter(vals) do self:add(key,v) end
endlongstringliteral
function Multimap:addValues(key, vals)
for v in iter(vals) do
self:add(key, v)
end
end
apidoc[Multimap.addValues] = doc
doc = beginlongstringliteral
Multimap:allocate(key) - Allocate an empty collection of values for
key.
endlongstringliteral
function Multimap:allocate(key)
self.mMap:add(key, self.mValuesPrototype:new())
end
apidoc[Multimap.allocate] = doc
doc = beginlongstringliteral
Multimap:get(key) - Returns the collection of values stored against
key, or nil if no mapping exists.
Synonym: operator () ( so a:get(b) == a(b) )
Examples:
{a=[1,2,3]}:get(a) == [1,2,3]
{a=[1,2,3]}:get(b) == nil
endlongstringliteral
function Multimap:get(key)
return self.mMap:get(key)
end
apidoc[Multimap.get] = doc
Multimap.__call = Multimap.get
doc = beginlongstringliteral
Multimap:iter([key]) - Returns an iterator over this map, or, if key
is specified, over the values collection stored against key.
If key is specified but maps to nil, an empty iterator is returned.
endlongstringliteral
function Multimap:iter(key)
if self.mSize == 0 then return iter.EMPTY end
if key then
local t = self.mMap:get(key)
return t and t:iter() or iter.EMPTY
else
return self.mMap:iter()
end
end
apidoc[Multimap.iter] = doc
doc = beginlongstringliteral
Multimap:allValsIter() - Returns an iterator over all the values
stored in this map.
Example:
mmap = {a=[1,2,3],b=[4,5,6]}
print( vector(mmap:allValsIter()) )
[1, 2, 3, 4, 5, 6]
endlongstringliteral
function Multimap:allValsIter()
return iter.chain(iter.exchange(self:iter()))
end
apidoc[Multimap.allValsIter] = doc
doc = beginlongstringliteral
Multimap:remove(key [,value]) - Remove all values stored against key,
or, if value is specified, remove only value in the collection stored
against key.
endlongstringliteral
function Multimap:remove(key, value)
if not value then
local vals = self.mMap:remove(key)
self.mSize = self.mSize - (vals and vals:size() or 0)
return vals
else
local vals = self:get(key)
if not vals then return nil
else
assert(vals.removeElement, "This method expects the values collection to have a removeElement method.")
local removed = vals:removeElement(value)
self.mSize = self.mSize - (removed and 1 or 0)
return removed
end
end
end
apidoc[Multimap.remove] = doc
doc = beginlongstringliteral
Multimap:contains(key) - Returns true iff self:get(key) ~= nil
endlongstringliteral
function Multimap:contains(key)
return self.mMap:contains(key)
end
apidoc[Multimap.contains] = doc
doc = beginlongstringliteral
Multimap:size() - Returns the number of keys in this Multimap.
Multimap:valSize() returns the number of values in this Multimap.
endlongstringliteral
function Multimap:size() return self.mMap:size() end
apidoc[Multimap.size] = doc
doc = beginlongstringliteral
Multimap:valSize() - Returns the number of values in this Multimap.
Multimap:size() returns the number of keys in this Multimap.
endlongstringliteral
function Multimap:valSize() return self.mSize end
apidoc[Multimap.valSize] = doc
doc = beginlongstringliteral
Multimap:__eq(other) - Returns true if self and other both have the
same set of keys and collections of values.
endlongstringliteral
function Multimap:__eq(other)
return self:valSize()==other:valSize() and maps.unorderedEquals(self, other)
end
apidoc[Multimap.__eq] = doc
doc = beginlongstringliteral
Multimap:test() - Unit test.
endlongstringliteral
function Multimap:test()
local multimap = function(...) return self:make(unpack(arg)) end
local vector = sano.makers.vector
local set = sano.makers.set
local count = iter.count
local m = multimap(iter.chain(count(10),count(6,15)), count(20))
assert(m == multimap(iter.chain(count(10),count(6,15)), count(20)))
assert(m(6)==vector(6,11),tostring(m(6)).." "..tostring(vector(6,11)))
assert(vector(m:iter(6)) == vector(6,11))
assert(m:valSize()==20)
assert(m:size()==15)
assert(set(m:allValsIter())==set(count(20)))
assert(m:remove(6)==vector(6,11))
assert(m:valSize()==18)
assert(m:size()==14)
assert(m:remove(7,12)==12)
assert(m(7)==vector{7})
local m2 = sano.HashMap:new()
self:decorate(m2)
m2:add(1,1); m2:add(1,2)
assert(m2:get(1)==vector(1,2))
end
apidoc[Multimap.test] = doc
oop.mixin(Multimap, maps.mixins.convenienceMethods)
oop.mixin(Multimap, maps.mixins.plauralizeHelper)
return Multimap
]],
}
-- -------------------------------------------
local function doNothing() end
setmetatable(autoloader, autoloader)
local function load(t, name)
if not archive[name] then
error(name..' not found in archive')
end
local text = archive[name]
text = string.gsub(
string.gsub(text, 'beginlongstringliteral', '[['),
'endlongstringliteral', ']]')
t[name] = assert(loadstring(text)())
local loader = onLoad or doNothing
loader(name, rawget(t,name));
return rawget(t, name)
end
autoloader.__index = function(t, name)
return load(t, name)
end
function autoloader.loadAll()
for k,_ in pairs(archive) do load(autoloader, k) end
end
autoloader.source = archive
return autoloader