How is the LRU deleted in this LRUCache implementation? - python

I'm going over the optimal Leetcode solution for LRU Cache LeetCode Medium question. The original problem can be found here: https://leetcode.com/problems/lru-cache/description/
I'm confused by a specific portion of the solution code.
class Node:
def __init__(self, key, val):
self.key, self.val = key, val
self.prev = self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.cap = capacity
self.cache = {} # map key to node
self.left, self.right = Node(0, 0), Node(0, 0)
self.left.next, self.right.prev = self.right, self.left
# remove node from list
def remove(self, node):
prev, nxt = node.prev, node.next
prev.next, nxt.prev = nxt, prev
# insert node at right
def insert(self, node):
prev, nxt = self.right.prev, self.right
prev.next = nxt.prev = node
node.next, node.prev = nxt, prev
def get(self, key: int) -> int:
if key in self.cache:
self.remove(self.cache[key])
self.insert(self.cache[key])
return self.cache[key].val
return -1
def put(self, key: int, value: int) -> None:
if key in self.cache:
self.remove(self.cache[key])
self.cache[key] = Node(key, value)
self.insert(self.cache[key])
if len(self.cache) > self.cap:
# remove from the list and delete the LRU from hashmap
**lru = self.left.next
self.remove(lru)**
del self.cache[lru.key]
Why am I deleting the lru? In other words why is self.left.next represent the least recently used key?
To illustrate this let's say your capacity param is 2. And you currently have two nodes comprising your doubly linked list: [1,1] and [2,2]
Now you put a 3rd node [3,3] so the linked list looks like this: [2,2] [1,1] [3,3]. But you have exceeded the capacity constraint of two so you need to remove the LRU (least recently used) node which is [2,2]. For lru = self.left.next ... why does it equal [2,2] and not [1,1]?

