3

I have been stuck on this for half of a day. Could I get some tips or pointers on how to swap just the head and tail of a linked list (not reverse the entire list) without copying their data?

class myNode:

    def __init__(data, next):
         self.data = data
         self.next = next


class MyLinkedList:

    def __init__(self):
         self.head = None
         self.tail = None

If I could see the code and have an explanation to it, that would be awesome!

edit:

Basically, my initial way of solving this problem is.

  1. Literate through the list to find the tail ( by stopping the while loop when the node.next is None, meaning I have reached the end)

and then linking the tail to the head.next

  1. Literate through the list to find the 2nd last node of the list and linking that to the head.

  2. Linking the original head to null as now the head should be swapped to the tail.

  • 1
    Mutate the head-1 and the tail-1. – Ignacio Vazquez-Abrams Jun 07 '18 at 04:32
  • @IgnacioVazquez-Abrams Could you show the code? thank you –  Jun 07 '18 at 04:35
  • @Waffle You should highlight your code in the editor and click on the {} button to indent it 4 spaces so it all shows up as code to us. Also, does your class give a doubly linked list so that each node knows its head and tail, or do `self.head` and `self.tail` somehow represent the head and tail of the entire linked list? – Hans Musgrave Jun 07 '18 at 04:49
  • @HansMusgrave its a singly linked list so the mere self.head and self.tail has no value! –  Jun 07 '18 at 04:54

4 Answers4

1

There are a few edge cases which are best handled separately to keep the code simple. They are lists of size zero, one and two.

For the first two of those, the list will not change at all. For size two, only the head and tail will change, no internal nodes will (because there are no internal nodes).

In that case, the list on the left becomes the one on the right with (| indicates a null pointer and h/t are the head and tail pointers):

h    t               h    t
A -> B -> |          B -> A -> |

with (this is pseudo-code rather than Python, .n specifies the next pointer):

                     h=A  t=B  A.n=B  B.n=|
tail.next = head     h=A  t=B  A.n=B  B.n=A
head.next = null     h=A  t=B  A.n=|  B.n=A
swap head, tail      h=B  t=A  A.n=|  B.n=A

For all other cases (three or more nodes), the penultimate node must have its pointer changed to point to the new tail. The two-node case above can be easier because it knows the head is the penultimate. The following diagram shows the case for size four (I have marked the penultimate node with p):

h         p    t          h         p    t
A -> B -> C -> D -> |     D -> B -> C -> A -> |

This can be achieved with the following operations:

                          h=A  t=D  p=C A.n=B  B.n=C C.n=D  D.n=|
tail.next = head.next     h=A  t=D  p=C A.n=B  B.n=C C.n=D  D.n=B
head.next = None          h=A  t=D  p=C A.n=|  B.n=C C.n=D  D.n=B
penu.next = head          h=A  t=D  p=C A.n=|  B.n=C C.n=A  D.n=B
swap head, tail           h=D  t=A  p=C A.n=|  B.n=C C.n=A  D.n=B

So, in terms of putting that into Python, the following program contains both the methods and a test harness so you can see it in operation:

class MyNode:
    def __init__(self, data, next = None):
         self.data = data
         self.next = next

class MyList:
    def __init__(self):
         self.head = None
         self.tail = None

    def push_back(self, data):
        if self.tail is None:
            self.head = MyNode(data)
            self.tail = self.head
        else:
            self.tail.next = MyNode(data)
            self.tail = self.tail.next

    def dump(self, desc):
        print(desc, end=' ')
        node = self.head
        while node is not None:
            print(node.data, end=' -> ')
            node = node.next
        print('|')

    def swap(self):
        # For empty and size 1, no change.

        if self.head is None: return
        if self.head.next is None: return

        # For size 2, easy swap.

        if self.head.next.next is None:
            self.tail.next = self.head
            self.head.next = None
            self.head, self.tail = self.tail, self.head
            return

        # For size 3+, little more complex, need the
        # penultimate node as well as head and tail.

        penu = self.head
        while penu.next != self.tail:
            penu = penu.next

        self.tail.next = self.head.next
        self.head.next = None
        penu.next = self.head
        self.head, self.tail = self.tail, self.head

First the node class, modified as per Hans' suggestion to default the next pointer. Then the list class itself, with initialisation as per your original question.

It also has a push_back method since a list is of little use if you can't put anything in it, and a dump method so we can see what the list looks like after each operation.

The only other part of that class is the swap method, the logic which has been covered in the earlier part of this answer.

And, of course, what self-respecting class could exist without some sort of test code. The following will test lists of varying sizes so that you can see the swap operation works as expected:

x = MyList()

# Do various sizes.

