Extended list

This script com­bi­nes 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” excep­tion.

new( {…args} ) - Returns a new list, you can spec­i­fy the ini­tial val­ues as argu­ments.
dis­pose() - Destroys the list
shuf­fle() - Shuf­fles the list val­ues in-place, using Fisher–Yates algo­rithm.
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” excep­tion.
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 ele­ment.
pop() - Removes the last ele­ment from a list and returns the val­ue of that ele­ment.
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, endIn­dex ) - 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{, … val­ues} ) - 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 han­dler.
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 han­dler.
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{, sym­bol­Order} ) - Sorts the list on given “column”.
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 com­mas.
lastIn­dex­Of( searchEle­ment, startIn­dex ) - 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 sym­bol.
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 func­tion.

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 auto­mat­i­cal­ly?

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 respon­se.

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 respon­se, 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 cre­at­ed?

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.