The move_to_end
method is implemented as part of Python’s OrderedDict
class in the collections
module. Internally, OrderedDict
uses a combination of a hash table and a doubly linked list to maintain the order of elements, which allows efficient insertion, deletion, and reordering.
Here’s a step-by-step explanation of how move_to_end
is implemented, followed by an equivalent Python implementation:
Key Functionality
move_to_end
performs two key actions:
- Removes the specified key-value pair from its current position in the linked list.
- Re-inserts the key-value pair at the end (or at the beginning, if
last=False
is specified).
Parameters
key
: The key to be moved.last
: A boolean value indicating whether to move the key to the end (last=True
) or the beginning (last=False
).
Implementation Steps
- Locate the Node:
- Use the hash table to find the node corresponding to the given key.
- Unlink the Node:
- Remove the node from its current position in the doubly linked list by updating the pointers of its previous and next nodes.
- Re-insert the Node:
- Depending on the value of
last
, link the node either at the tail (end) or just after the head (beginning) of the linked list.
- Depending on the value of
Equivalent Implementation
Below is an equivalent Python implementation of move_to_end
:
class Node:
def __init__(self, key, value):
self.key = key
self.value = value
self.prev = None
self.next = None
class OrderedDict:
def __init__(self):
self.head = Node(None, None) # Dummy head
self.tail = Node(None, None) # Dummy tail
self.head.next = self.tail
self.tail.prev = self.head
self.map = {} # Hash table to store key-node mapping
def insert_at_end(self, node):
# Insert node before the tail
tail_prev = self.tail.prev
tail_prev.next = node
node.prev = tail_prev
node.next = self.tail
self.tail.prev = node
def insert_at_beginning(self, node):
# Insert node after the head
head_next = self.head.next
self.head.next = node
node.prev = self.head
node.next = head_next
head_next.prev = node
def unlink(self, node):
# Remove the node from the linked list
prev_node = node.prev
next_node = node.next
prev_node.next = next_node
next_node.prev = prev_node
def move_to_end(self, key, last=True):
if key not in self.map:
raise KeyError(f"Key {key} not found")
node = self.map[key]
self.unlink(node) # Remove the node from its current position
if last:
self.insert_at_end(node) # Re-insert at the end
else:
self.insert_at_beginning(node) # Re-insert at the beginning
def set(self, key, value):
# Add or update a key-value pair
if key in self.map:
node = self.map[key]
node.value = value
self.move_to_end(key)
else:
new_node = Node(key, value)
self.map[key] = new_node
self.insert_at_end(new_node)
def pop(self, key):
# Remove a key-value pair
if key not in self.map:
raise KeyError(f"Key {key} not found")
node = self.map.pop(key)
self.unlink(node)
return node.value
def __repr__(self):
# Visualize the OrderedDict for demonstration
elements = []
current = self.head.next
while current != self.tail:
elements.append((current.key, current.value))
current = current.next
return f"OrderedDict({elements})"
# Example Usage
od = OrderedDict()
od.set('a', 1)
od.set('b', 2)
od.set('c', 3)
print(od) # OrderedDict([('a', 1), ('b', 2), ('c', 3)])
od.move_to_end('a')
print(od) # OrderedDict([('b', 2), ('c', 3), ('a', 1)])
od.move_to_end('c', last=False)
print(od) # OrderedDict([('c', 3), ('b', 2), ('a', 1)])
Explanation of Key Methods
-
unlink(node)
:- Removes a node from its current position by updating the
prev
andnext
pointers of its neighboring nodes.
- Removes a node from its current position by updating the
-
insert_at_end(node)
:- Inserts a node just before the
tail
sentinel.
- Inserts a node just before the
-
insert_at_beginning(node)
:- Inserts a node just after the
head
sentinel.
- Inserts a node just after the
-
move_to_end(key, last=True)
:- Combines the unlinking and re-inserting logic based on the
last
parameter.
- Combines the unlinking and re-inserting logic based on the
Why Use a Doubly Linked List?
- Efficient Reordering:
- By maintaining pointers to the previous and next nodes, reordering operations like
move_to_end
are performed in (O(1)) time.
- By maintaining pointers to the previous and next nodes, reordering operations like
- Efficient Insertion and Deletion:
- Adding or removing nodes does not require shifting other elements, unlike an array-based approach.
This structure ensures that OrderedDict
provides efficient and flexible handling of ordered key-value pairs.
Leave a Reply