zip-like function that fails if a particular iterator is not consumed
up vote
1
down vote
favorite
I would like a zip like function that fails if the right-most iterator is not consumed. It should yield until the failure.
For example
>>> a = ['a', 'b', 'c']
>>> b = [1, 2, 3, 4]
>>> myzip(a, b)
Traceback (most recent call last):
...
ValueError: rightmost iterable was not consumed
>>> list(myzip(b, a))
[(1, 'a'), (2, 'b'), (3, 'c')]
Perhaps there a function in the standard library that can help with this?
Important Note:
In the real context the iterators are not over objects so I can't just check the length or index them.
Edit:
This is what I have come up with so far
def myzip(*iterables):
iters = [iter(i) for i in iterables]
zipped = zip(*iters)
try:
next(iters[-1])
raise ValueError('rightmost iterable was not consumed')
except StopIteration:
return zipped
Is this the best solution? It doesn't keep the state of the iterator because I call next on it, which might be a problem.
python python-3.x iterator iterable
add a comment |
up vote
1
down vote
favorite
I would like a zip like function that fails if the right-most iterator is not consumed. It should yield until the failure.
For example
>>> a = ['a', 'b', 'c']
>>> b = [1, 2, 3, 4]
>>> myzip(a, b)
Traceback (most recent call last):
...
ValueError: rightmost iterable was not consumed
>>> list(myzip(b, a))
[(1, 'a'), (2, 'b'), (3, 'c')]
Perhaps there a function in the standard library that can help with this?
Important Note:
In the real context the iterators are not over objects so I can't just check the length or index them.
Edit:
This is what I have come up with so far
def myzip(*iterables):
iters = [iter(i) for i in iterables]
zipped = zip(*iters)
try:
next(iters[-1])
raise ValueError('rightmost iterable was not consumed')
except StopIteration:
return zipped
Is this the best solution? It doesn't keep the state of the iterator because I call next on it, which might be a problem.
python python-3.x iterator iterable
Note that the code you edited in formyzip()
won't work - it'll always give you theValueError
as long as the last iterable has at least one element in it to begin with. This is because - unlike in Python 2, wherezip()
will consume the iterables immediately, and return a list - in Python 3zip()
returns a generator and consumes the iterables lazily. This means that when you callnext()
in the current version ofmyzip()
, no items have been consumed byzip()
yet, and all the iterators are still at the very beginning.
– Aleksi Torhamo
Nov 11 at 13:47
Yeah I did realize this when I tried to use this, I am using a solution that usesyield from
like in your answer.
– Ross
Nov 12 at 6:54
add a comment |
up vote
1
down vote
favorite
up vote
1
down vote
favorite
I would like a zip like function that fails if the right-most iterator is not consumed. It should yield until the failure.
For example
>>> a = ['a', 'b', 'c']
>>> b = [1, 2, 3, 4]
>>> myzip(a, b)
Traceback (most recent call last):
...
ValueError: rightmost iterable was not consumed
>>> list(myzip(b, a))
[(1, 'a'), (2, 'b'), (3, 'c')]
Perhaps there a function in the standard library that can help with this?
Important Note:
In the real context the iterators are not over objects so I can't just check the length or index them.
Edit:
This is what I have come up with so far
def myzip(*iterables):
iters = [iter(i) for i in iterables]
zipped = zip(*iters)
try:
next(iters[-1])
raise ValueError('rightmost iterable was not consumed')
except StopIteration:
return zipped
Is this the best solution? It doesn't keep the state of the iterator because I call next on it, which might be a problem.
python python-3.x iterator iterable
I would like a zip like function that fails if the right-most iterator is not consumed. It should yield until the failure.
For example
>>> a = ['a', 'b', 'c']
>>> b = [1, 2, 3, 4]
>>> myzip(a, b)
Traceback (most recent call last):
...
ValueError: rightmost iterable was not consumed
>>> list(myzip(b, a))
[(1, 'a'), (2, 'b'), (3, 'c')]
Perhaps there a function in the standard library that can help with this?
Important Note:
In the real context the iterators are not over objects so I can't just check the length or index them.
Edit:
This is what I have come up with so far
def myzip(*iterables):
iters = [iter(i) for i in iterables]
zipped = zip(*iters)
try:
next(iters[-1])
raise ValueError('rightmost iterable was not consumed')
except StopIteration:
return zipped
Is this the best solution? It doesn't keep the state of the iterator because I call next on it, which might be a problem.
python python-3.x iterator iterable
python python-3.x iterator iterable
edited Nov 11 at 9:17
asked Nov 11 at 8:48
Ross
386211
386211
Note that the code you edited in formyzip()
won't work - it'll always give you theValueError
as long as the last iterable has at least one element in it to begin with. This is because - unlike in Python 2, wherezip()
will consume the iterables immediately, and return a list - in Python 3zip()
returns a generator and consumes the iterables lazily. This means that when you callnext()
in the current version ofmyzip()
, no items have been consumed byzip()
yet, and all the iterators are still at the very beginning.
– Aleksi Torhamo
Nov 11 at 13:47
Yeah I did realize this when I tried to use this, I am using a solution that usesyield from
like in your answer.
– Ross
Nov 12 at 6:54
add a comment |
Note that the code you edited in formyzip()
won't work - it'll always give you theValueError
as long as the last iterable has at least one element in it to begin with. This is because - unlike in Python 2, wherezip()
will consume the iterables immediately, and return a list - in Python 3zip()
returns a generator and consumes the iterables lazily. This means that when you callnext()
in the current version ofmyzip()
, no items have been consumed byzip()
yet, and all the iterators are still at the very beginning.
– Aleksi Torhamo
Nov 11 at 13:47
Yeah I did realize this when I tried to use this, I am using a solution that usesyield from
like in your answer.
– Ross
Nov 12 at 6:54
Note that the code you edited in for
myzip()
won't work - it'll always give you the ValueError
as long as the last iterable has at least one element in it to begin with. This is because - unlike in Python 2, where zip()
will consume the iterables immediately, and return a list - in Python 3 zip()
returns a generator and consumes the iterables lazily. This means that when you call next()
in the current version of myzip()
, no items have been consumed by zip()
yet, and all the iterators are still at the very beginning.– Aleksi Torhamo
Nov 11 at 13:47
Note that the code you edited in for
myzip()
won't work - it'll always give you the ValueError
as long as the last iterable has at least one element in it to begin with. This is because - unlike in Python 2, where zip()
will consume the iterables immediately, and return a list - in Python 3 zip()
returns a generator and consumes the iterables lazily. This means that when you call next()
in the current version of myzip()
, no items have been consumed by zip()
yet, and all the iterators are still at the very beginning.– Aleksi Torhamo
Nov 11 at 13:47
Yeah I did realize this when I tried to use this, I am using a solution that uses
yield from
like in your answer.– Ross
Nov 12 at 6:54
Yeah I did realize this when I tried to use this, I am using a solution that uses
yield from
like in your answer.– Ross
Nov 12 at 6:54
add a comment |
4 Answers
4
active
oldest
votes
up vote
1
down vote
accepted
There's a few different ways you can go about doing this.
You could use the normal
zip()
with an iterator and manually check that it gets exhausted.def check_consumed(it):
try:
next(it)
except StopIteration:
pass
else:
raise ValueError('rightmost iterable was not consumed')
b_it = iter(b)
list(zip(a, b_it))
check_consumed(b_it)You could wrap the normal
zip()
to do the check for you.def myzip(a, b):
b_it = iter(b)
yield from zip(a, b_it)
# Or, if you're on a Python version that doesn't have yield from:
#for item in zip(a, b_it):
# yield item
check_consumed(b_it)
list(myzip(a, b))You could write your own
zip()
from scratch, usingiter()
andnext()
.(No code for this one, as option 2 is superior to this one in every way)
Is there a way to peek the iterator, so that its state is not modified.
– Ross
Nov 11 at 9:19
@Ross: If you mean "get an error whena
has 3 items andb
has 4 items, without consuming any items from either", the answer is "no", AFAIK. Even if you just don't want to consume one extra item fromb
, I think the answer is still "no". You could useitertools.chain()
to "add the consumed items back afterwards", however. (You can't literally add them back, but you can create a new iterator that gives you the consumed items first, and then continues giving the unconsumed items)
– Aleksi Torhamo
Nov 11 at 9:25
Okay, thanks, luckily for my case I can get around it. I forgot about theyield from
! Very nice solution.
– Ross
Nov 11 at 9:29
add a comment |
up vote
0
down vote
I think this one does the work by checking if the last consumer was completely consumed before returning
# Example copied from https://stackoverflow.com/questions/19151/build-a-basic-python-iterator
class Counter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 3: def __next__(self)
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
# modified from https://docs.python.org/3.5/library/functions.html#zip
def myzip(*iterables):
sentinel = object()
iterators = [iter(it) for it in iterables]
while iterators:
result =
for it in iterators:
elem = next(it, sentinel)
if elem is sentinel:
elem = next(iterators[-1], sentinel)
if elem is not sentinel:
raise ValueError("rightmost iterable was not consumed")
else:
return
result.append(elem)
yield tuple(result)
a = Counter(1,7)
b = range(9)
for val in myzip(a,b):
print(val)
add a comment |
up vote
0
down vote
There is already a zip_longest in itertools that allows for "expansion" of the shorter iterable by a default value.
Use that and check if your default value occurs: if so, it would have been a case of "rightmost element not consumed"
:
class MyError(ValueError):
"""Unique "default" value that is recognizeable and allows None to be in your values."""
pass
from itertools import zip_longest
isMyError = lambda x:isinstance(x,MyError)
def myzip(a,b):
"""Raises MyError if any non-consumed elements would occur using default zip()."""
K = zip_longest(a,b, fillvalue=MyError())
if all(not isMyError(t) for q in K for t in q):
return zip(a,b)
raise MyError("Not all items are consumed")
a = ['a', 'b', 'c', 'd']
b = [1, 2, 3, 4]
f = myzip(a, b)
print(list(f))
try:
a = ['a', 'b', ]
b = [1, 2, 3, 4]
f = myzip(a, b)
print(list(f))
except MyError as e:
print(e)
Output:
[('a', 1), ('b', 2), ('c', 3), ('d', 4)]
Not all items are consumed
This consumes (worst case) the full zipped list once to check and then returns it as iterable.
add a comment |
up vote
0
down vote
Other option using zip_longest from itertools. It returns also true or false if all lists are consumed. Maybe not the most efficient way, but could be improved:
from itertools import zip_longest
a = ['a', 'b', 'c', 'd']
b = [1, 2, 3, 4, 5]
c = ['aa', 'bb', 'cc', 'dd', 'ee', 'ff']
def myzip(*iterables):
consumed = True
zips =
for zipped in zip_longest(*iterables):
if None in zipped:
consumed = False
else:
zips.append(zipped)
return [zips, consumed]
list(myzip(a, b, c))
#=> [[('a', 1, 'aa'), ('b', 2, 'bb'), ('c', 3, 'cc'), ('d', 4, 'dd')], False]
Please see edit to the question. Added an "Important Note".
– Ross
Nov 11 at 9:00
Can you provide a chunk of a real context object? I supposed it was a list.
– iGian
Nov 11 at 9:05
Try usingiter(['a', 'b', 'c'])
anditer([1, 2, 3, 4])
(obviously they can only be used once)
– Ross
Nov 11 at 9:10
I don't know if I got the point, but I made an edit based on your update.
– iGian
Nov 11 at 9:27
I don't want it to end at the shortest, I want it to end at the rightmost.
– Ross
Nov 11 at 9:28
|
show 1 more comment
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
There's a few different ways you can go about doing this.
You could use the normal
zip()
with an iterator and manually check that it gets exhausted.def check_consumed(it):
try:
next(it)
except StopIteration:
pass
else:
raise ValueError('rightmost iterable was not consumed')
b_it = iter(b)
list(zip(a, b_it))
check_consumed(b_it)You could wrap the normal
zip()
to do the check for you.def myzip(a, b):
b_it = iter(b)
yield from zip(a, b_it)
# Or, if you're on a Python version that doesn't have yield from:
#for item in zip(a, b_it):
# yield item
check_consumed(b_it)
list(myzip(a, b))You could write your own
zip()
from scratch, usingiter()
andnext()
.(No code for this one, as option 2 is superior to this one in every way)
Is there a way to peek the iterator, so that its state is not modified.
– Ross
Nov 11 at 9:19
@Ross: If you mean "get an error whena
has 3 items andb
has 4 items, without consuming any items from either", the answer is "no", AFAIK. Even if you just don't want to consume one extra item fromb
, I think the answer is still "no". You could useitertools.chain()
to "add the consumed items back afterwards", however. (You can't literally add them back, but you can create a new iterator that gives you the consumed items first, and then continues giving the unconsumed items)
– Aleksi Torhamo
Nov 11 at 9:25
Okay, thanks, luckily for my case I can get around it. I forgot about theyield from
! Very nice solution.
– Ross
Nov 11 at 9:29
add a comment |
up vote
1
down vote
accepted
There's a few different ways you can go about doing this.
You could use the normal
zip()
with an iterator and manually check that it gets exhausted.def check_consumed(it):
try:
next(it)
except StopIteration:
pass
else:
raise ValueError('rightmost iterable was not consumed')
b_it = iter(b)
list(zip(a, b_it))
check_consumed(b_it)You could wrap the normal
zip()
to do the check for you.def myzip(a, b):
b_it = iter(b)
yield from zip(a, b_it)
# Or, if you're on a Python version that doesn't have yield from:
#for item in zip(a, b_it):
# yield item
check_consumed(b_it)
list(myzip(a, b))You could write your own
zip()
from scratch, usingiter()
andnext()
.(No code for this one, as option 2 is superior to this one in every way)
Is there a way to peek the iterator, so that its state is not modified.
– Ross
Nov 11 at 9:19
@Ross: If you mean "get an error whena
has 3 items andb
has 4 items, without consuming any items from either", the answer is "no", AFAIK. Even if you just don't want to consume one extra item fromb
, I think the answer is still "no". You could useitertools.chain()
to "add the consumed items back afterwards", however. (You can't literally add them back, but you can create a new iterator that gives you the consumed items first, and then continues giving the unconsumed items)
– Aleksi Torhamo
Nov 11 at 9:25
Okay, thanks, luckily for my case I can get around it. I forgot about theyield from
! Very nice solution.
– Ross
Nov 11 at 9:29
add a comment |
up vote
1
down vote
accepted
up vote
1
down vote
accepted
There's a few different ways you can go about doing this.
You could use the normal
zip()
with an iterator and manually check that it gets exhausted.def check_consumed(it):
try:
next(it)
except StopIteration:
pass
else:
raise ValueError('rightmost iterable was not consumed')
b_it = iter(b)
list(zip(a, b_it))
check_consumed(b_it)You could wrap the normal
zip()
to do the check for you.def myzip(a, b):
b_it = iter(b)
yield from zip(a, b_it)
# Or, if you're on a Python version that doesn't have yield from:
#for item in zip(a, b_it):
# yield item
check_consumed(b_it)
list(myzip(a, b))You could write your own
zip()
from scratch, usingiter()
andnext()
.(No code for this one, as option 2 is superior to this one in every way)
There's a few different ways you can go about doing this.
You could use the normal
zip()
with an iterator and manually check that it gets exhausted.def check_consumed(it):
try:
next(it)
except StopIteration:
pass
else:
raise ValueError('rightmost iterable was not consumed')
b_it = iter(b)
list(zip(a, b_it))
check_consumed(b_it)You could wrap the normal
zip()
to do the check for you.def myzip(a, b):
b_it = iter(b)
yield from zip(a, b_it)
# Or, if you're on a Python version that doesn't have yield from:
#for item in zip(a, b_it):
# yield item
check_consumed(b_it)
list(myzip(a, b))You could write your own
zip()
from scratch, usingiter()
andnext()
.(No code for this one, as option 2 is superior to this one in every way)
edited Nov 11 at 9:19
answered Nov 11 at 9:16
Aleksi Torhamo
4,45622534
4,45622534
Is there a way to peek the iterator, so that its state is not modified.
– Ross
Nov 11 at 9:19
@Ross: If you mean "get an error whena
has 3 items andb
has 4 items, without consuming any items from either", the answer is "no", AFAIK. Even if you just don't want to consume one extra item fromb
, I think the answer is still "no". You could useitertools.chain()
to "add the consumed items back afterwards", however. (You can't literally add them back, but you can create a new iterator that gives you the consumed items first, and then continues giving the unconsumed items)
– Aleksi Torhamo
Nov 11 at 9:25
Okay, thanks, luckily for my case I can get around it. I forgot about theyield from
! Very nice solution.
– Ross
Nov 11 at 9:29
add a comment |
Is there a way to peek the iterator, so that its state is not modified.
– Ross
Nov 11 at 9:19
@Ross: If you mean "get an error whena
has 3 items andb
has 4 items, without consuming any items from either", the answer is "no", AFAIK. Even if you just don't want to consume one extra item fromb
, I think the answer is still "no". You could useitertools.chain()
to "add the consumed items back afterwards", however. (You can't literally add them back, but you can create a new iterator that gives you the consumed items first, and then continues giving the unconsumed items)
– Aleksi Torhamo
Nov 11 at 9:25
Okay, thanks, luckily for my case I can get around it. I forgot about theyield from
! Very nice solution.
– Ross
Nov 11 at 9:29
Is there a way to peek the iterator, so that its state is not modified.
– Ross
Nov 11 at 9:19
Is there a way to peek the iterator, so that its state is not modified.
– Ross
Nov 11 at 9:19
@Ross: If you mean "get an error when
a
has 3 items and b
has 4 items, without consuming any items from either", the answer is "no", AFAIK. Even if you just don't want to consume one extra item from b
, I think the answer is still "no". You could use itertools.chain()
to "add the consumed items back afterwards", however. (You can't literally add them back, but you can create a new iterator that gives you the consumed items first, and then continues giving the unconsumed items)– Aleksi Torhamo
Nov 11 at 9:25
@Ross: If you mean "get an error when
a
has 3 items and b
has 4 items, without consuming any items from either", the answer is "no", AFAIK. Even if you just don't want to consume one extra item from b
, I think the answer is still "no". You could use itertools.chain()
to "add the consumed items back afterwards", however. (You can't literally add them back, but you can create a new iterator that gives you the consumed items first, and then continues giving the unconsumed items)– Aleksi Torhamo
Nov 11 at 9:25
Okay, thanks, luckily for my case I can get around it. I forgot about the
yield from
! Very nice solution.– Ross
Nov 11 at 9:29
Okay, thanks, luckily for my case I can get around it. I forgot about the
yield from
! Very nice solution.– Ross
Nov 11 at 9:29
add a comment |
up vote
0
down vote
I think this one does the work by checking if the last consumer was completely consumed before returning
# Example copied from https://stackoverflow.com/questions/19151/build-a-basic-python-iterator
class Counter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 3: def __next__(self)
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
# modified from https://docs.python.org/3.5/library/functions.html#zip
def myzip(*iterables):
sentinel = object()
iterators = [iter(it) for it in iterables]
while iterators:
result =
for it in iterators:
elem = next(it, sentinel)
if elem is sentinel:
elem = next(iterators[-1], sentinel)
if elem is not sentinel:
raise ValueError("rightmost iterable was not consumed")
else:
return
result.append(elem)
yield tuple(result)
a = Counter(1,7)
b = range(9)
for val in myzip(a,b):
print(val)
add a comment |
up vote
0
down vote
I think this one does the work by checking if the last consumer was completely consumed before returning
# Example copied from https://stackoverflow.com/questions/19151/build-a-basic-python-iterator
class Counter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 3: def __next__(self)
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
# modified from https://docs.python.org/3.5/library/functions.html#zip
def myzip(*iterables):
sentinel = object()
iterators = [iter(it) for it in iterables]
while iterators:
result =
for it in iterators:
elem = next(it, sentinel)
if elem is sentinel:
elem = next(iterators[-1], sentinel)
if elem is not sentinel:
raise ValueError("rightmost iterable was not consumed")
else:
return
result.append(elem)
yield tuple(result)
a = Counter(1,7)
b = range(9)
for val in myzip(a,b):
print(val)
add a comment |
up vote
0
down vote
up vote
0
down vote
I think this one does the work by checking if the last consumer was completely consumed before returning
# Example copied from https://stackoverflow.com/questions/19151/build-a-basic-python-iterator
class Counter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 3: def __next__(self)
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
# modified from https://docs.python.org/3.5/library/functions.html#zip
def myzip(*iterables):
sentinel = object()
iterators = [iter(it) for it in iterables]
while iterators:
result =
for it in iterators:
elem = next(it, sentinel)
if elem is sentinel:
elem = next(iterators[-1], sentinel)
if elem is not sentinel:
raise ValueError("rightmost iterable was not consumed")
else:
return
result.append(elem)
yield tuple(result)
a = Counter(1,7)
b = range(9)
for val in myzip(a,b):
print(val)
I think this one does the work by checking if the last consumer was completely consumed before returning
# Example copied from https://stackoverflow.com/questions/19151/build-a-basic-python-iterator
class Counter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 3: def __next__(self)
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
# modified from https://docs.python.org/3.5/library/functions.html#zip
def myzip(*iterables):
sentinel = object()
iterators = [iter(it) for it in iterables]
while iterators:
result =
for it in iterators:
elem = next(it, sentinel)
if elem is sentinel:
elem = next(iterators[-1], sentinel)
if elem is not sentinel:
raise ValueError("rightmost iterable was not consumed")
else:
return
result.append(elem)
yield tuple(result)
a = Counter(1,7)
b = range(9)
for val in myzip(a,b):
print(val)
answered Nov 11 at 9:14
Eternal_flame-AD
3126
3126
add a comment |
add a comment |
up vote
0
down vote
There is already a zip_longest in itertools that allows for "expansion" of the shorter iterable by a default value.
Use that and check if your default value occurs: if so, it would have been a case of "rightmost element not consumed"
:
class MyError(ValueError):
"""Unique "default" value that is recognizeable and allows None to be in your values."""
pass
from itertools import zip_longest
isMyError = lambda x:isinstance(x,MyError)
def myzip(a,b):
"""Raises MyError if any non-consumed elements would occur using default zip()."""
K = zip_longest(a,b, fillvalue=MyError())
if all(not isMyError(t) for q in K for t in q):
return zip(a,b)
raise MyError("Not all items are consumed")
a = ['a', 'b', 'c', 'd']
b = [1, 2, 3, 4]
f = myzip(a, b)
print(list(f))
try:
a = ['a', 'b', ]
b = [1, 2, 3, 4]
f = myzip(a, b)
print(list(f))
except MyError as e:
print(e)
Output:
[('a', 1), ('b', 2), ('c', 3), ('d', 4)]
Not all items are consumed
This consumes (worst case) the full zipped list once to check and then returns it as iterable.
add a comment |
up vote
0
down vote
There is already a zip_longest in itertools that allows for "expansion" of the shorter iterable by a default value.
Use that and check if your default value occurs: if so, it would have been a case of "rightmost element not consumed"
:
class MyError(ValueError):
"""Unique "default" value that is recognizeable and allows None to be in your values."""
pass
from itertools import zip_longest
isMyError = lambda x:isinstance(x,MyError)
def myzip(a,b):
"""Raises MyError if any non-consumed elements would occur using default zip()."""
K = zip_longest(a,b, fillvalue=MyError())
if all(not isMyError(t) for q in K for t in q):
return zip(a,b)
raise MyError("Not all items are consumed")
a = ['a', 'b', 'c', 'd']
b = [1, 2, 3, 4]
f = myzip(a, b)
print(list(f))
try:
a = ['a', 'b', ]
b = [1, 2, 3, 4]
f = myzip(a, b)
print(list(f))
except MyError as e:
print(e)
Output:
[('a', 1), ('b', 2), ('c', 3), ('d', 4)]
Not all items are consumed
This consumes (worst case) the full zipped list once to check and then returns it as iterable.
add a comment |
up vote
0
down vote
up vote
0
down vote
There is already a zip_longest in itertools that allows for "expansion" of the shorter iterable by a default value.
Use that and check if your default value occurs: if so, it would have been a case of "rightmost element not consumed"
:
class MyError(ValueError):
"""Unique "default" value that is recognizeable and allows None to be in your values."""
pass
from itertools import zip_longest
isMyError = lambda x:isinstance(x,MyError)
def myzip(a,b):
"""Raises MyError if any non-consumed elements would occur using default zip()."""
K = zip_longest(a,b, fillvalue=MyError())
if all(not isMyError(t) for q in K for t in q):
return zip(a,b)
raise MyError("Not all items are consumed")
a = ['a', 'b', 'c', 'd']
b = [1, 2, 3, 4]
f = myzip(a, b)
print(list(f))
try:
a = ['a', 'b', ]
b = [1, 2, 3, 4]
f = myzip(a, b)
print(list(f))
except MyError as e:
print(e)
Output:
[('a', 1), ('b', 2), ('c', 3), ('d', 4)]
Not all items are consumed
This consumes (worst case) the full zipped list once to check and then returns it as iterable.
There is already a zip_longest in itertools that allows for "expansion" of the shorter iterable by a default value.
Use that and check if your default value occurs: if so, it would have been a case of "rightmost element not consumed"
:
class MyError(ValueError):
"""Unique "default" value that is recognizeable and allows None to be in your values."""
pass
from itertools import zip_longest
isMyError = lambda x:isinstance(x,MyError)
def myzip(a,b):
"""Raises MyError if any non-consumed elements would occur using default zip()."""
K = zip_longest(a,b, fillvalue=MyError())
if all(not isMyError(t) for q in K for t in q):
return zip(a,b)
raise MyError("Not all items are consumed")
a = ['a', 'b', 'c', 'd']
b = [1, 2, 3, 4]
f = myzip(a, b)
print(list(f))
try:
a = ['a', 'b', ]
b = [1, 2, 3, 4]
f = myzip(a, b)
print(list(f))
except MyError as e:
print(e)
Output:
[('a', 1), ('b', 2), ('c', 3), ('d', 4)]
Not all items are consumed
This consumes (worst case) the full zipped list once to check and then returns it as iterable.
edited Nov 11 at 9:24
answered Nov 11 at 9:19
Patrick Artner
18.9k51940
18.9k51940
add a comment |
add a comment |
up vote
0
down vote
Other option using zip_longest from itertools. It returns also true or false if all lists are consumed. Maybe not the most efficient way, but could be improved:
from itertools import zip_longest
a = ['a', 'b', 'c', 'd']
b = [1, 2, 3, 4, 5]
c = ['aa', 'bb', 'cc', 'dd', 'ee', 'ff']
def myzip(*iterables):
consumed = True
zips =
for zipped in zip_longest(*iterables):
if None in zipped:
consumed = False
else:
zips.append(zipped)
return [zips, consumed]
list(myzip(a, b, c))
#=> [[('a', 1, 'aa'), ('b', 2, 'bb'), ('c', 3, 'cc'), ('d', 4, 'dd')], False]
Please see edit to the question. Added an "Important Note".
– Ross
Nov 11 at 9:00
Can you provide a chunk of a real context object? I supposed it was a list.
– iGian
Nov 11 at 9:05
Try usingiter(['a', 'b', 'c'])
anditer([1, 2, 3, 4])
(obviously they can only be used once)
– Ross
Nov 11 at 9:10
I don't know if I got the point, but I made an edit based on your update.
– iGian
Nov 11 at 9:27
I don't want it to end at the shortest, I want it to end at the rightmost.
– Ross
Nov 11 at 9:28
|
show 1 more comment
up vote
0
down vote
Other option using zip_longest from itertools. It returns also true or false if all lists are consumed. Maybe not the most efficient way, but could be improved:
from itertools import zip_longest
a = ['a', 'b', 'c', 'd']
b = [1, 2, 3, 4, 5]
c = ['aa', 'bb', 'cc', 'dd', 'ee', 'ff']
def myzip(*iterables):
consumed = True
zips =
for zipped in zip_longest(*iterables):
if None in zipped:
consumed = False
else:
zips.append(zipped)
return [zips, consumed]
list(myzip(a, b, c))
#=> [[('a', 1, 'aa'), ('b', 2, 'bb'), ('c', 3, 'cc'), ('d', 4, 'dd')], False]
Please see edit to the question. Added an "Important Note".
– Ross
Nov 11 at 9:00
Can you provide a chunk of a real context object? I supposed it was a list.
– iGian
Nov 11 at 9:05
Try usingiter(['a', 'b', 'c'])
anditer([1, 2, 3, 4])
(obviously they can only be used once)
– Ross
Nov 11 at 9:10
I don't know if I got the point, but I made an edit based on your update.
– iGian
Nov 11 at 9:27
I don't want it to end at the shortest, I want it to end at the rightmost.
– Ross
Nov 11 at 9:28
|
show 1 more comment
up vote
0
down vote
up vote
0
down vote
Other option using zip_longest from itertools. It returns also true or false if all lists are consumed. Maybe not the most efficient way, but could be improved:
from itertools import zip_longest
a = ['a', 'b', 'c', 'd']
b = [1, 2, 3, 4, 5]
c = ['aa', 'bb', 'cc', 'dd', 'ee', 'ff']
def myzip(*iterables):
consumed = True
zips =
for zipped in zip_longest(*iterables):
if None in zipped:
consumed = False
else:
zips.append(zipped)
return [zips, consumed]
list(myzip(a, b, c))
#=> [[('a', 1, 'aa'), ('b', 2, 'bb'), ('c', 3, 'cc'), ('d', 4, 'dd')], False]
Other option using zip_longest from itertools. It returns also true or false if all lists are consumed. Maybe not the most efficient way, but could be improved:
from itertools import zip_longest
a = ['a', 'b', 'c', 'd']
b = [1, 2, 3, 4, 5]
c = ['aa', 'bb', 'cc', 'dd', 'ee', 'ff']
def myzip(*iterables):
consumed = True
zips =
for zipped in zip_longest(*iterables):
if None in zipped:
consumed = False
else:
zips.append(zipped)
return [zips, consumed]
list(myzip(a, b, c))
#=> [[('a', 1, 'aa'), ('b', 2, 'bb'), ('c', 3, 'cc'), ('d', 4, 'dd')], False]
edited Nov 11 at 10:12
answered Nov 11 at 8:58
iGian
2,5992621
2,5992621
Please see edit to the question. Added an "Important Note".
– Ross
Nov 11 at 9:00
Can you provide a chunk of a real context object? I supposed it was a list.
– iGian
Nov 11 at 9:05
Try usingiter(['a', 'b', 'c'])
anditer([1, 2, 3, 4])
(obviously they can only be used once)
– Ross
Nov 11 at 9:10
I don't know if I got the point, but I made an edit based on your update.
– iGian
Nov 11 at 9:27
I don't want it to end at the shortest, I want it to end at the rightmost.
– Ross
Nov 11 at 9:28
|
show 1 more comment
Please see edit to the question. Added an "Important Note".
– Ross
Nov 11 at 9:00
Can you provide a chunk of a real context object? I supposed it was a list.
– iGian
Nov 11 at 9:05
Try usingiter(['a', 'b', 'c'])
anditer([1, 2, 3, 4])
(obviously they can only be used once)
– Ross
Nov 11 at 9:10
I don't know if I got the point, but I made an edit based on your update.
– iGian
Nov 11 at 9:27
I don't want it to end at the shortest, I want it to end at the rightmost.
– Ross
Nov 11 at 9:28
Please see edit to the question. Added an "Important Note".
– Ross
Nov 11 at 9:00
Please see edit to the question. Added an "Important Note".
– Ross
Nov 11 at 9:00
Can you provide a chunk of a real context object? I supposed it was a list.
– iGian
Nov 11 at 9:05
Can you provide a chunk of a real context object? I supposed it was a list.
– iGian
Nov 11 at 9:05
Try using
iter(['a', 'b', 'c'])
and iter([1, 2, 3, 4])
(obviously they can only be used once)– Ross
Nov 11 at 9:10
Try using
iter(['a', 'b', 'c'])
and iter([1, 2, 3, 4])
(obviously they can only be used once)– Ross
Nov 11 at 9:10
I don't know if I got the point, but I made an edit based on your update.
– iGian
Nov 11 at 9:27
I don't know if I got the point, but I made an edit based on your update.
– iGian
Nov 11 at 9:27
I don't want it to end at the shortest, I want it to end at the rightmost.
– Ross
Nov 11 at 9:28
I don't want it to end at the shortest, I want it to end at the rightmost.
– Ross
Nov 11 at 9:28
|
show 1 more comment
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53247137%2fzip-like-function-that-fails-if-a-particular-iterator-is-not-consumed%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Note that the code you edited in for
myzip()
won't work - it'll always give you theValueError
as long as the last iterable has at least one element in it to begin with. This is because - unlike in Python 2, wherezip()
will consume the iterables immediately, and return a list - in Python 3zip()
returns a generator and consumes the iterables lazily. This means that when you callnext()
in the current version ofmyzip()
, no items have been consumed byzip()
yet, and all the iterators are still at the very beginning.– Aleksi Torhamo
Nov 11 at 13:47
Yeah I did realize this when I tried to use this, I am using a solution that uses
yield from
like in your answer.– Ross
Nov 12 at 6:54