lua-users home
lua-l archive

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


Am 20.12.2012 23:12, schrieb Jay Carlson:
On Dec 20, 2012, at 11:02 AM, Philipp Janda wrote:

And collect/icollect ([1], slightly altered) are actually surprisingly interesting:

    icollect( {}, 1, pairs( t ) )        --> keys( t )
    icollect( {}, 2, pairs( t ) )        --> values( t )
    icollect( {}, 2, ipairs( t ) )       --> shallow_arraycopy( t )
    icollect( {}, 1, io.lines( fname ) ) --> my @lines = <FILE>;
    icollect( t1, 2, ipairs( t2 ) )      --> append( t1, t2 )

    collect( {}, 1, 2, pairs( t ) )  --> shallow_tablecopy( t )
    collect( {}, 1, 2, ipairs( t ) ) --> shallow_arraycopy( t )
    collect( {}, 2, 1, ipairs( t ) ) --> invert( t ), makeset( t )
    collect( t1, 1, 2, pairs( t2 ) ) --> extend( t1, t2 )

I really like this, but I hate numbers. Consider a function swap():

    swap(1, 2)
      -> return 2, 1
    swap(1, 2, 3)
      -> return 2, 1, 3
    swap(1, nil)
      -> return nil, 1

    -- I'm ultra-paranoid. Would you ever want this to work?
    swap(1)
      -> error()

I thought it was a shame you can't write a no-memory iterator that applies swap() to another iterator's stepping. (You can't because the "next step" function is only called with *first* value it previously returned.)

That is not the (only) reason. Since you are wrapping another iterator, you need to store its iterator function somewhere. Otherwise this would work (without extra memory):

    do
      local function dup1( a, ... )
        return a, a, ...
      end

      function dups1( f, s, var )
        return function( st, v )
          return dup1( f( st, v ) ) -- need f here!
        end, s, var
      end
    end

    -- icollect would always take the 2nd value
    icollect( {}, pairs( t ) )          --> values( t )
    icollect( {}, dups1( pairs( t ) ) ) --> keys( t )


Btw., the memory overhead is constant even for swaps, and you are allocating a table anyway ...

Perhaps a wrapper calling "swap" per step could be called swaps()--it fits with pairs() and ipairs().

That way those examples could become

   icollect({}, pairs( t ) ) -> keys(t)
   icollect({}, swaps( pairs( t ) )) -> values(t)

   collect({}, swaps( ipairs( t ) )) -> invert/Set(t)

or you could use:

    icollect( {}, identity, pairs( t ) ) --> keys( t )
    icollect( {}, swap, pairs( t ) )     --> values( t )
    collect( {}, swap, ipairs( t ) )     --> invert( t )

*But*:
- the extra function would be mandatory
- some iterators (e.g. string.gmatch) can generate more than two values, so we might need
    collect( {}, swap_1_4_and_2_3, s:gmatch( "..." ) )
- using some arbitrary indices (even when it's 1 and 2) implicitly is worse than passing numbers - I find numbers pretty clear, and there is precedent (select, table.insert, etc.), and you could make the numbers optional for a common case (alternatively you could make the table optional)

On the pro side: You could actually use a for-loop in collect/icollect instead of the tail recursive function.

Philipp