ezr-eg1.lua: week 1 of ten. Columns, streaming, forgetting. Tutorial and tests in one file: prose lives in these comments, each demo is one eg[] function that reseeds, prints, then asserts; no crash means pass.
sudo apt install lua5.4 luajit # Debian, Ubuntu
brew install lua luajit # macOS
sudo ln -sf /usr/bin/lua5.4 /usr/local/bin/lua # Debian
lua ezr-eg1.lua --egs # list this week's demos
lua ezr-eg1.lua --all # run them all; "failures: 0"
lua ezr-eg1.lua --col # run just one
luajit ezr-eg1.lua --all # 10-50x faster, same output
A column watches values stream past and keeps a tiny summary: a Num holds mean and standard deviation (by Welford’s method), a Sym holds counts, mode and entropy. Nothing stores the values themselves. Welford also runs backwards: subtract a value and the stats roll back, so a table can forget rows as cheaply as it learned them.
Num and Sym answer the same questions – add, sub, mid, div, norm, dist, holds, reset – one polymorphic protocol, two implementations (mid is mu or mode; div is sd or entropy). Everything downstream (tables, distance, trees) talks to that protocol, never to the type.
Num, Sym, columnProtocol (add, sub, mid, div, norm, dist, holds, reset), protocol, noir, welford, stream, mode, entropy
local abs = math.abspackage.path = (arg and arg[0] or ""):gsub("[^/]*$","")
.. "?.lua;" .. package.pathall defs below land here; reads fall through to ezr (and, through it, to lib and _G)
local _ENV = setmetatable({}, {__index = require"ezr"})
if setfenv then setfenv(1, _ENV) endeg, doc = {}, {}
doc["--egs"] = "list this week's demos"
eg["--egs"] = function()
for _,k in ipairs(keys(eg)) do
print(("%-10s %s"):format(k, doc[k] or "")) end end
doc["--repl"] = "an interactive prompt; bare names resolve"
eg["--repl"] = function() repl(_ENV) end
doc["--all"] = "all the demos; fail if any of them do"
eg["--all"] = function( bad)
bad = 0
for _,k in ipairs(keys(eg)) do
if k ~= "--all" and k ~= "--egs" and k ~= "--repl" and
run(eg, k) == false then bad = bad + 1 end end
print("failures: " .. bad)
assert(bad == 0) endlua ezr-eg1.lua --the
One table holds every setting, parsed from the help text.
The command line can update any of them before a demo runs.
doc["--the"] = "the settings, all in one table"
eg["--the"] = function()
print(show(the))
assert(the.seed == 1234567891 and the.round == 2) endlua ezr-eg1.lua --csv
Stream a table off disk, one row at a time. Row one is the
header: uppercase = number, lowercase = symbol, trailing
“-“/”+” = goal, “X” = ignore (see the glossary’s noir).
doc["--csv"] = "stream rows from the default csv file"
eg["--csv"] = function( n)
n = 0
for row in csv(the.file) do
n = n + 1
if n <= 3 then print(show(row)) end end
print(n .. " rows")
assert(n == 399) endlua ezr-eg1.lua --col
Five numbers in, two stats out; three symbols in, a mode
and an entropy. adds() drives any column’s add() in a loop.
doc["--col"] = "Num and Sym summaries"
eg["--col"] = function( n,s)
n = adds{1,2,3,4,5}
s = adds({"a","a","b"}, Sym())
print(show{mu=n:mid(), sd=n:div(),
mode=s:mid(), ent=s:div()})
assert(n:mid() == 3 and s:mid() == "a") endlua ezr-eg1.lua --without
Column subtraction: pour b into a, then pour it out again.
What is left must be a, to within float noise.
doc["--without"] = "(a+b) minus b == a"
eg["--without"] = function( a,b,w)
a, b = adds{1,2,3,4,5}, adds{10,20,30}
w = adds({10,20,30}, adds{1,2,3,4,5}) - b
print(show{mu=w.mu, sd=w:div()})
assert(abs(w.mu - a.mu) < 1e-9) endlua ezr-eg1.lua --sub
The same trick, one value at a time: watch a hundred
numbers, remember the stats, then add fifty more and
forget them again. The stats round-trip.
doc["--sub"] = "add, then forget: the stats round-trip"
eg["--sub"] = function( c,n1,mu1,xtra)
c = Num()
for _ = 1, 100 do c:add(rand()) end
n1, mu1 = c.n, c.mu
xtra = {}
for j = 1, 50 do xtra[j] = rand(); c:add(xtra[j]) end
for _,v in ipairs(xtra) do c:add(v, -1) end
print(show{n=c.n, mu=c.mu, was=mu1})
assert(c.n == n1 and abs(c.mu - mu1) < 1e-9) endCheck your port:
Seed, srand and rand from
ezr-lib.lua; start that file with
local Seed,srand,rand; end it with
return {srand=srand, rand=rand}. Print 20 random
numbers. Then port that file to Python: same seed,
SAME 20 numbers, or the port is wrong.go(eg)
return _ENV