xtuples.xtuples

  1# ---------------------------------------------------------------
  2
  3import abc
  4import typing
  5import dataclasses
  6import collections
  7
  8import operator
  9import itertools
 10import functools
 11
 12# ---------------------------------------------------------------
 13
 14# NOTE: at worst, not worse, than an un-optimised canonical solution
 15
 16# where I cribbed from the itertools recipes (and other python docs), all credit to the original authors.
 17
 18# where i didn't, i probably should have.
 19
 20# ---------------------------------------------------------------
 21
 22REGISTRY = {}
 23
 24# ---------------------------------------------------------------
 25
 26def pipe(f, obj, *args, at = None, **kwargs):
 27    if at is None:
 28        return f(obj, *args, **kwargs)
 29    elif isinstance(at, int):
 30        return f(*args[:at], obj, *args[at:], **kwargs)
 31    elif isinstance(at, str):
 32        return f(*args, **{at: obj}, **kwargs)
 33    else:
 34        assert False, at
 35
 36# ---------------------------------------------------------------
 37
 38
 39# TODO: some kind of validation placeholder?
 40# called in init, eg. quarter in [1 .. 4]
 41
 42class nTuple(abc.ABC):
 43
 44    @abc.abstractmethod
 45    def __abstract__(self):
 46        # NOTE: here to prevent initialise instances of this
 47        # but rather use the decorator and typing.NamedTuple
 48        return
 49
 50    @staticmethod
 51    def pipe(obj, f, *args, at = None, **kwargs):
 52        """
 53        >>> example = Example(1, "a")
 54        >>> example.pipe(lambda a, b: a, None)
 55        Example(x=1, s='a', it=iTuple())
 56        >>> example.pipe(lambda a, b: a, None, at = 1)
 57        >>> example.pipe(lambda a, b: a, None, at = 'b')
 58        >>> example.pipe(lambda a, b: a, a=None, at = 'b')
 59        >>> example.pipe(lambda a, b: a, b=None, at = 'a')
 60        Example(x=1, s='a', it=iTuple())
 61        >>> example.pipe(lambda a, b: a, None, at = 0)
 62        Example(x=1, s='a', it=iTuple())
 63        """
 64        return pipe(f, obj, *args, at = at, **kwargs)
 65
 66    @staticmethod
 67    def partial(obj, f, *args, **kwargs):
 68        return functools.partial(f, obj, *args, **kwargs)
 69
 70    @classmethod
 71    def is_subclass(cls, t):
 72        """
 73        >>> nTuple.is_subclass(tuple)
 74        False
 75        >>> nTuple.is_subclass(Example(1, "a"))
 76        False
 77        >>> nTuple.is_subclass(Example)
 78        True
 79        """
 80        try:
 81            is_sub = issubclass(t, tuple)
 82        except:
 83            is_sub = False
 84        return (
 85            is_sub and
 86            hasattr(t, "cls") and
 87            hasattr(t, "pipe") and
 88            hasattr(t, "partial")
 89        )
 90
 91    @classmethod
 92    def is_instance(cls, obj):
 93        """
 94        >>> nTuple.is_instance(tuple)
 95        False
 96        >>> nTuple.is_instance(Example)
 97        False
 98        >>> nTuple.is_instance(Example(1, "a"))
 99        True
100        """
101        return (
102            cls.is_subclass(type(obj)) and
103            hasattr(obj, '_asdict') and
104            hasattr(obj, '_fields')
105        )
106
107
108    @staticmethod
109    def annotations(obj):
110        """
111        >>> ex = Example(1, "a")
112        >>> ex.pipe(ex.cls.annotations)
113        {'x': <class 'int'>, 's': <class 'str'>, 'it': <class 'xtuples.xtuples.iTuple'>}
114        """
115        return fDict(obj.__annotations__)
116
117    @classmethod
118    def as_dict(cls, obj):
119        """
120        >>> ex = Example(1, "a")
121        >>> ex.pipe(ex.cls.as_dict)
122        {'x': 1, 's': 'a', 'it': iTuple()}
123        """
124        return fDict(obj._asdict())
125
126    @classmethod
127    def decorate(meta, cls):
128        assert cls.__name__ not in REGISTRY
129        cls.pipe = meta.pipe
130        cls.partial = meta.partial
131        cls.cls = meta
132        REGISTRY[cls.__name__] = cls
133        return cls
134
135# ---------------------------------------------------------------
136
137class fDict(collections.UserDict):
138    __slots__ = ()
139
140    data: dict
141
142    def pipe(self, f, *args, at=None, **kwargs):
143        """
144        >>> fDict({0: 1}).pipe(lambda d: d.map_values(
145        ...     lambda v: v + 1
146        ... ))
147        {0: 2}
148        """
149        res = pipe(f, self, *args, at = at, **kwargs)
150        if isinstance(res, dict):
151            return fDict(res)
152        return res
153
154    def partial(self, f, *args, **kwargs):
155        """
156        >>> f = fDict({0: 1}).partial(
157        ...     lambda d, n: d.map_values(lambda v: v + n)
158        ... )
159        >>> f(1)
160        {0: 2}
161        >>> f(2)
162        {0: 3}
163        """
164        return functools.partial(f, self, *args, **kwargs)
165
166    def keys_tuple(self):
167        """
168        >>> fDict({0: 1}).keys_tuple()
169        iTuple(0)
170        """
171        return iTuple.from_keys(self)
172
173    def values_tuple(self):
174        """
175        >>> fDict({0: 1}).values_tuple()
176        iTuple(1)
177        """
178        return iTuple.from_values(self)
179    
180    def items_tuple(self):
181        """
182        >>> fDict({0: 1}).items_tuple()
183        iTuple((0, 1))
184        """
185        return iTuple.from_items(self)
186
187    # NOTE: we have separate map implementations 
188    # as they are constant size, dict to dict
189    # other iterator functions should use iTuple (from the above)
190
191    def map_keys(self, f, *args, **kwargs):
192        """
193        >>> fDict({0: 1}).map_keys(lambda v: v + 1)
194        {1: 1}
195        """
196        return fDict(
197            (f(k, *args, **kwargs), v) for k, v in self.items()
198        )
199
200    def map_values(self, f, *args, **kwargs):
201        """
202        >>> fDict({0: 1}).map_values(lambda v: v + 1)
203        {0: 2}
204        """
205        return fDict(
206            (k, f(v, *args, **kwargs)) for k, v in self.items()
207        )
208
209    def map_items(self, f, *args, **kwargs):
210        """
211        >>> fDict({0: 1}).map_items(lambda k, v: (v, k))
212        {1: 0}
213        """
214        return fDict(
215            f(k, v, *args, **kwargs) for k, v in self.items()
216        )
217
218    def invert(self):
219        """
220        >>> fDict({0: 1}).invert()
221        {1: 0}
222        """
223        return fDict((v, k) for k, v in self.items())
224
225# ---------------------------------------------------------------
226
227@dataclasses.dataclass(init = False, repr=True)
228class iTuple(collections.UserList, tuple): # type: ignore
229    __slots__ = ()
230
231    data: tuple # type: ignore
232
233    # -----
234
235    @staticmethod
236    def __new__(cls, data = None):
237        # NOTE: we use cls not array
238        # so sub-classing *does* change identity
239        if isinstance(data, cls):
240            return data
241        return super().__new__(cls, data=data)
242
243    @staticmethod
244    def wrap_tuple(data):
245        return data if isinstance(data, tuple) else tuple(data)
246    
247    def __init__(self, data = None):
248        # TODO: option for lazy init?
249        self.data = (
250            tuple() if data is None
251            else self.wrap_tuple(data)
252        )
253
254    def __repr__(self):
255        s = super().__repr__()
256        return "{}({})".format(
257            type(self).__name__,
258            s[1:-2 if s[-2] == "," else -1],
259        )
260
261    def __hash__(self):
262        return hash(self.data)
263
264    @classmethod
265    def decorate(meta, cls):
266        assert cls.__name__ not in REGISTRY
267        REGISTRY[cls.__name__] = cls
268        return cls
269
270    # -----
271
272    @classmethod
273    def range(cls, *args, **kwargs):
274        """
275        >>> iTuple.range(3)
276        iTuple(0, 1, 2)
277        """
278        return cls(range(*args, **kwargs))
279
280    @classmethod
281    def from_keys(cls, d):
282        """
283        >>> iTuple.from_keys({i: i + 1 for i in range(2)})
284        iTuple(0, 1)
285        """
286        return cls(d.keys())
287        
288    @classmethod
289    def from_values(cls, d):
290        """
291        >>> iTuple.from_values({i: i + 1 for i in range(2)})
292        iTuple(1, 2)
293        """
294        return cls(d.values())
295        
296    @classmethod
297    def from_items(cls, d):
298        """
299        >>> iTuple.from_items({i: i + 1 for i in range(2)})
300        iTuple((0, 1), (1, 2))
301        """
302        return cls(d.items())
303
304    # -----
305
306    def pipe(self, f, *args, at = None, **kwargs):
307        """
308        >>> iTuple.range(2).pipe(lambda it: it)
309        iTuple(0, 1)
310        >>> iTuple.range(2).pipe(
311        ...     lambda it, v: it.map(lambda x: x * v), 2
312        ... )
313        iTuple(0, 2)
314        """
315        return pipe(f, self, *args, at = at, **kwargs)
316
317    def partial(self, f, *args, **kwargs):
318        """
319        >>> f = iTuple.range(2).partial(
320        ...     lambda it, v: it.map(lambda x: x * v)
321        ... )
322        >>> f(2)
323        iTuple(0, 2)
324        >>> f(3)
325        iTuple(0, 3)
326        """
327        return functools.partial(f, self, *args, **kwargs)
328
329    # -----
330
331    def len(self):
332        """
333        >>> iTuple.range(3).len()
334        3
335        """
336        return len(self)
337
338    def append(self, value, *values):
339        """
340        >>> iTuple.range(1).append(1)
341        iTuple(0, 1)
342        >>> iTuple.range(1).append(1, 2)
343        iTuple(0, 1, 2)
344        >>> iTuple.range(1).append(1, 2, 3)
345        iTuple(0, 1, 2, 3)
346        >>> iTuple.range(1).append(1, (2,))
347        iTuple(0, 1, (2,))
348        """
349        return self + (value, *values)
350
351    def prepend(self, value, *values):
352        """
353        >>> iTuple.range(1).prepend(1)
354        iTuple(1, 0)
355        >>> iTuple.range(1).prepend(1, 2)
356        iTuple(1, 2, 0)
357        >>> iTuple.range(1).prepend(1, 2, 3)
358        iTuple(1, 2, 3, 0)
359        >>> iTuple.range(1).prepend(1, (2,))
360        iTuple(1, (2,), 0)
361        """
362        return (value, *values) + self
363
364    def zip(self, *itrs, lazy = False):
365        """
366        >>> iTuple([[1, 1], [2, 2], [3, 3]]).zip()
367        iTuple((1, 2, 3), (1, 2, 3))
368        >>> iTuple([iTuple.range(3), iTuple.range(1, 4)]).zip()
369        iTuple((0, 1), (1, 2), (2, 3))
370        >>> iTuple.range(3).zip(iTuple.range(1, 4))
371        iTuple((0, 1), (1, 2), (2, 3))
372        """
373        if len(itrs) == 0:
374            res = zip(*self)
375        else:
376            res = zip(self, *itrs)
377        return res if lazy else iTuple(data=res)
378
379    def flatten(self):
380        """
381        >>> iTuple.range(3).map(lambda x: [x]).flatten()
382        iTuple(0, 1, 2)
383        """
384        return iTuple(itertools.chain(*self))
385
386    def extend(self, value, *values):
387        """
388        >>> iTuple.range(1).extend((1,))
389        iTuple(0, 1)
390        >>> iTuple.range(1).extend([1])
391        iTuple(0, 1)
392        >>> iTuple.range(1).extend([1], [2])
393        iTuple(0, 1, 2)
394        >>> iTuple.range(1).extend([1], [[2]])
395        iTuple(0, 1, [2])
396        >>> iTuple.range(1).extend([1], [[2]], [2])
397        iTuple(0, 1, [2], 2)
398        """
399        return iTuple(itertools.chain.from_iterable(
400            (self, value, *values)
401        ))
402
403    def pretend(self, value, *values):
404        """
405        >>> iTuple.range(1).pretend((1,))
406        iTuple(1, 0)
407        >>> iTuple.range(1).pretend([1])
408        iTuple(1, 0)
409        >>> iTuple.range(1).pretend([1], [2])
410        iTuple(1, 2, 0)
411        >>> iTuple.range(1).pretend([1], [[2]])
412        iTuple(1, [2], 0)
413        >>> iTuple.range(1).pretend([1], [[2]], [2])
414        iTuple(1, [2], 2, 0)
415        """
416        return iTuple(itertools.chain.from_iterable(
417            (value, *values, self)
418        ))
419
420    def filter_eq(self, v, f = None, eq = None, lazy = False):
421        """
422        >>> iTuple.range(3).filter_eq(1)
423        iTuple(1)
424        """
425        if f is None and eq is None:
426            res = filter(lambda x: x == v, self)
427        elif f is not None:
428            res = filter(lambda x: f(x) == v, self)
429        elif eq is not None:
430            res = filter(lambda x: eq(x, v), self)
431        else:
432            res = filter(lambda x: eq(f(x), v), self)
433        return res if lazy else type(self)(data=res)
434
435    def filter(self, f, eq = None, lazy = False):
436        """
437        >>> iTuple.range(3).filter(lambda x: x > 1)
438        iTuple(2)
439        """
440        return self.filter_eq(True, f = f, eq = eq, lazy = lazy)
441
442    def map(self, f, *iterables, lazy = False):
443        """
444        >>> iTuple.range(3).map(lambda x: x * 2)
445        iTuple(0, 2, 4)
446        """
447        res = map(f, self, *iterables)
448        return res if lazy else iTuple(data=res)
449
450    def enumerate(self):
451        """
452        >>> iTuple.range(3).enumerate()
453        iTuple((0, 0), (1, 1), (2, 2))
454        """
455        # TODO: allow lazy
456        return iTuple(enumerate(self))
457
458    def groupby(
459        self, 
460        f, 
461        lazy = False, 
462        keys = False,
463        pipe= None,
464    ):
465        """
466        >>> iTuple.range(3).groupby(lambda x: x < 2)
467        iTuple((0, 1), (2,))
468        >>> iTuple.range(3).groupby(
469        ...    lambda x: x < 2, keys=True, pipe=fDict
470        ... )
471        {True: (0, 1), False: (2,)}
472        """
473        # TODO: lazy no keys
474        res = itertools.groupby(self, key=f)
475        if lazy and keys and pipe is None:
476            return res
477        if pipe is None:
478            pipe = iTuple
479        if keys:
480            return pipe((k, tuple(g),) for k, g in res)
481        else:
482            return pipe(tuple(g) for k, g in res)
483
484    def first(self):
485        """
486        >>> iTuple.range(3).first()
487        0
488        """
489        return self[0]
490    
491    def last(self):
492        """
493        >>> iTuple.range(3).last()
494        2
495        """
496        return self[-1]
497
498    def first_where(self, f):
499        """
500        >>> iTuple.range(3).first_where(lambda v: v > 0)
501        1
502        """
503        for v in self:
504            if f(v):
505                return v
506        return None
507
508    def last_where(self, f):
509        """
510        >>> iTuple.range(3).last_where(lambda v: v < 2)
511        1
512        """
513        for v in reversed(self):
514            if f(v):
515                return v
516        return None
517
518    def take(self, n):
519        """
520        >>> iTuple.range(3).take(2)
521        iTuple(0, 1)
522        """
523        return self[:n]
524
525    def tail(self, n):
526        """
527        >>> iTuple.range(3).tail(2)
528        iTuple(1, 2)
529        """
530        return self[-n:]
531
532    def reverse(self, lazy = False):
533        """
534        >>> iTuple.range(3).reverse()
535        iTuple(2, 1, 0)
536        """
537        if lazy:
538            return reversed(self)
539        return type(self)(data=reversed(self))
540
541    def take_while(self, f, n = None, lazy = False):
542        """
543        >>> iTuple.range(3).take_while(lambda v: v < 1)
544        iTuple(0)
545        """
546        def iter():
547            i = 0
548            for v in self:
549                if f(v) and (n is None or i < n):
550                    yield v
551                    i += 1
552                else:
553                    return
554        res = iter()
555        return res if lazy else type(self)(data=res)
556
557    def tail_while(self, f, n = None):
558        """
559        >>> iTuple.range(3).tail_while(lambda v: v > 1)
560        iTuple(2)
561        """
562        i = 0
563        for v in reversed(self):
564            if f(v) and (n is None or i < n):
565                i += 1
566            else:
567                break
568        return self.tail(i)
569
570    # NOTE: from as in, starting from first true
571    # versus above, which is until first false
572    def take_after(self, f, n = None, lazy = False):
573        """
574        >>> iTuple.range(3).take_after(lambda v: v < 1)
575        iTuple(1, 2)
576        >>> iTuple.range(3).take_after(lambda v: v < 1, n = 1)
577        iTuple(1)
578        """
579        def iter():
580            i = 0
581            for v in self:
582                if f(v):
583                    pass
584                elif n is None or i < n:
585                    yield v
586                    i += 1
587                else:
588                    return
589        res = iter()
590        return res if lazy else type(self)(data=res)
591
592    def tail_after(self, f, n = None):
593        """
594        >>> iTuple.range(3).tail_after(lambda v: v < 2)
595        iTuple(0, 1)
596        >>> iTuple.range(3).tail_after(lambda v: v < 2, 1)
597        iTuple(1)
598        """
599        l = 0
600        r = 0
601        for v in reversed(self):
602            if not f(v):
603                l += 1
604            elif n is None or r < n:
605                r += 1
606            else:
607                break
608        return self.tail(l + r).take(r)
609
610    def islice(self, left = None, right = None):
611        """
612        >>> iTuple.range(5).islice(1, 3)
613        iTuple(1, 2)
614        """
615        return self[left:right]
616
617    def unique(self):
618        """
619        >>> iTuple([1, 1, 3, 2, 4, 2, 3]).unique()
620        iTuple(1, 3, 2, 4)
621        """
622        def iter():
623            seen = set()
624            seen_add = seen.add
625            seen_contains = seen.__contains__
626            for v in itertools.filterfalse(seen_contains, self):
627                seen_add(v)
628                yield v
629        return type(self)(data=iter())
630    
631    def sort(self, f = lambda v: v):
632        """
633        >>> iTuple.range(3).reverse().sort()
634        iTuple(0, 1, 2)
635        >>> iTuple.range(3).sort()
636        iTuple(0, 1, 2)
637        """
638        return type(self)(data=sorted(self, key = f))
639
640    def accumulate(self, f, initial = None, lazy = False):
641        """
642        >>> iTuple.range(3).accumulate(lambda acc, v: v)
643        iTuple(0, 1, 2)
644        >>> iTuple.range(3).accumulate(lambda acc, v: v, initial=0)
645        iTuple(0, 0, 1, 2)
646        >>> iTuple.range(3).accumulate(operator.add)
647        iTuple(0, 1, 3)
648        """
649        res = itertools.accumulate(self, func=f, initial=initial)
650        return res if lazy else iTuple(data=res)
651
652    def foldcum(self, *args, **kwargs):
653        """
654        >>> iTuple.range(3).foldcum(lambda acc, v: v)
655        iTuple(0, 1, 2)
656        >>> iTuple.range(3).foldcum(operator.add)
657        iTuple(0, 1, 3)
658        """
659        return self.accumulate(*args, **kwargs)
660
661    def fold(self, f, initial=None):
662        """
663        >>> iTuple.range(3).fold(lambda acc, v: v)
664        2
665        >>> iTuple.range(3).fold(lambda acc, v: v, initial=0)
666        2
667        >>> iTuple.range(3).fold(operator.add)
668        3
669        """
670        if initial is not None:
671            res = functools.reduce(f, self, initial)
672        else:
673            res = functools.reduce(f, self)
674        return res
675
676    # -----
677
678    # combinatorics
679
680    # -----
681
682# ---------------------------------------------------------------
683
684@nTuple.decorate
685class Example(typing.NamedTuple):
686    """
687    >>> ex = Example(1, "a")
688    >>> ex
689    Example(x=1, s='a', it=iTuple())
690    >>> ex.cls
691    <class 'xtuples.xtuples.nTuple'>
692    >>> ex.pipe(lambda nt: nt.x)
693    1
694    >>> f = ex.partial(lambda nt, v: nt.x * v)
695    >>> f(2)
696    2
697    >>> f(3)
698    3
699    """
700    # NOTE: cls, pipe, partial are mandatory boilerplate
701
702    x: int
703    s: str
704    it: iTuple = iTuple([])
705
706    @property
707    def cls(self):
708        ...
709
710    def pipe(self, f, *args, at = None, **kwargs):
711        ...
712
713    def partial(self, f, *args, at = None, **kwargs):
714        ...
715
716# ---------------------------------------------------------------
717
718# TODO: context manager to control
719# if we add the type information when writing to json or not
720
721# TODO: context mananger to control
722# lazy default behaviour (ie. default to lazy or not)
723
724__all__ = [
725    "iTuple",
726    "nTuple",
727    "fDict",
728    "Example",
729]
730
731# ---------------------------------------------------------------
@dataclasses.dataclass(init=False, repr=True)
class iTuple(collections.UserList, builtins.tuple):
229@dataclasses.dataclass(init = False, repr=True)
230class iTuple(collections.UserList, tuple): # type: ignore
231    __slots__ = ()
232
233    data: tuple # type: ignore
234
235    # -----
236
237    @staticmethod
238    def __new__(cls, data = None):
239        # NOTE: we use cls not array
240        # so sub-classing *does* change identity
241        if isinstance(data, cls):
242            return data
243        return super().__new__(cls, data=data)
244
245    @staticmethod
246    def wrap_tuple(data):
247        return data if isinstance(data, tuple) else tuple(data)
248    
249    def __init__(self, data = None):
250        # TODO: option for lazy init?
251        self.data = (
252            tuple() if data is None
253            else self.wrap_tuple(data)
254        )
255
256    def __repr__(self):
257        s = super().__repr__()
258        return "{}({})".format(
259            type(self).__name__,
260            s[1:-2 if s[-2] == "," else -1],
261        )
262
263    def __hash__(self):
264        return hash(self.data)
265
266    @classmethod
267    def decorate(meta, cls):
268        assert cls.__name__ not in REGISTRY
269        REGISTRY[cls.__name__] = cls
270        return cls
271
272    # -----
273
274    @classmethod
275    def range(cls, *args, **kwargs):
276        """
277        >>> iTuple.range(3)
278        iTuple(0, 1, 2)
279        """
280        return cls(range(*args, **kwargs))
281
282    @classmethod
283    def from_keys(cls, d):
284        """
285        >>> iTuple.from_keys({i: i + 1 for i in range(2)})
286        iTuple(0, 1)
287        """
288        return cls(d.keys())
289        
290    @classmethod
291    def from_values(cls, d):
292        """
293        >>> iTuple.from_values({i: i + 1 for i in range(2)})
294        iTuple(1, 2)
295        """
296        return cls(d.values())
297        
298    @classmethod
299    def from_items(cls, d):
300        """
301        >>> iTuple.from_items({i: i + 1 for i in range(2)})
302        iTuple((0, 1), (1, 2))
303        """
304        return cls(d.items())
305
306    # -----
307
308    def pipe(self, f, *args, at = None, **kwargs):
309        """
310        >>> iTuple.range(2).pipe(lambda it: it)
311        iTuple(0, 1)
312        >>> iTuple.range(2).pipe(
313        ...     lambda it, v: it.map(lambda x: x * v), 2
314        ... )
315        iTuple(0, 2)
316        """
317        return pipe(f, self, *args, at = at, **kwargs)
318
319    def partial(self, f, *args, **kwargs):
320        """
321        >>> f = iTuple.range(2).partial(
322        ...     lambda it, v: it.map(lambda x: x * v)
323        ... )
324        >>> f(2)
325        iTuple(0, 2)
326        >>> f(3)
327        iTuple(0, 3)
328        """
329        return functools.partial(f, self, *args, **kwargs)
330
331    # -----
332
333    def len(self):
334        """
335        >>> iTuple.range(3).len()
336        3
337        """
338        return len(self)
339
340    def append(self, value, *values):
341        """
342        >>> iTuple.range(1).append(1)
343        iTuple(0, 1)
344        >>> iTuple.range(1).append(1, 2)
345        iTuple(0, 1, 2)
346        >>> iTuple.range(1).append(1, 2, 3)
347        iTuple(0, 1, 2, 3)
348        >>> iTuple.range(1).append(1, (2,))
349        iTuple(0, 1, (2,))
350        """
351        return self + (value, *values)
352
353    def prepend(self, value, *values):
354        """
355        >>> iTuple.range(1).prepend(1)
356        iTuple(1, 0)
357        >>> iTuple.range(1).prepend(1, 2)
358        iTuple(1, 2, 0)
359        >>> iTuple.range(1).prepend(1, 2, 3)
360        iTuple(1, 2, 3, 0)
361        >>> iTuple.range(1).prepend(1, (2,))
362        iTuple(1, (2,), 0)
363        """
364        return (value, *values) + self
365
366    def zip(self, *itrs, lazy = False):
367        """
368        >>> iTuple([[1, 1], [2, 2], [3, 3]]).zip()
369        iTuple((1, 2, 3), (1, 2, 3))
370        >>> iTuple([iTuple.range(3), iTuple.range(1, 4)]).zip()
371        iTuple((0, 1), (1, 2), (2, 3))
372        >>> iTuple.range(3).zip(iTuple.range(1, 4))
373        iTuple((0, 1), (1, 2), (2, 3))
374        """
375        if len(itrs) == 0:
376            res = zip(*self)
377        else:
378            res = zip(self, *itrs)
379        return res if lazy else iTuple(data=res)
380
381    def flatten(self):
382        """
383        >>> iTuple.range(3).map(lambda x: [x]).flatten()
384        iTuple(0, 1, 2)
385        """
386        return iTuple(itertools.chain(*self))
387
388    def extend(self, value, *values):
389        """
390        >>> iTuple.range(1).extend((1,))
391        iTuple(0, 1)
392        >>> iTuple.range(1).extend([1])
393        iTuple(0, 1)
394        >>> iTuple.range(1).extend([1], [2])
395        iTuple(0, 1, 2)
396        >>> iTuple.range(1).extend([1], [[2]])
397        iTuple(0, 1, [2])
398        >>> iTuple.range(1).extend([1], [[2]], [2])
399        iTuple(0, 1, [2], 2)
400        """
401        return iTuple(itertools.chain.from_iterable(
402            (self, value, *values)
403        ))
404
405    def pretend(self, value, *values):
406        """
407        >>> iTuple.range(1).pretend((1,))
408        iTuple(1, 0)
409        >>> iTuple.range(1).pretend([1])
410        iTuple(1, 0)
411        >>> iTuple.range(1).pretend([1], [2])
412        iTuple(1, 2, 0)
413        >>> iTuple.range(1).pretend([1], [[2]])
414        iTuple(1, [2], 0)
415        >>> iTuple.range(1).pretend([1], [[2]], [2])
416        iTuple(1, [2], 2, 0)
417        """
418        return iTuple(itertools.chain.from_iterable(
419            (value, *values, self)
420        ))
421
422    def filter_eq(self, v, f = None, eq = None, lazy = False):
423        """
424        >>> iTuple.range(3).filter_eq(1)
425        iTuple(1)
426        """
427        if f is None and eq is None:
428            res = filter(lambda x: x == v, self)
429        elif f is not None:
430            res = filter(lambda x: f(x) == v, self)
431        elif eq is not None:
432            res = filter(lambda x: eq(x, v), self)
433        else:
434            res = filter(lambda x: eq(f(x), v), self)
435        return res if lazy else type(self)(data=res)
436
437    def filter(self, f, eq = None, lazy = False):
438        """
439        >>> iTuple.range(3).filter(lambda x: x > 1)
440        iTuple(2)
441        """
442        return self.filter_eq(True, f = f, eq = eq, lazy = lazy)
443
444    def map(self, f, *iterables, lazy = False):
445        """
446        >>> iTuple.range(3).map(lambda x: x * 2)
447        iTuple(0, 2, 4)
448        """
449        res = map(f, self, *iterables)
450        return res if lazy else iTuple(data=res)
451
452    def enumerate(self):
453        """
454        >>> iTuple.range(3).enumerate()
455        iTuple((0, 0), (1, 1), (2, 2))
456        """
457        # TODO: allow lazy
458        return iTuple(enumerate(self))
459
460    def groupby(
461        self, 
462        f, 
463        lazy = False, 
464        keys = False,
465        pipe= None,
466    ):
467        """
468        >>> iTuple.range(3).groupby(lambda x: x < 2)
469        iTuple((0, 1), (2,))
470        >>> iTuple.range(3).groupby(
471        ...    lambda x: x < 2, keys=True, pipe=fDict
472        ... )
473        {True: (0, 1), False: (2,)}
474        """
475        # TODO: lazy no keys
476        res = itertools.groupby(self, key=f)
477        if lazy and keys and pipe is None:
478            return res
479        if pipe is None:
480            pipe = iTuple
481        if keys:
482            return pipe((k, tuple(g),) for k, g in res)
483        else:
484            return pipe(tuple(g) for k, g in res)
485
486    def first(self):
487        """
488        >>> iTuple.range(3).first()
489        0
490        """
491        return self[0]
492    
493    def last(self):
494        """
495        >>> iTuple.range(3).last()
496        2
497        """
498        return self[-1]
499
500    def first_where(self, f):
501        """
502        >>> iTuple.range(3).first_where(lambda v: v > 0)
503        1
504        """
505        for v in self:
506            if f(v):
507                return v
508        return None
509
510    def last_where(self, f):
511        """
512        >>> iTuple.range(3).last_where(lambda v: v < 2)
513        1
514        """
515        for v in reversed(self):
516            if f(v):
517                return v
518        return None
519
520    def take(self, n):
521        """
522        >>> iTuple.range(3).take(2)
523        iTuple(0, 1)
524        """
525        return self[:n]
526
527    def tail(self, n):
528        """
529        >>> iTuple.range(3).tail(2)
530        iTuple(1, 2)
531        """
532        return self[-n:]
533
534    def reverse(self, lazy = False):
535        """
536        >>> iTuple.range(3).reverse()
537        iTuple(2, 1, 0)
538        """
539        if lazy:
540            return reversed(self)
541        return type(self)(data=reversed(self))
542
543    def take_while(self, f, n = None, lazy = False):
544        """
545        >>> iTuple.range(3).take_while(lambda v: v < 1)
546        iTuple(0)
547        """
548        def iter():
549            i = 0
550            for v in self:
551                if f(v) and (n is None or i < n):
552                    yield v
553                    i += 1
554                else:
555                    return
556        res = iter()
557        return res if lazy else type(self)(data=res)
558
559    def tail_while(self, f, n = None):
560        """
561        >>> iTuple.range(3).tail_while(lambda v: v > 1)
562        iTuple(2)
563        """
564        i = 0
565        for v in reversed(self):
566            if f(v) and (n is None or i < n):
567                i += 1
568            else:
569                break
570        return self.tail(i)
571
572    # NOTE: from as in, starting from first true
573    # versus above, which is until first false
574    def take_after(self, f, n = None, lazy = False):
575        """
576        >>> iTuple.range(3).take_after(lambda v: v < 1)
577        iTuple(1, 2)
578        >>> iTuple.range(3).take_after(lambda v: v < 1, n = 1)
579        iTuple(1)
580        """
581        def iter():
582            i = 0
583            for v in self:
584                if f(v):
585                    pass
586                elif n is None or i < n:
587                    yield v
588                    i += 1
589                else:
590                    return
591        res = iter()
592        return res if lazy else type(self)(data=res)
593
594    def tail_after(self, f, n = None):
595        """
596        >>> iTuple.range(3).tail_after(lambda v: v < 2)
597        iTuple(0, 1)
598        >>> iTuple.range(3).tail_after(lambda v: v < 2, 1)
599        iTuple(1)
600        """
601        l = 0
602        r = 0
603        for v in reversed(self):
604            if not f(v):
605                l += 1
606            elif n is None or r < n:
607                r += 1
608            else:
609                break
610        return self.tail(l + r).take(r)
611
612    def islice(self, left = None, right = None):
613        """
614        >>> iTuple.range(5).islice(1, 3)
615        iTuple(1, 2)
616        """
617        return self[left:right]
618
619    def unique(self):
620        """
621        >>> iTuple([1, 1, 3, 2, 4, 2, 3]).unique()
622        iTuple(1, 3, 2, 4)
623        """
624        def iter():
625            seen = set()
626            seen_add = seen.add
627            seen_contains = seen.__contains__
628            for v in itertools.filterfalse(seen_contains, self):
629                seen_add(v)
630                yield v
631        return type(self)(data=iter())
632    
633    def sort(self, f = lambda v: v):
634        """
635        >>> iTuple.range(3).reverse().sort()
636        iTuple(0, 1, 2)
637        >>> iTuple.range(3).sort()
638        iTuple(0, 1, 2)
639        """
640        return type(self)(data=sorted(self, key = f))
641
642    def accumulate(self, f, initial = None, lazy = False):
643        """
644        >>> iTuple.range(3).accumulate(lambda acc, v: v)
645        iTuple(0, 1, 2)
646        >>> iTuple.range(3).accumulate(lambda acc, v: v, initial=0)
647        iTuple(0, 0, 1, 2)
648        >>> iTuple.range(3).accumulate(operator.add)
649        iTuple(0, 1, 3)
650        """
651        res = itertools.accumulate(self, func=f, initial=initial)
652        return res if lazy else iTuple(data=res)
653
654    def foldcum(self, *args, **kwargs):
655        """
656        >>> iTuple.range(3).foldcum(lambda acc, v: v)
657        iTuple(0, 1, 2)
658        >>> iTuple.range(3).foldcum(operator.add)
659        iTuple(0, 1, 3)
660        """
661        return self.accumulate(*args, **kwargs)
662
663    def fold(self, f, initial=None):
664        """
665        >>> iTuple.range(3).fold(lambda acc, v: v)
666        2
667        >>> iTuple.range(3).fold(lambda acc, v: v, initial=0)
668        2
669        >>> iTuple.range(3).fold(operator.add)
670        3
671        """
672        if initial is not None:
673            res = functools.reduce(f, self, initial)
674        else:
675            res = functools.reduce(f, self)
676        return res
iTuple(data=None)
249    def __init__(self, data = None):
250        # TODO: option for lazy init?
251        self.data = (
252            tuple() if data is None
253            else self.wrap_tuple(data)
254        )
@staticmethod
def wrap_tuple(data):
245    @staticmethod
246    def wrap_tuple(data):
247        return data if isinstance(data, tuple) else tuple(data)
@classmethod
def decorate(meta, cls):
266    @classmethod
267    def decorate(meta, cls):
268        assert cls.__name__ not in REGISTRY
269        REGISTRY[cls.__name__] = cls
270        return cls
@classmethod
def range(cls, *args, **kwargs):
274    @classmethod
275    def range(cls, *args, **kwargs):
276        """
277        >>> iTuple.range(3)
278        iTuple(0, 1, 2)
279        """
280        return cls(range(*args, **kwargs))
>>> iTuple.range(3)
iTuple(0, 1, 2)
@classmethod
def from_keys(cls, d):
282    @classmethod
283    def from_keys(cls, d):
284        """
285        >>> iTuple.from_keys({i: i + 1 for i in range(2)})
286        iTuple(0, 1)
287        """
288        return cls(d.keys())
>>> iTuple.from_keys({i: i + 1 for i in range(2)})
iTuple(0, 1)
@classmethod
def from_values(cls, d):
290    @classmethod
291    def from_values(cls, d):
292        """
293        >>> iTuple.from_values({i: i + 1 for i in range(2)})
294        iTuple(1, 2)
295        """
296        return cls(d.values())
>>> iTuple.from_values({i: i + 1 for i in range(2)})
iTuple(1, 2)
@classmethod
def from_items(cls, d):
298    @classmethod
299    def from_items(cls, d):
300        """
301        >>> iTuple.from_items({i: i + 1 for i in range(2)})
302        iTuple((0, 1), (1, 2))
303        """
304        return cls(d.items())
>>> iTuple.from_items({i: i + 1 for i in range(2)})
iTuple((0, 1), (1, 2))
def pipe(self, f, *args, at=None, **kwargs):
308    def pipe(self, f, *args, at = None, **kwargs):
309        """
310        >>> iTuple.range(2).pipe(lambda it: it)
311        iTuple(0, 1)
312        >>> iTuple.range(2).pipe(
313        ...     lambda it, v: it.map(lambda x: x * v), 2
314        ... )
315        iTuple(0, 2)
316        """
317        return pipe(f, self, *args, at = at, **kwargs)
>>> iTuple.range(2).pipe(lambda it: it)
iTuple(0, 1)
>>> iTuple.range(2).pipe(
...     lambda it, v: it.map(lambda x: x * v), 2
... )
iTuple(0, 2)
def partial(self, f, *args, **kwargs):
319    def partial(self, f, *args, **kwargs):
320        """
321        >>> f = iTuple.range(2).partial(
322        ...     lambda it, v: it.map(lambda x: x * v)
323        ... )
324        >>> f(2)
325        iTuple(0, 2)
326        >>> f(3)
327        iTuple(0, 3)
328        """
329        return functools.partial(f, self, *args, **kwargs)
>>> f = iTuple.range(2).partial(
...     lambda it, v: it.map(lambda x: x * v)
... )
>>> f(2)
iTuple(0, 2)
>>> f(3)
iTuple(0, 3)
def len(self):
333    def len(self):
334        """
335        >>> iTuple.range(3).len()
336        3
337        """
338        return len(self)
>>> iTuple.range(3).len()
3
def append(self, value, *values):
340    def append(self, value, *values):
341        """
342        >>> iTuple.range(1).append(1)
343        iTuple(0, 1)
344        >>> iTuple.range(1).append(1, 2)
345        iTuple(0, 1, 2)
346        >>> iTuple.range(1).append(1, 2, 3)
347        iTuple(0, 1, 2, 3)
348        >>> iTuple.range(1).append(1, (2,))
349        iTuple(0, 1, (2,))
350        """
351        return self + (value, *values)
>>> iTuple.range(1).append(1)
iTuple(0, 1)
>>> iTuple.range(1).append(1, 2)
iTuple(0, 1, 2)
>>> iTuple.range(1).append(1, 2, 3)
iTuple(0, 1, 2, 3)
>>> iTuple.range(1).append(1, (2,))
iTuple(0, 1, (2,))
def prepend(self, value, *values):
353    def prepend(self, value, *values):
354        """
355        >>> iTuple.range(1).prepend(1)
356        iTuple(1, 0)
357        >>> iTuple.range(1).prepend(1, 2)
358        iTuple(1, 2, 0)
359        >>> iTuple.range(1).prepend(1, 2, 3)
360        iTuple(1, 2, 3, 0)
361        >>> iTuple.range(1).prepend(1, (2,))
362        iTuple(1, (2,), 0)
363        """
364        return (value, *values) + self
>>> iTuple.range(1).prepend(1)
iTuple(1, 0)
>>> iTuple.range(1).prepend(1, 2)
iTuple(1, 2, 0)
>>> iTuple.range(1).prepend(1, 2, 3)
iTuple(1, 2, 3, 0)
>>> iTuple.range(1).prepend(1, (2,))
iTuple(1, (2,), 0)
def zip(self, *itrs, lazy=False):
366    def zip(self, *itrs, lazy = False):
367        """
368        >>> iTuple([[1, 1], [2, 2], [3, 3]]).zip()
369        iTuple((1, 2, 3), (1, 2, 3))
370        >>> iTuple([iTuple.range(3), iTuple.range(1, 4)]).zip()
371        iTuple((0, 1), (1, 2), (2, 3))
372        >>> iTuple.range(3).zip(iTuple.range(1, 4))
373        iTuple((0, 1), (1, 2), (2, 3))
374        """
375        if len(itrs) == 0:
376            res = zip(*self)
377        else:
378            res = zip(self, *itrs)
379        return res if lazy else iTuple(data=res)
>>> iTuple([[1, 1], [2, 2], [3, 3]]).zip()
iTuple((1, 2, 3), (1, 2, 3))
>>> iTuple([iTuple.range(3), iTuple.range(1, 4)]).zip()
iTuple((0, 1), (1, 2), (2, 3))
>>> iTuple.range(3).zip(iTuple.range(1, 4))
iTuple((0, 1), (1, 2), (2, 3))
def flatten(self):
381    def flatten(self):
382        """
383        >>> iTuple.range(3).map(lambda x: [x]).flatten()
384        iTuple(0, 1, 2)
385        """
386        return iTuple(itertools.chain(*self))
>>> iTuple.range(3).map(lambda x: [x]).flatten()
iTuple(0, 1, 2)
def extend(self, value, *values):
388    def extend(self, value, *values):
389        """
390        >>> iTuple.range(1).extend((1,))
391        iTuple(0, 1)
392        >>> iTuple.range(1).extend([1])
393        iTuple(0, 1)
394        >>> iTuple.range(1).extend([1], [2])
395        iTuple(0, 1, 2)
396        >>> iTuple.range(1).extend([1], [[2]])
397        iTuple(0, 1, [2])
398        >>> iTuple.range(1).extend([1], [[2]], [2])
399        iTuple(0, 1, [2], 2)
400        """
401        return iTuple(itertools.chain.from_iterable(
402            (self, value, *values)
403        ))
>>> iTuple.range(1).extend((1,))
iTuple(0, 1)
>>> iTuple.range(1).extend([1])
iTuple(0, 1)
>>> iTuple.range(1).extend([1], [2])
iTuple(0, 1, 2)
>>> iTuple.range(1).extend([1], [[2]])
iTuple(0, 1, [2])
>>> iTuple.range(1).extend([1], [[2]], [2])
iTuple(0, 1, [2], 2)
def pretend(self, value, *values):
405    def pretend(self, value, *values):
406        """
407        >>> iTuple.range(1).pretend((1,))
408        iTuple(1, 0)
409        >>> iTuple.range(1).pretend([1])
410        iTuple(1, 0)
411        >>> iTuple.range(1).pretend([1], [2])
412        iTuple(1, 2, 0)
413        >>> iTuple.range(1).pretend([1], [[2]])
414        iTuple(1, [2], 0)
415        >>> iTuple.range(1).pretend([1], [[2]], [2])
416        iTuple(1, [2], 2, 0)
417        """
418        return iTuple(itertools.chain.from_iterable(
419            (value, *values, self)
420        ))
>>> iTuple.range(1).pretend((1,))
iTuple(1, 0)
>>> iTuple.range(1).pretend([1])
iTuple(1, 0)
>>> iTuple.range(1).pretend([1], [2])
iTuple(1, 2, 0)
>>> iTuple.range(1).pretend([1], [[2]])
iTuple(1, [2], 0)
>>> iTuple.range(1).pretend([1], [[2]], [2])
iTuple(1, [2], 2, 0)
def filter_eq(self, v, f=None, eq=None, lazy=False):
422    def filter_eq(self, v, f = None, eq = None, lazy = False):
423        """
424        >>> iTuple.range(3).filter_eq(1)
425        iTuple(1)
426        """
427        if f is None and eq is None:
428            res = filter(lambda x: x == v, self)
429        elif f is not None:
430            res = filter(lambda x: f(x) == v, self)
431        elif eq is not None:
432            res = filter(lambda x: eq(x, v), self)
433        else:
434            res = filter(lambda x: eq(f(x), v), self)
435        return res if lazy else type(self)(data=res)
>>> iTuple.range(3).filter_eq(1)
iTuple(1)
def filter(self, f, eq=None, lazy=False):
437    def filter(self, f, eq = None, lazy = False):
438        """
439        >>> iTuple.range(3).filter(lambda x: x > 1)
440        iTuple(2)
441        """
442        return self.filter_eq(True, f = f, eq = eq, lazy = lazy)
>>> iTuple.range(3).filter(lambda x: x > 1)
iTuple(2)
def map(self, f, *iterables, lazy=False):
444    def map(self, f, *iterables, lazy = False):
445        """
446        >>> iTuple.range(3).map(lambda x: x * 2)
447        iTuple(0, 2, 4)
448        """
449        res = map(f, self, *iterables)
450        return res if lazy else iTuple(data=res)
>>> iTuple.range(3).map(lambda x: x * 2)
iTuple(0, 2, 4)
def enumerate(self):
452    def enumerate(self):
453        """
454        >>> iTuple.range(3).enumerate()
455        iTuple((0, 0), (1, 1), (2, 2))
456        """
457        # TODO: allow lazy
458        return iTuple(enumerate(self))
>>> iTuple.range(3).enumerate()
iTuple((0, 0), (1, 1), (2, 2))
def groupby(self, f, lazy=False, keys=False, pipe=None):
460    def groupby(
461        self, 
462        f, 
463        lazy = False, 
464        keys = False,
465        pipe= None,
466    ):
467        """
468        >>> iTuple.range(3).groupby(lambda x: x < 2)
469        iTuple((0, 1), (2,))
470        >>> iTuple.range(3).groupby(
471        ...    lambda x: x < 2, keys=True, pipe=fDict
472        ... )
473        {True: (0, 1), False: (2,)}
474        """
475        # TODO: lazy no keys
476        res = itertools.groupby(self, key=f)
477        if lazy and keys and pipe is None:
478            return res
479        if pipe is None:
480            pipe = iTuple
481        if keys:
482            return pipe((k, tuple(g),) for k, g in res)
483        else:
484            return pipe(tuple(g) for k, g in res)
>>> iTuple.range(3).groupby(lambda x: x < 2)
iTuple((0, 1), (2,))
>>> iTuple.range(3).groupby(
...    lambda x: x < 2, keys=True, pipe=fDict
... )
{True: (0, 1), False: (2,)}
def first(self):
486    def first(self):
487        """
488        >>> iTuple.range(3).first()
489        0
490        """
491        return self[0]
>>> iTuple.range(3).first()
0
def last(self):
493    def last(self):
494        """
495        >>> iTuple.range(3).last()
496        2
497        """
498        return self[-1]
>>> iTuple.range(3).last()
2
def first_where(self, f):
500    def first_where(self, f):
501        """
502        >>> iTuple.range(3).first_where(lambda v: v > 0)
503        1
504        """
505        for v in self:
506            if f(v):
507                return v
508        return None
>>> iTuple.range(3).first_where(lambda v: v > 0)
1
def last_where(self, f):
510    def last_where(self, f):
511        """
512        >>> iTuple.range(3).last_where(lambda v: v < 2)
513        1
514        """
515        for v in reversed(self):
516            if f(v):
517                return v
518        return None
>>> iTuple.range(3).last_where(lambda v: v < 2)
1
def take(self, n):
520    def take(self, n):
521        """
522        >>> iTuple.range(3).take(2)
523        iTuple(0, 1)
524        """
525        return self[:n]
>>> iTuple.range(3).take(2)
iTuple(0, 1)
def tail(self, n):
527    def tail(self, n):
528        """
529        >>> iTuple.range(3).tail(2)
530        iTuple(1, 2)
531        """
532        return self[-n:]
>>> iTuple.range(3).tail(2)
iTuple(1, 2)
def reverse(self, lazy=False):
534    def reverse(self, lazy = False):
535        """
536        >>> iTuple.range(3).reverse()
537        iTuple(2, 1, 0)
538        """
539        if lazy:
540            return reversed(self)
541        return type(self)(data=reversed(self))
>>> iTuple.range(3).reverse()
iTuple(2, 1, 0)
def take_while(self, f, n=None, lazy=False):
543    def take_while(self, f, n = None, lazy = False):
544        """
545        >>> iTuple.range(3).take_while(lambda v: v < 1)
546        iTuple(0)
547        """
548        def iter():
549            i = 0
550            for v in self:
551                if f(v) and (n is None or i < n):
552                    yield v
553                    i += 1
554                else:
555                    return
556        res = iter()
557        return res if lazy else type(self)(data=res)
>>> iTuple.range(3).take_while(lambda v: v < 1)
iTuple(0)
def tail_while(self, f, n=None):
559    def tail_while(self, f, n = None):
560        """
561        >>> iTuple.range(3).tail_while(lambda v: v > 1)
562        iTuple(2)
563        """
564        i = 0
565        for v in reversed(self):
566            if f(v) and (n is None or i < n):
567                i += 1
568            else:
569                break
570        return self.tail(i)
>>> iTuple.range(3).tail_while(lambda v: v > 1)
iTuple(2)
def take_after(self, f, n=None, lazy=False):
574    def take_after(self, f, n = None, lazy = False):
575        """
576        >>> iTuple.range(3).take_after(lambda v: v < 1)
577        iTuple(1, 2)
578        >>> iTuple.range(3).take_after(lambda v: v < 1, n = 1)
579        iTuple(1)
580        """
581        def iter():
582            i = 0
583            for v in self:
584                if f(v):
585                    pass
586                elif n is None or i < n:
587                    yield v
588                    i += 1
589                else:
590                    return
591        res = iter()
592        return res if lazy else type(self)(data=res)
>>> iTuple.range(3).take_after(lambda v: v < 1)
iTuple(1, 2)
>>> iTuple.range(3).take_after(lambda v: v < 1, n = 1)
iTuple(1)
def tail_after(self, f, n=None):
594    def tail_after(self, f, n = None):
595        """
596        >>> iTuple.range(3).tail_after(lambda v: v < 2)
597        iTuple(0, 1)
598        >>> iTuple.range(3).tail_after(lambda v: v < 2, 1)
599        iTuple(1)
600        """
601        l = 0
602        r = 0
603        for v in reversed(self):
604            if not f(v):
605                l += 1
606            elif n is None or r < n:
607                r += 1
608            else:
609                break
610        return self.tail(l + r).take(r)
>>> iTuple.range(3).tail_after(lambda v: v < 2)
iTuple(0, 1)
>>> iTuple.range(3).tail_after(lambda v: v < 2, 1)
iTuple(1)
def islice(self, left=None, right=None):
612    def islice(self, left = None, right = None):
613        """
614        >>> iTuple.range(5).islice(1, 3)
615        iTuple(1, 2)
616        """
617        return self[left:right]
>>> iTuple.range(5).islice(1, 3)
iTuple(1, 2)
def unique(self):
619    def unique(self):
620        """
621        >>> iTuple([1, 1, 3, 2, 4, 2, 3]).unique()
622        iTuple(1, 3, 2, 4)
623        """
624        def iter():
625            seen = set()
626            seen_add = seen.add
627            seen_contains = seen.__contains__
628            for v in itertools.filterfalse(seen_contains, self):
629                seen_add(v)
630                yield v
631        return type(self)(data=iter())
>>> iTuple([1, 1, 3, 2, 4, 2, 3]).unique()
iTuple(1, 3, 2, 4)
def sort(self, f=<function iTuple.<lambda>>):
633    def sort(self, f = lambda v: v):
634        """
635        >>> iTuple.range(3).reverse().sort()
636        iTuple(0, 1, 2)
637        >>> iTuple.range(3).sort()
638        iTuple(0, 1, 2)
639        """
640        return type(self)(data=sorted(self, key = f))
>>> iTuple.range(3).reverse().sort()
iTuple(0, 1, 2)
>>> iTuple.range(3).sort()
iTuple(0, 1, 2)
def accumulate(self, f, initial=None, lazy=False):
642    def accumulate(self, f, initial = None, lazy = False):
643        """
644        >>> iTuple.range(3).accumulate(lambda acc, v: v)
645        iTuple(0, 1, 2)
646        >>> iTuple.range(3).accumulate(lambda acc, v: v, initial=0)
647        iTuple(0, 0, 1, 2)
648        >>> iTuple.range(3).accumulate(operator.add)
649        iTuple(0, 1, 3)
650        """
651        res = itertools.accumulate(self, func=f, initial=initial)
652        return res if lazy else iTuple(data=res)
>>> iTuple.range(3).accumulate(lambda acc, v: v)
iTuple(0, 1, 2)
>>> iTuple.range(3).accumulate(lambda acc, v: v, initial=0)
iTuple(0, 0, 1, 2)
>>> iTuple.range(3).accumulate(operator.add)
iTuple(0, 1, 3)
def foldcum(self, *args, **kwargs):
654    def foldcum(self, *args, **kwargs):
655        """
656        >>> iTuple.range(3).foldcum(lambda acc, v: v)
657        iTuple(0, 1, 2)
658        >>> iTuple.range(3).foldcum(operator.add)
659        iTuple(0, 1, 3)
660        """
661        return self.accumulate(*args, **kwargs)
>>> iTuple.range(3).foldcum(lambda acc, v: v)
iTuple(0, 1, 2)
>>> iTuple.range(3).foldcum(operator.add)
iTuple(0, 1, 3)
def fold(self, f, initial=None):
663    def fold(self, f, initial=None):
664        """
665        >>> iTuple.range(3).fold(lambda acc, v: v)
666        2
667        >>> iTuple.range(3).fold(lambda acc, v: v, initial=0)
668        2
669        >>> iTuple.range(3).fold(operator.add)
670        3
671        """
672        if initial is not None:
673            res = functools.reduce(f, self, initial)
674        else:
675            res = functools.reduce(f, self)
676        return res
>>> iTuple.range(3).fold(lambda acc, v: v)
2
>>> iTuple.range(3).fold(lambda acc, v: v, initial=0)
2
>>> iTuple.range(3).fold(operator.add)
3
Inherited Members
collections.UserList
insert
pop
remove
clear
copy
count
index
class nTuple(abc.ABC):
 44class nTuple(abc.ABC):
 45
 46    @abc.abstractmethod
 47    def __abstract__(self):
 48        # NOTE: here to prevent initialise instances of this
 49        # but rather use the decorator and typing.NamedTuple
 50        return
 51
 52    @staticmethod
 53    def pipe(obj, f, *args, at = None, **kwargs):
 54        """
 55        >>> example = Example(1, "a")
 56        >>> example.pipe(lambda a, b: a, None)
 57        Example(x=1, s='a', it=iTuple())
 58        >>> example.pipe(lambda a, b: a, None, at = 1)
 59        >>> example.pipe(lambda a, b: a, None, at = 'b')
 60        >>> example.pipe(lambda a, b: a, a=None, at = 'b')
 61        >>> example.pipe(lambda a, b: a, b=None, at = 'a')
 62        Example(x=1, s='a', it=iTuple())
 63        >>> example.pipe(lambda a, b: a, None, at = 0)
 64        Example(x=1, s='a', it=iTuple())
 65        """
 66        return pipe(f, obj, *args, at = at, **kwargs)
 67
 68    @staticmethod
 69    def partial(obj, f, *args, **kwargs):
 70        return functools.partial(f, obj, *args, **kwargs)
 71
 72    @classmethod
 73    def is_subclass(cls, t):
 74        """
 75        >>> nTuple.is_subclass(tuple)
 76        False
 77        >>> nTuple.is_subclass(Example(1, "a"))
 78        False
 79        >>> nTuple.is_subclass(Example)
 80        True
 81        """
 82        try:
 83            is_sub = issubclass(t, tuple)
 84        except:
 85            is_sub = False
 86        return (
 87            is_sub and
 88            hasattr(t, "cls") and
 89            hasattr(t, "pipe") and
 90            hasattr(t, "partial")
 91        )
 92
 93    @classmethod
 94    def is_instance(cls, obj):
 95        """
 96        >>> nTuple.is_instance(tuple)
 97        False
 98        >>> nTuple.is_instance(Example)
 99        False
100        >>> nTuple.is_instance(Example(1, "a"))
101        True
102        """
103        return (
104            cls.is_subclass(type(obj)) and
105            hasattr(obj, '_asdict') and
106            hasattr(obj, '_fields')
107        )
108
109
110    @staticmethod
111    def annotations(obj):
112        """
113        >>> ex = Example(1, "a")
114        >>> ex.pipe(ex.cls.annotations)
115        {'x': <class 'int'>, 's': <class 'str'>, 'it': <class 'xtuples.xtuples.iTuple'>}
116        """
117        return fDict(obj.__annotations__)
118
119    @classmethod
120    def as_dict(cls, obj):
121        """
122        >>> ex = Example(1, "a")
123        >>> ex.pipe(ex.cls.as_dict)
124        {'x': 1, 's': 'a', 'it': iTuple()}
125        """
126        return fDict(obj._asdict())
127
128    @classmethod
129    def decorate(meta, cls):
130        assert cls.__name__ not in REGISTRY
131        cls.pipe = meta.pipe
132        cls.partial = meta.partial
133        cls.cls = meta
134        REGISTRY[cls.__name__] = cls
135        return cls

