WeBWorK Problems

using a list of lists

using a list of lists

by Dick Lane -
Number of replies: 3
I am writing several problems, each chooses a random picture and asks some questions about it.  My first version uses several lists with the following structure.

@FIG = ( "a.png" , "b.png" , "c.png" , "d.png" ) ;
@XLO = ( 0 , 1 , 2 , 3 ) ;
@YHI = ( 6 , 7 , 8 , 9 ) ;
$k = random( 0 , 3 , 1 ) ;
$art = $FIG[$k] ;
$x0  = $XLO[$k] ;
$y1  = $YHI[$k] ;

The problem text displays the picture with \{image($art)\} and uses the numeric variables in its prose.  (Hint and Solution also use the numeric variables.)

My second version uses a list of lists similar to the following.

@DATA = ( ["a.png",0,6] , ["b.png",1,7] , ["c.png",2,8] , ["d.png",3,9] ) ;
$k = random( 0 , 3 , 1 ) ;
$art = $DATA[$k][0] ;
$x0  = $DATA[$k][1] ;
$y1  = $DATA[$k][2] ;

I prefer the second structure because it keeps related data in distinct lists.

Now comes my question related to some failures for the second version.  My initial tries had CURV be the kth item of DATA and then selected its components:
@CURV = $DATA[$k] ;
$art = $CURV[0] ;
$x0  = $CURV[1] ;
$y1  = $CURV[2] ;

I tried swapping $ with @ (or @ for $) in defining CURV, but kept getting syntax errors.

Is it necessary to use the two-level indexing, i.e., $DATA[$k][13]  ?
In reply to Dick Lane

Re: using a list of lists

by Davide Cervone -

The issue you are struggling with is the difference between an array and reference to an array. Note that parentheses form an array (something you can put in a variable starting with @) while square brackets return a array reference, which is a scalar (it is essentially a pointer to the array). So when you do

    @DATA = ( ["a.png",0,6] , ["b.png",1,7] , ["c.png",2,8] , ["d.png",3,9] ) ;
you have created an array of references to arrays, and $DATA[$k] is one of those references, not the array itself. The assignment @CURVE = $DATA[$k] does not really do what you want, since you are assigning a scalar value (the array reference on the right) to the array on the left; you don't get the CONTENTS of the array pointed to by $DAT[$K], but only the reference to it.

There are two ways to dereference the array. If you have set $CURVE = $DATA[$k] (so that $CURVE is now the array reference for the k'th array), then you can do

    $art = $CURVE->[0];
    $x0 = $CURVE->[1];
    $y1 = $CURVE->[2];
sicne the -> operator dereferences a reference.

You can also access the array as a whole by dereferencing it with the @ operator. For example:

    @CURVE = @{$DATA[$k]};
    $art = $CURVE[0];
    $x0 = $CURVE[1];
    $y1 = $CURVE[2];
or more compactly:
    ($art,$x0,$y1) = @{$DATA[$k]};

I found references in Perl pretty confusing when I first encountered them, but they are really pretty useful. Hope this helped clarify things.


In reply to Davide Cervone

Re: using a list of lists

by Dick Lane -
Thanks, Davide, for help as I struggle with Perl syntax.

My original post omitted the comment that a more homogeneous data structure did not work --- even if I moved the string component elsewhere.

@DATA = ( (0,6) , (1,7) , (2,8) , (3,9) ) ;
$k = random( 0 , 3 , 1 ) ;
$x0  = $DATA[$k][1] ;
$y1  = $DATA[$k][2] ;

For my actual problems, only one item extracted from the randomly chosen list needs to be a Math Object --- the following remark ("However") anticipates a future situation.

I like the elegant multi-item assignment at the end of your comment
    ($art,$x0,$y1) = @{$DATA[$k]};
but suspect that a Math Objects version
    ($art,$x0,$y1) = Compute( "@{$DATA[$k]}" ) ;
would fail.  However (!),I doubt that we need something of the form
    ($art,$x0,$y1) = map( Compute , @{$DATA[$k]} ) ;
[In other words, the Math Objects paradigm is very useful for individual objects but I see little immediate need to generalize beyond that.]

ps:  thanks for your work on Math Objects !!

In reply to Dick Lane

Re: using a list of lists

by Davide Cervone -
You are welcome for MathObjects, I'm glad you are able to make use of them.

As for

    @DATA = ( (0,6) , (1,7) , (2,8) , (3,9) ) ;
you probably discovered that Perl "flattens" arrays of arrays, so this was effectively the same as
    @DATA = ( 0, 6, 1, 7, 2, 8, 3, 9 ) ;
so the array of references (using square brackets) is the only way to do what you want, here.

As for mapping compute over a list, you can use the MathObject List object to do that for you:

    ($art,$x0,$y1) = List(@{$DATA[$k]})->value;
This first creates a List out of the contents of the array pointed to by $DATA[$k}, which converts its entries into MathObjects automatically, and then "unpacks" the MathObject into a Perl list of its contents, which you then break up into the three variables on the left.

Or you could use Lists in your original DATA array:

    @DATA = ( List(0,6) , List(1,7) , List(2,8) , List(3,9) ) ;
    ($art,$x0,$y1) = $DATA[$k]->value;
As usual, there are several ways to do anything in Perl. :-) Davide