-
Notifications
You must be signed in to change notification settings - Fork 1
/
linkedlist.py
288 lines (257 loc) · 11.3 KB
/
linkedlist.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
#!python
class Node(object):
def __init__(self, data):
"""Initialize this node with the given data."""
self.data = data
self.next = None
def __repr__(self):
"""Return a string representation of this node."""
return 'Node({!r})'.format(self.data)
class LinkedList(object):
def __init__(self, iterable=None):
"""Initialize this linked list and append the given items, if any."""
self.head = None # First node
self.tail = None # Last node
self.size = 0 # Number of nodes
# Append the given items
if iterable is not None:
for item in iterable:
self.append(item)
def __str__(self):
"""Return a formatted string representation of this linked list."""
items = ['({!r})'.format(item) for item in self.items()]
return '[{}]'.format(' -> '.join(items))
def __repr__(self):
"""Return a string representation of this linked list."""
return 'LinkedList({!r})'.format(self.items())
def items(self):
"""Return a list of all items in this linked list.
Best and worst case running time: Theta(n) for n items in the list
because we always need to loop through all n nodes."""
# Create an empty list of results
result = [] # Constant time to create a new list
# Start at the head node
node = self.head # Constant time to assign a variable reference
# Loop until the node is None, which is one node too far past the tail
while node is not None: # Always n iterations because no early exit
# Append this node's data to the results list
result.append(node.data) # Constant time to append to a list
# Skip to the next node
node = node.next # Constant time to reassign a variable
# Now result contains the data from all nodes
return result # Constant time to return a list
def is_empty(self):
"""Return True if this linked list is empty, or False."""
return self.head is None
def length(self):
"""Return the length of this linked list by traversing its nodes.
Best and worst case running time: ??? under what conditions? [TODO]"""
# Node counter initialized to zero
node_count = 0
# Start at the head node
node = self.head
# Loop until the node is None, which is one node too far past the tail
while node is not None:
# Count one for this node
node_count += 1
# Skip to the next node
node = node.next
# Now node_count contains the number of nodes
return node_count
def get_at_index(self, index):
"""Return the item at the given index in this linked list, or
raise ValueError if the given index is out of range of the list size.
Best case running time: O(1) under what conditions? [TODO]
Worst case running time: O(n) under what conditions? [TODO]"""
# Check if the given index is out of range and if so raise an error
if not (0 <= index < self.size):
raise ValueError('List index out of range: {}'.format(index))
# TODO: Find the node at the given index and return its data
if index == 0: # Best Case O(1)
return self.head.data
if (self.size - 1) == index: # Best Case O(1)
return self.tail.data
current_node = self.head
index_counter = 0
while current_node != None:
if index_counter == index:
return current_node.data
current_node = current_node.next
index_counter += 1
# list_of_ll = self.items() # O(n)
# return list_of_ll[index]
def insert_at_index(self, index, item):
"""Insert the given item at the given index in this linked list, or
raise ValueError if the given index is out of range of the list size.
Best case running time: ??? under what conditions? [TODO]
Worst case running time: ??? under what conditions? [TODO]"""
# Check if the given index is out of range and if so raise an error
if not (0 <= index <= self.size):
raise ValueError('List index out of range: {}'.format(index))
# TODO: Find the node before the given index and insert item after it
if self.is_empty() or self.size == 1:
self.prepend(item)
return
if index == (self.size):
self.append(item)
return
previous_node = None
current_node = self.head
new_node = Node(item)
index_counter = 0
while current_node != None:
if index_counter == index: # found
# insert new node
new_node.next = previous_node.next
previous_node.next = new_node
self.size += 1
return
previous_node = current_node
current_node = current_node.next
index_counter += 1
def append(self, item):
"""Insert the given item at the tail of this linked list.
Best and worst case running time: ??? under what conditions? [TODO]"""
# Create a new node to hold the given item
new_node = Node(item)
# Check if this linked list is empty
if self.is_empty():
# Assign head to new node
self.head = new_node
else:
# Otherwise insert new node after tail
self.tail.next = new_node
# Update tail to new node regardless
self.tail = new_node
self.size += 1
def prepend(self, item):
"""Insert the given item at the head of this linked list.
Best and worst case running time: ??? under what conditions? [TODO]"""
# Create a new node to hold the given item
new_node = Node(item)
# Check if this linked list is empty
if self.is_empty():
# Assign tail to new node
self.tail = new_node
else:
# Otherwise insert new node before head
new_node.next = self.head
# Update head to new node regardless
self.head = new_node
self.size += 1
def find(self, quality):
"""Return an item from this linked list satisfying the given quality.
Best case running time: Omega(1) if item is near the head of the list.
Worst case running time: O(n) if item is near the tail of the list or
not present and we need to loop through all n nodes in the list."""
# Start at the head node
node = self.head # Constant time to assign a variable reference
# Loop until the node is None, which is one node too far past the tail
while node is not None: # Up to n iterations if we don't exit early
# Check if this node's data satisfies the given quality function
if quality(node.data): # Constant time to call quality function
# We found data satisfying the quality function, so exit early
return node.data # Constant time to return data
# Skip to the next node
node = node.next # Constant time to reassign a variable
# We never found data satisfying quality, but have to return something
return None # Constant time to return None
def replace(self, old_item, new_item):
"""Replace the given old_item in this linked list with given new_item
using the same node, or raise ValueError if old_item is not found.
Best case running time: O(1) under what conditions? [TODO]
Worst case running time: O(n) under what conditions? [TODO]"""
# TODO: Find the node containing the given old_item and replace its
# data with new_item, without creating a new node object
current_node = self.head
if current_node.data == old_item:
current_node.data = new_item
return
if self.tail.data == old_item:
self.tail.data = new_item
return
while current_node.next != None:
if current_node.data == old_item:
current_node.data = new_item
return
current_node = current_node.next
raise ValueError('Item not found: {}'.format(old_item))
def delete(self, item):
"""Delete the given item from this linked list, or raise ValueError.
Best case running time: ??? under what conditions? [TODO]
Worst case running time: ??? under what conditions? [TODO]"""
# Start at the head node
node = self.head
# Keep track of the node before the one containing the given item
previous = None
# Create a flag to track if we have found the given item
found = False
# Loop until we have found the given item or the node is None
while not found and node is not None:
# Check if the node's data matches the given item
if node.data == item:
# We found data matching the given item, so update found flag
found = True
else:
# Skip to the next node
previous = node
node = node.next
# Check if we found the given item or we never did and reached the tail
if found:
# Check if we found a node in the middle of this linked list
if node is not self.head and node is not self.tail:
# Update the previous node to skip around the found node
previous.next = node.next
# Unlink <-(delete) the found node from its next node
node.next = None
self.size -= 1
# Check if we found a node at the head
if node is self.head:
# Update head to the next node
self.head = node.next
# Unlink the found node from the next node
node.next = None
self.size -= 1
# Check if we found a node at the tail
if node is self.tail:
# Check if there is a node before the found node
if previous is not None:
# Unlink the previous node from the found node
previous.next = None
self.size -= 1
# Update tail to the previous node regardless
self.tail = previous
else:
# Otherwise raise an error to tell the user that delete has failed
raise ValueError('Item not found: {}'.format(item))
def test_linked_list():
ll = LinkedList()
print(ll)
print('Appending items:')
ll.append('A')
print(ll)
ll.append('B')
print(ll)
ll.append('C')
print(ll)
print('head: {}'.format(ll.head))
print('tail: {}'.format(ll.tail))
print('size: {}'.format(ll.size))
print('length: {}'.format(ll.length()))
print('Getting items by index:')
for index in range(ll.size):
item = ll.get_at_index(index)
print('get_at_index({}): {!r}'.format(index, item))
print('Deleting items:')
ll.delete('B')
print(ll)
ll.delete('C')
print(ll)
ll.delete('A')
print(ll)
print('head: {}'.format(ll.head))
print('tail: {}'.format(ll.tail))
print('size: {}'.format(ll.size))
print('length: {}'.format(ll.length()))
if __name__ == '__main__':
test_linked_list()