Version: 23.4

# Iterators

Iteration is the process of stepping through a sequence of values. An iterator is an entity that provides such a sequence of values, one at a time. In SenseTalk, lists, ranges and property lists can all be used as iterators. In addition, you can create your own custom iterator objects. There are several ways that you can step through each of the values supplied by an iterator and work with each one in turn.

## Iterating Using Repeat With Each​

When you need to work with each value supplied by an iterator and perform some actions with that value, use a repeat with each loop. For example, here's a loop that uses each value from a list:

``repeat with each color in ["red", "orange", "yellow", "green"]  put "I think " & color & " is a very pretty color."end repeat``

To iterate over all of the values in a range, use repeat with each just as you would with a list:

``repeat with each letter in "A" to "G"  repeat with each digit in 1 to 3    put letter & digit -- Puts "A1", "A2", "A3", "B1", "B2", ...  end repeatend repeat``

## Iterating Using Each Expressions​

When you need to process each value (or a selected set of values) from a list or other iterator to produce a list of result values, an each expression can be very effective. The result of an each expression is always a list. See Each Expressions.

## Iterating Using `NextValue`​

The `nextValue` function will return the next sequential value from an iterator. When the sequence is exhausted and no more values are available from that iterator, the special constant value `end` is returned. The `currentValue` function will return the current value again without advancing the iteration.

``put 100 to 200 by 50 into rangeput range's nextValue --> 100put range's nextValue --> 150put range's currentValue --> 150put range's nextValue --> 200put range's nextValue --> ⓔ ⓝ ⓓ``

## Modifying Iteration Using `CurrentIndex`​

Both lists and ranges have a `currentIndex` property. When a list or range is first created, the value of the `currentIndex` is zero. It is also reset to zero at the start of a repeat with each loop or an each expression for that iterator.

During iteration, the `currentIndex` property is incremented each time a value is retrieved from the iterator. The iterator's `currentIndex` can be accessed to determine the item number of the current value in the source list or range.

``set foo to [a,b,c,d,e]put foo's currentIndex & each item of foo --> [1a,2b,3c,4d,5e]``

What's more, the `currentIndex` can be changed to alter the course of iteration. For example, this repeat loop will only display the odd numbers in the range:

``set myRange to 1..100repeat with each item of myRange  put it  add 1 to myRange.currentIndex -- skips the next itemend repeat``

## Changing a List During Iteration​

While iterating over the items in a list using `repeat with each`, an `each` expression, or the `nextValue` function, SenseTalk uses the `currentIndex` property of the list to keep track of the iteration. Whenever items are inserted, deleted, or replaced in a list, the `currentIndex` property is adjusted appropriately to reflect the change. So it is possible to add, remove, or replace items in a list during iteration without causing problems.

## Custom Iterators​

Lists and ranges are the most common sources used as iterators. You can also create a custom iterator by making a script or object with a `nextValue` handler that returns the next value in a sequence. The values returned can be anything, and the implementation may be as simple as returning the `nextValue` from a list that is a property of the object, or may be the next result calculated from some internal state maintained by the object. Iterators of this type are sometimes called "generators" because they generate values one at a time as needed.

To be used as an iterator, there are a few rules an object must follow. First, it must have an `objectType` property with a value of "iterator". This tells SenseTalk to call the object's `nextValue` handler to obtain each value for a repeat with each loop or an each expression. Second, of course, it must have a `nextValue` handler to supply each value in the sequence. The `nextValue` handler should return the value `end` when the end of the sequence has been reached and no more values are available. If it doesn't do this, a repeat loop should include an exit repeat statement to break out of the loop at some point or it will continue forever. An `each` expression should not be used with an iterator that doesn't eventually return `end`.

In addition to a `nextValue` handler, a custom iterator may optionally also have a `startIteration` handler. If present, this handler will be called before `nextValue` is called by either a `repeat with each` loop or an `each` expression to allow the iterator to set up any initial conditions, or to reset its state following any earlier iteration.

Here is an example of a custom iterator that generates values from the Fibonacci sequence, in which each value is the sum of the two preceding values (note that although the Fibonacci sequence is infinite, this iterator only returns the values up to ten thousand, allowing it to be used with an each expression):

``set FibonacciGen to {a:0, b:1, objectType:"iterator", script: {{to handle nextValue  if my b is more than ten thousand then return end  set [my a, my b] to [my b, my a + my b]  return my aend nextValue}} }set fib to a new FibonacciGenrepeat 10  insert fib's nextValue into firstTenend repeatput firstTen --> [1,1,2,3,5,8,13,21,34,55]put fib.nextValue --> 89put each item of fib --> [144,233,377,610,987,1597,2584,4181,6765]``

