Extended list

This script combines some useful methods of AS3 Array with those of Lingo List. Moreover, some of the original Lingo list methods like add(), append(), deleteAt() and getAt() are modified. For example, getAt() method returns VOID instead of “index out of range” exception.

new( {…args} ) Returns a new list, you can specify the initial values as arguments.
dispose() Destroys the list
shuffle() Shuffles the list values in-place, using Fisher–Yates algorithm.
add( {…args} )* Adds values to a list. For a sorted list, the values are placed in their proper order.
getAt()* Prevents "Index out of range" exception.
deleteAt()* deleteAt()*
append( {…args} )* Adds the specified values to the end of a list.
shift() Removes the first element from an array and returns that element.
pop() Removes the last element from a list and returns the value of that element.
unshift( …args ) Adds one or more elements to the beginning of a list and returns the new count of the list.
slice( startIndex, endIndex ) Returns a new list that consists of a range of elements from the original list, without modifying the original list.
splice( startIndex, deleteCount{, … values} ) Adds elements to and removes elements from a list.
some( handler{, object} ) Executes a test function on each item in the list until an item is reached that returns TRUE.
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.
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.
forEach( handler{, object} ) Executes a handler on each item in the list.
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.
sort( {symbolOrder} ) Sorts the list in ascending or descending order.( #ASC / #DESC )
sortOn( intColumnIndex{, symbolOrder} ) Sorts the list on given "column".
concat() Concatenates the elements specified in the parameters with the elements in a list and creates a new list.
reverse() Reverses the list in-place.
join( separator ) Returns a string, constructed by items in the list, with the separator inserted between them.
toString() Returns a string that represents the elements in the specified list, separated by commas.
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.
typeOf() Returns the name of the script as a symbol.

Show code

[code lang=”lingo” gutter=”true” toolbar=”true” collapse=”false”]
— "Xlist" parent script
— Author: Vladin M. Mitov
— May, 2011
— Last Update – February, 2012
— https://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
[/code]


An example of filter() 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 excellent complement to the Lingo list function.

I have one question on the disposal of lists returned by the extended list object. Examples are : splice, slice, concat, filter, etc. which create instances of the extended list object.

Normally, when I use Lingo’s native lists as a local variable in a handler, I do not have to explicitly destroy the variable upon exit. Now, suppose in one of my handlers I call the filter function of the extended list and receive a filtered list, which is local to my handler, would I have to destroy the filtered list explicitly? Would it cause a memory leak if I don’t destroy it?

I suppose, the same would apply to your extended list filter function as well. You create a new instance of the list and simply return it to the calling function. Would this have created a memory leak, or would Lingo’s garbage collector take care of it automatically?

Forgive me if the question sounds stupid. But I am confused and hence tempted to ask.

Thanks.

Hi, Manish!

Sorry for the delayed response.

Frankly, I have not tested the issues you mentioned and can not say anything for sure.
Are you face any problems with memory leaks using the “Xlist”?

Cheers,
vlad

Thanks for the response, Vlad.

Unfortunately, I don’t think there is any way of knowing if this would cause a memory leak.

What is known is that Director takes care of releasing the memory used up by a local variable when it goes out of scope. So, a normal list assigned to a local variable would be automatically disposed. What I don’t know is when a list is wrapped into a property variable (like it is in xList), would Director dispose 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 apologies, Vlad. xList does not cause any memory leaks.

Since retVal is local to each method in which it create a new xList child object, Director disposes it once it is out of scope. Therefore, if the variable to which the returned list is assigned in a calling handler is also local to that handler, Director would similarly dispose the variable once it is out of scope, causing the xList child object reference count to become 0. So, no memory leak would occur.

Leave a Reply