0
$\begingroup$

I would like to write Mathematica code to read in a list of sequences, and print the sequence definition ($a_n$), along with the sequence evaluated at a few values of $n$.

sequence[a_, q_] := (Print["Exercise ", q];
  Clear[n];
  Print["\!\(\*SubscriptBox[\(a\), \(n\)]\)=", a[n]]; 
  For[n = 1, n <= 4, n++, Print[Subscript["a", n], "=", a[n]]];)
b = {2^n, Factorial[n]};
For[q = 1, q <= 2, q++,
 a[n_] := b[[q]]; sequence[a, q]]

The code works as intended (the first time it runs):

Exercise 1

$a_n=2^n$

$a_1=2$

$a_2=4$

$a_3=8$

$a_4=16$

Exercise 2

$a_n=n!$

$a_1=1$

$a_2=2$

$a_3=6$

$a_4=24$

If we run the code a second time, we get this output!:

Exercise 1

$a_n=32$

$a_1=32$

$a_2=32$

$a_3=32$

$a_4=32$

Exercise 2

$a_n=120$

$a_1=120$

$a_2=120$

$a_3=120$

$a_4=120$

Apparently, Mathematica now holds that $n=5$ after going through the loops. I've tried placing Clear[n] into various parts of the code, but it doesn't seem to work. Could I please have some help with

  1. showing how to improve my code so that it can run multiple times,
  2. understanding what exactly is going on.

Thank you!

$\endgroup$

1 Answer 1

2
$\begingroup$

To understand what's going on, consider what happens every time sequence is called. It runs a for loop, and that for loop sets the value of n to be 1 through 5 iteratively (n++ is evaluated before the check is evaluated, so the check fails after n gets set to 5). Once the for loop is done, n has the value 5. But of course, just defining sequence doesn't execute it, so when we execute this block of code the first time, b is evaluated with an n that is still undefined, which leads to everything working as expected. But if you re-execute that whole block, then b is now re-evaluated with n having value 5. So all subsequent b[[1]] and b[[2]] have constant values.

A better approach would be to apply these principles:

  • Separate computation from display
  • Avoid side effects--specifically don't use "global" variables
  • Make it data-driven

So, let's first define the data that will drive our process:

ExerciseFunctions = {2^# &, #! &, #^2 &}

You previously used b for this and used expressions with "exposed" n. You also hardcoded your loop to the size of b, which means if you want to change b, you'd need to change your loop. I've added a third function to demonstrate that we aren't dependent on having exactly two functions.

Now let's define a function to help with display--we basically repeat a formula/equation several times, so let's bundle that up as it's own function:

DisplayFormula[func_, val_] := TraditionalForm[Subscript["a", val] == func[val]]

I made some display choices, but you can obviously make whatever choices you want. Tweak it until you have something you like.

Now let's write a function to do the whole display for one exercise:

DisplayExercise[exerciseId_, func_, count_] :=
  Column[
    {Style[StringForm["Exercise ``", exerciseId], Bold],
     Style[DisplayFormula[func, HoldForm[n]], Blue],
     Splice[DisplayFormula[func, #] & /@ Range[count]]}]

We've used our helper function. We've parameterized the number of examples. We've abstracted the function/formula concept. We've also provided an actual output value with Column rather than rely on the side-effect of Print. You could test this out with something like

DisplayExercise[1, ExerciseFunctions[[1]], 4]

Play with the arguments to get a feel for what's going on.

Okay, now we put it all together to make a single display:

TableForm[
  MapIndexed[DisplayExercise[Sequence @@ #2, #1, 4] &, ExerciseFunctions], 
  TableDirections -> Row, 
  TableSpacing -> {5, 0}]

Again, I've made some display choices, but you can tweak to your heart's content.

$\endgroup$
8
  • $\begingroup$ Great answer! Thank you! Especially helpful are your list of principles. These are general practices to try to stick to, and apply beyond this particular task. $\endgroup$
    – mjw
    Commented Mar 13, 2023 at 18:55
  • $\begingroup$ I eventually saw how I was setting $n$ so that b became fixed at b[5]. Is the only way to use anonymous variables (#) to make sure they are local? There are constructs such as Module and Block, but this does not seem the place for them. $\endgroup$
    – mjw
    Commented Mar 13, 2023 at 18:56
  • $\begingroup$ I don't understand your comment. Maybe start a chat thread to discuss (as I suspect it would take us too far away from the original question). $\endgroup$
    – lericr
    Commented Mar 13, 2023 at 18:58
  • $\begingroup$ Was just commenting that ExerciseFunctions = {2^# &, #! &, #^2 &} seems to be the best way to handle this to ensure the 'variables' inside what I was calling b remain local. I actually thought of using different variable names, but it's more or less the same thing. I guess these functions are called 'pure functions'. I am not accustomed to programming with 'pure functions' but perhaps to become more proficient in $\textit{Mathematica,}$ I should practice writing my functions this way. $\endgroup$
    – mjw
    Commented Mar 13, 2023 at 19:04
  • $\begingroup$ Oh. Yeah, there really isn't a notion of truly local variables in Mathematica. Mathematica achieves "local" variables with a bit of naming magic. Block is actually the closest thing to a truly local scope, but there are caveats around that also. $\endgroup$
    – lericr
    Commented Mar 13, 2023 at 19:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.