8
$\begingroup$

This question already has an answer here:

This code adds random digits to lists, and it works fine:

a = {{1}, {2}, {3}};
Do[
  j = RandomInteger[{1, Length[a]}];
  AppendTo[a[[j]], RandomInteger[9]];
  Print[a], {i, 5}];

(* {{1,7},{2},{3}}
   {{1,7},{2,2},{3}}
   {{1,7},{2,2},{3,9}}
   {{1,7},{2,2},{3,9,1}}
   {{1,7,2},{2,2},{3,9,1}}  *)

But if I replace the 'j' inside the [[]] with the definition j in the previous line, everything goes haywire:

a = {{1}, {2}, {3}};
Do[
  AppendTo[a[[RandomInteger[{1, Length[a]}]]], RandomInteger[9]];
  Print[a], {i, 5}];

(* {{1},{1,7},{3}}
   {{1},{1,7},{1,4}}
   {{1,7},{1,7},{1,4}}
   {{1,7},{1,7},{1,7,9}}
   {{1,7,9},{1,7},{1,7,9}} *)

Is this a bug or something I'm doing wrong?

$\endgroup$

marked as duplicate by Szabolcs, Feyre, user31159, xzczd, Sjoerd C. de Vries Nov 12 '16 at 16:06

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • $\begingroup$ What exactly do you mean by "everything goes haywire"? It's hard to tell what's wrong, given the random numbers in the code (and therefore the outputs that are never the same). Can you elaborate? $\endgroup$ – march Nov 10 '16 at 21:09
  • $\begingroup$ @march Initially a = {{1}, {2}, {3}}; after the first step of appending it's {{1},{1,7},{3}} - you append 7 to {2} and get {1,7}. $\endgroup$ – corey979 Nov 10 '16 at 21:13
  • $\begingroup$ I believe I see the problem. RandomInteger[{1, Length[a]}] is evaluated twice in the second case. I will write a quick answer. $\endgroup$ – march Nov 10 '16 at 21:21
  • 1
    $\begingroup$ Answers by @Szabolcs and @march dispel the mystery. Note also that this case is a good use case for BlockRandom; that is, as an alternative to your first approach you could also use Do[AppendTo[a[[BlockRandom[RandomInteger[{1, Length[a]}]]]], RandomInteger[9]]; Print[a], {i, 5}]; $\endgroup$ – kglr Nov 10 '16 at 22:11
  • 5
    $\begingroup$ Related, possible duplicate: (107619) $\endgroup$ – Mr.Wizard Nov 12 '16 at 8:42
10
$\begingroup$

Here's the issue. In the second (non-working) code,

RandomInteger[{1, Length[a]}]

is evaluated twice, as we can see by Traceing the evaluation:

SeedRandom[2]
a = {{1}, {2}, {3}};
Trace[AppendTo[a[[RandomInteger[{1, Length[a]}]]], RandomInteger[9]], TraceInternal -> True]
  1. {RandomInteger[9], 8}
  2. AppendTo[a[[RandomInteger[{1, Length[a]}]]], 8]
  3. {{a, {{1}, {2}, {3}}}, {{{{a, {{1}, {2}, {3}}}, Length[{{1}, {2}, {3}}], 3}, {1, 3}}, RandomInteger[{1, 3}], 3}, {{1}, {2}, {3}}[[3]], {3}}
  4. a[[RandomInteger[{1, Length[a]}]]] = Append[{3}, 8]
  5. {Append[{3}, 8], {3, 8}}
  6. a[[RandomInteger[{1, Length[a]}]]] = {3, 8}
  7. {{{{a, {{1}, {2}, {3}}}, Length[{{1}, {2}, {3}}], 3}, {1, 3}}, RandomInteger[{1, 3}], 2}

We can see in Line 1 that RandomInteger[9] evaluates to 8, so we will be appending 8 to one of the lists. In Line 3, RandomInteger[{1, 3}] evaluates to 3, so we're going to append to a[[3]]. This happens on Line 5, where 8 is appended to {3} to make {3, 8}.

Now, the kicker: In Line 7, RandomInteger[{1, Length[a]}] is evaluated again, so it evaluates to a different number. In this case, it evaluates to 2, so instead of replacing a[[3]], we are replacing a[[2]] with {3, 8}. Hence the output after this evaluation is

a
(* {{1}, {3, 8}, {3}} *)

Now, the fix here is to do things the way you're doing it in the first code. I would probably wrap the entire thing in a Module with j as a local variable, but it's the same process.

$\endgroup$
  • $\begingroup$ Ooooooh... Interesting. So not a bug, but that second evaluation sure seems like an iffy design feature. Thank you for your insight and the time you put into writing this up. This was instructive and helpful. $\endgroup$ – Jerry Guern Nov 10 '16 at 22:06
  • 1
    $\begingroup$ @JerryGuern. Well, I think Szabolcs has a pretty good explanation of why the double evaluation occurs, in terms of what AppendTo is likely literally doing behind the scenes. I think that that particular "design choice" for AppendTo makes some amount of sense. $\endgroup$ – march Nov 10 '16 at 22:13
11
$\begingroup$

No, not a bug.

Let's think about how AppendTo may be implemented (even though the actual implementation isn't inspectable).

SetAttributes[appendTo, HoldFirst]
appendTo[a_, val_] := (a = Append[a, val])

What happens if we evaluate the following?

appendTo[ a[[ RandomInteger[{1,3}] ]],  x ]

It simply does this:

a[[ RandomInteger[{1,3}] ]] = Append[a[[ RandomInteger[{1,3}] ]], x]

That's because RandomInteger[...] didn't get evaluated before substitution due to the HoldFirst. Now we have two of them. And they may evaluate to different values. So we may get things like

a[[1]] = Append[ a[[3]], x ]

if the first random number we get is 1 and the second is 3.

I hope this makes it clear. I have a vague feeling that the same has been asked before in a different form.

$\endgroup$
  • $\begingroup$ Would you kindly review the link I just added in the comments below the question, and vote to close this question as a duplicate if you feel that is appropriate? $\endgroup$ – Mr.Wizard Nov 12 '16 at 8:43
  • $\begingroup$ @Mr.Wizard I knew there had to be a duplicate! $\endgroup$ – Szabolcs Nov 12 '16 at 8:54

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