BookmarkSubscribeRSS Feed
Craige_Hales

Staff

Joined:

Mar 21, 2013

Choose Language Hide Translation Bar

Loops

Problem

You need to execute some JSL more than once.

Solution

Use a for(...) or while(...) or one of the more exotic variations.


The for loop is the most common way to repeat some statements.
Cars = {"Ford", "Chevy", "Tesla"}; // a list
nCars = N Items( Cars );
// notice loop starts at one and <= counts to the end
For( iCar = 1, iCar <= nCars, iCar += 1,
car = Cars[iCar]; // JSL lists are 1-based
Write( "\!n", car, " has four wheels." );
);
Ford has four wheels.
Chevy has four wheels.
Tesla has four wheels.
 
The for loop with real numbers might not do what you expect; numbers like 0.1 are repeating binary fractions that are either slightly too small or slightly too big when truncated to a 64-bit double precision number. In this example, the final value that is very close to 3.0 is actually a bit large.
for(i=2, i<= 3, i+=.1, print(i);); // 2, 2.1, ... 2.9 (but not 3.0)
write("\!nFinally ",i-3); // 8.88178419700125e-16
2
2.1
2.2
2.3
2.4
2.5
2.6
2.7
2.8
2.9
Finally 8.88178419700125e-16
 
If you know you need an integer number of steps, use integers for the for statement and calculate the floating point value from them.
for(i=0,i<=10,i++,
	write(" ", 2 + i/10);
	// 2 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3
);
 2 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3
 
The while loop is simpler, but maybe less common, than the for loop. This example would be easier to read with a for loop; the next example is a better use of while.
authors = {"Margaret Mitchell", "J.D. Salinger", "Emily Brontë"};
iAuthor = 1;
While( iAuthor <= N Items( authors ),
    Write( "\!n", authors[iAuthor], " wrote one novel" );
    iAuthor += 1;
);
Margaret Mitchell wrote one novel
J.D. Salinger wrote one novel
Emily Brontë wrote one novel
 
The while loop can work with a list by removing elements until the list is empty.
animals = {"octopus", "spider", "scorpion"};
While( N Items( animals ) > 0, 
    // RemoveFrom modifies the animals list
    // and returns a list of (in this example)
    // one item
    animal = Remove From( animals, 1 ); 
	// subscript the list to get just the animal
    // name, without the curly braces for the list
    Write( "\!n", animal[1], " has eight legs" );
);
octopus has eight legs
spider has eight legs
scorpion has eight legs
 
For and While loops can use the break() statement to break out of the loop early and the continue() statement to skip the remaining statements and continue at the top. Continue() works best with a for loop that has the increment at the top.
For( i = 1, i <= 10, i++,
    If( i == 3, Continue() );
    If( i == 7, Break() );
    Write( " ", i ); // 1 2 4 5 6
);
1 2 4 5 6
 
ForEachRow works with a data table and is a shortcut that can be done other ways. The main problem with ForEachRow is that it works with the current data table. Because of this I don't recommend using it if there is more than one table open, and I definitely don't recommend trying to nest ForEachRow loops because the inner loop and the outer loop will confuse each other about the current row.
dt = Open( "$sample_data/big class.jmp" );
For Each Row(
    If( Starts With( dt:name, "A" ),
        Write( " ", dt:name ); // ALICE ALFRED AMY
    )
);
 ALICE ALFRED AMY
 
The formula column loop is done internally in JMP. When you create a formula column in a data table, JMP evaluates the formula for each row. Later, when you change a value in another column that the formula depends on, JMP will re-evaluate rows that depend on the changed value. JMP does these evaluations during idle time. If you want JMP to do them sooner and faster, send the data table a <<RunFormulas message.
dt = Open( "$sample_data/big class.jmp" );
hw = dt << New Column( "HW_Ratio", formula( height / weight ) );
dt << RunFormulas; // all 40 values are computed
dt << Sort(by (HW_Ratio), replaceTable); // tall enough for my weight?
Capture.PNGTable with formula column
Many functions take a matrix argument and apply the function to every element. A matrix of results is returned.
s = Round(Sqrt( [1, 2, 3, 4, 5, 6, 7, 8, 9] ), 2);
write(s); // [1, 1.41, 1.73, 2, 2.24, 2.45, 2.65, 2.83, 3]
The matrix function Loc loops through a matrix and returns the locations of elements that Loc identifies. This is much faster than a for loop.
s = Round(Sqrt( [1, 2, 3, 4, 5, 6, 7, 8, 9] ), 2);
write(Loc(s > 2)); // [5, 6, 7, 8, 9] (element 4 is equal)
Using matrix subscripting can reorganize matrices much faster than for loops.
s = Round( Sqrt( [1, 2, 3, 4, 5, 6, 7, 8, 9] ), 2 );
s[Loc( s > 2 )] = -1;
Write( s ); // [1, 1.41, 1.73, 2, -1, -1, -1, -1, -1] 
 
