Extended list

This script com­bines some use­ful meth­ods of AS3 Array with those of Lin­go List. More­over, some of the orig­i­nal Lin­go list meth­ods like add(), append(), deleteAt() and getAt() are mod­i­fied. For exam­ple, getAt() method returns VOID instead of “index out of range” exception.

new( {…args} ) - Returns a new list, you can spec­i­fy the ini­tial val­ues as arguments.
dis­pose() - Destroys the list
shuf­fle() - Shuf­fles the list val­ues in-place, using Fisher–Yates algorithm.
add( {…args} )* - Adds val­ues to a list. For a sort­ed list, the val­ues are placed in their prop­er order.
getAt()* - Pre­vents “Index out of range” exception.
deleteAt()* - deleteAt()*
append( {…args} )* - Adds the spec­i­fied val­ues to the end of a list.
shift() - Removes the first ele­ment from an array and returns that element.
pop() - Removes the last ele­ment from a list and returns the val­ue of that element.
unshift( …args ) - Adds one or more ele­ments to the begin­ning of a list and returns the new count of the list.
slice( startIn­dex, endIndex ) - Returns a new list that con­sists of a range of ele­ments from the orig­i­nal list, with­out mod­i­fy­ing the orig­i­nal list.
splice( startIn­dex, delete­Count{, … values} ) - Adds ele­ments to and removes ele­ments from a list.
some( han­dler{, object} ) - Exe­cutes a test func­tion on each item in the list until an item is reached that returns TRUE.
every( han­dler{, object} ) - Exe­cutes a test han­dler on each item in the list until an item is reached that returns FALSE for the spec­i­fied handler.
fil­ter( han­dler{, object} ) - Exe­cutes a test han­dler on each item in the list and con­structs a new list for all items that return TRUE for the spec­i­fied handler.
forE­ach( han­dler{, object} ) - Exe­cutes a han­dler on each item in the list.
map( han­dler{, object} )  - Exe­cutes a han­dler on each item in a list, and con­structs a new list of items cor­re­spond­ing to the results of the han­dler on each item in the orig­i­nal list.
sort( {sym­bol­Order} ) - Sorts the list in ascend­ing or descend­ing order.( #ASC / #DESC )
sor­tOn( int­ColumnIn­dex{, symbolOrder} ) - Sorts the list on giv­en “col­umn”.
con­cat() - Con­cate­nates the ele­ments spec­i­fied in the para­me­ters with the ele­ments in a list and cre­ates a new list.
reverse() - Revers­es the list in-place.
join( sep­a­ra­tor ) - Returns a string, con­struct­ed by items in the list, with the sep­a­ra­tor insert­ed between them.
toString() - Returns a string that rep­re­sents the ele­ments in the spec­i­fied list, sep­a­rat­ed by commas.
lastIn­dex­Of( searchEle­ment, startIndex ) - Search­es for an item in a list, work­ing back­ward from the last item, and returns the index posi­tion of the match­ing item.
type­Of() - Returns the name of the script as a symbol.
Show code
-- "Xlist" parent script
-- Author: Vladin M. Mitov
-- May, 2011
-- Last Update - February, 2012
-- http://www.ed-multimedia.com

-- EXAMPLES:
------------------------------
-- myList = script( "xList" ).new( 1, 2, 3 )
-- put myList.interface()

-- myList.reverse()
-- put myList.toString()  -- "3,2,1"
-- myList.add( 4, 5, 6 )
-- put myList.toString()  -- "3,2,1,4,5,6"
-- myList.sort()
-- put myList.toString()  -- "1,2,3,4,5,6"

-- myList = script( "xList" ).new( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 )
-- put myList.filter( #getEven ).toString() -- "2,4,6,8,10"

-- A handler, defined in a movie script
--on getEven( aelement )
--  return( aelement mod 2 = 0 )
--end getEven

-------------------------------------------------
-- PROPERTIES
-------------------------------------------------
on ___________________________PROPERTIES
end
property ancestor

-------------------------------------------------
-- CONSTRUCTOR / DESTRUCTOR
-------------------------------------------------
on ___________________________CONSTRUCTOR_DESTRUCTOR
end
on new( me )
  ancestor = list()
  p = the paramCount
  repeat with i = 2 to p
    callAncestor( #add, me, param( i ) )
  end repeat
  return me
end new

on dispose( me )
  ancestor.deleteAll()
  ancestor = VOID
end dispose

-------------------------------------------------
-- PUBLIC METHODS
-------------------------------------------------
on ___________________________PUBLIC_METHODS
end

on getAt( me, n )
  if( n < 1 or n > count( ancestor ) ) then return VOID
  return ancestor[ n ]
end getAt

on deleteAt( me, n )
  if( n < 1 or n > count( ancestor ) ) then return VOID
  callAncestor( #deleteAt, me, n )
  return TRUE
end deleteAt

on lastIndexOf( me, searchElement, startIndex )
  if( voidP( startIndex ) ) then startIndex = count( ancestor )
  repeat with i = startIndex down to 1
    if( ancestor[ i ] =  searchElement ) then
      return i
    end if
  end repeat
  return VOID
end lastIndexOf

on add( me )
  p = the paramCount
  repeat with i = 2 to p
    callAncestor( #add, me, param( i ) )
  end repeat
end add

on addAt( me, pos, val )
  if( voidP( pos ) ) then return FALSE
  if( pos < 1  ) then return FALSE
  if( voidP( val ) ) then return FALSE
  callAncestor( #addAt, me, pos, val )
end addAt

on append( me )
  p = the paramCount
  repeat with i = 2 to p
    callAncestor( #append, me, param( i ) )
  end repeat
end append

on pop( me )
  n = callAncestor( #getLast, me )
  callAncestor( #deleteAt, me, count( ancestor ) )
  return n
end pop

on shift( me )
  e = callAncestor( #getAt, me, 1 )
  callAncestor( #deleteAt, me, 1 )
  return e
end shift

on unshift( me )
  p = the paramCount
  repeat with i = p down to 2
    callAncestor( #addAt, me, 1, param( i ) )
  end repeat
  return count( ancestor )
end unshift

on slice( me, startIndex, endIndex )
  retval = me.script.new()
  p = the paramCount
  if( voidP( startIndex ) ) then
    s = 1
    e = e = count( ancestor )
  end if
  if( p = 2 ) then
    if( startIndex = 1 or startIndex = 0 ) then
      s = 1
      e = count( ancestor )
    else if( startIndex < 0 ) then
      e = count( ancestor )
      s = startIndex + e + 1
    else if( startIndex > 0 ) then
      s = startIndex
      e = count( ancestor )
    end if
  else if( p = 3 ) then
    if( startIndex = 0 or endIndex = 0 ) then
      return retval
    else if( startIndex > 0 and endIndex < 0 ) then
      s = startIndex
      e = count( ancestor ) + endIndex + 1
    else if( startIndex < 0 and endIndex > 0 ) then
      return retval
    else if( startIndex > 0 and endIndex > 0 ) then
      if( startIndex = endIndex  ) then
        s = startIndex
        e = startIndex
      else if( startIndex > endIndex  ) then
        return retval
      else if( startIndex < endIndex  ) then
        s = startIndex
        e = endIndex
      end if
    else if( startIndex < 0 and endIndex < 0 ) then
      if( startIndex = endIndex  ) then
        s = count( ancestor ) + startIndex + 1
        e = s
      else if( startIndex > endIndex  ) then
        return retval
      else if( startIndex < endIndex  ) then
        s = count( ancestor ) + startIndex + 1
        e = count( ancestor ) + endIndex + 1
      end if
    end if
  end if
  repeat with i = s to e
    retval.add( ancestor[ i ] )
  end repeat
  return retval
end slice

on splice( me, startIndex, deleteCount, values )
  retval = me.script.new()
  p = the paramcount
  s = integer( startIndex )
  if( voidp( s ) or s = 0 ) then s = 1
  if( s < 0 ) then s = count( ancestor ) + s + 1
  if( p = 1 ) then
    return retval
  else if( p <= 2 ) then
    e = count( ancestor )
  else
    if( ilk( deleteCount ) <> #integer ) then deleteCount = 0
    e = min( count( ancestor ), s + deleteCount - 1 )
  end if
  repeat with i = e down to s
    retval.addAt( 1, ancestor[ i ] )
    callAncestor( #deleteAt, me, i )
  end repeat
  if( p < 4 ) then return retval
  repeat with j = p down to 4
    xparam = param( j )
    if( ilk( xparam ) = #list or ilk( xparam ) = #propList ) then
      cnt = count( xparam )
      repeat with jj = cnt down to 1
        callAncestor( #addAt, me, s, xparam[ jj ] )
      end repeat
    else
      callAncestor( #addAt, me, s, xparam )
    end if
  end repeat
  return retval
end splice

on some( me, ahandler, aobject )
  if( voidP( aobject ) ) then aobject = _movie
  cnt = count( ancestor )
  repeat with i = 1 to cnt
    element = ancestor[ i ]
    f = call( ahandler, aobject, element, i, me )
    if( f = TRUE ) then
      return f
    end if
  end repeat
  return FALSE
end some

on every( me, ahandler, aobject )
  if( voidP( aobject ) ) then aobject = _movie
  cnt = count( ancestor )
  repeat with i = 1 to cnt
    element = ancestor[ i ]
    f = call( ahandler, aobject, element )
    if( f = FALSE ) then
      return f
    end if
  end repeat
  return TRUE
end every

on filter( me, ahandler, aobject )
  if( voidP( aobject ) ) then aobject = _movie
  cnt = count( ancestor )
  retval = me.script.new()
  repeat with i = 1 to cnt
    element = ancestor[ i ]
    f = call( ahandler, aobject, element )
    if( f = TRUE ) then
      retval.append( element )
    end if
  end repeat
  return retval
end filter

on forEach( me, ahandler, aobject )
  if( voidP( aobject ) ) then aobject = _movie
  cnt = count( ancestor )
  repeat with i = cnt down to 1
    element = ancestor[ i ]
    f = call( ahandler, aobject, element, i, me )
  end repeat
end forEach

on map( me, ahandler, aobject )
  if( voidP( aobject ) ) then aobject = _movie
  cnt = count( ancestor )
  retval = me.script.new()
  repeat with i = 1 to cnt
    element = ancestor[ i ]
    retval.append( call( ahandler, aobject, element ) )
  end repeat
  return retval
end map

on shuffle( me )
  cnt = count( ancestor )
  if ( cnt = 0 ) then return FALSE
  repeat while ( cnt > 0 )
    j = random( cnt )
    tempi = ancestor[ cnt ]
    tempj = ancestor[ j ]
    ancestor[ cnt ] = tempj
    ancestor[ j ] = tempi
    cnt = cnt - 1
  end repeat
  return TRUE
end shuffle

on count( me )
  if( voidP( ancestor ) ) then return VOID
  return count( ancestor )
end count

on concat( me )
  p = the paramCount
  t = callAncestor( #duplicate, me )
  retval = me.script.new()
  repeat with i = 2 to p
    e = param( i )
    if( ilk( e ) = #list ) then
      cnt = count( e )
      repeat with j = 1 to cnt
        t.append( e[ j ] )
      end repeat
    else
      t.append( e )
    end if
  end repeat
  retval.ancestor = t
  return retval
end concat

on reverse( me )
  left = 1
  right = count( ancestor )
  repeat while( left < right )
    temp = ancestor[ left ]
    ancestor[ left ]  = ancestor[ right ]
    ancestor[ right ] = temp
    left = left + 1
    right = right - 1
  end repeat
end reverseList

on join( me, sep )
  t = EMPTY
  cnt = count( ancestor )
  repeat with i = 1 to cnt
    put ancestor[ i ] & sep after t
  end repeat
  len = t.length
  reallength = ( len - sep.length ) + 1
  repeat with j = len down to reallength
    delete t.char[ t.length ]
  end repeat
  return t
end join

on toString( me )
  t = me.join( "," )
  return t
end toString

on sort( me, order )
  if( voidP( order ) or [#ASC, #DESC].getPos( order ) = 0 ) then order = #ASC
  case( order ) of
    #ASC : 
      callAncestor( #sort, me )
    #DESC:
      callAncestor( #sort, me )
      me.reverse()
  end case
end sort

on sortOn( me, columntoSortOn, order )
  if ( ilk( ancestor ) <> #list ) then return FALSE
  if ( count( ancestor ) = 0 ) then return FALSE
  if ( ilk( ancestor[1] ) <> #list ) then return FALSE
  if( voidP( order ) or [#ASC, #DESC].getPos( order ) = 0 ) then order = #ASC
  case( order ) of
    #ASC :__sort_on_internal_ASC( me, columntoSortOn )
    #DESC:__sort_on_internal_DESC( me, columntoSortOn )
  end case
end sortOn

on is( me, atype )
  obj = me
  types = [ #list, #xlist ]
  t = obj.typeOf()
  repeat while t <> #xlist
    types.append( t )
    obj = obj.getAprop( #ancestor )
    t = obj.typeOf()
  end repeat
  put types
  return( types.getPos( atype ) > 0 )
end is

on typeOf( me )
  the itemdelimiter = quote
  return symbol( string( me ).item[ 2 ] )
end typeOf

on interface( me )
  msg = EMPTY
  put "new( {...args} )                                -- Returns a new list, you can specify the initial values as arguments." & RETURN after msg
  put "dispose()                                       -- Destroys the list" & RETURN after msg
  put "shuffle()                                       -- Shuffles the list values in-place, using Fisher–Yates algorithm." & RETURN after msg
  put "add( {...args} )*                               -- Adds values to a list. For a sorted list, the values are placed in their proper order." & RETURN after msg
  put "getAt()*                                        -- Prevents "&quote&"Index out of range"&quote&" exception." & RETURN after msg
  put "deleteAt()*                                     -- Prevents "&quote&"Index out of range"&quote&" exception." & RETURN after msg
  put "append( {...args} )*                            -- Adds the specified values to the end of a list." & RETURN after msg
  put "shift()                                         -- Removes the first element from an array and returns that element." & RETURN after msg
  put "pop()                                           -- Removes the last element from a list and returns the value of that element." & RETURN after msg
  put "unshift( ...args )                              -- Adds one or more elements to the beginning of a list and returns the new count of the list." & RETURN after msg
  put "slice( startIndex, endIndex )                   -- Returns a new list that consists of a range of elements from the original list, without modifying the original list." & RETURN after msg
  put "splice( startIndex, deleteCount{, ... values} ) -- Adds elements to and removes elements from a list." & RETURN after msg
  put "some( handler{, object} )                       -- Executes a test function on each item in the list until an item is reached that returns TRUE." & RETURN after msg
  put "every( handler{, object} )                      -- Executes a test handler on each item in the list until an item is reached that returns FALSE for the specified handler." & RETURN after msg
  put "filter( handler{, object} )                     -- Executes a test handler on each item in the list and constructs a new list for all items that return TRUE for the specified handler." & RETURN after msg
  put "forEach( handler{, object} )                    -- Executes a handler on each item in the list." & RETURN after msg
  put "map( handler{, object} )                        -- Executes a handler on each item in a list, and constructs a new list of items corresponding to the results of the handler on each item in the original list." & RETURN after msg
  put "sort( {symbolOrder} )                           -- Sorts the list ascending or descending ( #ASC / #DESC )" & RETURN after msg
  put "sortOn( intColumnIndex{, symbolOrder} )         -- Sorts the list on given "&quote&"column"&quote&"( #ASC or #DESC )." & RETURN after msg
  put "concat()                                        -- Concatenates the elements specified in the parameters with the elements in a list and creates a new list." & RETURN after msg
  put "reverse()                                       -- Reverses the list in-place." & RETURN after msg
  put "join( separator )                               -- Returns a string, constructed by items in the list, with the separator inserted between them." & RETURN after msg
  put "toString()                                      -- Returns a string that represents the elements in the specified list, separated by commas." & RETURN after msg
  put "lastIndexOf( searchElement, startIndex )        -- Searches for an item in a list, working backward from the last item, and returns the index position of the matching item." & RETURN after msg
  put "typeOf()                                        -- Returns the name of the script as a symbol." & RETURN after msg
  put "is()" after msg
  return msg
end interface

-------------------------------------------------
-- PRIVATE METHODS
-------------------------------------------------
on ___________________________PRIVATE_METHODS
end
on __sort_on_internal_ASC( me, c )
  t = list()
  t[ c ] = 0
  ancestor.add( t )
  cnt = count( ancestor )
  repeat with i = cnt down to 0
    flipped = FALSE
    j = 0
    repeat while j < i
      j = j + 1
      if ( j + 1 < cnt ) then
        aInt = float( ancestor[ j ][ c ] )
        bInt = float ( ancestor[ j + 1 ][ c ] )
        if ( aInt > bInt ) then
          t = ancestor[ j ]
          ancestor[ j ] = ancestor[ j + 1 ]
          ancestor[ j + 1 ] = t
          flipped = TRUE
        end if
      end if
    end repeat
  end repeat
  ancestor.deleteAt( cnt )
  return TRUE
end __sort_on_internal_ASC

on __sort_on_internal_DESC( me, c )
  t = list()
  t[ c ] = 0
  ancestor.add( t )
  cnt = count( ancestor )
  repeat with i = cnt down to 0
    flipped = FALSE
    j = 0
    repeat while j < i
      j = j + 1
      if ( j + 1 < cnt ) then
        aInt = float( ancestor[ j ][ c ] )
        bInt = float ( ancestor[ j + 1 ][ c ] )
        if ( aInt < bInt ) then
          t = ancestor[ j ]
          ancestor[ j ] = ancestor[ j + 1 ]
          ancestor[ j + 1 ] = t
          flipped = TRUE
        end if
      end if
    end repeat
  end repeat
  ancestor.deleteAt( cnt )
  return TRUE
end __sort_on_internal_DESC

on ___________________________END_XLIST
end 


An exam­ple of fil­ter() method:
myList = script( "xList" ).new( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 )
put myList.filter( #getEven ).toString() -- "2,4,6,8,10"

-- A handler, defined in a movie script:
on getEven( aelement )
  return( aelement mod 2 = 0 )
end getEven
 
Comments

Added sort() method.

sort(  {order} )
order - #Symbol; #ASC or #DESC

Hi Vlad,

An excel­lent com­ple­ment to the Lin­go list function.

I have one ques­tion on the dis­pos­al of lists returned by the extend­ed list object. Exam­ples are : splice, slice, con­cat, fil­ter, etc. which cre­ate instances of the extend­ed list object.

Nor­mal­ly, when I use Lingo’s native lists as a local vari­able in a han­dler, I do not have to explic­it­ly destroy the vari­able upon exit. Now, sup­pose in one of my han­dlers I call the fil­ter func­tion of the extend­ed list and receive a fil­tered list, which is local to my han­dler, would I have to destroy the fil­tered list explic­it­ly? Would it cause a mem­o­ry leak if I don’t destroy it?

I sup­pose, the same would apply to your extend­ed list fil­ter func­tion as well. You cre­ate a new instance of the list and sim­ply return it to the call­ing func­tion. Would this have cre­at­ed a mem­o­ry leak, or would Lingo’s garbage col­lec­tor take care of it automatically?

For­give me if the ques­tion sounds stu­pid. But I am con­fused and hence tempt­ed to ask.

Thanks.

Hi, Man­ish!

Sor­ry for the delayed response. 

Frankly, I have not test­ed the issues you men­tioned and can not say any­thing for sure.
Are you face any prob­lems with mem­o­ry leaks using the “Xlist”?

Cheers,
vlad

Thanks for the response, Vlad.

Unfor­tu­nate­ly, I don’t think there is any way of know­ing if this would cause a mem­o­ry leak.

What is known is that Direc­tor takes care of releas­ing the mem­o­ry used up by a local vari­able when it goes out of scope. So, a nor­mal list assigned to a local vari­able would be auto­mat­i­cal­ly dis­posed. What I don’t know is when a list is wrapped into a prop­er­ty vari­able (like it is in xList), would Direc­tor dis­pose the xList object also when it goes out of scope of an xList method, since the xList object is itself local to the method in which it was created?

My apolo­gies, Vlad. xList does not cause any mem­o­ry leaks.

Since ret­Val is local to each method in which it cre­ate a new xList child object, Direc­tor dis­pos­es it once it is out of scope. There­fore, if the vari­able to which the returned list is assigned in a call­ing han­dler is also local to that han­dler, Direc­tor would sim­i­lar­ly dis­pose the vari­able once it is out of scope, caus­ing the xList child object ref­er­ence count to become 0. So, no mem­o­ry leak would occur.