grscheller.datastructures.tuples
Tuple based datastructures:
Tuple-like object with FP behaviors.
Tuple Types
- FTuple: Tuple-like object with FP behaviors
1# Copyright 2023-2024 Geoffrey R. Scheller 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15""" 16### Tuple based datastructures: 17 18Tuple-like object with FP behaviors. 19 20##### Tuple Types 21 22* **FTuple:** Tuple-like object with FP behaviors 23 24""" 25 26from __future__ import annotations 27 28from typing import Callable, cast, Iterator, Optional 29from grscheller.fp.iterables import FM, accumulate, concat, exhaust, merge 30 31__all__ = ['FTuple'] 32 33class FTuple[D](): 34 """ 35 #### Functional Tuple 36 37 * immutable tuple-like data structure with a functional interface 38 * supports both indexing and slicing 39 * FTuple addition & int multiplication supported 40 * addition concatenates results, types must agree 41 * both left and right multiplication supported 42 43 """ 44 __slots__ = '_ds' 45 46 def __init__(self, *ds: D): 47 self._ds = ds 48 49 def __iter__(self) -> Iterator[D]: 50 return iter(self._ds) 51 52 def __reversed__(self) -> Iterator[D]: 53 return reversed(self._ds) 54 55 def __bool__(self) -> bool: 56 return bool(len(self._ds)) 57 58 def __len__(self) -> int: 59 return len(self._ds) 60 61 def __repr__(self) -> str: 62 return 'FTuple(' + ', '.join(map(repr, self)) + ')' 63 64 def __str__(self) -> str: 65 return "((" + ", ".join(map(repr, self)) + "))" 66 67 def __eq__(self, other: object) -> bool: 68 if not isinstance(other, type(self)): 69 return False 70 return self._ds == other._ds 71 72 def __getitem__(self, sl: slice|int) -> FTuple[D]|Optional[D]: 73 if isinstance(sl, slice): 74 return FTuple(*self._ds[sl]) 75 try: 76 item = self._ds[sl] 77 except IndexError: 78 item = None 79 return item 80 81 def foldL[L](self, 82 f: Callable[[L, D], L], 83 start: Optional[L]=None, 84 default: Optional[L]=None) -> Optional[L]: 85 """ 86 **Fold Left** 87 88 * fold left with an optional starting value 89 * first argument of function `f` is for the accumulated value 90 * throws `ValueError` when FTuple empty and a start value not given 91 92 """ 93 it = iter(self._ds) 94 if start is not None: 95 acc = start 96 elif self: 97 acc = cast(L, next(it)) # L = D in this case 98 else: 99 if default is None: 100 msg = 'Both start and default cannot be None for an empty FTuple' 101 raise ValueError('FTuple.foldL - ' + msg) 102 acc = default 103 for v in it: 104 acc = f(acc, v) 105 return acc 106 107 def foldR[R](self, 108 f: Callable[[D, R], R], 109 start: Optional[R]=None, 110 default: Optional[R]=None) -> Optional[R]: 111 """ 112 **Fold Right** 113 114 * fold right with an optional starting value 115 * second argument of function `f` is for the accumulated value 116 * throws `ValueError` when FTuple empty and a start value not given 117 118 """ 119 it = reversed(self._ds) 120 if start is not None: 121 acc = start 122 elif self: 123 acc = cast(R, next(it)) # R = D in this case 124 else: 125 if default is None: 126 msg = 'Both start and default cannot be None for an empty FTuple' 127 raise ValueError('FTuple.foldR - ' + msg) 128 acc = default 129 for v in it: 130 acc = f(v, acc) 131 return acc 132 133 def copy(self) -> FTuple[D]: 134 """ 135 **Copy** 136 137 Return a shallow copy of the FTuple in O(1) time & space complexity. 138 139 """ 140 return FTuple(*self) 141 142 def __add__(self, other: FTuple[D]) -> FTuple[D]: 143 return FTuple(*concat(iter(self), other)) 144 145 def __mul__(self, num: int) -> FTuple[D]: 146 return FTuple(*self._ds.__mul__(num if num > 0 else 0)) 147 148 def __rmul__(self, num: int) -> FTuple[D]: 149 return FTuple(*self._ds.__mul__(num if num > 0 else 0)) 150 151 def accummulate(self, f: Callable[[L, D], L], s: Optional[L]=None) -> FTuple[L]: 152 """ 153 **Accumulate partial folds** 154 155 Accumulate partial fold results in an FTuple with an optional starting value. 156 157 """ 158 if s is None: 159 return FTuple(*accumulate(self, f)) 160 else: 161 return FTuple(*accumulate(self, f, s)) 162 163 def map[T](self, f: Callable[[D], T]) -> FTuple[T]: 164 return FTuple(*map(f, self)) 165 166 def flatMap[T](self, f: Callable[[D], FTuple[T]], type: FM=FM.CONCAT) -> FTuple[T]: 167 """ 168 **Bind function to FTuple** 169 170 Bind function `f` to the FTuple. 171 172 * type = CONCAT: sequentially concatenate iterables one after the other 173 * type = MERGE: merge iterables together until one is exhausted 174 * type = Exhaust: merge iterables together until all are exhausted 175 176 """ 177 match type: 178 case FM.CONCAT: 179 return FTuple(*concat(*map(lambda x: iter(x), map(f, self)))) 180 case FM.MERGE: 181 return FTuple(*merge(*map(lambda x: iter(x), map(f, self)))) 182 case FM.EXHAUST: 183 return FTuple(*exhaust(*map(lambda x: iter(x), map(f, self)))) 184 case '*': 185 raise ValueError('Unknown FM type')
class
FTuple(typing.Generic[D]):
34class FTuple[D](): 35 """ 36 #### Functional Tuple 37 38 * immutable tuple-like data structure with a functional interface 39 * supports both indexing and slicing 40 * FTuple addition & int multiplication supported 41 * addition concatenates results, types must agree 42 * both left and right multiplication supported 43 44 """ 45 __slots__ = '_ds' 46 47 def __init__(self, *ds: D): 48 self._ds = ds 49 50 def __iter__(self) -> Iterator[D]: 51 return iter(self._ds) 52 53 def __reversed__(self) -> Iterator[D]: 54 return reversed(self._ds) 55 56 def __bool__(self) -> bool: 57 return bool(len(self._ds)) 58 59 def __len__(self) -> int: 60 return len(self._ds) 61 62 def __repr__(self) -> str: 63 return 'FTuple(' + ', '.join(map(repr, self)) + ')' 64 65 def __str__(self) -> str: 66 return "((" + ", ".join(map(repr, self)) + "))" 67 68 def __eq__(self, other: object) -> bool: 69 if not isinstance(other, type(self)): 70 return False 71 return self._ds == other._ds 72 73 def __getitem__(self, sl: slice|int) -> FTuple[D]|Optional[D]: 74 if isinstance(sl, slice): 75 return FTuple(*self._ds[sl]) 76 try: 77 item = self._ds[sl] 78 except IndexError: 79 item = None 80 return item 81 82 def foldL[L](self, 83 f: Callable[[L, D], L], 84 start: Optional[L]=None, 85 default: Optional[L]=None) -> Optional[L]: 86 """ 87 **Fold Left** 88 89 * fold left with an optional starting value 90 * first argument of function `f` is for the accumulated value 91 * throws `ValueError` when FTuple empty and a start value not given 92 93 """ 94 it = iter(self._ds) 95 if start is not None: 96 acc = start 97 elif self: 98 acc = cast(L, next(it)) # L = D in this case 99 else: 100 if default is None: 101 msg = 'Both start and default cannot be None for an empty FTuple' 102 raise ValueError('FTuple.foldL - ' + msg) 103 acc = default 104 for v in it: 105 acc = f(acc, v) 106 return acc 107 108 def foldR[R](self, 109 f: Callable[[D, R], R], 110 start: Optional[R]=None, 111 default: Optional[R]=None) -> Optional[R]: 112 """ 113 **Fold Right** 114 115 * fold right with an optional starting value 116 * second argument of function `f` is for the accumulated value 117 * throws `ValueError` when FTuple empty and a start value not given 118 119 """ 120 it = reversed(self._ds) 121 if start is not None: 122 acc = start 123 elif self: 124 acc = cast(R, next(it)) # R = D in this case 125 else: 126 if default is None: 127 msg = 'Both start and default cannot be None for an empty FTuple' 128 raise ValueError('FTuple.foldR - ' + msg) 129 acc = default 130 for v in it: 131 acc = f(v, acc) 132 return acc 133 134 def copy(self) -> FTuple[D]: 135 """ 136 **Copy** 137 138 Return a shallow copy of the FTuple in O(1) time & space complexity. 139 140 """ 141 return FTuple(*self) 142 143 def __add__(self, other: FTuple[D]) -> FTuple[D]: 144 return FTuple(*concat(iter(self), other)) 145 146 def __mul__(self, num: int) -> FTuple[D]: 147 return FTuple(*self._ds.__mul__(num if num > 0 else 0)) 148 149 def __rmul__(self, num: int) -> FTuple[D]: 150 return FTuple(*self._ds.__mul__(num if num > 0 else 0)) 151 152 def accummulate(self, f: Callable[[L, D], L], s: Optional[L]=None) -> FTuple[L]: 153 """ 154 **Accumulate partial folds** 155 156 Accumulate partial fold results in an FTuple with an optional starting value. 157 158 """ 159 if s is None: 160 return FTuple(*accumulate(self, f)) 161 else: 162 return FTuple(*accumulate(self, f, s)) 163 164 def map[T](self, f: Callable[[D], T]) -> FTuple[T]: 165 return FTuple(*map(f, self)) 166 167 def flatMap[T](self, f: Callable[[D], FTuple[T]], type: FM=FM.CONCAT) -> FTuple[T]: 168 """ 169 **Bind function to FTuple** 170 171 Bind function `f` to the FTuple. 172 173 * type = CONCAT: sequentially concatenate iterables one after the other 174 * type = MERGE: merge iterables together until one is exhausted 175 * type = Exhaust: merge iterables together until all are exhausted 176 177 """ 178 match type: 179 case FM.CONCAT: 180 return FTuple(*concat(*map(lambda x: iter(x), map(f, self)))) 181 case FM.MERGE: 182 return FTuple(*merge(*map(lambda x: iter(x), map(f, self)))) 183 case FM.EXHAUST: 184 return FTuple(*exhaust(*map(lambda x: iter(x), map(f, self)))) 185 case '*': 186 raise ValueError('Unknown FM type')
Functional Tuple
- immutable tuple-like data structure with a functional interface
- supports both indexing and slicing
- FTuple addition & int multiplication supported
- addition concatenates results, types must agree
- both left and right multiplication supported
def
foldL( self, f: 'Callable[[L, D], L]', start: 'Optional[L]' = None, default: 'Optional[L]' = None) -> 'Optional[L]':
82 def foldL[L](self, 83 f: Callable[[L, D], L], 84 start: Optional[L]=None, 85 default: Optional[L]=None) -> Optional[L]: 86 """ 87 **Fold Left** 88 89 * fold left with an optional starting value 90 * first argument of function `f` is for the accumulated value 91 * throws `ValueError` when FTuple empty and a start value not given 92 93 """ 94 it = iter(self._ds) 95 if start is not None: 96 acc = start 97 elif self: 98 acc = cast(L, next(it)) # L = D in this case 99 else: 100 if default is None: 101 msg = 'Both start and default cannot be None for an empty FTuple' 102 raise ValueError('FTuple.foldL - ' + msg) 103 acc = default 104 for v in it: 105 acc = f(acc, v) 106 return acc
Fold Left
- fold left with an optional starting value
- first argument of function
f
is for the accumulated value - throws
ValueError
when FTuple empty and a start value not given
def
foldR( self, f: 'Callable[[D, R], R]', start: 'Optional[R]' = None, default: 'Optional[R]' = None) -> 'Optional[R]':
108 def foldR[R](self, 109 f: Callable[[D, R], R], 110 start: Optional[R]=None, 111 default: Optional[R]=None) -> Optional[R]: 112 """ 113 **Fold Right** 114 115 * fold right with an optional starting value 116 * second argument of function `f` is for the accumulated value 117 * throws `ValueError` when FTuple empty and a start value not given 118 119 """ 120 it = reversed(self._ds) 121 if start is not None: 122 acc = start 123 elif self: 124 acc = cast(R, next(it)) # R = D in this case 125 else: 126 if default is None: 127 msg = 'Both start and default cannot be None for an empty FTuple' 128 raise ValueError('FTuple.foldR - ' + msg) 129 acc = default 130 for v in it: 131 acc = f(v, acc) 132 return acc
Fold Right
- fold right with an optional starting value
- second argument of function
f
is for the accumulated value - throws
ValueError
when FTuple empty and a start value not given
def
copy(self) -> 'FTuple[D]':
134 def copy(self) -> FTuple[D]: 135 """ 136 **Copy** 137 138 Return a shallow copy of the FTuple in O(1) time & space complexity. 139 140 """ 141 return FTuple(*self)
Copy
Return a shallow copy of the FTuple in O(1) time & space complexity.
def
accummulate(self, f: 'Callable[[L, D], L]', s: 'Optional[L]' = None) -> 'FTuple[L]':
152 def accummulate(self, f: Callable[[L, D], L], s: Optional[L]=None) -> FTuple[L]: 153 """ 154 **Accumulate partial folds** 155 156 Accumulate partial fold results in an FTuple with an optional starting value. 157 158 """ 159 if s is None: 160 return FTuple(*accumulate(self, f)) 161 else: 162 return FTuple(*accumulate(self, f, s))
Accumulate partial folds
Accumulate partial fold results in an FTuple with an optional starting value.
def
flatMap( self, f: 'Callable[[D], FTuple[T]]', type: grscheller.fp.iterables.FM = <FM.CONCAT: 1>) -> 'FTuple[T]':
167 def flatMap[T](self, f: Callable[[D], FTuple[T]], type: FM=FM.CONCAT) -> FTuple[T]: 168 """ 169 **Bind function to FTuple** 170 171 Bind function `f` to the FTuple. 172 173 * type = CONCAT: sequentially concatenate iterables one after the other 174 * type = MERGE: merge iterables together until one is exhausted 175 * type = Exhaust: merge iterables together until all are exhausted 176 177 """ 178 match type: 179 case FM.CONCAT: 180 return FTuple(*concat(*map(lambda x: iter(x), map(f, self)))) 181 case FM.MERGE: 182 return FTuple(*merge(*map(lambda x: iter(x), map(f, self)))) 183 case FM.EXHAUST: 184 return FTuple(*exhaust(*map(lambda x: iter(x), map(f, self)))) 185 case '*': 186 raise ValueError('Unknown FM type')
Bind function to FTuple
Bind function f
to the FTuple.
- type = CONCAT: sequentially concatenate iterables one after the other
- type = MERGE: merge iterables together until one is exhausted
- type = Exhaust: merge iterables together until all are exhausted