In the course of implementing the "Variable Elimination" algorithm for a Bayes' Nets program, I encountered an unexpected bug that was the result of an iterative map transformation of a sequence of objects.
For simplicity's sake, I'll use an analogous piece of code here:
>>> nums = [1, 2, 3]
>>> for x in [4, 5, 6]:
... # Uses n if x is odd, uses (n + 10) if x is even
... nums = map(
... lambda n: n if x % 2 else n + 10,
... nums,
... )
...
>>> list(nums)
[31, 32, 33]
This is definitely the wrong result. Since [4, 5, 6] contains two even numbers, 10
should be added to each element at most twice. I was getting unexpected behaviour with this in the VE algorithm as well, so I modified it to convert the map
iterator to a list
after each iteration.
>>> nums = [1, 2, 3]
>>> for x in [4, 5, 6]:
... # Uses n if x is odd, uses (n + 10) if x is even
... nums = map(
... lambda n: n if x % 2 else n + 10,
... nums,
... )
... nums = list(nums)
...
>>> list(nums)
[21, 22, 23]
From my understanding of iterables, this modification shouldn't change anything, but it does. Clearly, the n + 10
transform for the not x % 2
case is applied one fewer times in the list
-ed version.
My Bayes Nets program worked as well after finding this bug, but I'm looking for an explanation as to why it occurred.
This behaviour is 3.x-specific, and a special case of What do lambda function closures capture?. Because 3.x's map
is lazy, the first version of the code won't evaluate the lambdas until list
is used outside the loop, at which point the final value of x
is used each time. In 2.x, map
simply creates a list, and list(nums)
is redundant.