Module grscheller.datastructures.queues

Queue based datastructures.

  • stateful queue data structures with amortized O(1) pushes and pops
  • obtaining length (number of elements) of a queue is an O(1) operation
  • implemented with Python List based circular array in a "has-a" relationship
  • these data structures will resize themselves as needed
  • these queues are O(1) indexible, convenient but ignorable feature
  • Python's None is not stored in these data structures as a value

Types of Queues:

  • class FIFOQueue: First In, First Out Queue
  • class LIFOQueue: Last In, First Out Queue
  • class DoubleQueue: Double Ended Queue
Expand source code
# Copyright 2023-2024 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.

"""Queue based datastructures.

* stateful queue data structures with amortized O(1) pushes and pops
* obtaining length (number of elements) of a queue is an O(1) operation
* implemented with Python List based circular array in a "has-a" relationship
* these data structures will resize themselves as needed
* these queues are O(1) indexible, convenient but ignorable feature
* Python's `None` is not stored in these data structures as a value

Types of Queues:

* class **FIFOQueue**: First In, First Out Queue
* class **LIFOQueue**: Last In, First Out Queue
* class **DoubleQueue**: Double Ended Queue
"""

from __future__ import annotations

__all__ = ['DoubleQueue', 'FIFOQueue', 'LIFOQueue']
__author__ = "Geoffrey R. Scheller"
__copyright__ = "Copyright (c) 2023-2024 Geoffrey R. Scheller"
__license__ = "Apache License 2.0"

from typing import Any, Callable
from .core.fp import FP
from grscheller.circular_array import CircularArray

class QueueBase():
    """Abstract base class for stateful queue-based data structures

    * primarily for DRY implementation inheritance of queue type classes
    * derived classes used will resize themselves as needed
    * each queue object "has-a" (contains) a circular array to store its data
    * None is not stored as a value on Queue data structures
    """
    __slots__ = '_ca',

    def __init__(self, *ds):
        """Construct a queue data structure. Cull None values."""
        self._ca = CircularArray()
        for d in ds:
            if d is not None:
                self._ca.pushR(d)

    def __iter__(self):
        """Iterator yielding data currently stored in queue. Data yielded in
        natural FIFO order.
        """
        cached = self._ca.copy()
        for pos in range(len(cached)):
            yield cached[pos]

    def __reversed__(self):
        """Reverse iterate over the current state of the queue."""
        cached = self._ca.copy()
        for pos in range(len(cached)-1, -1, -1):
            yield cached[pos]

    def __repr__(self):
        return f'{self.__class__.__name__}(' + ', '.join(map(repr, self)) + ')'

    def __bool__(self):
        """Returns true if queue is not empty."""
        return len(self._ca) > 0

    def __len__(self):
        """Returns current number of values in queue."""
        return len(self._ca)

    def __eq__(self, other):
        """Returns True if all the data stored in both compare as equal.
        Worst case is O(n) behavior for the true case.
        """
        if not isinstance(other, type(self)):
            return False
        return self._ca == other._ca

    def map(self, f: Callable[[Any], Any]) -> None:
        """Apply function over the queue's contents. Suppress any None values
        returned by f.
        """
        self._ca = QueueBase(*map(f, self))._ca

    def reverse(self):
        """Reverse the elements in the Queue"""
        self._ca = self._ca.reverse()

    def __getitem__(self, index: int) -> Any:
        cnt = len(self)
        if -cnt <= index < cnt:
            return self._ca[index]
        else:
            return None

    def __setitem__(self, index: int, value):
        typePath = 'grscheller.datastructures.queues.'
        queueType = lambda queue: str(type(queue)).split(typePath)[-1].partition("'")[0]

        cnt = len(self)
        if -cnt <= index < cnt:
            if value is not None:
                self._ca[index] = value
            else:
                msg = f'None values are not allowed in {queueType(self)} queues.'
                raise ValueError(msg)
        else:
            if cnt > 0:
                msg1 = f'Out of bounds: '
                msg2 = f'index = {index} not from {-cnt} to {cnt-1} '
                msg3 = f'while setting value from a {queueType(self)}.'
                raise IndexError(msg1 + msg2 + msg3)
            else:
                msg0 = f'Trying to set value from an empty {queueType(self)}.'
                raise IndexError(msg0)

