-2

There I was, all this time, under the impression that a, b, c = c, a, b is the same as a, c, b = c, b, a... I thought this was a way to assign variables at the same time so you don't have to create a bunch of temp vars. But apparently they're different because one breaks my code.

Here's my original/working implementation:

class Node:
    def __init__(self, v = None, next = None):
        self.v = v
        self.next = next
    def __repr__(self):
        return "Node(v=%r, nextV=%r)" % (self.v, self.next.v if self.next else None)

a = Node(1)
b = Node(2)
a.next = b

def flip(nodeA, nodeB):
    nodeB, nodeA.next, nodeA = nodeA, nodeB, nodeA.next
    return (nodeA, nodeB)

a, b = flip(a, b)
print "A=%r; B=%r" % (a, b)

Its correct/intended behavior is to swap the two nodes in a linked list, as shown by the output below:

A=Node(v=2, nextV=None); B=Node(v=1, nextV=2)

However, if I reorder the flip function like so:

def flip(nodeA, nodeB):
    nodeB, nodeA, nodeA.next = nodeA, nodeA.next, nodeB
    return (nodeA, nodeB)

...that output is broken:

A=Node(v=2, nextV=2); B=Node(v=1, nextV=2)

Node A ended up with a pointer back to itself (its nextV and v are identical), so an attempt to follow this tree would recurse forever.

