Skip to content

Commit 1833bb7

Browse files
committed
mimic C code more closely for itertools.tee
1 parent 58e366a commit 1833bb7

File tree

1 file changed

+86
-30
lines changed

1 file changed

+86
-30
lines changed

graalpython/lib-graalpython/itertools.py

Lines changed: 86 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,62 +1034,118 @@ def __reduce__(self):
10341034
return (type(self), (self.data, self.selectors))
10351035

10361036

1037+
class _tee_dataobject:
1038+
LINKCELLS = 32
1039+
1040+
@__graalpython__.builtin_method
1041+
def __init__(self, it, values=None, nxt=None):
1042+
self.it = it
1043+
if values:
1044+
self.values = values
1045+
self.numread = len(values)
1046+
if self.numread == _tee_dataobject.LINKCELLS:
1047+
self.nextlink = nxt
1048+
elif self.numread > _tee_dataobject.LINKCELLS:
1049+
raise ValueError(f"_tee_dataobject should nove have more than {_tee_dataobject.LINKCELLS} links")
1050+
elif nxt is not None:
1051+
raise ValueError("_tee_dataobject shouldn't have a next if not full")
1052+
else:
1053+
self.values = [None] * _tee_dataobject.LINKCELLS
1054+
self.numread = 0
1055+
self.running = False
1056+
self.nextlink = nxt
1057+
1058+
@__graalpython__.builtin_method
1059+
def _jumplink(self):
1060+
if not self.nextlink:
1061+
self.nextlink = _tee_dataobject(self.it)
1062+
return self.nextlink
1063+
1064+
@__graalpython__.builtin_method
1065+
def __getitem__(self, i):
1066+
if i < self.numread:
1067+
return self.values[i]
1068+
else:
1069+
if self.running:
1070+
raise RuntimeError("cannot re-enter the tee iterator")
1071+
self.running = True
1072+
try:
1073+
value = next(self.it)
1074+
finally:
1075+
self.running = False
1076+
self.numread += 1
1077+
self.values[i] = value
1078+
return value
1079+
1080+
@__graalpython__.builtin_method
1081+
def __reduce__(self):
1082+
return type(self), (self.it, self.values, self.nextlink)
1083+
1084+
10371085
class _tee:
10381086
# This uses a linked list of fixed size lists where
10391087
# the last item in the fixed size list is a link to
10401088
# another fixed size list. Once all _tee instances have
10411089
# traversed given fixed size list, it'll become a garbage
10421090
# to be collected
10431091
@__graalpython__.builtin_method
1044-
def __init__(self, it, buffer=None):
1045-
self.itemIndex = 0
1046-
if buffer is None:
1047-
# Support for direct creation of _tee from user code,
1048-
# where the ctor takes a single iterable object
1049-
self.it = iter(it)
1050-
self.buffer = [None] * 8
1092+
def __new__(cls, iterable):
1093+
it = iter(iterable)
1094+
if isinstance(it, _tee):
1095+
return it.__copy__()
10511096
else:
1052-
self.it = it
1053-
self.buffer = buffer
1097+
to = object.__new__(_tee)
1098+
to.dataobj = _tee_dataobject(it)
1099+
to.index = 0
1100+
return to
10541101

10551102
@__graalpython__.builtin_method
10561103
def __iter__(self):
10571104
return self
10581105

10591106
@__graalpython__.builtin_method
10601107
def __next__(self):
1061-
# jump to the next buffer if necessary
1062-
lastIndex = len(self.buffer) - 1
1063-
if self.itemIndex == lastIndex:
1064-
if self.buffer[lastIndex] is None:
1065-
self.buffer[lastIndex] = [None] * 8
1066-
self.buffer = self.buffer[lastIndex]
1067-
self.itemIndex = 0
1068-
# take existing item from the buffer or advance the iterator
1069-
if self.buffer[self.itemIndex] is None:
1070-
result = next(self.it)
1071-
self.buffer[self.itemIndex] = result
1072-
else:
1073-
result = self.buffer[self.itemIndex]
1074-
self.itemIndex += 1
1075-
return result
1108+
if self.index >= _tee_dataobject.LINKCELLS:
1109+
self.dataobj = self.dataobj._jumplink()
1110+
self.index = 0
1111+
value = self.dataobj[self.index]
1112+
self.index += 1
1113+
return value
1114+
1115+
@__graalpython__.builtin_method
1116+
def __reduce__(self):
1117+
return type(self), ((),), (self.dataobj, self.index)
1118+
1119+
@__graalpython__.builtin_method
1120+
def __setstate__(self, state):
1121+
if not isinstance(state, tuple) or len(state) != 2:
1122+
raise TypeError("state is not a 2-tuple")
1123+
if not isinstance(state[0], _tee_dataobject):
1124+
raise TypeError("state is not a _tee_dataobject")
1125+
self.dataobj = state[0]
1126+
if state[1] < 0 or state[1] > _tee_dataobject.LINKCELLS:
1127+
raise ValueError("Index out of range")
1128+
self.index = int(state[1])
10761129

10771130
@__graalpython__.builtin_method
10781131
def __copy__(self):
1079-
return _tee(self.it, self.buffer)
1132+
to = object.__new__(_tee)
1133+
to.dataobj = self.dataobj
1134+
to.index = self.index
1135+
return to
10801136

10811137

10821138
def tee(iterable, n=2):
10831139
if not isinstance(n, int):
10841140
raise TypeError()
10851141
if n < 0:
10861142
raise ValueError("n must be >=0")
1143+
if n == 0:
1144+
return tuple()
10871145
# if the iterator can be copied, use that instead of _tee
10881146
# note: this works for _tee itself
10891147
it = iter(iterable)
10901148
copy = getattr(it, "__copy__", None)
1091-
if callable(copy):
1092-
return tuple([it] + [it.__copy__() for i in range(1, n)])
1093-
else:
1094-
queue = [None] * 8
1095-
return tuple(_tee(it, queue) for i in range(0, n))
1149+
if not callable(copy):
1150+
it = _tee(it)
1151+
return tuple([it] + [it.__copy__() for i in range(1, n)])

0 commit comments

Comments
 (0)