Module grscheller.datastructures.flarray
Module grscheller.datastructure.flarray - Fixed length array
Module implementing a data structure with a fixed length and O(1) data access. The arrays will have length > 0 and are guaranteed not to change size.
Note: None values are allowed in this data structures due to the fixed length size guarantees provided by the FLArray class.
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.flarray - Fixed length array
Module implementing a data structure with a fixed length and O(1) data
access. The arrays will have length > 0 and are guaranteed not to change size.
Note: None values are allowed in this data structures due to the
fixed length size guarantees provided by the FLArray class.
"""
from __future__ import annotations
__all__ = ['FLArray']
__author__ = "Geoffrey R. Scheller"
__copyright__ = "Copyright (c) 2023 Geoffrey R. Scheller"
__license__ = "Appache License 2.0"
from typing import Any, Callable, Never, Union
from itertools import chain
from .core.iterlib import exhaust, merge
class FLArray():
"""Class implementing a stateful fixed length array data structure of
length > 0.
Guaranteed to be of length |size| for size != 0
If size not indicated (or 0), size to data provided. Also when no data
is provided, return array with a default value and a size = 1.
If size > 0, pad data on right with a default or slice off trailing data.
If size < 0, pad data on left with a default or slice off initial data.
Permits storing None as a value.
"""
def __init__(self, *ds, size: int = 0, default: Any = None):
"""Construct a fixed length array, None values allowed."""
dlist = list(ds)
dsize = len(dlist)
match (size, abs(size) == dsize, abs(size) > dsize):
case (0, _, _):
# default to the size of the data given
if dsize > 0:
self._size = dsize
self._list = dlist
else:
# ensure FLArray not empty
self._size = 1
self._list = [default]
case (_, True, _):
# no size inconsistencies
if dsize > 0:
self._size = dsize
self._list = dlist
else:
# ensure FLArray not empty
self._size = 1
self._list = [default]
case (_, _, True):
if size > 0:
# pad higher indexes (on "right")
self._size = size
self._list = dlist + [default]*(size - dsize)
else:
# pad lower indexes (on "left")
dlist.reverse()
dlist += [default]*(-size - dsize)
dlist.reverse()
self._size = -size
self._list = dlist + [default]*(size - dsize)
case _:
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 __bool__(self):
"""Return true if all stored values are not None."""
for value in self:
if value is not None:
return True
return False
def __len__(self) -> int:
"""Returns the size of the flarray"""
return self._size
def __getitem__(self, index: int) -> Union[Any, Never]:
size = self._size
if not -size <= index < size:
l = -size
h = size - 1
msg = f'FLArray 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 not -size <= index < size:
l = -size
h = size - 1
msg = f'FLArray index = {index} not between {l} and {h}'
msg += ' while setting value'
raise IndexError(msg)
self._list[index] = value
def __iter__(self):
"""Iterate over the current dtate of the flarray. 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 flarray. Copy is
made so original source can safely mutate.
"""
for data in reversed(self._list.copy()):
yield data
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._list == other._list
def __repr__(self):
"""Display data in flarray"""
# __iter__ already makes a defensive copy
return "[|" + ", ".join(map(repr, self)) + "|]"
def __add__(self, other: FLArray) -> FLArray:
"""Add FLArrays component-wise left to right."""
if (lhs := self._size) != (rhs := other._size):
msg = 'FLArray size mismatch: '
msg += f'LHS size={lhs} but RHS size={rhs}'
raise ValueError(msg)
flarray = FLArray(size=lhs)
for ii in range(lhs):
flarray[ii] = self[ii] + other[ii]
return flarray
def copy(self) -> FLArray:
"""Return shallow copy of the flarray in O(n) time & space complexity"""
return FLArray(*self)
def reverse(self) -> None:
"""Reversed the FLArray"""
self._list.reverse()
def map(self, f: Callable[[Any], Any], mut: bool=True) -> FLArray|None:
"""Apply function over flarray contents.
Mutate the FLArray if mut=True (the default), otherwise return
a new FLArray with the mapped contents.
"""
flarray = FLArray(*map(f, self))
if mut:
self._list = flarray._list
return None
else:
return flarray
def flatMap(self, f: Callable[[Any], FLArray]) -> FLArray:
"""Apply function and flatten result, returns only a
new instance since size may change.
Merge the flarrays produced sequentially left-to-right.
"""
return FLArray(*chain(*map(iter, map(f, self))))
def mergeMap(self, f: Callable[[Any], FLArray]) -> FLArray:
"""Apply function and flatten result, returns only a instance
since size may change.
Round Robin Merge the flarrays produced until first cached
flarray is exhausted.
"""
return FLArray(*merge(*map(iter, map(f, self))))
def exhaustMap(self, f: Callable[[Any], FLArray]) -> FLArray:
"""Apply function and flatten result, returns new instance
only since size may change.
Round Robin Merge the flarrays produced until all cached
flarrays are exhausted.
"""
return FLArray(*exhaust(*map(iter, map(f, self))))
if __name__ == "__main__":
pass
Classes
class FLArray (*ds, size: int = 0, default: Any = None)
-
Class implementing a stateful fixed length array data structure of length > 0.
Guaranteed to be of length |size| for size != 0
If size not indicated (or 0), size to data provided. Also when no data is provided, return array with a default value and a size = 1.
If size > 0, pad data on right with a default or slice off trailing data.
If size < 0, pad data on left with a default or slice off initial data.
Permits storing None as a value.
Construct a fixed length array, None values allowed.
Expand source code
class FLArray(): """Class implementing a stateful fixed length array data structure of length > 0. Guaranteed to be of length |size| for size != 0 If size not indicated (or 0), size to data provided. Also when no data is provided, return array with a default value and a size = 1. If size > 0, pad data on right with a default or slice off trailing data. If size < 0, pad data on left with a default or slice off initial data. Permits storing None as a value. """ def __init__(self, *ds, size: int = 0, default: Any = None): """Construct a fixed length array, None values allowed.""" dlist = list(ds) dsize = len(dlist) match (size, abs(size) == dsize, abs(size) > dsize): case (0, _, _): # default to the size of the data given if dsize > 0: self._size = dsize self._list = dlist else: # ensure FLArray not empty self._size = 1 self._list = [default] case (_, True, _): # no size inconsistencies if dsize > 0: self._size = dsize self._list = dlist else: # ensure FLArray not empty self._size = 1 self._list = [default] case (_, _, True): if size > 0: # pad higher indexes (on "right") self._size = size self._list = dlist + [default]*(size - dsize) else: # pad lower indexes (on "left") dlist.reverse() dlist += [default]*(-size - dsize) dlist.reverse() self._size = -size self._list = dlist + [default]*(size - dsize) case _: 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 __bool__(self): """Return true if all stored values are not None.""" for value in self: if value is not None: return True return False def __len__(self) -> int: """Returns the size of the flarray""" return self._size def __getitem__(self, index: int) -> Union[Any, Never]: size = self._size if not -size <= index < size: l = -size h = size - 1 msg = f'FLArray 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 not -size <= index < size: l = -size h = size - 1 msg = f'FLArray index = {index} not between {l} and {h}' msg += ' while setting value' raise IndexError(msg) self._list[index] = value def __iter__(self): """Iterate over the current dtate of the flarray. 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 flarray. Copy is made so original source can safely mutate. """ for data in reversed(self._list.copy()): yield data 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._list == other._list def __repr__(self): """Display data in flarray""" # __iter__ already makes a defensive copy return "[|" + ", ".join(map(repr, self)) + "|]" def __add__(self, other: FLArray) -> FLArray: """Add FLArrays component-wise left to right.""" if (lhs := self._size) != (rhs := other._size): msg = 'FLArray size mismatch: ' msg += f'LHS size={lhs} but RHS size={rhs}' raise ValueError(msg) flarray = FLArray(size=lhs) for ii in range(lhs): flarray[ii] = self[ii] + other[ii] return flarray def copy(self) -> FLArray: """Return shallow copy of the flarray in O(n) time & space complexity""" return FLArray(*self) def reverse(self) -> None: """Reversed the FLArray""" self._list.reverse() def map(self, f: Callable[[Any], Any], mut: bool=True) -> FLArray|None: """Apply function over flarray contents. Mutate the FLArray if mut=True (the default), otherwise return a new FLArray with the mapped contents. """ flarray = FLArray(*map(f, self)) if mut: self._list = flarray._list return None else: return flarray def flatMap(self, f: Callable[[Any], FLArray]) -> FLArray: """Apply function and flatten result, returns only a new instance since size may change. Merge the flarrays produced sequentially left-to-right. """ return FLArray(*chain(*map(iter, map(f, self)))) def mergeMap(self, f: Callable[[Any], FLArray]) -> FLArray: """Apply function and flatten result, returns only a instance since size may change. Round Robin Merge the flarrays produced until first cached flarray is exhausted. """ return FLArray(*merge(*map(iter, map(f, self)))) def exhaustMap(self, f: Callable[[Any], FLArray]) -> FLArray: """Apply function and flatten result, returns new instance only since size may change. Round Robin Merge the flarrays produced until all cached flarrays are exhausted. """ return FLArray(*exhaust(*map(iter, map(f, self))))
Methods
def copy(self) ‑> FLArray
-
Return shallow copy of the flarray in O(n) time & space complexity
Expand source code
def copy(self) -> FLArray: """Return shallow copy of the flarray in O(n) time & space complexity""" return FLArray(*self)
def exhaustMap(self, f: Callable[[Any], FLArray]) ‑> FLArray
-
Apply function and flatten result, returns new instance only since size may change.
Round Robin Merge the flarrays produced until all cached flarrays are exhausted.
Expand source code
def exhaustMap(self, f: Callable[[Any], FLArray]) -> FLArray: """Apply function and flatten result, returns new instance only since size may change. Round Robin Merge the flarrays produced until all cached flarrays are exhausted. """ return FLArray(*exhaust(*map(iter, map(f, self))))
def flatMap(self, f: Callable[[Any], FLArray]) ‑> FLArray
-
Apply function and flatten result, returns only a new instance since size may change.
Merge the flarrays produced sequentially left-to-right.
Expand source code
def flatMap(self, f: Callable[[Any], FLArray]) -> FLArray: """Apply function and flatten result, returns only a new instance since size may change. Merge the flarrays produced sequentially left-to-right. """ return FLArray(*chain(*map(iter, map(f, self))))
def map(self, f: Callable[[Any], Any], mut: bool = True) ‑> FLArray | None
-
Apply function over flarray contents.
Mutate the FLArray if mut=True (the default), otherwise return a new FLArray with the mapped contents.
Expand source code
def map(self, f: Callable[[Any], Any], mut: bool=True) -> FLArray|None: """Apply function over flarray contents. Mutate the FLArray if mut=True (the default), otherwise return a new FLArray with the mapped contents. """ flarray = FLArray(*map(f, self)) if mut: self._list = flarray._list return None else: return flarray
def mergeMap(self, f: Callable[[Any], FLArray]) ‑> FLArray
-
Apply function and flatten result, returns only a instance since size may change.
Round Robin Merge the flarrays produced until first cached flarray is exhausted.
Expand source code
def mergeMap(self, f: Callable[[Any], FLArray]) -> FLArray: """Apply function and flatten result, returns only a instance since size may change. Round Robin Merge the flarrays produced until first cached flarray is exhausted. """ return FLArray(*merge(*map(iter, map(f, self))))
def reverse(self) ‑> None
-
Reversed the FLArray
Expand source code
def reverse(self) -> None: """Reversed the FLArray""" self._list.reverse()