Module grscheller.datastructures.fclarray

Module grscheller.datastructure.fclarray - constant length array.

Module implementing an mutable fixed length data structure with O(1) data access. All mutating methods are guaranteed not to change the length of the data structure.

Note: None values are not allowed in this data structures. Due to the fixed length size guarantees provided by the FCLArray class, a "default" value is needed if a None is attemped to be stored. If no default value is given, the empty tuple () is used. Once set, the default value is immutable. A copy method is provided to create a new FCLArray with a different default value.

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.

"""Module grscheller.datastructure.fclarray - constant length array.

Module implementing an mutable fixed length data structure with O(1) data
access. All mutating methods are guaranteed not to change the length of the
data structure.

Note: None values are not allowed in this data structures. Due to the
      fixed length size guarantees provided by the FCLArray class, a "default"
      value is needed if a None is attemped to be stored. If no default value
      is given, the empty tuple () is used. Once set, the default value is
      immutable. A copy method is provided to create a new FCLArray with
      a different default value.
"""

from __future__ import annotations

__all__ = ['FCLArray']
__author__ = "Geoffrey R. Scheller"
__copyright__ = "Copyright (c) 2023 Geoffrey R. Scheller"
__license__ = "Appache License 2.0"

from typing import Any, Callable, Never, Union
from .core.fp import FP

class FCLArray(FP):
    """Functional Constant Length Array

    Class implementing an mutable fixed length array data structure whose
    mutaing methods are guaranteed not to change the length of the data
    structure.

    If size set to None, size to data provided.
    If size > 0, pad data on right with default value or slice off trailing data.
    If size < 0, pad data on left with default value or slice off initial data.

    Does not permits storing None as a value. If a default value is not given,
    the empty tuple () is used. A better choice would be to generate an
    "unhappy path" monadic "subtype" with Nothing or Right().
    """
    def __init__(self, *ds, size: int|None=None, default: Any=None):
        """Construct a fixed length array, None values not allowed."""
        if default is None:
            self._default = ()
        else:
            self._default = default

        dlist = []
        for d in ds:
            if d is not None:
                dlist.append(d)
            else:
                dlist.append(self._default)
        dsize = len(dlist)

        if size is None:
            abs_size = size = dsize
        else:
            abs_size = abs(size)

        if abs_size == dsize:
            # no size inconsistencies
            self._size = dsize
            self._list = dlist
        elif abs_size > dsize:
            if size > 0:
                # pad higher indexes (on "right")
                self._size = size
                self._list = dlist + [self._default]*(size - dsize)
            else:
                # pad lower indexes (on "left")
                dlist.reverse()
                dlist += [self._default]*(-size - dsize)
                dlist.reverse()
                self._size = -size
                self._list = dlist + [self._default]*(size - dsize)
        else:
            if size > 0:
                # take left slice, ignore extra data at end
                self._size = size
                self._list = dlist[0:size]
            else:
                # take right slice, ignore extra data at beginning
                self._size = -size
                self._list = dlist[dsize+size:]

    def __iter__(self):
        """Iterate over the current state of the FCLArray. Copy is made
        so original source can safely mutate.
        """
        for data in self._list.copy():
            yield data

    def __reversed__(self):
        """Reverse iterate over the current state of the FCLArray. Copy is made
        so original source can safely mutate.
        """
        for data in reversed(self._list.copy()):
            yield data

    def __repr__(self):
        repr1 = f'{self.__class__.__name__}('
        repr2 = ', '.join(map(repr, self))
        if repr2 == '':
            repr3 = f'default={self._default})'
        else:
            repr3 = f', default={self._default})'
        return repr1 + repr2 + repr3

    def __str__(self):
        return '[|' + ', '.join(map(repr, self)) + '|]'

    def __bool__(self):
        """Return false only is all array
        values are equal to the default value.
        """
        default = self._default
        for value in self:
            if value != default:
                return True
        return False

    def __len__(self) -> int:
        """Returns the size of the FCLArray"""
        return self._size

    def __getitem__(self, index: int) -> Union[Any, Never]:
        size = self._size
        if size == 0:
            msg = 'Attempt to index an empty FCLArray'
            raise IndexError(msg)

        if index < -size or index >= size:
            l = -size
            h = size - 1
            msg = f'FCLArray index = {index} not between {l} and {h}'
            msg += ' while getting value'
            raise IndexError(msg)

        return self._list[index]

    def __setitem__(self, index: int, value: Any) -> Union[None, Never]:
        size = self._size
        if size == 0:
            msg = 'Attempt to index an empty FCLArray'
            raise IndexError(msg)

        if index < -size or index >= size:
            l = -size
            h = size - 1
            msg = f'FCLArray index = {index} not between {l} and {h}'
            msg += ' while setting value'
            raise IndexError(msg)
        
        if value is not None:
            self._list[index] = value
        else:
            self._list[index] = self._default

    def __eq__(self, other: Any):
        """Returns True if all the data stored in both compare as equal. Worst
        case is O(n) behavior for the true case. The default value play no ro;e
        in determining equality.
        """
        if not isinstance(other, type(self)):
            return False
        return self._list == other._list

    def __add__(self, other: Any) -> FCLArray:
        """Concatenate components and return new FCLArray with default value set
        to that of the LHS FCLArray"""
        if not isinstance(other, type(self)):
            msg = 'Type mismatch: FCLArrays concatenate only with other FCLArrays'
            raise ValueError(msg)
        return FCLArray(*self, *other, default=self._default)

    def copy(self, size: int|None=None, default: Any=None) -> FCLArray:
        """Return shallow copy of the FCLArray in O(n) time & space complexity.
        Optionally change the FCLArray's default value. Does not affect any
        contained values of the previous default value.
        """
        return self.map(lambda x: x, size=size, default=default)

    def default(self) -> Any:
        """Return the default value used to swap with None."""
        return self._default

    def reverse(self) -> None:
        """Reverse the elements of the FCLArray. Mutates the FCLArray."""
        self._list.reverse()

    def map(self, f: Callable[[Any], Any], size: int|None=None, default: Any=None) -> FCLArray:
        """Apply function f over the FCLArray contents. Return a new FCLArray
        with the mapped contents. Size to the data unless size is given. Use
        self._default if f returns None and a default is not given.
        """
        if default is None:
            default = self._default
        return FCLArray(*map(f, self), size=size, default=default)

    def mapSelf(self, f: Callable[[Any], Any]) -> None:
        """Mutate the CLArray by appling function over the CLArray contents."""
        self._list = FCLArray(*map(f, self), default=self._default)._list

