Problem with function inside brackets. Bug?
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.
Here's the issue. In the second (non-working) code,
RandomInteger[{1, Length[a]}]
is evaluated twice, as we can see by Trace
ing the evaluation:
SeedRandom[2]
a = {{1}, {2}, {3}};
Trace[AppendTo[a[[RandomInteger[{1, Length[a]}]]], RandomInteger[9]], TraceInternal -> True]
{RandomInteger[9], 8}
AppendTo[a[[RandomInteger[{1, Length[a]}]]], 8]
{{a, {{1}, {2}, {3}}}, {{{{a, {{1}, {2}, {3}}}, Length[{{1}, {2}, {3}}], 3}, {1, 3}}, RandomInteger[{1, 3}], 3}, {{1}, {2}, {3}}[[3]], {3}}
a[[RandomInteger[{1, Length[a]}]]] = Append[{3}, 8]
{Append[{3}, 8], {3, 8}}
a[[RandomInteger[{1, Length[a]}]]] = {3, 8}
{{{{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.