Helper class that provides a standard way to create an ABC using inheritance.

@staticmethod
def pipe(obj, f, *args, at=None, **kwargs):
52    @staticmethod
53    def pipe(obj, f, *args, at = None, **kwargs):
54        """
55        >>> example = Example(1, "a")
56        >>> example.pipe(lambda a, b: a, None)
57        Example(x=1, s='a', it=iTuple())
58        >>> example.pipe(lambda a, b: a, None, at = 1)
59        >>> example.pipe(lambda a, b: a, None, at = 'b')
60        >>> example.pipe(lambda a, b: a, a=None, at = 'b')
61        >>> example.pipe(lambda a, b: a, b=None, at = 'a')
62        Example(x=1, s='a', it=iTuple())
63        >>> example.pipe(lambda a, b: a, None, at = 0)
64        Example(x=1, s='a', it=iTuple())
65        """
66        return pipe(f, obj, *args, at = at, **kwargs)
>>> example = Example(1, "a")
>>> example.pipe(lambda a, b: a, None)
Example(x=1, s='a', it=iTuple())
>>> example.pipe(lambda a, b: a, None, at = 1)
>>> example.pipe(lambda a, b: a, None, at = 'b')
>>> example.pipe(lambda a, b: a, a=None, at = 'b')
>>> example.pipe(lambda a, b: a, b=None, at = 'a')
Example(x=1, s='a', it=iTuple())
>>> example.pipe(lambda a, b: a, None, at = 0)
Example(x=1, s='a', it=iTuple())
@staticmethod
def partial(obj, f, *args, **kwargs):
68    @staticmethod
69    def partial(obj, f, *args, **kwargs):
70        return functools.partial(f, obj, *args, **kwargs)
@classmethod
def is_subclass(cls, t):
72    @classmethod
73    def is_subclass(cls, t):
74        """
75        >>> nTuple.is_subclass(tuple)
76        False
77        >>> nTuple.is_subclass(Example(1, "a"))
78        False
79        >>> nTuple.is_subclass(Example)
80        True
81        """
82        try:
83            is_sub = issubclass(t, tuple)
84        except:
85            is_sub = False
86        return (
87            is_sub and
88            hasattr(t, "cls") and
89            hasattr(t, "pipe") and
90            hasattr(t, "partial")
91        )
>>> nTuple.is_subclass(tuple)
False
>>> nTuple.is_subclass(Example(1, "a"))
False
>>> nTuple.is_subclass(Example)
True
@classmethod
def is_instance(cls, obj):
 93    @classmethod
 94    def is_instance(cls, obj):
 95        """
 96        >>> nTuple.is_instance(tuple)
 97        False
 98        >>> nTuple.is_instance(Example)
 99        False
100        >>> nTuple.is_instance(Example(1, "a"))
101        True
102        """
103        return (
104            cls.is_subclass(type(obj)) and
105            hasattr(obj, '_asdict') and
106            hasattr(obj, '_fields')
107        )
>>> nTuple.is_instance(tuple)
False
>>> nTuple.is_instance(Example)
False
>>> nTuple.is_instance(Example(1, "a"))
True
@staticmethod
def annotations(obj):
110    @staticmethod
111    def annotations(obj):
112        """
113        >>> ex = Example(1, "a")
114        >>> ex.pipe(ex.cls.annotations)
115        {'x': <class 'int'>, 's': <class 'str'>, 'it': <class 'xtuples.xtuples.iTuple'>}
116        """
117        return fDict(obj.__annotations__)
>>> ex = Example(1, "a")
>>> ex.pipe(ex.cls.annotations)
{'x': <class 'int'>, 's': <class 'str'>, 'it': <class 'xtuples.xtuples.iTuple'>}
@classmethod
def as_dict(cls, obj):
119    @classmethod
120    def as_dict(cls, obj):
121        """
122        >>> ex = Example(1, "a")
123        >>> ex.pipe(ex.cls.as_dict)
124        {'x': 1, 's': 'a', 'it': iTuple()}
125        """
126        return fDict(obj._asdict())
>>> ex = Example(1, "a")
>>> ex.pipe(ex.cls.as_dict)
{'x': 1, 's': 'a', 'it': iTuple()}
@classmethod
def decorate(meta, cls):
128    @classmethod
129    def decorate(meta, cls):
130        assert cls.__name__ not in REGISTRY
131        cls.pipe = meta.pipe
132        cls.partial = meta.partial
133        cls.cls = meta
134        REGISTRY[cls.__name__] = cls
135        return cls
class fDict(collections.UserDict):
139class fDict(collections.UserDict):
140    __slots__ = ()
141
142    data: dict
143
144    def pipe(self, f, *args, at=None, **kwargs):
145        """
146        >>> fDict({0: 1}).pipe(lambda d: d.map_values(
147        ...     lambda v: v + 1
148        ... ))
149        {0: 2}
150        """
151        res = pipe(f, self, *args, at = at, **kwargs)
152        if isinstance(res, dict):
153            return fDict(res)
154        return res
155
156    def partial(self, f, *args, **kwargs):
157        """
158        >>> f = fDict({0: 1}).partial(
159        ...     lambda d, n: d.map_values(lambda v: v + n)
160        ... )
161        >>> f(1)
162        {0: 2}
163        >>> f(2)
164        {0: 3}
165        """
166        return functools.partial(f, self, *args, **kwargs)
167
168    def keys_tuple(self):
169        """
170        >>> fDict({0: 1}).keys_tuple()
171        iTuple(0)
172        """
173        return iTuple.from_keys(self)
174
175    def values_tuple(self):
176        """
177        >>> fDict({0: 1}).values_tuple()
178        iTuple(1)
179        """
180        return iTuple.from_values(self)
181    
182    def items_tuple(self):
183        """
184        >>> fDict({0: 1}).items_tuple()
185        iTuple((0, 1))
186        """
187        return iTuple.from_items(self)
188
189    # NOTE: we have separate map implementations 
190    # as they are constant size, dict to dict
191    # other iterator functions should use iTuple (from the above)
192
193    def map_keys(self, f, *args, **kwargs):
194        """
195        >>> fDict({0: 1}).map_keys(lambda v: v + 1)
196        {1: 1}
197        """
198        return fDict(
199            (f(k, *args, **kwargs), v) for k, v in self.items()
200        )
201
202    def map_values(self, f, *args, **kwargs):
203        """
204        >>> fDict({0: 1}).map_values(lambda v: v + 1)
205        {0: 2}
206        """
207        return fDict(
208            (k, f(v, *args, **kwargs)) for k, v in self.items()
209        )
210
211    def map_items(self, f, *args, **kwargs):
212        """
213        >>> fDict({0: 1}).map_items(lambda k, v: (v, k))
214        {1: 0}
215        """
216        return fDict(
217            f(k, v, *args, **kwargs) for k, v in self.items()
218        )
219
220    def invert(self):
221        """
222        >>> fDict({0: 1}).invert()
223        {1: 0}
224        """
225        return fDict((v, k) for k, v in self.items())
def pipe(self, f, *args, at=None, **kwargs):
144    def pipe(self, f, *args, at=None, **kwargs):
145        """
146        >>> fDict({0: 1}).pipe(lambda d: d.map_values(
147        ...     lambda v: v + 1
148        ... ))
149        {0: 2}
150        """
151        res = pipe(f, self, *args, at = at, **kwargs)
152        if isinstance(res, dict):
153            return fDict(res)
154        return res
>>> fDict({0: 1}).pipe(lambda d: d.map_values(
...     lambda v: v + 1
... ))
{0: 2}
def partial(self, f, *args, **kwargs):
156    def partial(self, f, *args, **kwargs):
157        """
158        >>> f = fDict({0: 1}).partial(
159        ...     lambda d, n: d.map_values(lambda v: v + n)
160        ... )
161        >>> f(1)
162        {0: 2}
163        >>> f(2)
164        {0: 3}
165        """
166        return functools.partial(f, self, *args, **kwargs)
>>> f = fDict({0: 1}).partial(
...     lambda d, n: d.map_values(lambda v: v + n)
... )
>>> f(1)
{0: 2}
>>> f(2)
{0: 3}
def keys_tuple(self):
168    def keys_tuple(self):
169        """
170        >>> fDict({0: 1}).keys_tuple()
171        iTuple(0)
172        """
173        return iTuple.from_keys(self)
>>> fDict({0: 1}).keys_tuple()
iTuple(0)
def values_tuple(self):
175    def values_tuple(self):
176        """
177        >>> fDict({0: 1}).values_tuple()
178        iTuple(1)
179        """
180        return iTuple.from_values(self)
>>> fDict({0: 1}).values_tuple()
iTuple(1)
def items_tuple(self):
182    def items_tuple(self):
183        """
184        >>> fDict({0: 1}).items_tuple()
185        iTuple((0, 1))
186        """
187        return iTuple.from_items(self)
>>> fDict({0: 1}).items_tuple()
iTuple((0, 1))
def map_keys(self, f, *args, **kwargs):
193    def map_keys(self, f, *args, **kwargs):
194        """
195        >>> fDict({0: 1}).map_keys(lambda v: v + 1)
196        {1: 1}
197        """
198        return fDict(
199            (f(k, *args, **kwargs), v) for k, v in self.items()
200        )
>>> fDict({0: 1}).map_keys(lambda v: v + 1)
{1: 1}
def map_values(self, f, *args, **kwargs):
202    def map_values(self, f, *args, **kwargs):
203        """
204        >>> fDict({0: 1}).map_values(lambda v: v + 1)
205        {0: 2}
206        """
207        return fDict(
208            (k, f(v, *args, **kwargs)) for k, v in self.items()
209        )
>>> fDict({0: 1}).map_values(lambda v: v + 1)
{0: 2}
def map_items(self, f, *args, **kwargs):
211    def map_items(self, f, *args, **kwargs):
212        """
213        >>> fDict({0: 1}).map_items(lambda k, v: (v, k))
214        {1: 0}
215        """
216        return fDict(
217            f(k, v, *args, **kwargs) for k, v in self.items()
218        )
>>> fDict({0: 1}).map_items(lambda k, v: (v, k))
{1: 0}
def invert(self):
220    def invert(self):
221        """
222        >>> fDict({0: 1}).invert()
223        {1: 0}
224        """
225        return fDict((v, k) for k, v in self.items())
>>> fDict({0: 1}).invert()
{1: 0}
Inherited Members
collections.UserDict
UserDict
copy
fromkeys
collections.abc.MutableMapping
pop
popitem
clear
update
setdefault
collections.abc.Mapping
get
keys
items
values
@nTuple.decorate
class Example(typing.NamedTuple):
686@nTuple.decorate
687class Example(typing.NamedTuple):
688    """
689    >>> ex = Example(1, "a")
690    >>> ex
691    Example(x=1, s='a', it=iTuple())
692    >>> ex.cls
693    <class 'xtuples.xtuples.nTuple'>
694    >>> ex.pipe(lambda nt: nt.x)
695    1
696    >>> f = ex.partial(lambda nt, v: nt.x * v)
697    >>> f(2)
698    2
699    >>> f(3)
700    3
701    """
702    # NOTE: cls, pipe, partial are mandatory boilerplate
703
704    x: int
705    s: str
706    it: iTuple = iTuple([])
707
708    @property
709    def cls(self):
710        ...
711
712    def pipe(self, f, *args, at = None, **kwargs):
713        ...
714
715    def partial(self, f, *args, at = None, **kwargs):
716        ...
>>> ex = Example(1, "a")
>>> ex
Example(x=1, s='a', it=iTuple())
>>> ex.cls
<class 'xtuples.xtuples.nTuple'>
>>> ex.pipe(lambda nt: nt.x)
1
>>> f = ex.partial(lambda nt, v: nt.x * v)
>>> f(2)
2
>>> f(3)
3
Example(x: int, s: str, it: xtuples.xtuples.iTuple = iTuple())

