PREP 2014 Question Authoring - Archived

evaluating a function at multiple values in an array

evaluating a function at multiple values in an array

by Gavin LaRose -
Number of replies: 3

Here is another question that's come up a couple of times in various forms:

How does one evaluate a function at all the values in an array? For example, if one defines a function of x, and an array containing certain values of x. Does one define a function f as a variable ($f, say) or as an array (@f, say) that would contain the function evaluated?

To make this more concrete, let's suppose that we have an array of x-values, for example:

@x = ();
for ( my $x=0; $x<=10; $x+=0.5 ) {
    push( @x, $x );
}

(There are a number of subtleties here: (1) note that we use $x as our increment variable in the for loop, and increment it by 0.5 each time; (2) we use the Perl command push to push that value on the end of the array @x; and (3) Perl is quite happy to maintain a scalar variable $x and an array variable @x in the same context.)

Now, suppose that we're interested in the function f(x) = sin(x+2). We could define an array of values of this function, and we could define a MathObject for the function and evaluate it at the points in the array. For example, to define the array of function values, we could do the following:

@f = ();
foreach my $x ( @x ) {
    push( @f, sin($x**2) );
}

This uses Perl's sine function to generate the array. Alternately, we could do the same thing with a MathObject function:

$f = Compute( "sin(x^2)" );
@f = ();
foreach my $x ( @x ) {
    push( @f, $f->eval(x=>$x) );
}

Finally, suppose we want to display the values in a table in the text of the problem. The easiest way is probably to use our arrays to do this:

BEGIN_TEXT
\{begintable(22)\}
\{row( "\(x =\)", @x )\}
\{row( "\(f =\)", @f )\}
\{endtable()\}
END_TEXT

Gavin

In reply to Gavin LaRose

Re: evaluating a function at multiple values in an array

by Gavin LaRose -

A follow up to this one:

Davide points out that there are possible issues with using the real number $x in the loop, because of the accumulation of round-off error. This is an issue that shows up occasionally in WeBWorK problems as well. The underlying issue is that while real numbers like 0.1, 0.2, etc., are exact numbers, they aren't necessarily stored as exact values because the computer stores them in a binary register with finite length. The short take-away from this is that it's better to code my first example with an integer counter:

@x = ();
for ( my $i=0; $i<=20; $i++ ) {
    push( @x, $i*0.5 );
}

(Note also that we routinely use the increment operator ++ in these loops: writing $i++ says "add one to the value of $i and retain the resulting value.)

The slightly longer take-away is that if you find that your problem starts to show strange values like -1.11022302462516e-16, it's really saying that the value is (or should be) zero, but there are round-off issues at play (e-16 means "times 10^(-16)"). To avoid this or correct the problem, try to increase the amount of time you spend on exact calculations, or round values when you need to.

Gavin

In reply to Gavin LaRose

Re: evaluating a function at multiple values in an array

by Davide Cervone -
Just for fun, I'm going to show you another way to generate this array (there is always more than one way to do something in Perl).

First, there is an easy way to generate consecutive integers, using the .. operator. So

    @a = (1..10);
is the list
     @a = (1,2,3,4,5,6,7,8,9,10);
(You can also do this for letters, so ('a'..'d') is the same as ('a','b','c','d').)

There is also a command called map that takes a code block and an array and performs the code on each element of the array. Within the code, the variable $_ refers to the array element. So

    @x = map {.5*$_} (1..20);
produces the same @x as Gavin's code more efficiently (but harder to read for those not familiar with map).

There is also a looping command foreach that is similar to a for loop, but instead of giving an initial condition, a termination test, and an increment function, you give it an array and it runs the variable through that array. This is similar to map, but you get to specify the name of the loop variable. So, for example, you could do

    @x = ();
    foreach $i (1..20) {
      push(@x,.5*$i}
    }
This can sometimes make your loops easier to specify. Note that this works for any array, with any type of entry, so
    foreach $c ('a'..'d') {
      ... do something with $c ...
    }
would let you loop through the letters a through d. Similarly, if you have an array @a and want to do something to the entries in the array, use
    foreach $x (@a) {
      ... do something with $x ...
    }
In situations where you don't actually need to index into the array, this can be very helpful. Also, note that if you set the value of $x within the loop, that will change the value within @a as well, so this is a way of modifying the entries in @a one at a time. Also very useful at times.