Module grscheller.datastructures.dqueue
Double sided queue
Module implementing a Queue with amortized O(1) insertions & deletions from either end. Obtaining length (number of elements) of a Dqueue is also a O(1) operation.
Implemented with a Python List based circular array.
Expand source code
# Copyright 2023 Geoffrey R. Scheller
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Double sided queue
Module implementing a Queue with amortized O(1) insertions & deletions from
either end. Obtaining length (number of elements) of a Dqueue is also a O(1)
operation.
Implemented with a Python List based circular array.
"""
__all__ = ['Dqueue']
__author__ = "Geoffrey R. Scheller"
__copyright__ = "Copyright (c) 2023 Geoffrey R. Scheller"
__license__ = "Appache License 2.0"
class Dqueue:
"""
Double sided queue datastructure. Will resize itself as needed.
Exceptions
----------
Does not throw exceptions. The Dqueue class consistently uses None to
represent the absence of a value. Therefore care needs to be taken when
None "values" are stored in Dqueue objects.
"""
def __init__(self, *data):
"""
Parameters
----------
*data : 'any'
Any type data, except 'None', to prepopulate the stack.
The data is pushed onto the stack left to right.
"""
size = len(data)
capacity = size + 2
self._capacity = capacity
self._count = size
self._front = 0
self._rear = (size - 1) % capacity
self._queue = list(data)
self._queue.append(None)
self._queue.append(None)
def _isFull(self):
"""Returns true if dqueue is full."""
return self._count == self._capacity
def _double(self):
"""Double capacity of dqueue."""
if self._front > self._rear:
frontPart = self._queue[self._front:]
rearPart = self._queue[:self._rear+1]
else:
frontPart = self._queue
rearPart = []
self._queue = frontPart + rearPart + [None]*(self._capacity)
self._capacity *= 2
self._front = 0
self._rear = self._count - 1
def _compact(self):
"""Compact the datastructure as much as possible."""
match self._count:
case 0:
self._queue = [None]*2
self._capacity = 2
self._front = 0
self._rear = 1
case 1:
self._queue = [self._queue[self._front], None]
self._capacity = 2
self._front = 0
self._rear = 0
case _:
if self._front > self._rear:
frontPart = self._queue[self._front:]
rearPart = self._queue[:self._rear+1]
else:
frontPart = self._queue[self._front:self._rear+1]
rearPart = []
self._queue = frontPart + rearPart
self._capacity = self._count
self._front = 0
self._rear = self._capacity - 1
def pushR(self, data):
"""Push data on rear of dqueue. Return the value being pushed."""
if self._isFull():
self._double()
self._rear = (self._rear + 1) % self._capacity
self._queue[self._rear] = data
self._count += 1
return self
def pushL(self, data):
"""Push data on front of dqueue. Return the value being pushed."""
if self._isFull():
self._double()
self._front = (self._front - 1) % self._capacity
self._queue[self._front] = data
self._count += 1
return self
def popR(self):
"""Pop data off rear of dqueue."""
if self._count == 0:
return None
else:
data = self._queue[self._rear]
self._queue[self._rear] = None
self._rear = (self._rear - 1) % self._capacity
self._count -= 1
return data
def popL(self):
"""Pop data off front of dqueue."""
if self._count == 0:
return None
else:
data = self._queue[self._front]
self._queue[self._front] = None
self._front = (self._front + 1) % self._capacity
self._count -= 1
return data
def __iter__(self):
"""Iterator yielding data stored in dequeue, does not consume data.
To export contents of the Dqueue to a list, do
myList = list(myDqueue)
"""
if self._count > 0:
pos = self._front
while pos != self._rear:
yield self._queue[pos]
pos = (pos + 1) % self._capacity
yield self._queue[pos]
def __eq__(self, other):
"""
Returns True if all the data stored on the two dqueues are the same.
Worst case is O(n) behavior for the True case.
Parameters
----------
other : 'all'
"""
if not isinstance(other, type(self)):
return False
if self._count != other._count:
return False
cnt = self._count
left = self
frontL = self._front
capL = self._capacity
right = other
frontR = other._front
capR = other._capacity
nn = 0
while nn < cnt:
if left._queue[(frontL+nn)%capL] != right._queue[(frontR+nn)%capR]:
return False
nn += 1
return True
def __repr__(self):
"""Display data in dqueue."""
dataListStrs = []
for data in self:
dataListStrs.append(repr(data))
return ">< " + " | ".join(dataListStrs) + " ><"
def __len__(self):
"""Returns current number of values in dequeue."""
return self._count
def __getitem__(self, ii):
"""Together with __len__ method, allows reversed() function to return
a reverse iterator. Also allows for fetching values via indexing
Dqueue objects, but not assigning to them. Valid indexes are from
0 <= ii < self._count. Returns 'None' for an invalid index. Indexed
values run left (front) to right (rear). If None "values" are pushed
to the dqueue, care should be taken if when checking for index errors
via a None being returned. Does not throw IndexError Exceptions.
Returns
-------
data : 'any' | None
"""
if 0 <= ii < self._count:
return self._queue[(self._front + ii) % self._capacity]
else:
return None
def isEmpty(self):
"""Returns true if the dqueue is empty."""
return self._count == 0
def capacity(self):
"""Returns current capacity of dqueue."""
return self._capacity
def fractionFilled(self):
"""Returns current capacity of dqueue."""
return self._count/self._capacity
def resize(self, addCapacity = 0):
"""Compact dqueue and add extra capacity"""
self._compact()
if addCapacity > 0:
self._queue = self._queue + [None]*addCapacity
self._capacity += addCapacity
if self._count == 0:
self._rear = self._capacity - 1
if __name__ == "__main__":
pass
Classes
class Dqueue (*data)
-
Double sided queue datastructure. Will resize itself as needed.
Exceptions
Does not throw exceptions. The Dqueue class consistently uses None to represent the absence of a value. Therefore care needs to be taken when None "values" are stored in Dqueue objects.
Parameters
*data : 'any' Any type data, except 'None', to prepopulate the stack. The data is pushed onto the stack left to right.
Expand source code
class Dqueue: """ Double sided queue datastructure. Will resize itself as needed. Exceptions ---------- Does not throw exceptions. The Dqueue class consistently uses None to represent the absence of a value. Therefore care needs to be taken when None "values" are stored in Dqueue objects. """ def __init__(self, *data): """ Parameters ---------- *data : 'any' Any type data, except 'None', to prepopulate the stack. The data is pushed onto the stack left to right. """ size = len(data) capacity = size + 2 self._capacity = capacity self._count = size self._front = 0 self._rear = (size - 1) % capacity self._queue = list(data) self._queue.append(None) self._queue.append(None) def _isFull(self): """Returns true if dqueue is full.""" return self._count == self._capacity def _double(self): """Double capacity of dqueue.""" if self._front > self._rear: frontPart = self._queue[self._front:] rearPart = self._queue[:self._rear+1] else: frontPart = self._queue rearPart = [] self._queue = frontPart + rearPart + [None]*(self._capacity) self._capacity *= 2 self._front = 0 self._rear = self._count - 1 def _compact(self): """Compact the datastructure as much as possible.""" match self._count: case 0: self._queue = [None]*2 self._capacity = 2 self._front = 0 self._rear = 1 case 1: self._queue = [self._queue[self._front], None] self._capacity = 2 self._front = 0 self._rear = 0 case _: if self._front > self._rear: frontPart = self._queue[self._front:] rearPart = self._queue[:self._rear+1] else: frontPart = self._queue[self._front:self._rear+1] rearPart = [] self._queue = frontPart + rearPart self._capacity = self._count self._front = 0 self._rear = self._capacity - 1 def pushR(self, data): """Push data on rear of dqueue. Return the value being pushed.""" if self._isFull(): self._double() self._rear = (self._rear + 1) % self._capacity self._queue[self._rear] = data self._count += 1 return self def pushL(self, data): """Push data on front of dqueue. Return the value being pushed.""" if self._isFull(): self._double() self._front = (self._front - 1) % self._capacity self._queue[self._front] = data self._count += 1 return self def popR(self): """Pop data off rear of dqueue.""" if self._count == 0: return None else: data = self._queue[self._rear] self._queue[self._rear] = None self._rear = (self._rear - 1) % self._capacity self._count -= 1 return data def popL(self): """Pop data off front of dqueue.""" if self._count == 0: return None else: data = self._queue[self._front] self._queue[self._front] = None self._front = (self._front + 1) % self._capacity self._count -= 1 return data def __iter__(self): """Iterator yielding data stored in dequeue, does not consume data. To export contents of the Dqueue to a list, do myList = list(myDqueue) """ if self._count > 0: pos = self._front while pos != self._rear: yield self._queue[pos] pos = (pos + 1) % self._capacity yield self._queue[pos] def __eq__(self, other): """ Returns True if all the data stored on the two dqueues are the same. Worst case is O(n) behavior for the True case. Parameters ---------- other : 'all' """ if not isinstance(other, type(self)): return False if self._count != other._count: return False cnt = self._count left = self frontL = self._front capL = self._capacity right = other frontR = other._front capR = other._capacity nn = 0 while nn < cnt: if left._queue[(frontL+nn)%capL] != right._queue[(frontR+nn)%capR]: return False nn += 1 return True def __repr__(self): """Display data in dqueue.""" dataListStrs = [] for data in self: dataListStrs.append(repr(data)) return ">< " + " | ".join(dataListStrs) + " ><" def __len__(self): """Returns current number of values in dequeue.""" return self._count def __getitem__(self, ii): """Together with __len__ method, allows reversed() function to return a reverse iterator. Also allows for fetching values via indexing Dqueue objects, but not assigning to them. Valid indexes are from 0 <= ii < self._count. Returns 'None' for an invalid index. Indexed values run left (front) to right (rear). If None "values" are pushed to the dqueue, care should be taken if when checking for index errors via a None being returned. Does not throw IndexError Exceptions. Returns ------- data : 'any' | None """ if 0 <= ii < self._count: return self._queue[(self._front + ii) % self._capacity] else: return None def isEmpty(self): """Returns true if the dqueue is empty.""" return self._count == 0 def capacity(self): """Returns current capacity of dqueue.""" return self._capacity def fractionFilled(self): """Returns current capacity of dqueue.""" return self._count/self._capacity def resize(self, addCapacity = 0): """Compact dqueue and add extra capacity""" self._compact() if addCapacity > 0: self._queue = self._queue + [None]*addCapacity self._capacity += addCapacity if self._count == 0: self._rear = self._capacity - 1
Methods
def capacity(self)
-
Returns current capacity of dqueue.
Expand source code
def capacity(self): """Returns current capacity of dqueue.""" return self._capacity
def fractionFilled(self)
-
Returns current capacity of dqueue.
Expand source code
def fractionFilled(self): """Returns current capacity of dqueue.""" return self._count/self._capacity
def isEmpty(self)
-
Returns true if the dqueue is empty.
Expand source code
def isEmpty(self): """Returns true if the dqueue is empty.""" return self._count == 0
def popL(self)
-
Pop data off front of dqueue.
Expand source code
def popL(self): """Pop data off front of dqueue.""" if self._count == 0: return None else: data = self._queue[self._front] self._queue[self._front] = None self._front = (self._front + 1) % self._capacity self._count -= 1 return data
def popR(self)
-
Pop data off rear of dqueue.
Expand source code
def popR(self): """Pop data off rear of dqueue.""" if self._count == 0: return None else: data = self._queue[self._rear] self._queue[self._rear] = None self._rear = (self._rear - 1) % self._capacity self._count -= 1 return data
def pushL(self, data)
-
Push data on front of dqueue. Return the value being pushed.
Expand source code
def pushL(self, data): """Push data on front of dqueue. Return the value being pushed.""" if self._isFull(): self._double() self._front = (self._front - 1) % self._capacity self._queue[self._front] = data self._count += 1 return self
def pushR(self, data)
-
Push data on rear of dqueue. Return the value being pushed.
Expand source code
def pushR(self, data): """Push data on rear of dqueue. Return the value being pushed.""" if self._isFull(): self._double() self._rear = (self._rear + 1) % self._capacity self._queue[self._rear] = data self._count += 1 return self
def resize(self, addCapacity=0)
-
Compact dqueue and add extra capacity
Expand source code
def resize(self, addCapacity = 0): """Compact dqueue and add extra capacity""" self._compact() if addCapacity > 0: self._queue = self._queue + [None]*addCapacity self._capacity += addCapacity if self._count == 0: self._rear = self._capacity - 1