Here is what I have so far, I want to create the function that would remove elements lower than a specified value in the doubly linked list or above the specified value.
class DoublyLinkedList:
class Node:
""" Nodes in list """
def __init__(self, element, prev=None, next_node=None):
self.element = element
self.prev = prev
self.next = next_node
def __init__(self):
self._header = self.Node(None, None, None)
self._trailer = self.Node(None, None, None)
self._header.next = self._trailer
self._trailer.prev = self._header
self._size = 0
def append(self, element):
:arg element: the value to add to this list
new_node = self.Node(element, self._trailer.prev, self._trailer)
self._trailer.prev.next = new_node
self._trailer.prev = new_node
self._size += 1
def __len__(self):
return self._size
def __str__(self):
# Python's list is used here to avoid string concatenation
result = []
current = self._header.next
for i in range(len(self)):
result.append(current.element)
current = current.next
return str(result)
def remove(self, low, high):
for element in self:
try:
if element.next < low:
element.next = element.next.next
elif element.next > high:
element.next = element.next.next
except Exception as e:
print('low must be less than or equal to high')
pass
^^ That is what I've tried so far ^^
here is how I wanted it to work:
I'm not sure how to get it to filter out the higher or lower values
DoublyLinkedList.append(5)
DoublyLinkedList.append(7)
DoublyLinkedList.append(8)
DoublyLinkedList.append(3)
DoublyLinkedList.append(9)
[5, 7, 8, 3, 9]
DoublyLinkedList.remove(5,8)
its output should be:
[5, 7, 8]
Some issues in your code:
append has a line that should be a comment (starting with #)
for element in self: will not work, as self is not iterable. Also, it is rarely a good idea to iterate over a collection with a for loop when you are planning to remove items from that same collection. It will be better to use a form of iteration that you have already used in the __str__ method.
element.next < low is comparing two different types: .next is a node, while low is a number. Calling that loop variable element is confusing, as that is also the name of the attribute of your nodes. You want to have something like current.next.element < low.
If the above is corrected, there is no reason why the try block should trigger an exception, let be that it has to do with how low and high relate to eachother. If you want to output a message when high > low, then don't do that in the loop, but just test for that condition before starting the loop.
When you remove one node, you should also decrease the list size.
You can avoid code repetition by using one if to compare with low and high in one expression, using operator chaining
DoublyLinkedList is the class, but a list should be an instance of that class. So you need to first create that instance, and then call the methods on that instance, not on the class.
Here is a correction of your code:
class DoublyLinkedList:
class Node:
""" Nodes in list """
def __init__(self, element, prev=None, next_node=None):
self.element = element
self.prev = prev
self.next = next_node
def __init__(self):
self._header = self.Node(None, None, None)
self._trailer = self.Node(None, None, None)
self._header.next = self._trailer
self._trailer.prev = self._header
self._size = 0
def append(self, element):
# :arg element: the value to add to this list
new_node = self.Node(element, self._trailer.prev, self._trailer)
self._trailer.prev.next = new_node
self._trailer.prev = new_node
self._size += 1
def __len__(self):
return self._size
def __str__(self):
result = []
current = self._header.next
for i in range(len(self)):
result.append(current.element)
current = current.next
return str(result)
def remove(self, low, high):
# Perform the check before the loop
if low > high:
print('low must be less than or equal to high')
return
# Iterate the nodes like in __str__, but start one node earlier
current = self._header
for i in range(len(self)):
# Don't compare the node, but its element.
# Use chained comparison;
if low <= current.next.element <= high:
# Only move to next node when not removing
current = current.next
else:
current.next = current.next.next
self._size -= 1 # Also reduce size
# Should create an instance and work with that
lst = DoublyLinkedList()
lst.append(5)
lst.append(7)
lst.append(8)
lst.append(3)
lst.append(9)
print(lst) # [5, 7, 8, 3, 9]
lst.remove(5,8)
print(lst) # [5, 7, 8]
Related
from typing import Any, Optional
class Node:
def __init__(self, value: Optional[Any] = None, next: Optional['Node'] = None) -> None:
self.value = value
self.next = next
def __str__(self) -> str:
return 'Node [{value}]'.format(
value=str(self.value)
)
class LinkedList:
def __init__(self) -> None:
self.head: Optional[Node] = None
self.length = 0
def __str__(self) -> str:
if self.head is not None:
current = self.head
values = [str(current.value)]
while current.next is not None:
current = current.next
values.append(str(current.value))
return '[{values}]'.format(values=' '.join(values))
return 'LinkedList []'
def append(self, elem: Any) -> None:
new_node = Node(elem)
if self.head is None:
self.head = new_node
return
last = self.head
while last.next:
last = last.next
last.next = new_node
self.length += 1
def remove(self, index) -> None:
cur_node = self.head
cur_index = 0
if self.length == 0 or self.length <= index:
raise IndexError
if cur_node is not None:
if index == 0:
self.head = cur_node.next
self.length -= 1
return
while cur_node is not None:
if cur_index == index:
break
prev = cur_node
cur_node = cur_node.next
cur_index += 1
prev.next = cur_node.next
self.length -= 1
my_list = LinkedList()
my_list.append(10)
my_list.append(20)
my_list.append(30)
print('Current list:', my_list)
# print('Getting the third element:', )
print('Removing the second element.')
my_list.remove(1)
print('New list:', my_list)
Result:
Current list: [10 20 30]
Getting the third element: 30
Removing the second element.
New list: [10 30]
In a singly linked list, a link is a link only to the next element, that is, it can only move towards the end of the list.
It is impossible to find out the address of the previous element based on the contents of the current node.
Tell me how to implement getting an element by index. And get index 2 (30) ?
Tell me how to implement getting an element by index.
Your instructor is inviting you to
def get(self, index: int) -> Node:
and then flesh out the definition.
You will find the definition of remove(self, index) already
offers all that you need.
Simply traverse the list in readonly manner,
without changing any .next attributes.
Though you may find it simpler to just use a for i in range(index):
loop, never bothering to examine self.length.
If a user tries e.g. .get(99), running off the end of the list,
it's unclear what the proper result should be.
Maybe an exception?
You might have latitude to specify what correct behavior would be.
And get index 2 ?
Once def get is correctly defined,
it's a simple call:
assert my_list.get(2).value == 30
I have made a Node and Deque class to represent a double linked list. I wrote a function to pop the last item of my list, but after executing the function it pops the first element instead.
My expected output:
my_list.push_front(1)
my_list.push_front(2)
my_list.push_front(3)
linked list is [3, 2, 1]
my_list.pop_back() --> [3,2]
My linked list function file:
class Node:
"""
Initialize empty node
"""
def __init__(self, data, prev = None, next = None):
self.data = data
self.next = next
self.prev = prev
class Deque:
"""
A double-ended queue
"""
def __init__(self):
"""
Initializes an empty Deque
"""
self.head = None
self.size = 1
def __len__(self):
"""
Computes the number of elements in the Deque
:return: The size of the Deque
"""
counter = 1
current = self.head
if self.head is None:
return 0
while current.next is not None:
counter += 1
current = current.next
return counter
def push_front(self, e): #needs work
"""
Inserts an element at the front of the Deque
:param e: An element to insert
"""
new_head = Node(data = e, next = self.head)
if self.head:
self.head.prev = new_head
self.head = new_head
def pop_back(self):
"""
Removes and returns the last element
:return: The (former) last element
"""
if self.head == None:
raise IndexError
curr = self.head
while curr.next:
curr = curr.next
save = self.head.data
self.head = self.head.next
self.size -= 1
return save
def listprint(self, node):
"""
Prints each element of the node front to back
:param node:
"""
while (node is not None):
print(node.data)
last = node
node = node.next
My main file:
def main():
my_list = Deque()
my_list.push_front(1)
my_list.push_front(2)
my_list.push_front(3)
my_list.pop_back()
my_list.listprint(my_list.head) #print elements in the list
In my pop_back() function, I was thinking of traversing until the end of the linked list, and then set self.head to self.head.next and decrease the size of the linked list by 1.
Here's what I think is the issue:
save = self.head.data
self.head = self.head.next
You want to remove the last one, but you're actually changing the reference for the head. If you want to change the reference of the last one, you should be doing:
while curr.next.next: # This allows you to stand on the previous of the last one
curr = curr.next
save = curr.next
curr.next = None
self.size -= 1
return save
The actual thing you're doing there it's a pop and not a dequeue
I'm working on a project where I manipulate a lot of sorted lists of elements, and I need to be able to remove any of these quickly. Since I don't need any kind of indexation, I thought that the doubly linked list structure would be best. I couldn't find any good pre-made module so I made my own:
class Node: # nodes for doubly-linked lists
def __init__(self, val, dll):
self.val = val
self.next = None
self.prev = None
self.dll = dll
class DLList: # doubly-linked lists
def __init__(self):
self.first = None
self.last = None
self.len = 0
# def __iter__(self):
# self.curr = self.first
# return self
#
# def __next__(self):
# if self.curr == None:
# raise StopIteration
# self.curr = self.curr.next
# if self.curr == None:
# raise StopIteration
# return self.curr
def append(self, val): # add a node with value val at the end of the list
node = Node(val, self)
node.prev = self.last
self.last = node
if self.first == None: # <=> if self was empty
self.first = node
self.len += 1
def appendleft(self, val): # same as previous, but at the beginning of the list
node = Node(val, self)
node.next = self.first
self.first = node
if self.last == None:
self.last = node
self.len += 1
def nodeat(self, i): # gives the ith node (starting at 0)
if i == -1:
return None
if i > self.len or i < -1:
raise IndexError('index out of range')
curr = self.first
for j in range(i):
curr = curr.next
return curr
def remove(self, node): # remove a given node in the list
if node.dll != self: #cannot remove a node that is not in the list
raise ValueError('node not in list')
p = node.prev
n = node.next
v = node.val
node.dll = None
if p != None:
p.next = n
else:
self.first = n
if n != None:
n.prev = p
else:
self.last = p
self.len -= 1
return v
def add(self, val, i): # add a node at the ith place in the list
node = Node(val, self)
if i > self.len:
raise IndexError('index out of range')
self.len += 1
previ = self.nodeat(i)
node.prev = previ.prev
node.next = previ
previ.prev = node
def clear(self): # empty the list
self.first = None
self.last = None
self.len = 0
def extend(self, iterable): # add the elements of iterable in order at the end of the list
for i in iterable:
self.append(i)
self.len += 1
def extendleft(self, iterable): # same as previous, but at the beginning (and in reverse order)
for i in iterable:
self.appendleft(i)
self.len += 1
def dll_to_list(self): # return a python list with the elements of the doubly-linked list
res = []
curr = self.first
while curr != None:
res.append(curr.val)
curr = curr.next
return res
def is_empty(self): # check whether the list is empty
return self.len == 0
Since I would lose time checking that the item I want to remove is in the list by browsing it, I added a pointer toward the list a Node is in inside the node, so that I can check that I'm not removing things from the wrong list.
Those lists are stocked in a Python dictionary, and at some point I started getting 'node not in list' errors. Does anyone know how it could appear? I never use anything but the methods listed here to manipulate the lists...
Otherwise, does anyone know about a well-coded module that I could use in place of this one?
Thanks!
A doubly linked list has links going both directions.
Example:
def append(self, val): # add a node with value val at the end of the list
node = Node(val, self) # new node, ok
node.prev = self.last # ok, the new nodes prev is the last node of your list
self.last = node # ok, your new node is now the last of your list
if self.first == None: # yeah, ok, if its empty its also the first one now
self.first = node
self.len += 1
but ... you do not set the back-direction:
node.prev.next = node # before node.prev = self.last
Similar in your other appends. You have to always clear/reset/set all the links in both directions if you add/remove things into a doubly-linked-list:
( Red are all the changed variabels on append )
Essentially your list is not complete - if you operate / iterate on it, things will go missing in unexpected ways
I was trying to implement a doubly linked list with the following functions:
_insertItemAfterNode(self,index)
_nodeBeforeIndex(self, index)
insertItemAtTheFront(self,item)
insertItemAtTheEnd(self,item)
insertItemAtIndex(self, index, item)
The last three functions should use the first two private methods.
This is what I have so far but I can't seem to work it. Any help is appreciated!!!
# DoublyLinkedLists
class Node(object):
def __init__(self, prev = None, data=None, next = None):
self._prev = prev
self._data = data
self._next = next
def __str__(self):
return str(self._data)
def __repr__(self):
return "Node(%s,%s,%s)" % (repr(self._prev), repr(self._data), repr (self._next))
def __eq__(self, other):
if other == None:
return False
else:
return self._prev == other._prev and self._data == other._data and self._next == other._next`enter code here`
class DoublyLinkedList(object):
def __init__(self):
self._first = Node(None, None, None)
self._length = 0
self._last = Node(None, None, None)
def __len__(self):
return self._length
def _insertItemAfterNode(self,item,aNode):
newNode = Node(aNode._prev, item, aNode._next)
aNode._next= newNode._prev
aNode._prev=newNode._next
self._length += 1
def _nodeBeforeIndex(self, index):
if 0 <= index <= self._length:
aNode = self._first
for i in range(index):
aNode = aNode._next
return aNode
else:
raise IndexError
def _nodeAfterIndex(self, index):
if 0<= index <= self.length:
aNode = self._last
for i in range(index):
aNode = aNode._prev
return aNode
else:
raise IndexError
def __getitem__(self, index):
return self._nodeBeforeIndex(index)._next._data
def insertAtEnd(self, item):
self._nodeAfterIndex(item)
def __iter__(self):
return DoublyLinkedListIterator(self)
def __setitem__(self, index, item):
self._nodeBeforeIndex(index)._next._data = item
def insertItemAtTheFront(self, item):
self._insertItemAfterNode(item, self._nodeBeforeIndex(0))
def insertItemAtTheEnd(self, item):
self._insertItemAfterNode(item, self._nodeBeforeIndex(self._length))
def insertItemAtIndex(self, index, item):
'''Valid range 0 <= index <= length.'''
self._insertItemAfterNode(item, self._nodeBeforeIndex(index))
def __iter__(self):
return DoublyLinkedListIterator(self)
def __repr__(self):
#used by print in abscence of __str__ .
plist = []
for item in self:
plist.append(item)
return "DoublyLinkedList(%s)" % str(plist)
class DoublyLinkedListIterator(object):
def __init__(self, aList):
self._list = aList
self._currentIndex = -1
self._currentNode = self._list._first
def __iter__(self):
return self
def __next__(self):
if self._currentIndex == self._list._length - 1:
raise StopIteration
else:
self._currentIndex += 1
self._currentNode = self._currentNode._next
return self._currentNode._data
def main():
x = DoublyLinkedList()
x.insertItemAtTheEnd(45)
print(x)
main()
I see two immediate issues with your code. There may be more issues that I haven't noticed, but these will probably prevent you from seeing any others until they are fixed.
The first issue is very simple. You have two sentinel nodes, _first and _last in your DoublyLinkedList class. However, they're not linked to each other, so when more nodes are added later, they won't be connected to both sentinels like they should be.
You can fix this by changing the __init__ method:
def __init__(self):
self._first = Node(None, None, None)
self._length = 0
self._last = Node(self._first, None, None) # pass first node as an arg here
self._first._next = self._last # make a link back too
The second issue is how you're adding nodes. There are a couple of places where you're assigning values from the _prev or _next attributes of a node, rather than referring to the node itself.
Lets think of a list containing A and C nodes, and you want to insert B in between them. You need to make B's node link to both A and C, and then modify C and A to link back to it. In your code, A is aNode, B is newNode and C is (at the start) aNode._next.
def _insertItemAfterNode(self, item, aNode):
newNode = Node(aNode, item, aNode._next) # link B to A and C
aNode._next._prev = newNode # link C back to B
aNode._next = newNode # and A to B too!
self._length += 1
There are a few things strange with your code. Let's dig into the first problem that I found:
def _insertItemAfterNode(self,item,aNode):
newNode = Node(aNode._prev, item, aNode._next)
aNode._next= newNode._prev
aNode._prev=newNode._next
self._length += 1
Assume that you have:
prevNode <-> aNode <-> nextNode
You now create a newNode which points to prevNode as its previous and nextNode as its follow-up. Then you reverse the pointers of aNode. prevNode still thinks its following node is aNode, but aNode now thinks that its following node is prevNode again.
/---------\
/----> aNode < v
prevNode <--------/ \-- nextNode
^ ^
\---- newNode --/
Which is not nearly what you want:
prevNode <-> aNode <-> newNode <-> nextNode
Here's a better version:
def _insertItemAfterNode(self,item,aNode):
# If the new Node follows aNode, than its preceeding node is aNode,
# not aNode._prev!
newNode = Node(aNode, item, aNode._next)
# Now we need to make aNode point at its new following Node, newNode
aNode._next = newNode
# And finally, we need to make the following Node notice that
# its preceeding Node has changed.
newNode._next._prev = newNode
self._length += 1
This doesn't care about any edge cases, for example what to do if aNode is the last node in the linked list.
What I recommend is that you take out a piece of paper and draw how the different links need to be changed for each of the operations that you need to make, because it is hard to keep all this in your head.
Make sure that you look at all the edge cases, ie. putting into an empty list, putting in item into the first and last positions.
And finally, while shuffling links around, always double check where each link is pointing at each instruction, and not to drop any nodes accidentally.
I want to search a value/character in a linked list and return the number of times the value/character is in the linked list. Also would it be easier if I just used recursion instead of tail recursion?
class MyList():
__slots__=('head','size')
class Empty():
__slots__=()
class NonEmpty():
__slots__=('data','next')
def mkMyList():
lst = MyList()
lst.head = mkEmpty()
lst.size = 0
return lst
def mkEmpty():
return Empty()
def mkNonEmpty(data,lst):
node = NonEmpty()
node.data = data
node.next = lst
return node
def count(l, value, c = 0):
l = mkMyList()
if l.head != value:
l.head = l.head.next
if l.head == value:
return count(l.head.next, value, c + 1)
if l.size == 0:
return c
When I try to test it, I get this:
count(s,'s',c= 0)
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
count(s,'s',c= 0)
File "C:\Users\Qasim\Desktop\Linked Lists.py", line 30, in count
l.head = l.head.next
AttributeError: 'Empty' object has no attribute 'next'
\
Rather than use recursion, I would use the iterator pattern. Here is one way to do it in the context of your problem:
class LinkedList(object):
class Node(object):
__slots__ = ('prev', 'next', 'value')
def __init__(self, prev=None, next=None, value=None):
self.prev = prev
self.next = next
self.value = value
def __init__(self, iterable=[]):
self.head = LinkedList.Node() # dummy node
self.tail = self.head
self.size = 0
for item in iterable:
self.append(item)
def __iter__(self):
current = self.head
while True:
if current.next is not None:
current = current.next
yield current.value
else:
raise StopIteration
def append(self, value):
self.tail.next = LinkedList.Node(prev=self.tail, value=value)
self.tail = self.tail.next
self.size += 1
def pop(self):
if self.size > 0:
value = self.tail.value
self.tail = self.tail.prev
self.tail.next = None
self.size -= 1
return value
else:
raise IndexError('pop from empty list')
def count(self, value):
cumsum = 0
for item in self:
if item == value:
cumsum += 1
return cumsum
By my defining a Python special method __iter__, one can sequentially access the elements of a LinkedList in the following manner:
l = LinkedList([1, 2, 3, 3, 3, 4, 5])
for value in l:
print(value)
which then makes the desired method count straight-forward to implement.
Note that I have used the Python generator syntax to implement __iter__, you can read about generators and the yield statement here.
Tracing your code:
l = mkMyList() # => head = Empty()
if l.head != value: # True since head is Empty()
l.head = l.head.next # Empty does not have a ".next" attribute
This is what the Traceback is telling you.
EDIT: Two more things: (1) I'm not sure why count is even calling mkMyList when it seems your intent is to pass it the list, l, in the function args. (2) I'm guessing you want to put the size-check if statement at the top of this function:
if l.size == 0:
return c
The issue I see is that the list in count is never initialized properly. In mkMyList(), the head element is set to and Empty, which has no next attribute. In count(), you only use mkMyList(). This means that l.head is an Empty, and there's no way it could have a next attribute. To fix this, I would recommend instantiating the list l using the given input.
With regards to the question about recursion: no, there is very little difference in terms of composing a tail recursive function versus a regularly recursive function.