grscheller.datastructures.tuples
Functional Tuple
FTuple
Immutable Tuple-like data structure with a functional interfaces.
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### Functional Tuple 17 18##### FTuple 19 20Immutable Tuple-like data structure with a functional interfaces. 21 22--- 23 24""" 25 26from __future__ import annotations 27 28from enum import auto, Enum 29from typing import Callable, Iterator, Generic, Optional, TypeVar 30from grscheller.fp.iterables import FM, accumulate, concat, exhaust, merge 31 32__all__ = ['FTuple'] 33 34T = TypeVar('T') 35S = TypeVar('S') 36 37class FTuple(Generic[T]): 38 """ 39 #### Class FTuple 40 41 Implements a Tuple-like object with FP behaviors. 42 43 """ 44 __slots__ = '_ds' 45 46 def __init__(self, *ds: T): 47 self._ds = ds 48 49 def __iter__(self) -> Iterator[T]: 50 return iter(self._ds) 51 52 def __reversed__(self) -> Iterator[T]: 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[T]|Optional[T]: 73 """Supports both indexing and slicing.""" 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(self, f: Callable[[S, T], S], start: Optional[S]=None, default: Optional[S]=None) -> S: 83 """ 84 ##### Fold Left 85 86 * fold left with an optional starting value 87 * first argument of function f is for the accumulated value 88 * if empty, return `start` if given, otherwise raise ValueError 89 90 """ 91 it = iter(self._ds) 92 if start is not None: 93 acc = start 94 elif not self: 95 if default is None: 96 msg = 'Both start and default cannot be None for an empty FTuple' 97 raise ValueError('FTuple.foldL - ' + msg) 98 acc = default 99 else: 100 acc = next(it) # type: ignore # in this case _S == _T 101 for v in it: 102 acc = f(acc, v) 103 return acc 104 105 def foldR(self, f: Callable[[T, S], S], start: Optional[S]=None, default: Optional[S]=None) -> S: 106 """ 107 ##### Fold Right 108 109 * fold right with an optional starting value 110 * second argument of function f is for the accumulated value 111 * if empty, return `start` if given, otherwise raise ValueError 112 113 """ 114 it = reversed(self._ds) 115 if start is not None: 116 acc = start 117 elif not self: 118 if default is None: 119 msg = 'Both start and default cannot be None for an empty FTuple' 120 raise ValueError('FTuple.foldR - ' + msg) 121 acc = default 122 else: 123 acc = next(it) # type: ignore # in this case _S == _T 124 for v in it: 125 acc = f(v, acc) 126 return acc 127 128 def copy(self) -> FTuple[T]: 129 """ 130 ##### Shallow Copy 131 132 Return shallow copy of the FTuple in O(1) time & space complexity. 133 134 """ 135 return FTuple(*self) 136 137 def map(self, f: Callable[[T], S]) -> FTuple[S]: 138 return FTuple(*map(f, self)) 139 140 def __add__(self, other: FTuple[T]) -> FTuple[T]: 141 """ 142 ##### Concatenate two FTuples 143 144 """ 145 return FTuple(*concat(iter(self), other)) 146 147 def __mul__(self, num: int) -> FTuple[T]: 148 """ 149 ##### Mult by int 150 151 Return an FTuple which repeats another FTuple num times. 152 153 """ 154 return FTuple(*self._ds.__mul__(num if num > 0 else 0)) 155 156 def accummulate(self, f: Callable[[S, T], S], s: Optional[S]=None) -> FTuple[S]: 157 """ 158 ##### Accumulate 159 160 Accumulate partial fold results in an FTuple with an optional starting value. 161 162 """ 163 if s is None: 164 return FTuple(*accumulate(self, f)) 165 else: 166 return FTuple(*accumulate(self, f, s)) 167 168 def flatMap(self, f: Callable[[T], FTuple[S]], type: FM=FM.CONCAT) -> FTuple[S]: 169 """ 170 ##### Bind function to FTuple 171 172 Bind function `f` to the FTuple. 173 174 * type = CONCAT: sequentially concatenate iterables one after the other 175 * type = MERGE: merge iterables together until one is exhausted 176 * type = Exhaust: merge iterables together until all are exhausted 177 178 """ 179 match type: 180 case FM.CONCAT: 181 return FTuple(*concat(*map(lambda x: iter(x), map(f, self)))) 182 case FM.MERGE: 183 return FTuple(*merge(*map(lambda x: iter(x), map(f, self)))) 184 case FM.EXHAUST: 185 return FTuple(*exhaust(*map(lambda x: iter(x), map(f, self)))) 186 case '*': 187 raise ValueError('Unknown FM type')
class
FTuple(typing.Generic[~T]):
38class FTuple(Generic[T]): 39 """ 40 #### Class FTuple 41 42 Implements a Tuple-like object with FP behaviors. 43 44 """ 45 __slots__ = '_ds' 46 47 def __init__(self, *ds: T): 48 self._ds = ds 49 50 def __iter__(self) -> Iterator[T]: 51 return iter(self._ds) 52 53 def __reversed__(self) -> Iterator[T]: 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[T]|Optional[T]: 74 """Supports both indexing and slicing.""" 75 if isinstance(sl, slice): 76 return FTuple(*self._ds[sl]) 77 try: 78 item = self._ds[sl] 79 except IndexError: 80 item = None 81 return item 82 83 def foldL(self, f: Callable[[S, T], S], start: Optional[S]=None, default: Optional[S]=None) -> S: 84 """ 85 ##### Fold Left 86 87 * fold left with an optional starting value 88 * first argument of function f is for the accumulated value 89 * if empty, return `start` if given, otherwise raise ValueError 90 91 """ 92 it = iter(self._ds) 93 if start is not None: 94 acc = start 95 elif not self: 96 if default is None: 97 msg = 'Both start and default cannot be None for an empty FTuple' 98 raise ValueError('FTuple.foldL - ' + msg) 99 acc = default 100 else: 101 acc = next(it) # type: ignore # in this case _S == _T 102 for v in it: 103 acc = f(acc, v) 104 return acc 105 106 def foldR(self, f: Callable[[T, S], S], start: Optional[S]=None, default: Optional[S]=None) -> S: 107 """ 108 ##### Fold Right 109 110 * fold right with an optional starting value 111 * second argument of function f is for the accumulated value 112 * if empty, return `start` if given, otherwise raise ValueError 113 114 """ 115 it = reversed(self._ds) 116 if start is not None: 117 acc = start 118 elif not self: 119 if default is None: 120 msg = 'Both start and default cannot be None for an empty FTuple' 121 raise ValueError('FTuple.foldR - ' + msg) 122 acc = default 123 else: 124 acc = next(it) # type: ignore # in this case _S == _T 125 for v in it: 126 acc = f(v, acc) 127 return acc 128 129 def copy(self) -> FTuple[T]: 130 """ 131 ##### Shallow Copy 132 133 Return shallow copy of the FTuple in O(1) time & space complexity. 134 135 """ 136 return FTuple(*self) 137 138 def map(self, f: Callable[[T], S]) -> FTuple[S]: 139 return FTuple(*map(f, self)) 140 141 def __add__(self, other: FTuple[T]) -> FTuple[T]: 142 """ 143 ##### Concatenate two FTuples 144 145 """ 146 return FTuple(*concat(iter(self), other)) 147 148 def __mul__(self, num: int) -> FTuple[T]: 149 """ 150 ##### Mult by int 151 152 Return an FTuple which repeats another FTuple num times. 153 154 """ 155 return FTuple(*self._ds.__mul__(num if num > 0 else 0)) 156 157 def accummulate(self, f: Callable[[S, T], S], s: Optional[S]=None) -> FTuple[S]: 158 """ 159 ##### Accumulate 160 161 Accumulate partial fold results in an FTuple with an optional starting value. 162 163 """ 164 if s is None: 165 return FTuple(*accumulate(self, f)) 166 else: 167 return FTuple(*accumulate(self, f, s)) 168 169 def flatMap(self, f: Callable[[T], FTuple[S]], type: FM=FM.CONCAT) -> FTuple[S]: 170 """ 171 ##### Bind function to FTuple 172 173 Bind function `f` to the FTuple. 174 175 * type = CONCAT: sequentially concatenate iterables one after the other 176 * type = MERGE: merge iterables together until one is exhausted 177 * type = Exhaust: merge iterables together until all are exhausted 178 179 """ 180 match type: 181 case FM.CONCAT: 182 return FTuple(*concat(*map(lambda x: iter(x), map(f, self)))) 183 case FM.MERGE: 184 return FTuple(*merge(*map(lambda x: iter(x), map(f, self)))) 185 case FM.EXHAUST: 186 return FTuple(*exhaust(*map(lambda x: iter(x), map(f, self)))) 187 case '*': 188 raise ValueError('Unknown FM type')
Class FTuple
Implements a Tuple-like object with FP behaviors.
def
foldL( self, f: Callable[[~S, ~T], ~S], start: Optional[~S] = None, default: Optional[~S] = None) -> ~S:
83 def foldL(self, f: Callable[[S, T], S], start: Optional[S]=None, default: Optional[S]=None) -> S: 84 """ 85 ##### Fold Left 86 87 * fold left with an optional starting value 88 * first argument of function f is for the accumulated value 89 * if empty, return `start` if given, otherwise raise ValueError 90 91 """ 92 it = iter(self._ds) 93 if start is not None: 94 acc = start 95 elif not self: 96 if default is None: 97 msg = 'Both start and default cannot be None for an empty FTuple' 98 raise ValueError('FTuple.foldL - ' + msg) 99 acc = default 100 else: 101 acc = next(it) # type: ignore # in this case _S == _T 102 for v in it: 103 acc = f(acc, v) 104 return acc
Fold Left
- fold left with an optional starting value
- first argument of function f is for the accumulated value
- if empty, return
start
if given, otherwise raise ValueError
def
foldR( self, f: Callable[[~T, ~S], ~S], start: Optional[~S] = None, default: Optional[~S] = None) -> ~S:
106 def foldR(self, f: Callable[[T, S], S], start: Optional[S]=None, default: Optional[S]=None) -> S: 107 """ 108 ##### Fold Right 109 110 * fold right with an optional starting value 111 * second argument of function f is for the accumulated value 112 * if empty, return `start` if given, otherwise raise ValueError 113 114 """ 115 it = reversed(self._ds) 116 if start is not None: 117 acc = start 118 elif not self: 119 if default is None: 120 msg = 'Both start and default cannot be None for an empty FTuple' 121 raise ValueError('FTuple.foldR - ' + msg) 122 acc = default 123 else: 124 acc = next(it) # type: ignore # in this case _S == _T 125 for v in it: 126 acc = f(v, acc) 127 return acc
Fold Right
- fold right with an optional starting value
- second argument of function f is for the accumulated value
- if empty, return
start
if given, otherwise raise ValueError
129 def copy(self) -> FTuple[T]: 130 """ 131 ##### Shallow Copy 132 133 Return shallow copy of the FTuple in O(1) time & space complexity. 134 135 """ 136 return FTuple(*self)
Shallow Copy
Return shallow copy of the FTuple in O(1) time & space complexity.
157 def accummulate(self, f: Callable[[S, T], S], s: Optional[S]=None) -> FTuple[S]: 158 """ 159 ##### Accumulate 160 161 Accumulate partial fold results in an FTuple with an optional starting value. 162 163 """ 164 if s is None: 165 return FTuple(*accumulate(self, f)) 166 else: 167 return FTuple(*accumulate(self, f, s))
Accumulate
Accumulate partial fold results in an FTuple with an optional starting value.
def
flatMap( self, f: Callable[[~T], FTuple[~S]], type: grscheller.fp.iterables.FM = <FM.CONCAT: 1>) -> FTuple[~S]:
169 def flatMap(self, f: Callable[[T], FTuple[S]], type: FM=FM.CONCAT) -> FTuple[S]: 170 """ 171 ##### Bind function to FTuple 172 173 Bind function `f` to the FTuple. 174 175 * type = CONCAT: sequentially concatenate iterables one after the other 176 * type = MERGE: merge iterables together until one is exhausted 177 * type = Exhaust: merge iterables together until all are exhausted 178 179 """ 180 match type: 181 case FM.CONCAT: 182 return FTuple(*concat(*map(lambda x: iter(x), map(f, self)))) 183 case FM.MERGE: 184 return FTuple(*merge(*map(lambda x: iter(x), map(f, self)))) 185 case FM.EXHAUST: 186 return FTuple(*exhaust(*map(lambda x: iter(x), map(f, self)))) 187 case '*': 188 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