if __name__ == "__main__":
    pass

Classes

class FCLArray (*ds, size: int | None = None, default: Any = None)

Functional Constant Length Array

Class implementing an mutable fixed length array data structure whose mutaing methods are guaranteed not to change the length of the data structure.

If size set to None, size to data provided. If size > 0, pad data on right with default value or slice off trailing data. If size < 0, pad data on left with default value or slice off initial data.

Does not permits storing None as a value. If a default value is not given, the empty tuple () is used. A better choice would be to generate an "unhappy path" monadic "subtype" with Nothing or Right().

Construct a fixed length array, None values not allowed.

Expand source code
class FCLArray(FP):
    """Functional Constant Length Array

    Class implementing an mutable fixed length array data structure whose
    mutaing methods are guaranteed not to change the length of the data
    structure.

    If size set to None, size to data provided.
    If size > 0, pad data on right with default value or slice off trailing data.
    If size < 0, pad data on left with default value or slice off initial data.

    Does not permits storing None as a value. If a default value is not given,
    the empty tuple () is used. A better choice would be to generate an
    "unhappy path" monadic "subtype" with Nothing or Right().
    """
    def __init__(self, *ds, size: int|None=None, default: Any=None):
        """Construct a fixed length array, None values not allowed."""
        if default is None:
            self._default = ()
        else:
            self._default = default

        dlist = []
        for d in ds:
            if d is not None:
                dlist.append(d)
            else:
                dlist.append(self._default)
        dsize = len(dlist)

        if size is None:
            abs_size = size = dsize
        else:
            abs_size = abs(size)

        if abs_size == dsize:
            # no size inconsistencies
            self._size = dsize
            self._list = dlist
        elif abs_size > dsize:
            if size > 0:
                # pad higher indexes (on "right")
                self._size = size
                self._list = dlist + [self._default]*(size - dsize)
            else:
                # pad lower indexes (on "left")
                dlist.reverse()
                dlist += [self._default]*(-size - dsize)
                dlist.reverse()
                self._size = -size
                self._list = dlist + [self._default]*(size - dsize)
        else:
            if size > 0:
                # take left slice, ignore extra data at end
                self._size = size
                self._list = dlist[0:size]
            else:
                # take right slice, ignore extra data at beginning
                self._size = -size
                self._list = dlist[dsize+size:]

    def __iter__(self):
        """Iterate over the current state of the FCLArray. Copy is made
        so original source can safely mutate.
        """
        for data in self._list.copy():
            yield data

    def __reversed__(self):
        """Reverse iterate over the current state of the FCLArray. Copy is made
        so original source can safely mutate.
        """
        for data in reversed(self._list.copy()):
            yield data

    def __repr__(self):
        repr1 = f'{self.__class__.__name__}('
        repr2 = ', '.join(map(repr, self))
        if repr2 == '':
            repr3 = f'default={self._default})'
        else:
            repr3 = f', default={self._default})'
        return repr1 + repr2 + repr3

    def __str__(self):
        return '[|' + ', '.join(map(repr, self)) + '|]'

    def __bool__(self):
        """Return false only is all array
        values are equal to the default value.
        """
        default = self._default
        for value in self:
            if value != default:
                return True
        return False

    def __len__(self) -> int:
        """Returns the size of the FCLArray"""
        return self._size

    def __getitem__(self, index: int) -> Union[Any, Never]:
        size = self._size
        if size == 0:
            msg = 'Attempt to index an empty FCLArray'
            raise IndexError(msg)

        if index < -size or index >= size:
            l = -size
            h = size - 1
            msg = f'FCLArray index = {index} not between {l} and {h}'
            msg += ' while getting value'
            raise IndexError(msg)

        return self._list[index]

    def __setitem__(self, index: int, value: Any) -> Union[None, Never]:
        size = self._size
        if size == 0:
            msg = 'Attempt to index an empty FCLArray'
            raise IndexError(msg)

        if index < -size or index >= size:
            l = -size
            h = size - 1
            msg = f'FCLArray index = {index} not between {l} and {h}'
            msg += ' while setting value'
            raise IndexError(msg)
        
        if value is not None:
            self._list[index] = value
        else:
            self._list[index] = self._default

    def __eq__(self, other: Any):
        """Returns True if all the data stored in both compare as equal. Worst
        case is O(n) behavior for the true case. The default value play no ro;e
        in determining equality.
        """
        if not isinstance(other, type(self)):
            return False
        return self._list == other._list

    def __add__(self, other: Any) -> FCLArray:
        """Concatenate components and return new FCLArray with default value set
        to that of the LHS FCLArray"""
        if not isinstance(other, type(self)):
            msg = 'Type mismatch: FCLArrays concatenate only with other FCLArrays'
            raise ValueError(msg)
        return FCLArray(*self, *other, default=self._default)

    def copy(self, size: int|None=None, default: Any=None) -> FCLArray:
        """Return shallow copy of the FCLArray in O(n) time & space complexity.
        Optionally change the FCLArray's default value. Does not affect any
        contained values of the previous default value.
        """
        return self.map(lambda x: x, size=size, default=default)

    def default(self) -> Any:
        """Return the default value used to swap with None."""
        return self._default

    def reverse(self) -> None:
        """Reverse the elements of the FCLArray. Mutates the FCLArray."""
        self._list.reverse()

    def map(self, f: Callable[[Any], Any], size: int|None=None, default: Any=None) -> FCLArray:
        """Apply function f over the FCLArray contents. Return a new FCLArray
        with the mapped contents. Size to the data unless size is given. Use
        self._default if f returns None and a default is not given.
        """
        if default is None:
            default = self._default
        return FCLArray(*map(f, self), size=size, default=default)

    def mapSelf(self, f: Callable[[Any], Any]) -> None:
        """Mutate the CLArray by appling function over the CLArray contents."""
        self._list = FCLArray(*map(f, self), default=self._default)._list

