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