1

I'm starting out here and love list comprehension. I found this following line of code for a practice problem of capitalizing every other letter in a string and it makes sense to me. The problem I'm hoping to get help with is figuring out how to write this code normally (for loop, if statements, etc. Beginners stuff) without list comprehension.

Here's the code that I started with that I want to break down:

s = input('Please enter a string: ')
answer = ''.join(char.upper() if idx % 2 else char.lower() for idx, char in enumerate(s))
print(answer)

And here is what I thought was a proper code to reproduce what this one above was doing:

s = input('Please enter a string: ')
for idx, char in enumerate(s):
    if idx % 2:
        s = char.upper()
    else:
        s = char.lower()
    answer = ''.join(s)
print(answer)

If I were to type Hello, I should get hElLo, but instead I get o

I'd appreciate any advice or tips on how to proceed here. Thanks!

Ali AzG
  • 1,861
  • 2
  • 18
  • 28
Bird270
  • 17
  • 2
  • 1
    `s` gets reassigned in every iteration, making the final `answer` as the final `char` which is indeed `o` from `hello`. – Chris May 20 '19 at 05:30
  • Possible duplicate of [Capitalise every other letter in a string in Python?](https://stackoverflow.com/questions/17865563/capitalise-every-other-letter-in-a-string-in-python) – bharatk May 20 '19 at 05:33
  • The question in that linked comment is about how to get python to capitalize every other letter. I understand how to do that. I am hoping to understand how this list comprehension using enumerate is actually written but using for loops and if statements. Basically, I hope to see how it would be properly written without the list comprehension to better understand both list comprehension and enumeration. – Bird270 May 20 '19 at 05:36
  • As Chris mentioned, you are trying reinitialize `s` and `answer` after every iteration, you can try initializing `answer = ''` at the start of the loop and add `answer += s` at the end of iteration – Sri Raghu Malireddi May 20 '19 at 05:37
  • @Chris Then might I ask how the list comprehension then doesn't suffer from a similar fate? – Bird270 May 20 '19 at 05:39
  • I would focus on creating a list, with list `append` and so on. Then finish with one `join` – hpaulj May 20 '19 at 05:40
  • @SriRaghuMalireddi Ah, that certainly makes sense. But then how does such a problem not come up with the list comprehension? Is it that every time it runs it stores the new char value to answer? – Bird270 May 20 '19 at 05:41

1 Answers1

2

Technically you're using a generator expression, and not a list comprehension. But the result is similar in this case.

You're reading input into s, but then you're reassigning s in each iteration, and then you're joining s.

You need to have a different variable. For the input and for the capitalized list. What a generator expression does is to return one value at a time, instead of building the whole capitalized list at once. But here you will need to declare it.

Also, it doesn't make sense to assign answer in each iteration, the join is the last thing you need to do, once your capitalized list is prepared.

This should work:

toCapitalize = input('Please enter a string: ')
capitalizedList = []

for idx, char in enumerate(toCapitalize):
    if idx % 2:
        capitalizedList.append(char.upper())
    else:
        capitalizedList.append(char.lower())

answer = ''.join(capitalizedList)
print(answer)

In case this helps, I've tried to reflect what line goes with what part of the generator expression below:

for idx, char in enumerate(toCapitalize):            # for idx, char in enumerate(s)
    if idx % 2: capitalizedList.append(char.upper()) # char.upper() if idx % 2
    else: capitalizedList.append(char.lower())       # else char.lower()
answer = ''.join(capitalizedList)                    # answer = ''.join()

Again, the capitalizedList variable is implicit in the generator expressions or list comprehensions.

Generator expressions

To understand generator expressions look at this code:

capitalize = 'hello'
generator = (char.upper() if idx % 2 else char.lower() for idx, char in enumerate(capitalize))
print(next(generator)) # h
print(next(generator)) # E
print(next(generator)) # l
print(next(generator)) # L
print(next(generator)) # o
print(next(generator)) # raises a StopIteration exception, we've reached the end.

Each call to next() calculates the result of the next iteration on the fly. Which is more memory efficient than building a whole list at once when you have big lists. In your case, the call to join() consumes all the generator, and joins the values returned.

As a list comprehension

Your code as a list comprehension would be:

s = input('Please enter a string: ')
answer = ''.join([ char.upper() if idx % 2 else char.lower() for idx, char in enumerate(s) ])
print(answer)

As I explained above, the difference is that here we're building a whole list at once, the generator is only returning one value at a time.

Your code as a generator

And finally, to be technically correct your code would be equivalent to this:

def generator(s):
    for idx, char in enumerate(s):
        if idx % 2:
            yield char.upper()
        else:
            yield char.lower()

answer = ''.join(generator(s))
print(answer)

That's how you build a generator in Python.

ArianJM
  • 676
  • 12
  • 25
  • That was exactly what I wanted to learn here! Thank you so much @ArianJM. Seeing this worked out like you have really makes it so easy to see where my misunderstandings were and where I now need to further focus on. Thanks again! – Bird270 May 21 '19 at 02:42
  • I'm glad it was helpful :-) – ArianJM May 21 '19 at 07:56