Ancestors

Methods

def copy(self, size: int | None = None, default: Any = None) ‑> FCLArray

Return shallow copy of the FCLArray in O(n) time & space complexity. Optionally change the FCLArray's default value. Does not affect any contained values of the previous default value.

Expand source code
def copy(self, size: int|None=None, default: Any=None) -> FCLArray:
    """Return shallow copy of the FCLArray in O(n) time & space complexity.
    Optionally change the FCLArray's default value. Does not affect any
    contained values of the previous default value.
    """
    return self.map(lambda x: x, size=size, default=default)
def default(self) ‑> Any

Return the default value used to swap with None.

Expand source code
def default(self) -> Any:
    """Return the default value used to swap with None."""
    return self._default
def map(self, f: Callable[[Any], Any], size: int | None = None, default: Any = None) ‑> FCLArray

Apply function f over the FCLArray contents. Return a new FCLArray with the mapped contents. Size to the data unless size is given. Use self._default if f returns None and a default is not given.

Expand source code
def map(self, f: Callable[[Any], Any], size: int|None=None, default: Any=None) -> FCLArray:
    """Apply function f over the FCLArray contents. Return a new FCLArray
    with the mapped contents. Size to the data unless size is given. Use
    self._default if f returns None and a default is not given.
    """
    if default is None:
        default = self._default
    return FCLArray(*map(f, self), size=size, default=default)
def mapSelf(self, f: Callable[[Any], Any]) ‑> None

Mutate the CLArray by appling function over the CLArray contents.

Expand source code
def mapSelf(self, f: Callable[[Any], Any]) -> None:
    """Mutate the CLArray by appling function over the CLArray contents."""
    self._list = FCLArray(*map(f, self), default=self._default)._list
def reverse(self) ‑> None

Reverse the elements of the FCLArray. Mutates the FCLArray.

Expand source code
def reverse(self) -> None:
    """Reverse the elements of the FCLArray. Mutates the FCLArray."""
    self._list.reverse()