What exactly is going on here? (Python 3.7.6)

76 Views Asked by At

I'm studying someone else's solution on Codewars and am a little puzzled about something. Here is a link to the original question: Reverse or Rotate?. Credit goes to the original author, falsetru.

Here's the solution:

def revrot(strng, sz):
    return ''.join(
        chunk[1:] + chunk[:1] if sum(int(d)**3 for d in chunk) % 2 else chunk[::-1]
        for chunk in map(''.join, zip(*[iter(strng)]*sz))
    )

I think I understand most of it. Except for this part:

zip(*[iter(strng)]*sz)

I think that the * used in this way signifies a non-keyworded variable-length argument list - meaning that there could be any number of pieces of the original string (strng), which are of length sz, for example, 6. The zip() function is receiving some variable number of iterables, which is what it requires, per the documentation. (Right?)

So then, map(''.join, zip(*[iter(strng)]*sz) first returns an iterator for the string strng. It returns this inside of a list. It seems like that list is then multiplied by sz (why?). It returns a variable number of results to zip (thus the *). zip() returns a tuple (I guess?), which is then passed through the join function via map.

Questions:

  1. Is that even close to being right?
  2. Why must iter(strng) be placed inside a list []?
  3. Why can you join the result of zip? I tried joining ('m',) as a test and got 'm'. Confused as to why that works as well.
  4. Can someone explain more about the *? I'm confused as to when I should use it...

Thanks. I'm still a late beginner in Python so I appreciate the help! (Even for just a piece of my question!)

1

There are 1 best solutions below

1
On BEST ANSWER

To understand what is happening, we want to analyze the statement

for chunk in map(''.join, zip(*[iter(strng)]*sz))

Inside out:

  1. iter(strng) returns an iterator that each time is accessed using next or in an loop consumes an element (a character) of strng and returns said element.
  2. [iter(strng)] is a list, its unique element is the iterator
  3. [iter(strng)]*sz is the concatenation of sz copies of the list, [iter(strng), ..., iter(strng)] containing sz times the same iterator object, I mean literally the same iterator object.
  4. *[iter(strng)]*sz is equivalent to *[iter(strng), ..., iter(strng)] and, when used in a function argument list, unpacks its contents: the function sees its list of arguments as (iter(strng), ..., iter(strng)).
  5. zip(*[iter(strng)]*sz) is hence equivalent to zip(iter(strng), ..., iter(strng)).
  6. At each iteration zip takes the first element of each of its arguments and place them in a tuple, but because the various references to iter all refer to the same, original instance of iter(strng) the first tuple returned by zip contains the first sz characters of strng, the second contains the sz+1 to 2*sz characters etc etc.
  7. Finally, each of this tuples is the argument of ''.join(), so we have a series of strings each long sz characters, spanning the original strng.

That's it.