for i in range(6):
    # Add an element except for first time (so we check empty list).

    if i > 0:
        x.push_back(i)

    # Show before and after figures, making sure we restore
    # list to original state.

    x.dump('Size = %d, before:'%(i))
    x.swap()
    x.dump('Size = %d, after :'%(i))
    x.swap()
    print()

Running this code results in:

Size = 0, before: |
Size = 0, after : |

Size = 1, before: 1 -> |
Size = 1, after : 1 -> |

Size = 2, before: 1 -> 2 -> |
Size = 2, after : 2 -> 1 -> |

Size = 3, before: 1 -> 2 -> 3 -> |
Size = 3, after : 3 -> 2 -> 1 -> |

Size = 4, before: 1 -> 2 -> 3 -> 4 -> |
Size = 4, after : 4 -> 2 -> 3 -> 1 -> |

Size = 5, before: 1 -> 2 -> 3 -> 4 -> 5 -> |
Size = 5, after : 5 -> 2 -> 3 -> 4 -> 1 -> |

Once you're satisfied that this works, you might want to look into making penu (the penultimate node pointer) into a member just like head and tail, and set it to None if the size is two or less. That way, you won't have to search every time you want to swap the nodes. It should be relatively easy to update this whenever calling push_back (or any other method which changes the size of list).

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
0

After reviewing the code @Waffle linked, the following seem to be the important classes. The first represents nodes in a linked list, and the second is a container class for nodes allowing easy representations of empty lists. The goal is to implement LinkedList.swap_front_back() in a way that mutates the represented list in-place.

class Node:
    def __init__(self, value=None, next=None):
        self.value = value
        self.next = next

class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None
        self.size = 0

    def swap_front_back(self):
        """TODO: Magic happens here"""

Since Python has an implicit return of None, using returns as a way of breaking out a function even if it supposedly "doesn't return anything" is a safe exit strategy. Hence, the following couple lines gets rid of the smallest edge cases:

if self.size < 2:
    return

The strategy I proposed for swapping the list in-place involved a second-to-last element, and all kinds of bad things happen if that happens to be the first element. In other words, we unfortunately have to deal with the case of a linked list of size 2 as a special case (for this implementation).

if self.size == 2:
    self.head, self.tail = self.tail, self.head
    self.head.next = self.tail
    self.tail.next = None
    return

In the general case, we need to change where the second-to-last element of the linked list points. If this doesn't happen the linked list becomes a cycle, and all kinds of bad things happen in algorithms which expect the list to end.

current = self.head
while current.next.next is not None:
    current = current.next

Once the second-to-last element is found, we can go through with the actual pointer manipulations.

current.next.next = self.head.next
# Now the former last element points to the second element
# A->B->C->D->B

current.next = self.head
# Now the second-to-last element points to the previous head
# A->B->C->A   D->B

self.head.next = None
# We don't want circles, so we eliminate A being first and last
# D->B->C->A

self.head, self.tail = self.tail, self.head
# Even after the internal manipulations are done, the
# LinkedList needs to be updated to reference the appropriate
# locations.

Putting it all together, we can create the desired method:

def swap_front_back(self):
    if self.size < 2:
        return

    if self.size == 2:
        self.head, self.tail = self.tail, self.head
        self.head.next = self.tail
        self.tail.next = None
        return

    current = self.head
    while current.next.next is not None:
        current = current.next

    self.tail.next = self.head.next
    current.next = self.head
    self.head.next = None
    self.head, self.tail= self.tail, self.head

This returns early if the LinkedList is so small that no swaps need happen, it deals with a transposition as a special case, and otherwise it first finds the second-to-last node and then uses that to adjust the requisite pointers.

