19

I want to randomly capitalize or lowercase each letter in a string. I'm new to working with strings in python, but I think because strings are immutable that I can't do the following:

i =0             
for c in sentence:
    case = random.randint(0,1)
    print("case = ", case)
    if case == 0:
        print("here0")
        sentence[i] = sentence[i].lower()
    else:
        print("here1")
        sentence[i] = sentence[i].upper()
    i += 1
print ("new sentence = ", sentence)

And get the error: TypeError: 'str' object does not support item assignment

But then how else could I do this?

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
user1045890
  • 379
  • 1
  • 5
  • 11

6 Answers6

29

You can use str.join with a generator expression like this:

from random import choice
sentence = 'Hello World'
print(''.join(choice((str.upper, str.lower))(c) for c in sentence))

Sample output:

heLlo WORLd
blhsing
  • 91,368
  • 6
  • 71
  • 106
  • Aren't list comprehensions faster with join? – U13-Forward Oct 23 '18 at 06:45
  • 2
    No, list comprehension would be slower because it would materialize all item values first to form a list before passing the list to `join`, resulting in more overhead in both time and memory. With a generator expression, the `join` method would be able to simply iterate through the generator output as the generator produces item values one by one. – blhsing Oct 23 '18 at 06:47
  • 2
    Nice.., thanks for telling me – U13-Forward Oct 23 '18 at 06:48
  • 9
    @U9-Forward You are correct. `join` needs a list, so giving it a list in the first place is faster than giving it a generator. [Authoritative reference](https://stackoverflow.com/a/9061024/3620003). – timgeb Oct 23 '18 at 06:51
  • 1
    @timgeb Oh i am right, wow, now i know :-) – U13-Forward Oct 23 '18 at 06:52
  • 4
    @timgeb I stand corrected then. Thanks. – blhsing Oct 23 '18 at 06:52
7

Build a new string.

Here's a solution with little changes to your original code:

>>> import random
>>> 
>>> def randomcase(s):
...:    result = ''
...:    for c in s:
...:        case = random.randint(0, 1)
...:        if case == 0:
...:            result += c.upper()
...:        else:
...:            result += c.lower()
...:    return result
...:
...:
>>> randomcase('Hello Stackoverflow!')
>>> 'hElLo StaCkoVERFLow!'

edit: deleted my oneliners because I like blhsing's better.

timgeb
  • 76,762
  • 20
  • 123
  • 145
4
import random
sentence='quick test'
print(''.join([char.lower() if random.randint(0,1) else char.upper() \
                   for char in sentence]))

qUiCK TEsT

Venkatachalam
  • 16,288
  • 9
  • 49
  • 77
3

Just change the string implementation to a list implementation. As string is immutable, you cannot change the value inside the object. But Lists can be, So I've only changed that part of your code. And make note that there are much better ways to do this, Follow here

import random
sentence = "This is a test sentence" # Strings are immutable
i =0
new_sentence = [] # Lists are mutable sequences
for c in sentence:
    case = random.randint(0,1)
    print("case = ", case)
    if case == 0:
        print("here0")
        new_sentence += sentence[i].lower() # append to the list
    else:
        print("here1")
        new_sentence += sentence[i].upper() # append to the list
    i += 1
print ("new sentence = ", new_sentence)

# to print as string
new_sent = ''.join(new_sentence)
print(new_sent)
Vineeth Sai
  • 3,389
  • 7
  • 23
  • 34
2

You can do like below

char_list = []            
for c in sentence:
    ucase = random.randint(0,1)
    print("case = ", case)
    if ucase:
        print("here1")
        char_list.append(c.upper())
    else:
        print("here0")
        char_list.append(c.lower())
print ("new sentence = ", ''.join(char_list))
ansu5555
  • 416
  • 2
  • 7
0

One way without involving python loop would be sending it to numpy and do vectorized operation over that. For example:

import numpy as np
def randomCapitalize(s):
    s  = np.array(s, 'c').view('u1')
    t  = np.random.randint(0, 2, len(s), 'u1') # Temporary array
    t *= s != 32 # ASCII 32 (i.e. space) should not be lowercased
    t *= 32 # Decrease ASCII by 32 to lowercase
    s -= t
    return s.view('S' + str(len(s)))[0]
randomCapitalize('hello world jfwojeo jaiofjowejfefjawivj a jofjawoefj')

which outputs:

b'HELLO WoRlD jFwoJEO JAioFjOWeJfEfJAWIvJ A JofjaWOefj'

This solution should be reasonably fast especially for long string. There are two limitations of this method:

  • The input must be fully lower case. You can try .lower() it first but thats technically low efficient.

  • It need special care for non-a-to-z character. In the example above, only space is handled

You can handle a lot more special characters at the same time by replacing

t *= s != 32

with

# using space, enter, comma, period as example
t *= np.isin(s, list(map(ord, ' \n,.')), invert=True)

For example:

s = 'ascii table and description. ascii stands for american standard code for information interchange. computers can only understand numbers, so an ascii code is the numerical representation of a character such as'
randomCapitalize(s)

which outputs:

b'ascII tABLe AnD descRiptIOn. ascii sTaNds for AmEricAN stanDaRD codE FOr InForMAtION iNTeRCHaNge. ComPUtERS can onLY UNdersTand nUMBers, So An asCIi COdE IS tHE nuMERIcaL rEPrEsEnTATion Of a CHaractEr such as'
ZisIsNotZis
  • 1,570
  • 1
  • 13
  • 30
  • Hey if something is wrong I'll fix it. Would you at least leave a comment for reason of downvote? – ZisIsNotZis Oct 24 '18 at 01:31
  • Not a downvoter, but i presume the issue is bringing Numpy and vectorised whatnot into a situation where the OP just needs to learn about mutable versus immutable containers. – Sneftel Oct 24 '18 at 21:49