Create new instance of Example(x, s, it)

x: int

Alias for field number 0

s: str

Alias for field number 1

Alias for field number 2

@staticmethod
def pipe(obj, f, *args, at=None, **kwargs):
52    @staticmethod
53    def pipe(obj, f, *args, at = None, **kwargs):
54        """
55        >>> example = Example(1, "a")
56        >>> example.pipe(lambda a, b: a, None)
57        Example(x=1, s='a', it=iTuple())
58        >>> example.pipe(lambda a, b: a, None, at = 1)
59        >>> example.pipe(lambda a, b: a, None, at = 'b')
60        >>> example.pipe(lambda a, b: a, a=None, at = 'b')
61        >>> example.pipe(lambda a, b: a, b=None, at = 'a')
62        Example(x=1, s='a', it=iTuple())
63        >>> example.pipe(lambda a, b: a, None, at = 0)
64        Example(x=1, s='a', it=iTuple())
65        """
66        return pipe(f, obj, *args, at = at, **kwargs)
>>> example = Example(1, "a")
>>> example.pipe(lambda a, b: a, None)
Example(x=1, s='a', it=iTuple())
>>> example.pipe(lambda a, b: a, None, at = 1)
>>> example.pipe(lambda a, b: a, None, at = 'b')
>>> example.pipe(lambda a, b: a, a=None, at = 'b')
>>> example.pipe(lambda a, b: a, b=None, at = 'a')
Example(x=1, s='a', it=iTuple())
>>> example.pipe(lambda a, b: a, None, at = 0)
Example(x=1, s='a', it=iTuple())
@staticmethod
def partial(obj, f, *args, **kwargs):
68    @staticmethod
69    def partial(obj, f, *args, **kwargs):
70        return functools.partial(f, obj, *args, **kwargs)
Inherited Members
builtins.tuple
index
count