[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: RFE: table ctor syntax enhancement for many-to-one mappings
- From: "Greg Ludwig" <greg@...>
- Date: Wed, 24 Aug 2011 00:04:57 -0700
Lorenzo Donati <lorenzodonatibz@interfree.it> wrote:
> On 19/08/2011 16.25, Tony Finch wrote:
> > Dirk Laurie<dpl@sun.ac.za> wrote:
> >>
> >> A less trivial example of the proposed syntax extension would be:
> >>
> >> tbl = { ['a','b','c']=good, [4,5,6]=bad, [7,'8','9']=so_so }
> >
> > function flatten_keys(template)
> > local tbl = {}
> > for keys,val in pairs(template) do
> > for i,key in ipairs(keys) do
> > tbl[key] = val
> > end
> > end
> > return kv
> > end
> >
> > tbl = flatten_keys{ [{'a','b','c'}] = good,
> > [{4,5,6}] = bad,
> > [{7,'8','9'}] = so_so }
> >
> > Tony.
>
> Unfortunately I found a rather big problem to your approach. It trades
expressiveness for robustness of code. In fact all goes well if the keys in
different groups are unique. Whenever you have duplicate keys, things go awry
> (I'll abbreviate flatten_keys with F):
>
> t = F{
> [{'a', 'b', 'c'}] = 1,
> [{'a'}] = 0,
> }
>
> what's the value of t['a'] ?
> with my proposal it would have been 0, since the entries would have been processed
in order, and the last assignment to t.a would have been t.a
> = 0 (table constructor guarantee sequential processing).
>
> But in this case the iteration in F among key groups is done using pairs, whose
iteration order is unspecified, so it may well be that
> {'a'} group will be processed before group
> {'a','b','c'}. In this case the syntax would be misleading. The problem is serious
because the iteration order may even change adding another group.
>
> One could argue that this idiom is to be used only with disjoint key groups, but
this could lead to brittle code: if someone entered a duplicated key by mistake,
it could lead to very hard to find bugs.
>
> A partial solution would be to track already processed keys:
>
>
> local function MapNTo1( t )
> local map = {}
> local already_seen = {}
> for keys, value in pairs( t ) do
> for _, key in ipairs( keys ) do
> if not already_seen[ key ] then
> already_seen[ key ] = true
> map[ key ] = value
> else
> error( "key already seen: " .. tostring( key ), 2 )
> end
> end
> end
> return map
> end
>
>
>
> But in a very large map
> (where my proposal would have been very helpful) this could help only partially:
>
>
> t = MapNTo1{ -- this is line N
> ...
> -- 50 lines of data
> ...
> } ---> error signaled at line N
>
> In which line lies actually the culprit?
>
>
> My proposal had some points after all :-)
>
>
>
> -- Lorenzo
>
Here are some other approaches that do not suffer from this problem:
---
function multi_setval(...)
local t = {}
for i=1,select('#',...) do
local keys = select(i,...)
local val = keys.val
for _,key in ipairs(keys) do
t[key] = val
end
end
return t
end
tbl = multi_setval(
{'a','b','c', val=good},
{4,5,6, val=bad},
{7,'8','9', val=so_so}
)
--- a slight variation (keylist and value alternate in args):
function multi_setval(...)
local t = {}
for i=1,select('#',...),2 do
local keys,val = select(i,...),select(i+1,...)
for _,key in ipairs(keys) do
t[key] = val
end
end
return t
end
tbl = multi_setval(
{'a','b','c'}, good,
{4,5,6}, bad,
{7,'8','9'}, so_so
)
--- an alternative that uses no temporary tables:
function multi_setval (...)
local t = {}
local sep = multi_setval_sep
local val = sep -- no value yet
for i=1,select('#',...) do
local key = select(i,...)
if key == sep then
val = sep -- reset
elseif val==sep then
val = key -- remember value
else
t[key] = val
end
end
return t
end
-- a unique value to serve as separator-arg in the call to multi_setval()
-- (also serves as 'var undefined' flag in the above code):
multi_setval_sep = {}
local _ = multi_setval_sep
tbl = multi_setval(
good, 'a','b','c', _,
bad, 4,5,6, _,
so_so, 7,'8','9'
)
--- like the preceding with a more natural call syntax (but slightly slower):
function multi_setval (...)
local t = {}
local sep = multi_setval_sep
local istart,i,n = 1,1,select('#',...)
while i<=n do
-- ...do nothing until we hit the separator...
if select(i,...) == sep then
local val = select(i+1,...)
for j=istart,i-1 do -- go back for keys
t[select(j,...)] = val
end
istart = i+1 -- first key of next set
end
i = i+1
end
return t
end
multi_setval_sep = {}
local _ = multi_setval_sep
tbl = multi_setval(
'a','b','c', _, good,
4,5,6, _, bad,
7,'8','9', _, so_so
)