Skip to content

Commit 791d48d

Browse files
committed
[GR-25399] Improve itertools tee.
PullRequest: graalpython/1183
2 parents dc0ebb0 + 88bc05e commit 791d48d

File tree

2 files changed

+60
-21
lines changed

2 files changed

+60
-21
lines changed

graalpython/com.oracle.graal.python.test/src/tests/test_pickle.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -57,5 +57,14 @@ def pickle_unpickle(self, obj):
5757
r_obj = pickle.loads(b_obj)
5858
self.assertEqual(r_obj, obj)
5959

60+
def test_teeiterator(self):
61+
import itertools
62+
teeit = itertools.tee([i for i in range(1, 20)])[0]
63+
[next(teeit) for i in range(1, 16)]
64+
b_obj = pickle.dumps(teeit, protocol=0)
65+
teeit2 = pickle.loads(b_obj)
66+
assert [16,17,18,19] == [next(teeit2) for i in range(1, 5)]
67+
assert [16,17,18,19] == [next(teeit) for i in range(1, 5)]
68+
6069
if __name__ == '__main__':
6170
unittest.main()

graalpython/lib-graalpython/itertools.py

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -860,32 +860,62 @@ def __reduce__(self):
860860
return (type(self), (self.data, self.selectors))
861861

862862

863-
def tee(iterable, n=2):
864-
import collections
865-
class _tee:
866-
@__graalpython__.builtin_method
867-
def __init__(self, it, deque, deques):
863+
class _tee:
864+
# This uses a linked list of fixed size lists where
865+
# the last item in the fixed size list is a link to
866+
# another fixed size list. Once all _tee instances have
867+
# traversed given fixed size list, it'll become a garbage
868+
# to be collected
869+
@__graalpython__.builtin_method
870+
def __init__(self, it, buffer=None):
871+
self.itemIndex = 0
872+
if buffer is None:
873+
# Support for direct creation of _tee from user code,
874+
# where the ctor takes a single iterable object
875+
self.it = iter(it)
876+
self.buffer = [None] * 8
877+
else:
868878
self.it = it
869-
self.deque = deque
870-
self.deques = deques
879+
self.buffer = buffer
880+
881+
@__graalpython__.builtin_method
882+
def __iter__(self):
883+
return self
884+
885+
@__graalpython__.builtin_method
886+
def __next__(self):
887+
# jump to the next buffer if necessary
888+
lastIndex = len(self.buffer) - 1
889+
if self.itemIndex == lastIndex:
890+
if self.buffer[lastIndex] is None:
891+
self.buffer[lastIndex] = [None] * 8
892+
self.buffer = self.buffer[lastIndex]
893+
self.itemIndex = 0
894+
# take existing item from the buffer or advance the iterator
895+
if self.buffer[self.itemIndex] is None:
896+
result = next(self.it)
897+
self.buffer[self.itemIndex] = result
898+
else:
899+
result = self.buffer[self.itemIndex]
900+
self.itemIndex += 1
901+
return result
871902

872-
@__graalpython__.builtin_method
873-
def __iter__(self):
874-
return self
903+
@__graalpython__.builtin_method
904+
def __copy__(self):
905+
return _tee(self.it, self.buffer)
875906

876-
@__graalpython__.builtin_method
877-
def __next__(self):
878-
if not self.deque:
879-
newval = next(self.it)
880-
for d in self.deques:
881-
d.append(newval)
882-
return self.deque.popleft()
883907

908+
def tee(iterable, n=2):
884909
if not isinstance(n, int):
885910
raise TypeError()
886911
if n < 0:
887912
raise ValueError("n must be >=0")
888-
889-
deques = [collections.deque() for i in range(n)]
913+
# if the iterator can be copied, use that instead of _tee
914+
# note: this works for _tee itself
890915
it = iter(iterable)
891-
return tuple(_tee(it, d, deques) for d in deques)
916+
copy = getattr(it, "__copy__", None)
917+
if callable(copy):
918+
return tuple([it] + [it.__copy__() for i in range(1, n)])
919+
else:
920+
queue = [None] * 8
921+
return tuple(_tee(it, queue) for i in range(0, n))

0 commit comments

Comments
 (0)