class FIFOQueue(QueueBase, FP):
    """Stateful single sided FIFO data structure. Will resize itself as needed. `None`
    represents the absence of a value and ignored if pushed onto the queue.
    """
    __slots__ = ()

    def __str__(self):
        return "<< " + " < ".join(map(str, self)) + " <<"

    def copy(self) -> FIFOQueue:
        """Return shallow copy of the FIFOQueue in O(n) time & space complexity."""
        fifoqueue = FIFOQueue()
        fifoqueue._ca = self._ca.copy()
        return fifoqueue

    def push(self, *ds: Any) -> None:
        """Push data on rear of the FIFOQueue & no return value."""
        for d in ds:
            if d != None:
                self._ca.pushR(d)

    def pop(self) -> Any:
        """Pop data off front of the FIFOQueue."""
        return self._ca.popL()

    def peakLastIn(self) -> Any:
        """Return last element pushed to the FIFOQueue without consuming it"""
        if self._ca:
            return self._ca[-1]
        else:
            return None

    def peakNextOut(self) -> Any:
        """Return next element ready to pop from the FIFOQueue."""
        if self._ca:
            return self._ca[0]
        else:
            return None

class LIFOQueue(QueueBase, FP):
    """Stateful single sided LIFO data structure. Will resize itself as needed. `None`
    represents the absence of a value and ignored if pushed onto the queue.
    """
    __slots__ = ()

    def __str__(self):
        return "|| " + " > ".join(map(str, self)) + " ><"

    def copy(self) -> LIFOQueue:
        """Return shallow copy of the FIFOQueue in O(n) time & space complexity."""
        lifoqueue = LIFOQueue()
        lifoqueue._ca = self._ca.copy()
        return lifoqueue

    def push(self, *ds: Any) -> None:
        """Push data on rear of the LIFOQueue & no return value."""
        for d in ds:
            if d != None:
                self._ca.pushR(d)

    def pop(self) -> Any:
        """Pop data off rear of the LIFOQueue."""
        return self._ca.popR()

    def peak(self) -> Any:
        """Return last element pushed to the LIFOQueue without consuming it."""
        if self._ca:
            return self._ca[-1]
        else:
            return None

class DoubleQueue(QueueBase, FP):
    """Stateful double sided queue datastructure. Will resize itself as needed. `None`
    represents the absence of a value and ignored if pushed onto the queue.
    """
    __slots__ = ()

    def __str__(self):
        return ">< " + " | ".join(map(str, self)) + " ><"

    def copy(self) -> DoubleQueue:
        """Return shallow copy of the DoubleQueue in O(n) time & space complexity."""
        dqueue = DoubleQueue()
        dqueue._ca = self._ca.copy()
        return dqueue

    def pushR(self, *ds: Any) -> None:
        """Push data left to right onto rear of the DoubleQueue."""
        for d in ds:
            if d != None:
                self._ca.pushR(d)

    def pushL(self, *ds: Any) -> None:
        """Push data left to right onto front of DoubleQueue."""
        for d in ds:
            if d != None:
                self._ca.pushL(d)

    def popR(self) -> Any:
        """Pop data off rear of the DoubleQueue."""
        return self._ca.popR()

    def popL(self) -> Any:
        """Pop data off front of the DoubleQueue."""
        return self._ca.popL()

    def peakR(self) -> Any:
        """Return right-most element of the DoubleQueue if it exists."""
        if self._ca:
            return self._ca[-1]
        else:
            return None

    def peakL(self) -> Any:
        """Return left-most element of the DoubleQueue if it exists."""
        if self._ca:
            return self._ca[0]
        else:
            return None

Classes

class DoubleQueue (*ds)

Stateful double sided queue datastructure. Will resize itself as needed. None represents the absence of a value and ignored if pushed onto the queue.

Construct a queue data structure. Cull None values.

