lua-users home
lua-l archive

[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
)