Tinderbox Forum

Testing for postion in a list

Sometimes when working with list items using .each() you need to do text (or code text) formatting like inserting a comma between items. Looping makes this difficult as you don’t want the same ‘fill’ applied to every items, but rather all except the last.

If you have a List or set and simply want to reformat it to string using a delimiter such as comma-and-a-space, then List.format() does the job. But…

If you need to act on the source list or perhaps construct new per-item values, then .format() can’t help. So, what to do? Here list.first and list.last are you friends.

In the demo below, I will not do anything complex with the list items but I will turn the list into a textual list, placed into $Text, but with:

  • add text before the first item
  • a comma+space after each list item except the last
  • add text after the last item.

As shown here:

Here I use a stamp but the same logic can be used elsewhere. This is the code:

$MyList.each(anItem){
   if(anItem==$MyList.first){
      $Text+="Starting at list item 1: ";
   };
   $Text+=anItem;
   if(anItem!=$MyList.last){
      $Text+=", ";
   }else{
      $Text+=". Done!";
   };
};

I could have split the last two tasks into two discrete if() tests, but as the ‘filler’ is added to all except the last list item, the else branch of the text must match only the last item. So, we can make the test more compact.

So, if you were wondering how to test for the first or last item in a list, you know know.

Here is the TBX document used to make the demo (minor changes from grab above, e.g. stamp name): test-list-position.tbx (77.2 KB)

Select the note ‘xx’ and apply the ‘Process list’ stamp to it. A stamp is also supplied to clear existing $Text.

I’ve made a note to self for aTbRef to better cross-reference documentation of .each() with .first and .last,

Perhaps the most common situation is building a list of elements that are separated by punctuation: Winken, Blinken, Nod. You want a comma to separate elements, but do NOT want a comma after the final element or before the first element.

A good way to to do this is:

$MyString="";
1...3.each(n) {
    var:string name = lookup_character_name(n);
    if (!$MyString.empty()) {
        $MyString += ",";
        }
    $MyString += name;
}

OK, making that into a usable stamp (including trying a function witihn a stamp for first time!):

$MyString="";
// 1...3 is a shorthand for a list "1;2;3".
1...3.each(n){
   var:string name=lookup_character_name(n);
   if(!$MyString.empty()){
      $MyString += ",";
   }
   $MyString += name;
};

function lookup_character_name(n){
   var:dictionary vNames;
   vNames = dictionary("1:winken;2:blinken;3:nod");
   return vNames[n];
};

[N.B.: for more on the ‘…’ operator see range.]

The stamp’s result is that $MyString in the stamped note has the value “winken,blinken,nod”.

The difference here, and not perhaps immediately clear to those unused to code, is the comma is added to $MyString before the list value and not after. In my original example the comma is added after the item. Although closer to how we write—i.e. "do I now need to add a comma because there are more items to follow?, this later version asks “have we already started making a list?”. So the example code in my first post’s example handling the comma:

if(anItem!=$MyList.last){

in in this case replaced by the test:

if(!$MyString.empty()){

This goes to show there is usually no single ‘right’ way to do things in code.

2 Likes