Everything beyond this point is an unusual usage pattern that should be avoided unless you've got a really good reason. It will be hard to understand later and very hard for someone else to maintain.
 
The J function creates a matrix and initializes it with a value. If that value is an expression, it is evaluated for every element. In this example, the third argument to J() is a pair of statements in parens; the first statement increments a counter and the second statement calculates a value. The last value calculated is the value of the expression made of several statements.
idx = 0; // not a recommended pattern!
mat = J( 2, 3, (idx += 1 ; Sqrt( idx )) );
Write( Round( mat, 1 ) ); // [1 1.4 1.7, 2 2.2 2.4]
The parallel assign function uses multiple threads to fill in an array. Unlike the J function 3rd argument, you can't depend on any particular ordering of the elements being calculated. Parallel assign tries to keep threads isolated from each other and really doesn't want its expression accessing global data.
bigMat = J( 500, 500, . );
// these are global x and y values that are only copied into each thread, below.
x = 2;
yy = 3;
// x=x and y=yy is copying global values to local values of the same/similar name.
// bigMat[rr, cc] determines the array that is being filled in and the 
// row and column names you'll use in the expression after the =.
// the if statement is an example expression, not terribly interesting but
// typical of the kind of thing parallel assign does well.
rc = Parallel Assign( {x = x, y = yy}, bigMat[rr, cc] = If( x == 2, rr * cc, y ) );
// Max dose NOT return an array, it returns the maximum value in the array.
// dividing by max scales the values from 0 to 1
bigMat = bigMat / Max( bigMat );
// new image expects values from 0 to 1
New Window( "demo", New Image( "rgb", {bigMat, 1 - bigMat, bigMat} ) );
Capture.PNG250,000 if statements evaluated
The pattern matcher can loop over a string and run your JSL as the match progresses.
text = // use the \[ and ]\ to escape the quotes in this text
"\[John Bartlett, who ran the University Book Store in Cambridge,
Massachusetts, was frequently asked for information on quotations and
he began a commonplace book of them for reference. In 1855, he
privately printed his compilation as A Collection of Familiar Quotations.
This first edition contained 258 pages of quotations by 169 authors,
chiefly the Bible, William Shakespeare, and the great English poets.
Bartlett wrote in the fourth edition that "it is not easy to determine in 
all cases the degree of familiarity that may belong to phrases and 
sentences which present themselves for admission; for what is familiar
to one class of readers may be quite new to another."  - Wikipedia]\";

words = [=> 0]; // an associative array that defaults  unknown keys to a value of 0

// this match repeats a pair of alternatives: a run of a-z or a run of NOT a-z.
// the runs of a-z are stored in an associative array
rc = Pat Match(
    text,
    Pat Repeat( // the loop starts here
        (Pat Regex( "[a-zA-Z]+" ) >> word
        +Pat Test( // insert word into words assoc array
            words[Lowercase( word )] += 1; // this JSL is evaluated for each word
            1; // the test must succeed for the pattern to continue
        )) | Pat Regex( "[^a-zA-Z]+" ) // skip non-words
    )
);

// make a table, load it from the words assoc array keys and values
dt = New Table( "word count",
    New Column( "word", character, Set Values( words << getkeys ) ),
    New Column( "count", Set Values( words << getvalues ) )
);

// sort by the count column
dt << sort( by( count ), order( descending ), replaceTable );
Capture.PNGTable of word counts
The scheduler can run your code again and again, every few seconds.
n = 0; // counter, just for this demo

dowork = Function( {},
    Print( "hi", n );
    n += 1;
    If( n < 10,
        Schedule( .5, dowork() );// half second from now, run me again
    , // else
        Schedule( 0, 0 ) << close; // no delay, no code
    );
);

dowork(); // start the loop
Recursion can walk through a tree.
// this tree walker looks for button boxes in a display
// box tree and prints the button name

treeWalker = Function( {box}, // parameter
    {child = box << child}, // local variables
    While( !Is Empty( child ),
        If( child << classname == "ButtonBox",
            Show( child << Get Button Name )
        );
        treeWalker( child ); // you could also use recurse(child)
        child = child << sib;
    )
);

x = V List Box(
    H List Box( Button Box( "x" ), Button Box( "z" ) ),
    Border Box( Button Box( "w" ) )
);

treeWalker( x ); // shows x, z, and w

child << Get Button Name = "x";
child << Get Button Name = "z";
child << Get Button Name = "w";

 

Discussion

Nesting loops can get quite slow. If you write an outer loop that examines every row in a table, and an inner loop that also examines every row in the table (perhaps looking for matches), JSL for loops will get painfully slow when the table has a 100,000 rows: the JSL in the inner loop will run 100,000 * 100,000 (10,000,000,000) times. If you can move this code into a matrix function, it will be faster, but you might need a different approach, sorting the table perhaps.

I've probably overlooked something.

See Also

http://www.jmp.com/support/help/Iterate.shtml

http://www.jmp.com/support/help/Conditional_and_Logical_Functions.shtml

and many other articles within the community

Article Labels
Article Tags
Contributors