3
\$\begingroup\$

I took this as a challenge and implemented linked list data structure in python

class Node:
 def __init__(self, data, nxt=None):
 self.data = data
 self.next = nxt
class LinkedList:
 def __init__(self, arr=None):
 if arr in [None, []]:
 self.head = None
 else:
 self.arrayToLinked(arr)
 def arrayToLinked(self, arr):
 self.head = Node(arr[0])
 temp = self.head
 for i in range(1, len(arr)):
 temp.next = Node(arr[i])
 temp = temp.next
 def append(self, new_val):
 new_node = Node(new_val)
 if self.head is None:
 self.head = new_node
 return
 last = self.head
 while last.next is not None:
 last = last.next
 last.next = new_node
 def linkedToArray(self):
 arr = []
 temp = self.head
 while temp is not None:
 arr.append(temp.data)
 temp = temp.next
 return arr
 def removeDuplicates(self):
 pass
 def sort(self):
 arr = []
 temp = self.head
 while temp is not None:
 arr.append(temp.data)
 temp = temp.next
 self.arrayToLinked(sorted(arr))
 def index(self, val):
 temp = self.head
 i = 0
 while temp is not None:
 if temp.data == val: return i
 i += 1
 return -1
 def min(self):
 mini = self.head.data
 temp = self.head
 while temp is not None:
 if mini > temp.data:
 mini = temp.data
 return mini
 def max(self):
 maxi = self.head.data
 temp = self.head
 while temp is not None:
 if maxi < temp.data:
 maxi = temp.data
 return maxi
 def push(self, data):
 new_node = Node(data)
 new_node.next = self.head
 self.head = new_node
 @staticmethod
 def insertNode(prev_node, new_val):
 new_node = Node(new_val)
 new_node.next = prev_node.next
 prev_node.next = new_node
 def insertIndex(self, pos, data):
 data = Node(data)
 i = 0
 temp = self.head
 while i < pos:
 if temp is None or temp.next is None:
 return
 temp = temp.next
 i += 1
 dum = temp
 temp = data
 temp.next = dum
 self.head = temp
 def delete(self, key):
 temp = self.head
 if temp is not None and temp.data == key:
 self.head = temp.next
 return
 prev = None
 while temp is not None:
 if temp.data == key:
 break
 prev = temp
 temp = temp.next
 if temp is None:
 return
 prev.next = temp.next
 def deleteIndex(self, pos):
 if self.head is None:
 return
 temp = self.head
 if pos == 0:
 self.head = temp.next
 return
 for i in range(pos - 1):
 temp = temp.next
 if temp is None:
 break
 if temp is None or temp.next is None:
 return
 val = temp.next.next
 temp.next = None
 temp.next = val
 def pop(self, pos):
 if self.head is None:
 return
 temp = self.head
 if pos == 0:
 self.head = temp.next
 return
 for i in range(pos - 1):
 temp = temp.next
 if temp is None:
 break
 if temp is None or temp.next is None:
 return
 val = temp.next.next
 pop_val = temp.next
 temp.next = None
 temp.next = val
 return pop_val
 def count(self, element):
 temp = self.head
 count = 0
 while temp is not None:
 if temp.data == element:
 count += 1
 temp = temp.next
 return count
 def remove(self, element):
 temp = self.head
 while temp is not None:
 if temp.next is not None and temp.next.data == element:
 dum = temp.next.next
 temp.next = None
 temp.next = dum
 temp = temp.next
 def isLoop(self):
 s = set()
 temp = self.head
 while temp:
 if temp in s:
 return True
 s.add(temp)
 temp = temp.next
 return False
 def findLoop(self):
 s = set()
 temp = self.head
 while temp:
 if temp in s:
 return temp
 s.add(temp)
 temp = temp.next
 def createLoop(self, data):
 ind = self.index(data)
 last = self.head
 while last.next is not None:
 last = last.next
 last.next = self[ind]
 def reverse(self):
 result = None
 temp = self.head
 while temp:
 result = Node(temp.data, temp)
 temp = temp.next
 self.head = result
 def len(self):
 c = 0
 temp = self.head
 while temp is not None:
 c += 1
 temp = temp.next
 return c
 def clear(self):
 self.head = None
 def copy(self):
 return self.arrayToLinked(self.linkedToArray())
 def __getitem__(self, index):
 arr = []
 temp = self.head
 while temp is not None:
 arr.append(temp)
 temp = temp.next
 return arr[index]
 def getValues(self, index):
 arr = []
 temp = self.head
 while temp is not None:
 arr.append(temp.data)
 temp = temp.next
 return arr[index]
 def print(self):
 arr = []
 m = self.head
 while m is not None:
 arr.append(str(m.data))
 m = m.next
 print(' -> '.join(arr))

