简体   繁体   中英

Why is zip(*) not restoring my original list

I'm trying to understand what is happening:

a = list('hello world')
b = [a[i::l]for i in range(8)]

I would then expect that:

zip(*b) == a

However what I get is the following:

 [('h', 'e', 'l', 'l', 'o', ' ', 'w', 'o')]

Maybe it's a failing on my part to understand what zip(*) doe but I thought it unpacks a list of list and makes a single list out of it. Where am I going wrong?

You missed a detail specific to zip() , as outlined in the documentation :

The iterator stops when the shortest input iterable is exhausted

hello world has 11 characters in it, a prime number, so apart from a list of 11 separate sequences with each one character, there is no way to produce a list of lists without at least one of those being shorter.

For example, if we presume that l = 8 (anything 5 and over would produce the output you've shown), then a is set to:

[['h', 'r'], ['e', 'l'], ['l', 'd'], ['l'], ['o'], [' '], ['w'], ['o']]

That's 8 lists, with the first containing 2 elements, the remainder have just one. So only the first elements of these are then used to produce combinations:

>>> [l[0] for l in b]
['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o']

You only looped 8 times, so there only 8 top-level lists in b to take letters from. For different values of l of 5 or up you'll get a different distribution of the remaining letters but with only 3 more characters remaining there aren't many ways of distributing those remaining letters across the lists, and with l below 8 , you just add repeated letters (as [0::l] and [7::l] are guaranteed to overlap for any l equal to 7 or lower).

You'd have to loop up to 11 times and take every 11th character to get something that'll zip to the same sequence:

>>> b = [a[i::11]for i in range(11)]
>>> b
[['h'], ['e'], ['l'], ['l'], ['o'], [' '], ['w'], ['o'], ['r'], ['l'], ['d']]
>>> list(zip(*b))
[('h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd')]

This still isn't the same as a , because zip() produces sequence of sequences (here it's just one because there is only a single value in each nested list). You could use next() to get the first (and only) element:

>>> a == list(next(zip(*b)))
True

You can instead use itertools.zip_longest() to continue iteration until the longest input iterable is exhausted, and add a default filler value to augment the shorter sequences. An empty string would work if you wanted to join the sequences back to whole strings again:

try:
    # Python 3
    from itertools import zip_longest
except ImportError:
    # Python 2 has the same object, but with i prefixed
    from itertools import izip_longest as zip_longest

result = list(zip_longest(*b, fillvalue=''))

This however produces two tuples; there are two columns in the input, after all:

>>> from itertools import zip_longest
>>> b = [a[i::8]for i in range(8)]
>>> list(zip_longest(*b, fillvalue=''))
[('h', 'e', 'l', 'l', 'o', ' ', 'w', 'o'), ('r', 'l', 'd', '', '', '', '', '')]

You'd have to chain them to re-combine them; itertools.chain.from_iterable() could do that:

>>> from itertools import chain
>>> ''.join(chain.from_iterable(zip_longest(*b, fillvalue='')))
'hello world'

This only works for l = 8 , again, because of overlapping slices for lower values of l , For l > 8 , you start out missing characters from the end as none of the 8 a[i::l] slices include those characters

>>> for l in range(2, 12):
...     print(f'{l:>2d}:', ''.join(chain.from_iterable(zip_longest(*[a[i::l] for i in range(8)], fillvalue=''))))
...
 2: hello wollo worlo worldworldrldd
 3: hello wolo worldworldld
 4: hello woo worldrld
 5: hello wo worldd
 6: hello woworld
 7: hello woorld
 8: hello world
 9: hello wold
10: hello wod
11: hello wo

Your code isn't so clear, and we don't know what the l really is! if u run it as it is, you will surely get an error saying that the l isn't defined.

However, for the zip function it stops at the shortest iterator, to force it keep going you should use zip_longest .

for more details on how zip function works check this : Python zip

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM