This larger example replaces the previous code on this wiki page and adds considerably more functionality. Sample output is at the end.
#!/usr/bin/env lux
-- Columnwise data manipulation
-- 21/02/2001 jcw@equi4.com
-- This code manages all data in Lua tables for now.
--
-- Views are created using:
-- view = View {'name1','name2',...}
--
-- Views can be indexed (1-based), producing a row reference:
-- row = view[123]
--
-- Info functions:
-- numcols = view:cols()
-- numrows = view:rows()
-- strlist = view:syms()
-- colnum = view:lookup(sym)
--
-- Data access functions:
-- value = view:item(rownum,colnum)
-- rowvec = view:getrow(rownum)
--
-- Data modification functions:
-- view:setrow(rownum,rowvec)
-- view:insert(rownum,value1,value2,...)
-- newpos = view:append(value1,value2,...)
-- view:delete(rownum)
--
-- Utility functions:
-- view:dump(?title?)
--
-- Rows have attributes which can be fetched and stored:
-- value = row.attr
-- value = row[colnum]
-- row.attr = value
-- row[colnum] = value
--
-- Read-only custom views are created using:
-- view = CustomView {
-- cols = function (v) ... end, -- returns width, int
-- syms = function (v) ... end, -- returns string list
-- rows = function (v) ... end, -- returns height, int
-- item = function (v,r,c) ... end, -- returns one data item
-- }
--
-- Pre-defined custom views:
-- result = view.sort() -- sort on all cols, ascending
-- result = view.sort('name') -- sort 'name' field, ascending
-- result = view.sort(+2,-1) -- sort col 2 asc and col 1 desc
-- result = view.filter(f) -- keep rows for which f is true
-- result = view.project(...) -- projection, args gives cols
-- result = view.rename(m) -- renames fields to new ones
-- result = view.explode(v) -- builds the cartesian product
-- result = view.compute(m) -- computes fields given as map
--
-- Note: only the "cols" and "syms" handlers are called during
-- construction. There is no data access (i.e. calls to "rows"
-- and "item" handlers are only performed while using results).
rowtag,viewtag,custviewtag=newtag(),newtag(),newtag()
viewops={}
-- return number of columns
function viewops:cols()
return self.d[0]
end
-- return a list of column names
function viewops:syms()
local r={}
for c=1,self.d[0] do r[c]=self.d[c] end
return r
end
-- return number of rows
function viewops:rows()
return self.n
end
-- return specified attribute
function viewops:item(r,c)
return self.c[c][r]
end
-- return position of a column name
function viewops:lookup(s)
local t=self:syms()
for i=1,getn(t) do if s==t[i] then return i end end
end
-- return specified row
function viewops:getrow(x)
local r={}
for c=1,self:cols() do r[c]=self:item(x,c) end
return r
end
-- set specified row to new values
function viewops:setrow(x,r)
for c=1,self:cols() do self.c[c][x]=r[c] end
end
-- insert args as row at specified position
function viewops:insert(pos,...)
self.n=self.n+1
for c=1,self:cols() do tinsert(self.c[c],pos,arg[c]) end
end
-- append args as row at end
function viewops:append(...)
self.n=self.n+1
for c=1,self:cols() do self.c[c][self.n]=arg[c] end
return self.n
end
-- delete specified row
function viewops:delete(pos)
for c=1,self:cols() do tremoved(self.c[c],pos) end
self.n=self.n-1
end
-- dump view structure and contents
function viewops:dump(title)
if title then write(strrep('- ',25),'\n ',title,' ') end
hdump(self:syms())
for i=1,self:rows() do
write(format(' %4d:\t',i))
hdump(self:getrow(i))
end
end
-- tag method to make views indexable
settagmethod(viewtag,'index',
function (t,x)
if type(x)=='number' then return settag({v=t,r=x},rowtag) end
assert(viewops[x],x)
return viewops[x]
end)
-- tag method to fetch row attributes
settagmethod(rowtag,'gettable',
function (t,c)
local v,r=rawget(t,'v'),rawget(t,'r')
assert(v and r)
assert(1<=r and r<=v:rows(),r)
if type(c)~='number' then c=v:lookup(c) end
if c then return v:item(r,c) end
end)
-- tag method to store row attributes
settagmethod(rowtag,'settable',
function (t,x,z)
local v,r=rawget(t,'v'),rawget(t,'r')
assert(v and r)
assert(1<=r and r<=v:rows(),r)
local c=v:lookup(x)
if c then v.c[c][r]=z end
end)
-- construct a new view with specified fields
function View(d)
local c={}
d[0]=getn(d)
for i=1,d[0] do d[d[i]]=i; c[i]={} end
return settag({t='View',n=0,d=d,c=c},viewtag)
end
-- tag method to make custom views indexable
settagmethod(custviewtag,'index',
function (t,x)
if type(x)=='number' then return settag({v=t,r=x},rowtag) end
local h=rawget(t,'h')
if x=='n' then return h.rows(t) end
return h[x] or function (...) return %h.dispatch(%x,arg) end
end)
-- construct a new custom view with the specified handlers
function CustomView(h)
if not h.dispatch then
h.dispatch = function (x,args) return call(viewops[x],args) end
end
return settag({h=h},custviewtag)
end
function intrun(n)
local r={}
for i=1,n do r[i]=i end
return r
end
function mysort(v)
local b,o,m=v.b,v.o,intrun(v:rows())
sort(m,
function (x,y)
for i=1,getn(%o) do
local c=%o[i]
if c<0 then
c=-c
if %b:item(x,c)>%b:item(y,c) then return 1 end
else
if %b:item(x,c)<%b:item(y,c) then return 1 end
end
if %b:item(x,c)~=%b:item(y,c) then return end
end
end)
v.m=m
return m
end
sorting={
cols = function (v) return v.b:cols() end,
syms = function (v) return v.b:syms() end,
rows = function (v) return v.b:rows() end,
item = function (v,r,c)
local m=rawget(v,'m') or mysort(v)
return v.b:item(m[r],c)
end,
}
function viewops:sort(...)
local r=CustomView(sorting)
for i=1,getn(arg) do
if type(arg[i])~='number' then arg[i]=self:lookup(arg[i]) end
end
if getn(arg)==0 then arg=intrun(self:cols()) end
r.b,r.o=self,arg
return r
end
function myfilter(v)
local b,f,m=v.b,v.f,{}
for i=1,b:rows() do if f(v,i) then tinsert(m,i) end end
v.m=m
return m
end
filtering={
cols = function (v) return v.b:cols() end,
syms = function (v) return v.b:syms() end,
rows = function (v) return getn(rawget(v,'m') or myfilter(v)) end,
item = function (v,r,c)
local m=rawget(v,'m') or myfilter(v)
return v.b:item(m[r],c)
end,
}
function viewops:filter(f)
local r=CustomView(filtering)
r.b,r.f=self,f
return r
end
projecting={
cols = function (v) return getn(v.m) end,
syms = function (v)
local s,l=v.b:syms(),{}
for i=1,getn(v.m) do l[i]=s[v.m[i]] end
return l
end,
rows = function (v) return v.b:rows() end,
item = function (v,r,c) return v.b:item(r,v.m[c]) end,
}
function viewops:project(...)
local r=CustomView(projecting)
for i=1,getn(arg) do
if type(arg[i])~='number' then arg[i]=self:lookup(arg[i]) end
end
r.b,r.m=self,arg
return r
end
renaming={
cols = function (v) return v.b:cols() end,
syms = function (v)
local s=v.b:syms()
for i=1,getn(s) do s[i]=v.m[s[i]] or s[i] end
return s
end,
rows = function (v) return v.b:rows() end,
item = function (v,r,c) return v.b:item(r,c) end,
}
function viewops:rename(m)
local r=CustomView(renaming)
r.b,r.m=self,m
return r
end
exploding={
cols = function (v) return v.b:cols()+v.m:cols() end,
syms = function (v)
local s,t=v.b:syms(),v.m:syms()
for i=1,getn(t) do tinsert(s,t[i]) end
return s
end,
rows = function (v) return v.b:rows()*v.m:rows() end,
item = function (v,r,c)
local k,n=v.b:cols(),v.m:rows()
if c<=k then
return v.b:item(bor((r-1)/n,0)+1,c) -- tricky int()
end
return v.m:item(imod((r-1),n)+1,c-k)
end,
}
function viewops:explode(v)
local r=CustomView(exploding)
r.b,r.m=self,v
return r
end
computing={
cols = function (v) return v.b:cols()+getn(v.m) end,
syms = function (v)
local s,n=v.b:syms(),v.b:cols()
for k,v in v.m do if type(v)=='number' then s[v+n]=k end end
return s
end,
rows = function (v) return v.b:rows() end,
item = function (v,r,c)
local k=v.b:cols()
if c<=k then return v.b:item(r,c) end
local f=v.m[c-k]
return f(v,r,c)
end,
}
function viewops:compute(m)
local r,l,i=CustomView(computing),{},0
for k,v in m do i=i+1 l[k],l[i]=i,v end
r.b,r.m=self,l
return r
end
-- horizontal dump
function hdump(t)
local s='('
for i=1,getn(t) do
local v=t[i]
if type(v)=='table' then v='<'..v:rows()..' rows>' end
write(s,v)
s=','
end
write(')\n')
end
if not omitDataInit then
-- sample data from Gadfly by Aaron Watters
frequents=View {'drinker','bar','perweek'}
frequents:append('adam','lolas',1)
frequents:append('woody','cheers',5)
frequents:append('sam','cheers',5)
frequents:append('norm','cheers',3)
frequents:append('wilt','joes',2)
frequents:append('norm','joes',1)
frequents:append('lola','lolas',6)
frequents:append('norm','lolas',2)
frequents:append('woody','lolas',1)
frequents:append('pierre','frankies',0)
serves=View {'bar','beer','quantity'}
serves:append('cheers','bud',500)
serves:append('cheers','samaddams',255)
serves:append('joes','bud',217)
serves:append('joes','samaddams',13)
serves:append('joes','mickies',2222)
serves:append('lolas','mickies',1515)
serves:append('lolas','pabst',333)
serves:append('winkos','rollingrock',432)
serves:append('frankies','snafu',5)
likes=View {'drinker','beer','perday'}
likes:append('adam','bud',2)
likes:append('wilt','rollingrock',1)
likes:append('sam','bud',2)
likes:append('norm','rollingrock',3)
likes:append('norm','bud',2)
likes:append('nan','sierranevada',1)
likes:append('woody','pabst',2)
likes:append('lola','mickies',5)
root=View {'frequents','serves','likes'}
root:append(frequents,serves,likes)
assert(likes[3].drinker=='sam')
likes[3].drinker='john'
assert(root[1].likes[3].drinker=='john')
likes[3].drinker='sam'
cvwdata={
{'bill',55},
{'john',44},
{'walt',33},
}
cvw=CustomView {
cols = function (v) return 2 end,
syms = function (v) return {'name','age'} end,
rows = function (v) return getn(cvwdata) end,
item = function (v,r,c) return cvwdata[r][c] end,
}
assert(cvw:lookup('age')==2)
assert(cvw[2].age==44)
end
if not omitDataDump then
frequents:dump('frequents')
serves:dump('serves')
likes:dump('likes')
root:dump('root')
cvw:dump('cvw')
frequents:sort('bar'):dump('sort')
frequents:filter(function (v,r) return v.b:item(r,3)==2 end):dump('filter')
frequents:project('perweek','drinker'):dump('project')
frequents:rename({perweek='w',drinker='d'}):dump('rename')
cvw:explode(likes):dump('explode')
cvw:compute({x=function (v,r,c) return 2*v[r].age end}):dump('compute')
end
Output from the above:
frequents (drinker,bar,perweek)
1: (adam,lolas,1)
2: (woody,cheers,5)
3: (sam,cheers,5)
4: (norm,cheers,3)
5: (wilt,joes,2)
6: (norm,joes,1)
7: (lola,lolas,6)
8: (norm,lolas,2)
9: (woody,lolas,1)
10: (pierre,frankies,0)
serves (bar,beer,quantity)
1: (cheers,bud,500)
2: (cheers,samaddams,255)
3: (joes,bud,217)
4: (joes,samaddams,13)
5: (joes,mickies,2222)
6: (lolas,mickies,1515)
7: (lolas,pabst,333)
8: (winkos,rollingrock,432)
9: (frankies,snafu,5)
likes (drinker,beer,perday)
1: (adam,bud,2)
2: (wilt,rollingrock,1)
3: (sam,bud,2)
4: (norm,rollingrock,3)
5: (norm,bud,2)
6: (nan,sierranevada,1)
7: (woody,pabst,2)
8: (lola,mickies,5)
root (frequents,serves,likes)
1: (<10 rows>,<9 rows>,<8 rows>)
cvw (name,age)
1: (bill,55)
2: (john,44)
3: (walt,33)
sort (drinker,bar,perweek)
1: (sam,cheers,5)
2: (woody,cheers,5)
3: (norm,cheers,3)
4: (pierre,frankies,0)
5: (norm,joes,1)
6: (wilt,joes,2)
7: (lola,lolas,6)
8: (woody,lolas,1)
9: (norm,lolas,2)
10: (adam,lolas,1)
filter (drinker,bar,perweek)
1: (wilt,joes,2)
2: (norm,lolas,2)
project (perweek,drinker)
1: (1,adam)
2: (5,woody)
3: (5,sam)
4: (3,norm)
5: (2,wilt)
6: (1,norm)
7: (6,lola)
8: (2,norm)
9: (1,woody)
10: (0,pierre)
rename (d,bar,w)
1: (adam,lolas,1)
2: (woody,cheers,5)
3: (sam,cheers,5)
4: (norm,cheers,3)
5: (wilt,joes,2)
6: (norm,joes,1)
7: (lola,lolas,6)
8: (norm,lolas,2)
9: (woody,lolas,1)
10: (pierre,frankies,0)
explode (name,age,drinker,beer,perday)
1: (bill,55,adam,bud,2)
2: (bill,55,wilt,rollingrock,1)
3: (bill,55,sam,bud,2)
4: (bill,55,norm,rollingrock,3)
5: (bill,55,norm,bud,2)
6: (bill,55,nan,sierranevada,1)
7: (bill,55,woody,pabst,2)
8: (bill,55,lola,mickies,5)
9: (john,44,adam,bud,2)
10: (john,44,wilt,rollingrock,1)
11: (john,44,sam,bud,2)
12: (john,44,norm,rollingrock,3)
13: (john,44,norm,bud,2)
14: (john,44,nan,sierranevada,1)
15: (john,44,woody,pabst,2)
16: (john,44,lola,mickies,5)
17: (walt,33,adam,bud,2)
18: (walt,33,wilt,rollingrock,1)
19: (walt,33,sam,bud,2)
20: (walt,33,norm,rollingrock,3)
21: (walt,33,norm,bud,2)
22: (walt,33,nan,sierranevada,1)
23: (walt,33,woody,pabst,2)
24: (walt,33,lola,mickies,5)
compute (name,age,x)
1: (bill,55,110)
2: (john,44,88)
3: (walt,33,66)
If you're curious about this design, have a look at the MetaKit database library which started all this - http://www.equi4.com/metakit/
Last modified
2001-02-23
2001-02-23
(216.232.136.19)
Note: you are looking at
the snapshot of an old wiki
- much of this information
is likely to be very outdated