## Property Lists as Iterators​

You also can use a property list as an iterator. When iterating in a repeat loop, the property list supplies each key and its value as a list of two items. An object that defines its own iteration (objects with an objectType of "iterator") use their own iteration; objects with a different objectType iterate over the [key,value] pairs for their properties.

``set people to {Marty:12, June:16, Aaron:6, Grace:11}repeat with each [name,age] in people  put !"[[name]] is [[age]] years old"end repeat``

When iterating over the properties of a property list outside of a repeat loop, `nextKey` can be used to retrieve the next key, `nextValue` to retrieve the next value, or `nextKeyValue` to retrieve the next [key,value] pair. `CurrentKey`, `currentValue`, and `currentKeyValue` can be used to obtain the corresponding value for the current property without advancing the iteration.

``set people to {Marty:12, June:16, Aaron:6, Grace:11}put people's nextKey --> "Aaron" (iteration starts with the first key alphabetically)put people's nextValue --> "11" (advances to next key and gets its value)put people's currentKey --> "Grace" (gets the current key without advancing)put people's nextKeyValue --> [June, 16] (advances and returns the key and its value)``

## Passing an Iterator As a Parameter​

An iterator can be passed as a parameter to another script or handler. It is passed with its current state intact, including the value of the `currentIndex` property of a list or range, and any properties of a custom iterator. So if the local script has been using values one at a time from the iterator (by using `nextValue`), the called script can continue using later values in the sequence.

Keep in mind that SenseTalk ordinarily makes copies of any parameter values, so the called script will receive its own copy of the iterator. To share the iterator so that both the local and the called scripts will be using a single sequence of values from the iterator, pass the iterator by reference. For example:

``set takeANumber to 1..20put takeANumber's nextValue --> 1put takeANumber's nextValue --> 2scriptThatTakesTwoNumbers @takeANumber // takes 3 and 4put takeANumber's nextValue --> 5``

Without the "@" to pass takeANumber by reference, the final call to takeANumber's nextValue would return 3 instead of 5. By passing a reference here, both scripts share the same sequence of values rather than splitting off a copy to the called script.

The `is an iterator` operator can be used to test whether a value (such as a parameter that was received) can be iterated:

``if param(1) is not an iterator then throw "An Iterator Is Required"``

## Restarting Iteration​

To restart iteration over a range or list beginning with the first value again, set its `currentIndex` to zero. The next time you access its `nextValue`, the first value in the range or list will be returned.

``set the currentIndex of sequence to zero -- reset iteration``

Some custom iterators may provide a mechanism for starting iteration again, and some may not. A custom iterator that is designed to be reusable by `repeat with each` loops and `each` expressions should implement a `startIteration` handler that can be called to reset it. Other custom iterators may implement a `currentIndex` property that can be reset just as you would for a list or range. But neither of those is required in a basic iterator.

## Assigned List Values​

Lists in SenseTalk grow dynamically as items are inserted or values are assigned to items beyond the previous extent of the list. This allows values to be assigned to items that may be widely dispersed in a list, as in this example:

``set list to["a","b"]put "g" into item 7 of listput "z" into item 26 of listput list --Puts [a,b,,,,,g,,,,,,,,,,,,,,,,,,,z]``

The list in this example has 26 items, although only 4 of them have assigned values. The other items are empty. Iterating over the items of this list will access all 26 items. In some cases it may be useful to be able to access only those list items that have explicitly been assigned a value. The `nextAssignedValue` function does this. It works like the `nextValue` function, but instead of simply incrementing the `currentIndex` property by 1 and returning the value at that index, it advances the `currentIndex` to the next item in the list that has an assigned value and returns that value. So, continuing the example above:

``put list's nextAssignedValue --> aput list's currentIndex --> 1put list's nextAssignedValue --> bput list's currentIndex --> 2put list's nextAssignedValue --> gput list's currentIndex --> 7put list's nextAssignedValue --> zput list's currentIndex --> 26put list's nextAssignedValue --> ⓔ ⓝ ⓓ``

Since both the `nextValue` and `nextAssignedValue` functions advance the `currentIndex` property of the list, you can mix calls to the two functions to obtain either the next sequential value of the list (whether assigned or not) or the next assigned value of the list after the current index as needed.

note

An empty value is not the same as an unassigned value. If an empty value is stored into an item of a list, that empty value will be returned by the `nextAssignedValue` function.