-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathslicing.py
More file actions
140 lines (111 loc) · 5.3 KB
/
slicing.py
File metadata and controls
140 lines (111 loc) · 5.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# -*- coding: utf-8 -*-
"""Operations on sequences with native slice syntax. Syntactic sugar, pure Python."""
__all__ = ["islice", "fup"]
from itertools import islice as islicef
from .fup import fupdate
from .it import first, lastn, butlastn
from .misc import CountingIterator
def islice(iterable):
"""Use itertools.islice with slice syntax, with some bonus features.
Usage::
islice(iterable)[idx_or_slice]
For convenience:
- Negative ``start``, ``stop`` are supported. **CAUTION**: using a negative
start or stop will force the iterable, because that is the only way to
know its length.
The desired elements are held in an internal buffer until they are yielded
by iterating over the `islice`.
- A single index (negative also allowed) is interpreted as a length-1
islice starting at that index. The slice is then immediately evaluated
and the item is returned.
Once negative indices have been handled, the slicing variant calls
``itertools.islice`` with the corresponding slicing parameters.
Examples::
from unpythonic import primes, s
p = primes()
assert tuple(islice(p)[10:15]) == (31, 37, 41, 43, 47)
assert tuple(islice(primes())[10:15]) == (31, 37, 41, 43, 47)
p = primes()
assert islice(p)[10] == 31
odds = islice(s(1, 2, ...))[::2]
assert tuple(islice(odds)[:5]) == (1, 3, 5, 7, 9)
assert tuple(islice(odds)[:5]) == (11, 13, 15, 17, 19) # five more
**CAUTION**: Keep in mind the slicing process consumes elements from the
iterable.
**CAUTION**: ``step``, if present, must be positive.
"""
# manually curry to take indices later, but expect them in subscript syntax to support slicing
class islice1:
"""Subscript me to perform the slicing."""
def __getitem__(self, k):
if isinstance(k, tuple):
raise TypeError(f"multidimensional indexing not supported, got {k}")
if isinstance(k, slice):
start, stop, step = k.start, k.stop, k.step
it = iter(iterable)
# One or both of start and stop may be negative or None.
# Step must be positive; filter first, then slice normally.
#
# A general iterable doesn't know its length (might not even be
# knowable; it may be a generator), so if we get a negative
# start or stop, the only way to find the correct position is
# to force the iterable until it ends (if ever).
if start and start < 0 and stop and stop < 0:
it = butlastn(-stop, lastn(-start, iterable))
start = stop = None
elif start and start < 0:
n, start = -start, None
if not stop:
it = lastn(n, iterable)
else: # stop and stop > 0:
# to adjust stop, we must know how many items are dropped
cit = CountingIterator(iterable)
it = tuple(lastn(n, cit)) # force to actually count (note lastn stores only <= n items)
n_dropped = max(0, cit.count - n) # max needed if start is past the start of iterable
stop -= n_dropped
assert stop >= 0
elif stop and stop < 0:
it = butlastn(-stop, iterable)
stop = None
return islicef(it, start, stop, step)
if k < 0:
return first(lastn(-k, iterable))
return first(islicef(iterable, k, k + 1))
return islice1()
# Basic idea, no negative index support:
# def islice(iterable):
# class islice1:
# """Subscript me to perform the slicing."""
# def __getitem__(self, k):
# if isinstance(k, tuple):
# raise TypeError(f"multidimensional indexing not supported, got {k}")
# if isinstance(k, slice):
# return islicef(iterable, k.start, k.stop, k.step)
# return first(islicef(iterable, k, k + 1))
# return islice1()
def fup(seq):
"""Functionally update a sequence.
Usage::
fup(seq)[idx_or_slice] << values
For when you want to be more functional than Python allows. Example::
from itertools import repeat
lst = (1, 2, 3, 4, 5)
assert fup(lst)[3] << 42 == (1, 2, 3, 42, 5)
assert fup(lst)[0::2] << tuple(repeat(10, 3)) == (10, 2, 10, 4, 10)
Limitations:
- Currently only one update specification is supported in a single ``fup()``.
If you need more, use ``fupdate`` directly.
Named after the sound a sequence makes when it is hit by a functional update.
"""
# two-phase manual curry, first expect a subscript, then an lshift.
class fup1:
"""Subscript me to specify index or slice where to fupdate."""
def __getitem__(self, k):
if isinstance(k, tuple):
raise TypeError(f"multidimensional indexing not supported, got {k}")
class fup2:
"""Left-shift me with values to perform the fupdate."""
def __lshift__(self, v):
return fupdate(seq, k, v)
return fup2()
return fup1()