4

I just started studying Python and created some kind of task for myself that I'm struggling to solve...

So, I'm on chapter on working with strings (accessing strings by index, changing etc).

My task is - capitalize every odd char of the string. I saw the solution here: Capitalise every other letter in a string in Python?

but I don't like it... wanted to solve this by slices. So what I produced is followig code:

T = 'somesampletexthere'  
R=""

for i in range(0, len(T[1::2])):
        R+=T[::2][i].upper()+T[1::2][i].lower()

print R

This code works great when there are even number of chars, for example with TESTTEXT but when the number is odd like here in EXAMPLE it will skipp last char (in this case E)

This is because the range is till len(T[1::2])) (length of even characters) but if I'll try the length of odd characters len(T[::2])) I'll get error:

IndexError: string index out of range

logically because number of odd chars will always be bigger on 1 (in case of text with odd number of characters).

So, my question is - how can I supress the error? So Python just return null or something in case the index is out of range of the given string?

Or any other solutions of the task using slices?

Community
  • 1
  • 1
Akhiles
  • 43
  • 1
  • 3
  • 1
    If you're just starting Python you should _seriously_ consider learning Python 3. Python 2 will no longer be supported after 2020. – PM 2Ring Oct 28 '16 at 12:12
  • @PM 2Ring just wanted to start with 2 in order to understnad more python 3 learning books :) – Akhiles Oct 28 '16 at 13:18

3 Answers3

5

You could just use enumerate and test for odd and even indices using a ternary operator. Then use join to join the string:

>>> T = 'somesampletexthere'
>>> ''.join(x.upper() if i%2 else x.lower() for i, x in enumerate(T))
'sOmEsAmPlEtExThErE'

You could handle whitespaces by using an external counter object provided by itertools.count:

>>> from itertools import count
>>> c = count()
>>> T = 'some sample text here'
>>> ''.join(x if x.isspace() else (x.upper() if next(c)%2 else x.lower()) for x in T)
'sOmE sAmPlE tExT hErE'
Moses Koledoye
  • 77,341
  • 8
  • 133
  • 139
  • What if the string has spaces in it? – Andy Oct 28 '16 at 11:58
  • 1
    @Andy The spaces are left as is. `upper` or `lower` will not modify them. – Moses Koledoye Oct 28 '16 at 11:58
  • Yeah I know, but the link he provided showed that he wanted to capitalize every other letter in the string. So you have to ignore the spaces. – Andy Oct 28 '16 at 12:00
  • Thanks! Its not the direction in which I was investigated, but it looks much better than in solution provided in separate thread, thanks a lot! – Akhiles Oct 28 '16 at 12:11
  • `x.isspace()` is probably a little more efficient than `not x.strip()` because it only has to return the boolean rather than constructing the stripped string. And arguably a little clearer. – PM 2Ring Oct 28 '16 at 12:19
4

Your are right to prefer a slicing solution over those in the linked question. (OTOH, that's a slightly different question because it skips spaces). However, your current code is rather inefficient because it recreates the T[::2] and T[1::2] slices on every iteration of the for loop. Also, calling .upper or .lower on single characters is less efficient than calling it on larger strings.

Here's an efficient way to do this using slicing.

T = 'somesampletexthere'
R = [''] * len(T)
R[::2], R[1::2] = T[::2].upper(), T[1::2].lower()
R = ''.join(R)
print(R)

output

SoMeSaMpLeTeXtHeRe

It may help you understand what's going on if we split up those assignments to R.

T = 'somesampletexthere'
R = [''] * len(T)
R[::2] = T[::2].upper()
print(R)
R[1::2] = T[1::2].lower()
print(R)
R = ''.join(R)
print(R)

output

['S', '', 'M', '', 'S', '', 'M', '', 'L', '', 'T', '', 'X', '', 'H', '', 'R', '']
['S', 'o', 'M', 'e', 'S', 'a', 'M', 'p', 'L', 'e', 'T', 'e', 'X', 't', 'H', 'e', 'R', 'e']
SoMeSaMpLeTeXtHeRe

We can do those slice assignments in either order:

T = 'somesampletexthere'
R = [''] * len(T)
R[1::2] = T[1::2].lower()
print(R)
R[::2] = T[::2].upper()
print(R)
R = ''.join(R)
print(R)

output

['', 'o', '', 'e', '', 'a', '', 'p', '', 'e', '', 'e', '', 't', '', 'e', '', 'e']
['S', 'o', 'M', 'e', 'S', 'a', 'M', 'p', 'L', 'e', 'T', 'e', 'X', 't', 'H', 'e', 'R', 'e']
SoMeSaMpLeTeXtHeRe

Just for fun, here's an alternative strategy that zips the upper & lower case strings together. We use izip_longest (or zip_longest in Python 3) so we can handle strings that have an odd length.

from itertools import izip_longest

T = 'somesampletexthere'
R = ''.join([c for t in izip_longest(T[::2].upper(), T[1::2].lower(), fillvalue='') for c in t])
print(R)

Although this version does it in one line I prefer my first version: I find it more readable, and it's probably a little faster.

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • Wow, thanks! Thats exactly what I wanted and the direction in which I was trying to investigate. – Akhiles Oct 28 '16 at 12:24
  • One question, here, I just can't understand the magic... so T[::2].upper() and T[1::2].lower() are strings and R at the moment of running 'join' is a list of strings in correct order... how this is happening? :) – Akhiles Oct 28 '16 at 12:29
  • @Akhiles: Magic! :) When you do a slice assignment to a list from a string Python unpacks the string into the list elements. It's similar to what happens if you do `a,b,c = 'abc'` - the string gets unpacked into the `(a,b,c)` tuple. – PM 2Ring Oct 28 '16 at 12:35
  • I mean I got the moment of assigment string to list (it's like adding char to float - you will get float, as I understood). So R[::2], R[1::2] are lists of characters already, I didn't get the trick why join() joined them in correct order... I guess I'll go and read about this function :) anyway thanks a lt! You rock! – Akhiles Oct 28 '16 at 12:40
  • @Akhiles Python is strongly typed, it won't let you add a char to a float. The chars are in the correct order because of the way we do the slice assignment: the transformed even chars get assigned to `R[::2]` and the transformed odd chars get assigned to`R[1::2]`, `.join` doesn't affect the order in any way, it just builds a string from the strings in the list you pass it. – PM 2Ring Oct 28 '16 at 12:49
  • sorry, I ment integer to float :) – Akhiles Oct 28 '16 at 12:51
  • sorry maybe to be dumb :( but I'm trying to get everything... so after assigment I print results of two R lists print R[::2], R[1::2] and get: ['S', 'M', 'S', 'M', 'L', 'T', 'X', 'H', 'R', 'G'] ['o', 'e', 'a', 'p', 'e', 'e', 't', 'e', 'e'] so in my understnading join should join them to string "SMSMLTXHRGoeapeetee" but why it works as we want... don't get... UPD, I get it... i get it! sorry , I get it when I just printed R :) without slices – Akhiles Oct 28 '16 at 12:55
  • @Akhiles: I've added a little more explanation to my answer. – PM 2Ring Oct 28 '16 at 13:03
0

Strings are not mutable, but lists are. So, we can create a list of lowercase characters of the string (from .lower()), use slice assignment to replace the appropriate ones with uppercase, and join them all back together:

lower_chars = list(T.lower())
lower_chars[::2] = T[::2].upper()
R = ''.join(lower_chars)
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153