6

Newbie question here. Consider the following two functions:

f1[n_] := (
  q = {0, 0};
  Do[q[[RandomInteger[{1, 2}]]] += 1, n];
  Return[q]
  )

f2[n_] := (
  q = {0, 0};
  Do[k = RandomInteger[{1, 2}]; q[[k]] += 1, n];
  Return[q]
  )

Both seem to be doing the same thing: create a list of zeros, increment a random element $n$ times and return the list. The difference is that the first version "inlines" RandomInteger call into the indexing, while the second defines an intermediate variable k.

The function f2 works as expected, while f1 does not. For example, f1 sometimes returns lists for which sum of elements is not equal to the input n, which seems very strange.

In[357]:= f1[10]

Out[357]= {6, 7}

Can someone point out why f1 and f2 are treated differently?

  • compare f1[2] // Trace and f2[2] // Trace – kglr Aug 02 '18 at 00:23
  • 2
    Related/duplicate: https://mathematica.stackexchange.com/questions/107619/is-dangerous-for-c-programmers. (The issue with AddTo is the same as with PreIncrement.) – Michael E2 Aug 02 '18 at 02:36

2 Answers2

15

Mathematica is an expression rewriting language. When it evaluates:

q[[RandomInteger[{1, 2}]]] += 1

It first rewrites it as:

q[[RandomInteger[{1, 2}]]] = q[[RandomInteger[{1, 2}]]] + 1

The result is that RandomInteger[{1, 2}] gets rewritten twice, possibly as different random integers.

John Doty
  • 13,712
  • 1
  • 22
  • 42
7

The main problem is that q[[k]] += 1 is equivalent to q[[k]] = q[[k]] + 1, and so RandomInteger gets called twice. When the random number is the same (which happens half the time), then the behavior is what you expect. When the random numbers are different, then the behavior is completely different. Here is a version of your code where I add some debugging (I also removed the completely superfluous Return statement):

f1[n_] := (
    q = {0,0};
    Do[q[[Echo@RandomInteger[{1,2}]]] += 1; Print[q], n];
    q
)

Here is a short example:

SeedRandom[3]
f1[3]

1

2

{0,1}

2

2

{0,2}

2

1

{3,2}

{3, 2}

Let's see what happens. In the first iteration, we have:

q[[2]] = q[[1]] + 1

and so the result is {0, 1}. The second iteration does:

q[[2]] = q[[2]] + 1

and so the result is {0, 2}. The final iteration does:

q[[1]] = q[[2]] + 1

and so we get {3, 2}.

Carl Woll
  • 130,679
  • 6
  • 243
  • 355