I want to make the code as short as possible, while maintaining neatness.

Thanks!

asked Nov 20, 2019 at 9:07
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

This code is pretty neat. One major improvement would be the addition of some magic methods, like __iter__, __getitem__, __setitem__ and __str__.

iter

The magic method you'll use the most wil be __iter__. It will allow you to do for node in linked_list

def __iter__(self):
 current = self.head
 while current:
 yield current
 current = current.next

If there is the possibility for loops in the linked list, this will go on forever. In that case, it might be best to raise a specific exception

class LoopListError(Exception): pass
...
def __iter__(self):
 current = self.head
 visited = set()
 while current:
 if current in visited:
 raise LoopListError("f{current} is part of a loop")
 set.add(current)
 yield current
 current = current.next

Make sure never to change the list while iterating over it. This might lead to strange errors.

__len__

len(self) can be renamed to __len_, so you can do len(linked_list) . It can also be implemented like this:

def __len__(self):
 return sum(1 for _ in self)

If there is a loop in the list, this wil raise the LoopListError. If, in that case, you want the length of the non-looped part of the list, then you can do:

def __len__(self):
 count = 0
 try:
 for _ in self:
 count += 1
 except LoopListError:
 pass
 return count

If you want it to iterate over the nodes values instead of the nodes themselves, you can just change the yield current to yield current.data. Whichever option is best depends on the design of the rest and the use of this list.

I think it's cleaner to provide a separate iter_values method:

def iter_values(self):
 return (node.data for node in self)

You don't need a specific min and max method any more, but can use the builtins

__getitem__

In your implementation, you load the complete linked list into a builtin list. This is not needed. You can use enumerate to loop over the elements, and keep track of the index

def __getitem__(self, index):
 for i, node in enumerate(self):
 if i == index:
 return node
 raise IndexError(f"{index} not found")

This works for positive indices. If you also want to accept negative indices, you need to convert the negative index to a positive one:

def __getitem__(self, index):
 if index < 0:
 l = len(self)
 if abs(index) > l:
 raise IndexError(f"{index} out of range")
 index = l - index
 for i, node in enumerate(self):
 if i == index:
 return node
 raise IndexError(f"{index} out of range")

__bool__

In python, by convention, empty containers are falsey. Their __bool__ function returns False.

def __bool__(self):
 return self.head is not None

arrayToLinked

In python, it's seldomly necessary to loop over an index. Instead of for i in range(1, len(arr)), you can use for value in arr:. This only needs a bit of special handling for the head of the list.

Your arrayToLinked method corresponds to list.extend(iterable) on an ordinary list. I only clears the list first. My suggestion would be to skip the clearing of the list. If the user wants a fresh list, he can either explicitly clear it himself, or call the constructor while providing the iterable:

def extend(self, iterable):
 it = iter(iterable)
 if not self:
 try:
 self.head = Node(next(it))
 except StopIteration:
 self.head = None
 for value in it:
 self.append(Node(value))
def __init__(self, iterable=None):
 self.head = None
 if iterable is not None:
 self.extend(iterable)

As409_conflict noted in the comments, this might not be the most performant method to use

if you provide a tail method,

def tail(self):
 """
 returns the last element in the linked list. 
 """
 if self.head is None:
 return None
 for current in self:
 pass
 return current
def extend(self, iterable):
 it = iter(iterable)
 if not self:
 try:
 self.head = Node(next(it))
 except StopIteration:
 return
 current = self.tail()
 for value in it:
 current.next = current = Node(value)

copy

The copy then becomes as simple as

def copy(self):
 return type(self)(self.iter_values())

sort

def sort(self):
 sorted_values = sorted(self.iter_values())
 self.clear()
 self.extend(sorted_values )

Or, If you want to return a new instance

def sort(self):
 return type(self)(sorted(self.iter_values()))

In general, I suggest you take a look at the Python data model, and what methods a standard list provides, and thy to mimic those behaviours

answered Nov 20, 2019 at 11:01
\$\endgroup\$
1
  • \$\begingroup\$ Due to the linear complexity of append, your extend complexity is squared. Better incorporate the looping there or introduce self._last. \$\endgroup\$ Commented Nov 22, 2019 at 11:54

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.