Expand source code
class DoubleQueue(QueueBase, FP):
    """Stateful double sided queue datastructure. Will resize itself as needed. `None`
    represents the absence of a value and ignored if pushed onto the queue.
    """
    __slots__ = ()

    def __str__(self):
        return ">< " + " | ".join(map(str, self)) + " ><"

    def copy(self) -> DoubleQueue:
        """Return shallow copy of the DoubleQueue in O(n) time & space complexity."""
        dqueue = DoubleQueue()
        dqueue._ca = self._ca.copy()
        return dqueue

    def pushR(self, *ds: Any) -> None:
        """Push data left to right onto rear of the DoubleQueue."""
        for d in ds:
            if d != None:
                self._ca.pushR(d)

    def pushL(self, *ds: Any) -> None:
        """Push data left to right onto front of DoubleQueue."""
        for d in ds:
            if d != None:
                self._ca.pushL(d)

    def popR(self) -> Any:
        """Pop data off rear of the DoubleQueue."""
        return self._ca.popR()

    def popL(self) -> Any:
        """Pop data off front of the DoubleQueue."""
        return self._ca.popL()

    def peakR(self) -> Any:
        """Return right-most element of the DoubleQueue if it exists."""
        if self._ca:
            return self._ca[-1]
        else:
            return None

    def peakL(self) -> Any:
        """Return left-most element of the DoubleQueue if it exists."""
        if self._ca:
            return self._ca[0]
        else:
            return None

Ancestors

  • grscheller.datastructures.queues.QueueBase
  • FP

Methods

def copy(self) ‑> DoubleQueue

Return shallow copy of the DoubleQueue in O(n) time & space complexity.

Expand source code
def copy(self) -> DoubleQueue:
    """Return shallow copy of the DoubleQueue in O(n) time & space complexity."""
    dqueue = DoubleQueue()
    dqueue._ca = self._ca.copy()
    return dqueue
def peakL(self) ‑> Any

Return left-most element of the DoubleQueue if it exists.

Expand source code
def peakL(self) -> Any:
    """Return left-most element of the DoubleQueue if it exists."""
    if self._ca:
        return self._ca[0]
    else:
        return None
def peakR(self) ‑> Any

Return right-most element of the DoubleQueue if it exists.

Expand source code
def peakR(self) -> Any:
    """Return right-most element of the DoubleQueue if it exists."""
    if self._ca:
        return self._ca[-1]
    else:
        return None
def popL(self) ‑> Any

Pop data off front of the DoubleQueue.

Expand source code
def popL(self) -> Any:
    """Pop data off front of the DoubleQueue."""
    return self._ca.popL()
def popR(self) ‑> Any

Pop data off rear of the DoubleQueue.

Expand source code
def popR(self) -> Any:
    """Pop data off rear of the DoubleQueue."""
    return self._ca.popR()
def pushL(self, *ds: Any) ‑> None

Push data left to right onto front of DoubleQueue.

Expand source code
def pushL(self, *ds: Any) -> None:
    """Push data left to right onto front of DoubleQueue."""
    for d in ds:
        if d != None:
            self._ca.pushL(d)
def pushR(self, *ds: Any) ‑> None

Push data left to right onto rear of the DoubleQueue.

Expand source code
def pushR(self, *ds: Any) -> None:
    """Push data left to right onto rear of the DoubleQueue."""
    for d in ds:
        if d != None:
            self._ca.pushR(d)

Inherited members

class FIFOQueue (*ds)

Stateful single sided FIFO data structure. Will resize itself as needed. None represents the absence of a value and ignored if pushed onto the queue.

Construct a queue data structure. Cull None values.

Expand source code
class FIFOQueue(QueueBase, FP):
    """Stateful single sided FIFO data structure. Will resize itself as needed. `None`
    represents the absence of a value and ignored if pushed onto the queue.
    """
    __slots__ = ()

    def __str__(self):
        return "<< " + " < ".join(map(str, self)) + " <<"

    def copy(self) -> FIFOQueue:
        """Return shallow copy of the FIFOQueue in O(n) time & space complexity."""
        fifoqueue = FIFOQueue()
        fifoqueue._ca = self._ca.copy()
        return fifoqueue

    def push(self, *ds: Any) -> None:
        """Push data on rear of the FIFOQueue & no return value."""
        for d in ds:
            if d != None:
                self._ca.pushR(d)

    def pop(self) -> Any:
        """Pop data off front of the FIFOQueue."""
        return self._ca.popL()

    def peakLastIn(self) -> Any:
        """Return last element pushed to the FIFOQueue without consuming it"""
        if self._ca:
            return self._ca[-1]
        else:
            return None

    def peakNextOut(self) -> Any:
        """Return next element ready to pop from the FIFOQueue."""
        if self._ca:
            return self._ca[0]
        else:
            return None