Why aren't these results identical? Shouldn't tuple unpacking behave as if all assignments happened simultaneously?

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
Raksha
  • 1,572
  • 2
  • 28
  • 53
  • @CharlesDuffy, there, happy? – Raksha Sep 23 '18 at 00:49
  • I gave an answer about how python assigns things from left to right (not simultaneously), but seeing the code I'm not sure the issue is with the assignment rather the implication of it, which is it's casing an infinite recursive loop – yuvi Sep 23 '18 at 00:57
  • 1
    @Raksha Please be respectful. – jhpratt Sep 23 '18 at 00:57
  • @CharlesDuffy ...? ... the whole code is literally in the question – Raksha Sep 23 '18 at 01:01
  • 3
    @Raksha As I said, keep it respectful. Calling people trolls is not going to help you get an answer. A [mcve] is necessary. I will attempt to answer your question after you provide one. I will also retract my downvote and close vote. – jhpratt Sep 23 '18 at 01:05
  • @Raksha your code is very hard to read. Also, I've been trying to help you and you're ignoring that. Again, assignment in python is from left to right, not "simultanious" (so the two examples you give are actually very different when executed). Why that's causing an infinite recursive loop? Well, I'm trying to understand what the hell your code is supposed to do but so many variables have completely innocuous names that it's very hard to follow. – yuvi Sep 23 '18 at 01:06
  • @jhpratt That's as minimal as I can make it... Sorry I'm not a Python guru – Raksha Sep 23 '18 at 01:06
  • @Raksha As @yuvi said, what do the variables mean then? Having names like `a`, `b`, `c`, `d`, `e`, `k`, `l`, and `v` don't help any. – jhpratt Sep 23 '18 at 01:08
  • @yuvi it doesn't assign left to right, because then you couldn't have sorting like `a[i], a[j] = a[j], a[i]`, because once you assign one variable, its initial value would be gone – Raksha Sep 23 '18 at 01:08
  • @Raksha, what does the flipK function do? Does it reverse the linked list? – Deepak Saini Sep 23 '18 at 01:20
  • @Raksha *"If the target list is a comma-separated list of targets: The object must be a sequence with the same number of items as the there are targets in the target list, and the items are assigned, **from left to right**, to the corresponding targets"* from the [python manual](https://docs.python.org/2.0/ref/assignment.html). It does not do it simultaneously. – yuvi Sep 23 '18 at 01:24
  • @Raksha as for why `a[i], a[j] = a[j], a[i]` works is because python evaluates the right side first, stores the values and then assigns them **from left to right**. You can read more [here](https://stackoverflow.com/questions/21047524/how-does-swapping-of-members-in-the-python-tuples-a-b-b-a-work-internally) – yuvi Sep 23 '18 at 01:31
  • 1
    BTW, would you be offended if I tried to edit the question to have a proper MCVE? – Charles Duffy Sep 23 '18 at 01:40
  • @CharlesDuffy not at all – Raksha Sep 23 '18 at 01:40
  • Do the changes I made make sense to you? Basically, I aimed to simplify the code to only revolve around the specific place where it breaks; generated clear output, showing both correct and incorrect states; and selected readable variable names. – Charles Duffy Sep 23 '18 at 02:10
  • @jhpratt, ...btw, do you see any further room for enhancements as to the question's clarity? (And if not, if you're not already on it, would you be willing to contribute to the reopen vote? – Charles Duffy Sep 23 '18 at 02:10
  • @CharlesDuffy LGTM. Already cast a reopen vote ☺ – jhpratt Sep 23 '18 at 02:13

1 Answers1

2

Because one of the items you're modifying is an attribute of another, these aren't independent of each other -- a serialization order is needed to determine what the operation will do, and that operation is left-to-right.

Let's look at how that plays out, by writing this code as it would be with temporary variables.


Given the following shared prelude:

old_nodeA      = nodeA
old_nodeB      = nodeB
old_nodeA_next = nodeA.next

The working code is akin to the following:

# nodeB, nodeA.next, nodeA = nodeA, nodeB, nodeA.next

nodeB      = old_nodeA
nodeA.next = old_nodeB       # nodeA is still the same as old_nodeA here
nodeA      = old_nodeA_next

Here's the broken one:

# nodeB, nodeA, nodeA.next = nodeA, nodeA.next, nodeB

nodeB      = old_nodeA
nodeA      = old_nodeA_next
nodeA.next = old_nodeB       # we're changing old_nodeA_next.next, not old_nodeA.next

The difference is that nodeA.next refers to the next attribute of a different nodeA between the two cases.


Let's look at how this works out at runtime in the case where everything works right, with some pseudocode showing object IDs so you can distinguish between objects being mutated in-place vs having references changed:

# Working implementation
###############################################################
# id(nodeA) # id(nodeB) # AAA.v # AAA.next # BBB.v # BBB.next # 
###############################################################
# AAA       # BBB       # 1     # BBB      # 2     # None     # Starting condition
# AAA       # AAA       # 1     # BBB      # 2     # None     # nodeB = old_nodeA
# AAA       # AAA       # 1     # BBB      # 2     # None     # nodeA.next = old_nodeB
# BBB       # AAA       # 1     # BBB      # 2     # None     # nodeA = old_nodeA_next

In the working scenario, we switched A and B's names to each refer to the opposite node; nothing else changed.

By contrast:

# Broken implementation
###############################################################
# id(nodeA) # id(nodeB) # AAA.v # AAA.next # BBB.v # BBB.next # 
###############################################################
# AAA       # BBB       # 1     # BBB      # 2     # None     # Starting condition
# AAA       # AAA       # 1     # BBB      # 2     # None     # nodeB = old_nodeA
# BBB       # AAA       # 1     # BBB      # 2     # None     # nodeA = old_nodeA_next
# BBB       # AAA       # 1     # BBB      # 2     # BBB      # nodeA.next = old_nodeB

When we got to nodeA.next = old_nodeB, the name nodeA had already been assigned the id originally associated with node B (BBB in our example), so we changed the original node B's next pointer to point to itself, generating the loop at the core of the problem.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • i don't understand what that means .... what "different" c? Does this "different" c not have a next? At which point exactly is it breaking? – Raksha Sep 23 '18 at 01:35
  • 1
    Do the comments I added help? – Charles Duffy Sep 23 '18 at 01:37
  • not really ... is this one of those things where `=` creates a pointer instead of a copy like with lists? So `old_c_next` is not just a node on its own even tho it's a new variable ... i'm just trying to visualize this whole process – Raksha Sep 23 '18 at 01:49
  • Keep in mind that `nodeA.next` is a different variable depending on what `nodeA` is. So if you change `nodeA` before you change `nodeA.next`, you're modifying which object it is which has the `next` attribute which is the assignment target. – Charles Duffy Sep 23 '18 at 02:09
  • Now I'm really confused :| ... why is it "`A=Node(v=2, nextV=None); B=Node(v=1, nextV=2)`"? if you go from left to right, the first thing to change is `nodeB` which is now `A`, so `B = Node(v=1, nextV=2)`, then `nodeA.next` goes to `nodeB`, so shouldn't it be `A = Node(v = 1, nextV = 2)` since `B` was 2? Or even 1 since we changed it and `B.v = 1`? Why is it None? Maybe someone could draw a picture? %\ ... I feel like I did after watching Predestination.... – Raksha Sep 23 '18 at 02:10
  • It's left-to-right, but you *do* have implicit temporary storage. Think of it as if it collects the old values left-to-right, and then assigns the new values left-to-right. The place where the breakage happens here is because the assignment targets change between when `nodeA` is assigned and when `nodeA.next` is assigned, because what `nodeA.next` means depends on the value of `nodeA`. – Charles Duffy Sep 23 '18 at 02:13
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/180600/discussion-between-charles-duffy-and-raksha). – Charles Duffy Sep 23 '18 at 02:15
  • 1
    @Raksha, btw, there was a bug in my table from the broken version as originally assembled; see corrections. – Charles Duffy Sep 23 '18 at 17:34