lua-users home
lua-l archive

Re: RFE: table ctor syntax enhancement for many-to-one mappings

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


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
)

AltStyle によって変換されたページ (->オリジナル) /