code is simple, it's just a double linked list with self.head and self.tail (two pointer)define initially , and node info is stored in dictionary
Node(0, 0)) <->(DO node insertion delete here) <-> (Node(0,0)
^ ^
head tail
all deletion and updation happenining in above space between these two node.
in simple terms:
for addition -> element is added at tail (as second last element)
for deletion -> element after head is deleted.
addition and deletion is same as what happen at double linked list.No need to explain.
accessing of node is done via dictionary by mapping key to node.
work flow:
when you read a node having a key:
if key present -> delete the key from dictionary and node
correspoinding to it from LruCache and add that node at tail
part(second last)
if key not present -> add the key and node created at last(tail part). check if size of dict exceed the self.capacity or
not. if yes then remove the first element (After head) from lrucache
double linked list.
in your example [[2,2], [1,1]]
here is double linked list in diagram
start ->
head (node(0, 0)) <-> tail (node(0,0)) # lru cache created
[2,2] added put operation
head (node(0, 0)) <-> Node(2,2) <-> tail (node(0,0))
[1,1] added put operation
head (node(0, 0)) <-> Node(2,2) <-> Node(1, 1) <-> tail (node(0,0))
[3,3] added put operation
head (node(0, 0)) <-> Node(2,2) <-> Node(1, 1) <-> Node(3,3) <->tail <->(node(0,0))
here size increase. since Node(2,2) used once and no get operation happen on it , it will be removed and cache become
head (node(0, 0)) <-> Node(1, 1) <-> Node(3,3) <->tail(node(0,0))
now suppose [1,3] put operation, having key present happen then this is how list will be changed
head (node(0, 0)) <-> Node(3,3) <-> Node(1,3) <->tail (node(0,0))
you can see for each operation if element is accessed ie get used and is found then element is added at right and remove from his present location
in case of put it is added at right, if capacity increases then first element after head is removed.
Hope this help

Related

Why is the time limit exceeding in reversing the linked list?

# Definition for singly-linked list.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
prev=ListNode(-1,head)
curr=head
dummy=head
while dummy.next:
curr.next=prev
prev=dummy
dummy=dummy.next
curr=dummy
return prev
I created three nodes, I used the prev and curr node for reversing and the dummy node for traversing ahead in the original linked list but I am getting time limit exceeded. I would like to know my logical error in this method
Some issues:
In the loop you modify the node's next attribute before saving the original next reference. So once that is done, you have lost all possibility to move forward in list, and dummy=dummy.next will now walk back since at the start of the iteration, dummy was equal to curr.
It is undesired to create a new node with value. Your code would link back to it in the first iteration of the loop. And because of the first problem, dummy becomes prev, which has a next reference to where you came from, creating an infinite loop. Instead of this -1 Node, just initialise prev as None.
The while condition is wrong. The loop should continue until you reach the very last node, so when curr is None.
Not a major issue, but why call that node reference dummy? There is nothing dummy about it. Use a name that describes what it references, for instance nextNode or nxt for short.
Correction:
class Solution:
def reverseList(self, head):
prev=None # Don't create a new node
curr=head
while curr: # Continue until the *last* node
nxt=curr.next # Save the next reference
curr.next=prev
prev=curr
curr=nxt # ... so you can move ahead
return prev

How to index each node in O(1) complexity and update all indexes each time when doing insertion/deletion/rotations in a Balanced AVL tree

The solution I have now is something of an in order traversal:
def update_index(self, current: AVLTreeNode) -> None:
"""
Call updated_index_aux to traverse the tree in order and update the index appropriately
"""
# if current is not None:
# return current.index
# return 0
self.index = 0
self.update_index_aux(current)
def update_index_aux(self, current: AVLTreeNode) -> None:
if current is not None: #Base case if current is None
self.update_index_aux(current.left)
current.index = self.index
self.index += 1
self.update_index_aux(current.right)
but this requires me to call this method each time i insert/delete which is not efficient, how do i make it O(1) such that everything is updated each time i insert/delete or perform a rotation. It seems similar to get_height() method in an AVL tree which is O(1) so this is called on insert and delete:
# Update height of current node
current.height = max(self.get_height(current.left),
self.get_height(current.right)) + 1
but im not sure how to take these solutions to make it O(1) so i dont have to traverse the tree each time i insert or delete.
my get_height function:
def get_height(self, current: AVLTreeNode) -> int:
if current is not None:
return current.height
return 0
Below is my insert function into a AVL tree for reference:
def insert_aux(self, current: AVLTreeNode, key: K, item: I) -> AVLTreeNode:
"""
Attempts to insert an item into the tree, it uses the Key to insert it
"""
if current is None: # base case: at the leaf
current = AVLTreeNode(key, item)
self.length += 1
elif key < current.key:
current.left = self.insert_aux(current.left, key, item)
elif key > current.key:
current.right = self.insert_aux(current.right, key, item)
else: # key == current.key
raise ValueError('Inserting duplicate item')
current.height = max(self.get_height(current.left), self.get_height(current.right)) + 1 #Update height of current node
current = self.rebalance(current) #Rebalance tree after insertion if needed
return current
The reason i need to index each node is because I need to code a function that finds and gets a range of items stored in the AVL tree based on a range of indexes. I did that part already but I can only do it when the nodes are already indexed and the only way i can think of to index the nodes is in order traversal to each node and updating the index accordingly but I cant do it this way so Im trying to think of a way to update each index during insertion/deletion/rotations without needing to traverse each node and update

How to interact head of linkedlist outside the LinkedList class in Python?

I am trying to solve the following problem on algoexpert:
Shift Linked List
Write a function that takes in the head of a Singly Linked List and an integer
k, shifts the list in place (i.e., doesn't create a brand new
list) by k positions, and returns its new head.
Shifting a Linked List means moving its nodes forward or backward and wrapping
them around the list where appropriate. For example, shifting a Linked List
forward by one position would make its tail become the new head of the linked
list.
Whether nodes are moved forward or backward is determined by whether
k is positive or negative.
Each LinkedList node has an integer value as well as
a next node pointing to the next node in the list or to
None / null if it's the tail of the list.
You can assume that the input Linked List will always have at least one node;
in other words, the head will never be None / null.
Sample Input
head = 0 -> 1 -> 2 -> 3 -> 4 -> 5 // the head node with value 0
k = 2
Sample Output
4 -> 5 -> 0 -> 1 -> 2 -> 3 // the new head node with value 4
The outline that the problem gives for the code is the following:
class LinkedList:
def __init__(self, value):
self.value = value
self.next = None
def shiftLinkedList(head, k):
#Write your code here.
pass
I suppose my background on linked list is very limited because from every resource I've read on linked lists, the general outline for it requires the node class and to have all the methods for rotating or shifting inside the LinkedList class.
I assume the head parameter for the function is going to be an integer that denotes the position of the list but how do I refer the head back to the original list? I already have the code written in my Thonny editor but I wrote the function inside the LinkedList class and simply called for it after making my list.
For example:
class Node:
def __init__self(data):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def push(self, newhead):
newnode = Node(new_data)
newnode.next = self.head
self.head = newnode
list1 = LinkedList()
list1.head = Node(1)
e2 = 2
e3 = 3
list1.head.next = e2
e2.next = e3
Only once I've established my linked list can I create a method inside the class to shift or rotate it. Or am I wrong?
I tried creating a function the way the algo wanted, but I am still stuck. I think what I am really confused about is whether the argument head is an integer or a LinkedList?
Here is my complete attempt:
class Node:
def __init__(self, data):
self.data = data #assign data
self.next = None #initialize next as null
class LinkedList:
#function to initalize the linked list object
def __init__(self):
self.head = None
def printList(self):
temp = self.head
while(temp):
print(temp.data)
temp = temp.next
def moveToFront(self):
tmp = self.head
sec_last = None
if not tmp or not tmp.next:
return
while tmp and tmp.next:
sec_last = tmp
tmp = tmp.next
sec_last.next = None
tmp.next = self.head
self.head = tmp
def shiftList(head, k):
if not head:
return
tmp = head
length = 1
while(temp.next != None):
tmp = tmp.next
length += 1
if(k>length):
k = k%length
k = length - k
if(k==0 or k==length):
return head
current = head
cmt = 1
while(cmt < k and current != None):
current = current.next
cmt += 1
if(current==None):
return head
kthnode = current
tmp.next = head
head = kthnode.next
kthnode.next = None
return head
I assume the head parameter for the function is going to be an integer that denotes the position of the list
No, the head of a list is a LinkedList instance. So head.value is the value of the first node in the list, and head.next is a reference to the second node in the list.
In your attempt you have changed the definition of LinkedList from the original, and created another class, called Node, which really is what LinkedList is supposed to be (except that you call a property data instead of value). It is understandable that you did this, as you wanted to have a kind of container class for the whole linked list, and reserve a separate class Node for what concerns a single node in that list. But this code challenge does not work with such a container class.
It just works with one class that represents a node, but through its next member the whole list is inferred from it. Moreover, they want the function to return a reference to the node that has become the head of the list once the shifting has completed. This is a different way of working than with a container class, in which you would mutate the head member, and would not return anything: the caller would just access the list through the modified head member.
So, what you intended to do is not inherently wrong, it just is not the data structure that this code challenge is working with. You should go with it.
what I am really confused about is whether the argument 'head' is an integer or a linkedlist
It is a node, but the list is inferred from it. It can be a bit confusing that they call their class LinkedList and not Node, but it is a matter of how you look at it.
So here is how you could solve it:
First find out what the last node is in the list. For this you will have to start from the head (you have nothing else) and step through the list until you bump into its end. Also maintain a counter so that at the end you know how many nodes there are in the list.
Then create a link from that tail node to the head node, so that the list now becomes circular: it has no end anymore
Find out which node comes k steps before the tail node in this cyclic list. As it is not possible to walk backwards in a list, you should actually walk size-k steps forward, and so you need the size of the list which you determined in the first step.
The node after that found node should become the head node.
Make that node itself the new tail node: break the link it has with the node after it (the new head).
Return the new head reference
One more thing should be done to protect this code against huge values for k. Let's say the list has 6 elements, and k is 600, then of course it makes no sense to run over the cyclic list 600 times, as we can predict that you'll end where you started! 600 is a multiple of 6... So to make this efficient, you need to reduce k to a number that is less than the size of the list. The formula for knowing how many steps forward you need to take is -k % size.
Here is the implementation of this idea:
def shiftLinkedList(head, k):
if not head:
return # Nothing to do
# Determine size of the list, and its tail node
size = 1
tail = head
while tail.next:
size += 1
tail = tail.next
# Temporarily make the list cyclic
tail.next = head
# Find out where to cut the cycle
for _ in range(-k % size):
tail = tail.next
head = tail.next
# Cut the cycle
tail.next = None
return head

Creating Binary Tree

Most of the questions I've searched for regarding binary trees shows the implementation of binary search trees, but not binary trees. The terms of a complete binary tree are:
Either an empty tree or it has 1 node with 2 children, where each
child is another Binary Tree.
All levels are full (except for possibly the last level)
All leaves on the bottom-most level are
as far left as possible.
I've come up with a concept but it doesn't seem to running through the recursion properly -- Does anyone know what I'm doing wrong?
class Node():
def __init__(self, key):
self.key = key
self.left = None
self.right = None
def add(self, key):
if self.key:
if self.left is None:
self.left = Node(key)
else:
self.left.add(key)
if self.right is None:
self.right = Node(key)
else:
self.right.add(key)
else:
self.key = key
return (self.key)
The problem in your code is that you are adding the same value multiple times. You add the node, and then still recurse deeper, where you do the same.
The deeper problem is that you don't really know where to insert the node before you have reached the bottom level of the tree, and have detected where that level is incomplete. Finding the correct insertion point may need a traversal through the whole tree... which is defeating the speed gain you would expect to get from using binary trees in the first place.
I provide here three solutions, starting with the most efficient:
1. Using a list as tree implementation
For complete trees there is a special consideration to make: if you number the nodes by level, starting with 0 for the root, and within each level from left to right, you notice that the number of a node's parent is (k-1)/2 when its own number is k. In the other direction: if a node with number k has children, then its left child has number k*2+1, and the right child has a number that is one greater.
Because the tree is complete, there will never be gaps in this numbering, and so you could store the nodes in a list, and use the indexes of that list for the node numbering. Adding a node to the tree now simply means you append it to that list. Instead of a Node object, you just have the tree list, and the index in that list is your node reference.
Here is an implementation:
class CompleteTree(list):
def add(self, key):
self.append(key)
return len(self) - 1
def left(self, i):
return i * 2 + 1 if i * 2 + 1 < len(self) else -1
def right(self, i):
return i * 2 + 2 if i * 2 + 2 < len(self) else -1
#staticmethod
def parent(i):
return (i - 1) // 2
def swapwithparent(self, i):
if i > 0:
p = self.parent(i)
self[p], self[i] = self[i], self[p]
def inorder(self, i=0):
left = self.left(i)
right = self.right(i)
if left >= 0:
yield from self.inorder(left)
yield i
if right >= 0:
yield from self.inorder(right)
#staticmethod
def depth(i):
return (i + 1).bit_length() - 1
Here is a demo that creates your example tree, and then prints the keys visited in an in-order traversal, indented by their depth in the tree:
tree = CompleteTree()
tree.add(1)
tree.add(2)
tree.add(3)
tree.add(4)
tree.add(5)
for node in tree.inorder():
print(" " * tree.depth(node), tree[node])
Of course, this means you have to reference nodes a bit different from when you would use a real Node class, but the efficiency gain pays off.
2. Using an extra property
If you know how many nodes there are in a (sub)tree, then from the bit representation of that number, you can know where exactly the next node should be added.
For instance, in your example tree you have 5 nodes. Imagine you want to add a 6 to that tree. The root node would tell you that you currently have 5 and so you need to update it to 6. In binary that is 110. Ignoring the left-most 1-bit, the rest of the bits tell you whether to go left or right. In this case, you should go right (1) and then finally left (0), creating the node in that direction. You can do this iteratively or recursively.
Here is an implementation with recursion:
class Node():
def __init__(self, key):
self.key = key
self.left = None
self.right = None
self.count = 1
def add(self, key):
self.count += 1
if self.left is None:
self.left = Node(key)
elif self.right is None:
self.right = Node(key)
# extract from the count the second-most significant bit:
elif self.count & (1 << (self.count.bit_length() - 2)):
self.right.add(key)
else:
self.left.add(key)
def inorder(self):
if self.left:
yield from self.left.inorder()
yield self
if self.right:
yield from self.right.inorder()
tree = Node(1)
tree.add(2)
tree.add(3)
tree.add(4)
tree.add(5)
for node in tree.inorder():
print(node.key)
3. Without extra property
If no property can be added to Node objects, then a more extensive search is needed to find the right insertion point:
class Node():
def __init__(self, key):
self.key = key
self.left = None
self.right = None
def newparent(self):
# Finds the node that should serve as parent for a new node
# It returns a tuple:
# if parent found: [-1, parent for new node]
# if not found: [height, left-most leaf]
# In the latter case, the subtree is perfect, and its left-most
# leaf is the node to be used, unless self is a right child
# and its sibling has the insertion point.
if self.right:
right = self.right.newparent()
if right[0] == -1: # found inbalance
return right
left = self.left.newparent()
if left[0] == -1: # found inbalance
return left
if left[0] != right[0]:
return [-1, right[1]] # found inbalance
# temporary result in perfect subtree
return [left[0]+1, left[1]]
elif self.left:
return [-1, self] # found inbalance
# temporary result for leaf
return [0, self]
def add(self, key):
_, parent = self.newparent()
if not parent.left:
parent.left = Node(key)
else:
parent.right = Node(key)
def __repr__(self):
s = ""
if self.left:
s += str(self.left).replace("\n", "\n ")
s += "\n" + str(self.key)
if self.right:
s += str(self.right).replace("\n", "\n ")
return s
tree = Node(1)
tree.add(2)
tree.add(3)
tree.add(4)
tree.add(5)
print(tree)
This searches recursively the tree from right to left, to find the candidate parent of the node to be added.
For large trees, this can be improved a bit, by doing a binary-search among paths from root to leaf, based on the length of those paths. But it will still not be as efficient as the first two solutions.
You can use the sklearn Decision trees, as they are able to be set up as binary decision trees as well. link to the documentation here.
You really need to augment your tree in some way. Since this is not a binary search tree, the only real information you have about each node is whether or not it has a left and right child. Unfortunately, this isn't helpful in navigating a complete binary tree. Imagine a complete binary tree with 10 levels. Until the 9th level, every single node has both a left child and a right child, so you have no way of knowing which path to take down to the leaves. So the question is, what information do you add to each node? I would add the count of nodes in that tree.
Maintaining the count is easy, since every time you descend down a subtree you know to add one to the count at that node. What you want to recognize is the leftmost imperfect subtree. Every perfect binary tree has n = 2^k - 1, where k is the number of levels and n is the number of nodes. There are quick and easy ways to check if a number is 1 less than a power of two (see the first answer to this question), and in fact in a complete binary tree every node has at most one child that isn't the root of a perfect binary tree. Follow a simple rule to add nodes:
If the left child is None, set root.left = Node(key) and return
Else if the right child is None, set root.right = Node(key) and return
If one of the children of the current node is the root of an imperfect subtree, make that node the current node (descend down that subtree)
Else if the sizes are unequal, make the node with the smaller subtree the current node.
Else, make the left child the current node.
By augmenting each node with the size of the subtree rooted there, you have all the information you need at every node to build a recursive solution.

Changing a singly linked list to a doubly linked list

I created a singly linked list function and my professor said for extra credit we can change it into a doubly linked list. I read a few things such as adding a prev_node function such as this.
class ListNode(object):
def __init__(self, item = None, prev = None, link = None):
'''creates a ListNode with the specified data value and link
post: creates a ListNode with the specified data value and link'''
self.item = item
self.prev = prev
self.link = link
However, I am confused on where to go from there. I know I need to add a tail as well as a head like I did here.
from DoublyListNode import ListNode
class LinkedList(object):
#--------------------------------------------------------------
def __init__(self, seq=()):
""" Pre: Creates a Linked List
Post: Creates a list containing the items in the seq=()"""
if seq == ():
# If there is no items to be put into the list, then it creates an empty one.
self.head = None
self.tail = None
else:
# Creates a node for the first item.
self.head = ListNode(seq[0], None)
# If there are remaining items, then they're added while keeping track of the last node.
last = self.head
for item in seq[1:]:
last.link = ListNode(item, None)
last = last.link
self.size = len(seq)
Could anyone tell me, ( NOT DO IT FOR ME), what I have to do in order to change my linkedlist into a doubly linkedlist? I know I have to reference now the tail as well as the head but I'm pretty stumped on how to do that.
I don't know Python, but here's a way to do it in Haskell:
import qualified Data.Vector as V
import Data.Vector (Vector, (!), fromList)
data DLL a = DLL {prevP :: Maybe (DLL a),
val :: a,
nextP :: Maybe (DLL a)}
list2DLL :: [a]->DLL a
list2DLL = vec2DLL . fromList
prev :: Int -> Maybe Int
prev i | i <= 0 = Nothing
prev i = Just (i-1)
next :: Int -> Int -> Maybe Int
next i lim | i < lim-1 = Just(i+1)
next _ _ = Nothing
vec2DLL :: Vector a -> DLL a
vec2DLL v = scaffold ! 0 where
scaffold = V.mapWithIndex go v
go i a = DLL (fmap (scaffold!) $ prev i)
a
(fmap (scaffold!) $ next i (length v))
I know I have to reference now the tail as well as the head but I'm pretty stumped on how to do that.
A great way to get started is to write out in English what it is you want to do:
I have a Linked List and I need to convert it to a doubly linked list. To do this, each node needs to keep track of a prev and next, and my list needs a head and tail node. When I create the list, head and tail should point at the same item. When I add a new item to my list, it should become the last node, its .prev element should be the previous .tail, and it should be the .next item for that item as well.
Once you have a description of what you want to do, then it should be relatively straightforward to translate that into code.

Categories

Resources