Ancestors

  • grscheller.datastructures.queues.QueueBase
  • FP

Methods

def copy(self) ‑> FIFOQueue

Return shallow copy of the FIFOQueue in O(n) time & space complexity.

Expand source code
def copy(self) -> FIFOQueue:
    """Return shallow copy of the FIFOQueue in O(n) time & space complexity."""
    fifoqueue = FIFOQueue()
    fifoqueue._ca = self._ca.copy()
    return fifoqueue
def peakLastIn(self) ‑> Any

Return last element pushed to the FIFOQueue without consuming it

Expand source code
def peakLastIn(self) -> Any:
    """Return last element pushed to the FIFOQueue without consuming it"""
    if self._ca:
        return self._ca[-1]
    else:
        return None
def peakNextOut(self) ‑> Any

Return next element ready to pop from the FIFOQueue.

Expand source code
def peakNextOut(self) -> Any:
    """Return next element ready to pop from the FIFOQueue."""
    if self._ca:
        return self._ca[0]
    else:
        return None
def pop(self) ‑> Any

Pop data off front of the FIFOQueue.

Expand source code
def pop(self) -> Any:
    """Pop data off front of the FIFOQueue."""
    return self._ca.popL()
def push(self, *ds: Any) ‑> None

Push data on rear of the FIFOQueue & no return value.

Expand source code
def push(self, *ds: Any) -> None:
    """Push data on rear of the FIFOQueue & no return value."""
    for d in ds:
        if d != None:
            self._ca.pushR(d)

Inherited members

class LIFOQueue (*ds)

Stateful single sided LIFO data structure. Will resize itself as needed. None represents the absence of a value and ignored if pushed onto the queue.

Construct a queue data structure. Cull None values.

Expand source code
class LIFOQueue(QueueBase, FP):
    """Stateful single sided LIFO data structure. Will resize itself as needed. `None`
    represents the absence of a value and ignored if pushed onto the queue.
    """
    __slots__ = ()

    def __str__(self):
        return "|| " + " > ".join(map(str, self)) + " ><"

    def copy(self) -> LIFOQueue:
        """Return shallow copy of the FIFOQueue in O(n) time & space complexity."""
        lifoqueue = LIFOQueue()
        lifoqueue._ca = self._ca.copy()
        return lifoqueue

    def push(self, *ds: Any) -> None:
        """Push data on rear of the LIFOQueue & no return value."""
        for d in ds:
            if d != None:
                self._ca.pushR(d)

    def pop(self) -> Any:
        """Pop data off rear of the LIFOQueue."""
        return self._ca.popR()

    def peak(self) -> Any:
        """Return last element pushed to the LIFOQueue without consuming it."""
        if self._ca:
            return self._ca[-1]
        else:
            return None

Ancestors

  • grscheller.datastructures.queues.QueueBase
  • FP

Methods

def copy(self) ‑> LIFOQueue

Return shallow copy of the FIFOQueue in O(n) time & space complexity.

Expand source code
def copy(self) -> LIFOQueue:
    """Return shallow copy of the FIFOQueue in O(n) time & space complexity."""
    lifoqueue = LIFOQueue()
    lifoqueue._ca = self._ca.copy()
    return lifoqueue
def peak(self) ‑> Any

Return last element pushed to the LIFOQueue without consuming it.

Expand source code
def peak(self) -> Any:
    """Return last element pushed to the LIFOQueue without consuming it."""
    if self._ca:
        return self._ca[-1]
    else:
        return None
def pop(self) ‑> Any

Pop data off rear of the LIFOQueue.

Expand source code
def pop(self) -> Any:
    """Pop data off rear of the LIFOQueue."""
    return self._ca.popR()
def push(self, *ds: Any) ‑> None

Push data on rear of the LIFOQueue & no return value.

Expand source code
def push(self, *ds: Any) -> None:
    """Push data on rear of the LIFOQueue & no return value."""
    for d in ds:
        if d != None:
            self._ca.pushR(d)

Inherited members