st_dec
st_dec

Reputation: 152

python - Iterator which duplicates elements of iterable object

I need to write a function duplicate(it) which returns iterator duplicating elements of iterable it. Examlpe: If it = [1, 2, 3] then list(duplicate(it)) == [1, 1, 2, 2, 3, 3]. I tried to implement DoubleIterator class and duplicate(it) function for lists (not sure about right way of implementation of DoubleIterator in this case, so useful advices are welcome):

class DoubleIterator():
    def __init__(self, data):
        self.data = data
        self.pos = 0
        self.limit = len(data)
        self.dup = 0

    def next(self): # __next__ for Python 3
        if self.dup != 0 and self.dup % 2 == 0:
            self.pos = self.pos + 1
        if self.pos < self.limit:
            self.dup += 1
            return self.data[self.pos]
        else:
            raise StopIteration

    def __iter__(self):
        return self

def duplicate(it):
    itr = DoubleIterator(it)
    return iter(itr)

It works fine for list(duplicate(it)) == [1, 1, 2, 2, 3, 3] example and something like:

dup = duplicate([1, 2, 3])
print next(dup) #1
print next(dup) #1
print next(dup) #2
print next(dup) #2
print next(dup) #3
print next(dup) #3

But one of the tests in task for this fucntion is dup = duplicate(iter([1, 2, 3]))and it gives an error: TypeError: object of type 'listiterator' has no len(). How should I implement duplicate function and iterator class in proper way?

Upvotes: 2

Views: 454

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1121226

You overcomplicated things a bit. All you need to do is get the next element and yield that twice. You do need to treat the input as an iterator, not as a sequence. Only sequences like lists have a length and can be indexed, all an iterator can do is give you the next value until it is exhausted.

For a class-based iterator, that means you need to store the element to be repeated so that every other call to next() can produce that stored value, while the other calls get the next element from the input iterator:

class DoubleIterator:
    _sentinel = object()

    def __init__(self, iterator):
        self._data = iter(iterator)
        self._repeat = self._sentinel

    def next(self): # __next__ for Python 3
        if self._repeat is self._sentinel:
            # get next element to yield, can raise StopIteration
            self._repeat = next(self._data)
            return self._repeat
        # repeat last element
        element = self._repeat
        self._repeat = self._sentinel
        return element

    def __iter__(self):
        return self

I used a unique sentinel to make sure that None values in the input iterator are supported, and I called iter() on the input iterable to make sure we can always call next() on that object.

However, it is much easier to just write a generator function here, and yield each element twice:

def double_iterator(iterator):
    for elem in iterator:
        yield elem
        yield elem

You can generalise that further with a loop, but if you only ever need to double the elements, I'd just stick to the simpler double yield.

Upvotes: 2

Patrick Haugh
Patrick Haugh

Reputation: 60944

I would just do it as a generator

def duplicate(it,n=2):
    for x in it:
        for _ in range(n):
            yield x

Upvotes: 4

Related Questions