Hans Musgrave
  • 6,613
  • 1
  • 18
  • 37
  • Did you miss the need to set a.next to none in the first paragraph? – paxdiablo Jun 07 '18 at 05:25
  • @paxdiablo Perhaps (I haven't run the code yet), but I'm pretty sure that's accomplished by `head.next = None`, since that is always done while `head` still references the original `head`. – Hans Musgrave Jun 07 '18 at 05:28
  • Yeah, I wasn't sure if your *code* did the right thing, I hadn't looked at that yet. It was just the *description* in the first para. – paxdiablo Jun 07 '18 at 05:29
  • @HansMusgrave while this is a bit helpful, is there an email I can reach you at so that I can post you what I have? thanks! –  Jun 07 '18 at 05:29
  • @Waffle, that's *not* how SO works. All comms should be posted here since helping *you* is actually a minor goal :-) The *major* goal is to help future people with the same problem. – paxdiablo Jun 07 '18 at 05:33
  • @Hans, it looks like your code does handle it. Should we have an `if head is None: return None` at the start to handle empty lists? – paxdiablo Jun 07 '18 at 05:34
  • @HansMusgrave how about making the function returning nothing? I just want to internally swap the head and the tail and nothing else. –  Jun 07 '18 at 05:36
  • @Waffle I'm fine handing out my email, but paxdiablo is right that communications regarding this problem should be kept here on SO. Is there anything else you needed it for? – Hans Musgrave Jun 07 '18 at 05:39
  • @paxdiablo I'll make the change for empty lists. – Hans Musgrave Jun 07 '18 at 05:39
  • @Waffle The internal swap is accomplished by the function. All that the return value allows you to do is deduce where the linked list starts. If you're also using the `MyLinkedList` class, one way you might use this for an **internal** change is something like `self.head = swap_first_last(self.head)`. – Hans Musgrave Jun 07 '18 at 05:41
  • @HansMusgrave I did send it to you through your contact page email. As this is homework, I did not want to just copy and paste the entire work on it. Im pretty sure I explained the important bits in the OP but maybe there is something I have missed to add. I will make sure to update the OP when i have understood the concept though! –  Jun 07 '18 at 05:44
  • @HansMusgrave hm, i get an assertion error with this:( –  Jun 07 '18 at 05:52
  • @Waffle I checked the source you linked in your e-mail, and the assertion error happens because of additional requirements your professor has imposed. In particular, my code **does not update `self.tail`**, and your professor's code asserts that `self.tail` is updated. – Hans Musgrave Jun 07 '18 at 06:03
  • @HansMusgrave Could you implement the self.tail as well? I'm not too sure if linked list is a difficulty concept to understand or I am just dumb for not understand this. –  Jun 07 '18 at 06:08
  • @Waffle These things take time. You have two types of references. Nodes point to each other, and the LinkedList points to some Nodes. To perform the swap, you need all the Nodes to point to the right locations (changing to D->B, C->A, and A->None in the running example), but you also need to tell the LinkedList that the new head is D and the new tail is A. – Hans Musgrave Jun 07 '18 at 06:27
  • @HansMusgrave ahhh i did not know you can use self.front, self.back = self.back, self.front. thank you! –  Jun 07 '18 at 06:29
  • @Waffle You're welcome. You can do it in three lines with a placeholder as well. `temp = self.front`, `self.front = self.back`, `self.back = temp`. Python just happens to have syntax to make that a bit nicer (although slower I'm pretty sure, if that matters). – Hans Musgrave Jun 07 '18 at 06:30
  • @HansMusgrave so the 3 pointers i wrote in the OP. I was in the right track right? –  Jun 07 '18 at 06:32
  • @Waffle That strategy works fine (though it traverses the list an extra time). Just keep in mind that not only are you changing the Node pointers, you also have to update self.front and self.back once the swap is complete. Moreover, really small lists have the potential to mess up that strategy, so dealing with them explicitly can be useful. – Hans Musgrave Jun 07 '18 at 06:34
  • @HansMusgrave for reversing the entire thing, could i possibly use a list as a temp holder and while removing each node and appending it to the list? and then, reverse(list) and finally, linking it back? –  Jun 07 '18 at 06:37
  • @HansMusgrave and essentially, the current.next.next is used to find the last node and the current.next is used to find the 2nd last node right? –  Jun 07 '18 at 06:42
  • @Waffle That works fine, though you should really ask that as a separate question. It's not really in the spirit of the question to use a secondary data structure though. The highest-voted answer [here](https://stackoverflow.com/questions/21529359/reversing-a-linked-list-in-python) seems to do exactly what you want, though you'll need some slight modifications since you have **two** classes, and you need to both **adjust the Node pointers** and **reset self.front and self.back**. – Hans Musgrave Jun 07 '18 at 06:42
  • @HansMusgrave would using a 2ndary data structure mess up the ids of the nodes once they are appended onto a list? –  Jun 07 '18 at 06:45
  • @Waffle The current.next.next is used in the while loop to identify the second-to-last node (which is the unique node with current.next not None and current.next.net None if size>2). I changed the other occurrence to make the usage more clear. – Hans Musgrave Jun 07 '18 at 06:50
  • @Waffle The secondary data structure would not mess up the ids. – Hans Musgrave Jun 07 '18 at 06:51
0

Generally, swaps rely on a third variable to store one of the values to be reversed. For instance, given list [3, 4, 1, 6, 5], swapping the first and the last can be accomplished like so:

l = [3, 4, 1, 6, 5]
_temp = l[0] #first value
l[0] = l[-1]
l[-1] = _temp
#[5, 4, 1, 6, 3]

The implementation below utilizes a default parameter that will store the reference to the first node in the list. Once the end of the list has been reached, the simple swap operation, similar to the swap above, will take place:

class LinkedList:
  def __init__(self, head=None):
    self.head = head
    self._next = None
  def insert_node(self, val):
    if self.head is None:
      self.head = val
    else:
      getattr(self._next, 'insert_node', lambda x:setattr(self, '_next', LinkedList(x)))(val)
  def swap(self, first = None, count = 0):
    if not self._next and self.head is not None:
       _head = getattr(self, 'head')
       if first:
         self.head = getattr(first, 'head')
         setattr(first, 'head', _head)
    else:
       if self.head:
         self._next.swap(first = self if not count else first, count = 1)
  @classmethod
  def load_list(cls, length = 5):
    _l = cls()
    import random
    for _ in range(length):
      _l.insert_node(random.randint(1, 100))
    return _l
  def flatten(self):
    if self.head is not None:
      return [self.head, *getattr(self._next, 'flatten', lambda :'')()]
    return ''
  def __repr__(self):
    return ', '.join(map(str, self.flatten()))

This simple recursion will work for lists of any size:

for i in range(10):
   l = LinkedList.load_list(length = i)
   _s = repr(l)
   l.swap()
   print(f'{_s} -> {repr(l)}')

Output:

 -> 
14 -> 14
80, 57 -> 57, 80
83, 44, 80 -> 80, 44, 83
94, 10, 42, 81 -> 81, 10, 42, 94
25, 26, 32, 31, 55 -> 55, 26, 32, 31, 25
8, 25, 25, 84, 83, 49 -> 49, 25, 25, 84, 83, 8
19, 95, 3, 33, 4, 56, 33 -> 33, 95, 3, 33, 4, 56, 19
97, 92, 37, 100, 27, 24, 33, 17 -> 17, 92, 37, 100, 27, 24, 33, 97
72, 24, 56, 18, 9, 64, 82, 85, 97 -> 97, 24, 56, 18, 9, 64, 82, 85, 72
Ajax1234
  • 69,937
  • 8
  • 61
  • 102
-1

Initially say you have A -> B -> C -> D -> None Head: A Tail: D

You want D -> B -> C -> A -> None Head: D, Tail: A

var next = self.head.next
var temp = self.head
self.head = self.tail
self.tail = temp
self.head.next = next
self.tail.next = None
Ilan Keshet
  • 514
  • 5
  • 19
  • I would want D -> B -> C -> A in this case. just swapping the head and the tail! –  Jun 07 '18 at 04:56
  • Code above should flip everything. – Ilan Keshet Jun 07 '18 at 05:00
  • It is not just a simple 3 line operation, as all the inner pointers also need to be flipped as well – Ilan Keshet Jun 07 '18 at 05:01
  • Im not looking to reverse the entire linked list. Im just looking to flip the head and the tail –  Jun 07 '18 at 05:01
  • If it was a doubly linked list, you could do it in like 3 operations. Not a singly linked list – Ilan Keshet Jun 07 '18 at 05:03
  • You cannot just flip the head and the tail in a singly linked list. – Ilan Keshet Jun 07 '18 at 05:04
  • oooh I understand now – Ilan Keshet Jun 07 '18 at 05:05
  • There I have modified it, and it is now what you want – Ilan Keshet Jun 07 '18 at 05:09
  • @Waffle This isn't valid Python code. I'm writing up an answer. – Hans Musgrave Jun 07 '18 at 05:13
  • Well.. it isn't in a function. it is close to it -- I don't really know what your linked list looks like. It is a guess – Ilan Keshet Jun 07 '18 at 05:14
  • Changed null to None... and a few other modifications. I tried at least. I am more of a C++/C# programmer. – Ilan Keshet Jun 07 '18 at 05:17
  • @HansMusgrave thank you! appreciate it! I appreciate your help as well. I have looked at tutorials on this, and my node and linkedlist class is the norm. Nothing special to it –  Jun 07 '18 at 05:18
  • @Ilan Keshet yes, I did change some of the code when i tried running it. Appreciate your help as well! –  Jun 07 '18 at 05:19
  • @IlanKeshet Sorry if that came across as disparaging. Your C++/C# background almost certainly makes you better qualified to talk about linked lists than me. It was the null/None and lack of colons that I was referring to, and I just wanted Waffle to know that syntax was a concern. – Hans Musgrave Jun 07 '18 at 05:27
  • @IlanKeshet Probably. The fact that he has two classes is a little interesting, so I tried to answer assuming he was only working with the `myNode` class and actually find the tail manually, but if `MyLinkedList` really has that much information your solution will be faster. – Hans Musgrave Jun 07 '18 at 05:31
  • @IlanKeshet this is actually more confusing.. damn :( –  Jun 07 '18 at 05:32
  • why more confusing :(. – Ilan Keshet Jun 07 '18 at 05:37
  • Assuming you already have the tail pointer set to the last guy, this should work... It is just swapping the two guys and then adjusting their corresponding next guys – Ilan Keshet Jun 07 '18 at 05:38
  • I have updated the OP on my initial thoughts on solving this! @IlanKeshet –  Jun 07 '18 at 05:41