5

I'm a beginner coder, and I recently created a function that takes a string and randomly capitalizes each letter.

def rand_upper(string):
    import random
    strList = [l for l in string.lower()] 
    newList = []
    for i in strList:
        j = random.randrange(2)
        if j == 1:
            letter = i.upper()
            newList.append(letter)
        else:
            newList.append(i)
    return "".join(newList)

The code works as I intended, but is there any way that I can make this code cleaner? I personally think this is hard to read. Are there any other ways to compress the code to make it more readable/efficient? thanks!

TheShin
  • 77
  • 5
  • 2
    I’m voting to close this question because it belongs on [codereview.se] – Tomerikoo Dec 07 '20 at 17:05
  • And anyway, a duplicate: [Randomly capitalize letters in string](https://stackoverflow.com/questions/52942497/randomly-capitalize-letters-in-string) – Tomerikoo Dec 07 '20 at 17:06
  • 1
    @Tomerikoo While this may be on-topic on CR, in the future, please don't use the existence of the Code Review site as a reason to close a question. Evaluate the request and use a reason like *Needs more focus* (as I have done here), *primarily opinion-based*, etc. Then you can mention to the OP that it can be posted on Code Review if it is [on-topic](https://codereview.stackexchange.com/help/on-topic). Please see [_Does being on-topic at another Stack Exchange site automatically make a question off-topic for Stack Overflow?_](https://meta.stackoverflow.com/q/287400/1575353) – Sᴀᴍ Onᴇᴌᴀ Dec 07 '20 at 17:07
  • [import statements always be at the top of a module](https://stackoverflow.com/questions/128478/should-import-statements-always-be-at-the-top-of-a-module) – DarrylG Dec 07 '20 at 17:07
  • 1
    @SᴀᴍOnᴇᴌᴀ thanks for that link! Duly noted. Anyway, it's a duplicate – Tomerikoo Dec 07 '20 at 17:11
  • @DarrylG Thank you for the tip! I'll keep that in mind. – TheShin Dec 07 '20 at 17:12
  • Does this answer your question? [Randomly capitalize letters in string](https://stackoverflow.com/questions/52942497/randomly-capitalize-letters-in-string) – Georgy Dec 08 '20 at 13:51

5 Answers5

7

Using choice instead, and calling lower and upper only once.

from random import choice

def rand_upper(string):
    return ''.join(map(choice, zip(string.lower(), string.upper())))

Even better, as Peter commented:

def rand_upper(string):
    return ''.join(map(choice, zip(string, string.swapcase())))

Another, based on Olvin_Roght's:

def rand_upper(string):
    return ''.join([c if getrandbits(1) else c.swapcase() for c in string])

Two more, mixing our solutions for the fastest so far:

def rand_upper(string):
    return ''.join([c if getrandbits(1) else d
                    for c, d in zip(string, string.swapcase())])
def rand_upper(string):
    return ''.join([z[getrandbits(1)] for z in zip(string, string.swapcase())])

Benchmark using string = rand_upper('a' * 1000):

739 μs  797 μs  725 μs  original
764 μs  787 μs  693 μs  original_2
713 μs  691 μs  680 μs  Samwise
699 μs  657 μs  682 μs  theCoder
477 μs  486 μs  490 μs  superb_rain
520 μs  476 μs  489 μs  Peter_Wood
135 μs  131 μs  141 μs  based_on_Olvin_Roght
120 μs  113 μs  121 μs  superb_Peter_Olvin
125 μs  117 μs  118 μs  superb_Peter_Olvin_2

(Not including Olvin's original because it's the only one with quadratic instead of linear time, so a comparison with a single size would be misleading.)

Code:

from timeit import repeat
from random import randrange, choice, getrandbits

def original(string):
    import random
    strList = [l for l in string.lower()] 
    newList = []
    for i in strList:
        j = random.randrange(2)
        if j == 1:
            letter = i.upper()
            newList.append(letter)
        else:
            newList.append(i)
    return "".join(newList)

def original_2(string):
    strList = [l for l in string.lower()] 
    newList = []
    for i in strList:
        j = randrange(2)
        if j == 1:
            letter = i.upper()
            newList.append(letter)
        else:
            newList.append(i)
    return "".join(newList)

def Samwise(string: str) -> str:
    return "".join(
        c.upper() if randrange(2) else c.lower() 
        for c in string
    )

def theCoder(string):
    return ''.join(choice((str.upper, str.lower))(c) for c in string)

def superb_rain(string):
    return ''.join(map(choice, zip(string.lower(), string.upper())))

def Peter_Wood(string):
    return ''.join(map(choice, zip(string, string.swapcase())))

def based_on_Olvin_Roght(string):
    return ''.join([c if getrandbits(1) else c.swapcase() for c in string])

def superb_Peter_Olvin(string):
    return ''.join([c if getrandbits(1) else d for c, d in zip(string, string.swapcase())])

def superb_Peter_Olvin_2(string):
    return ''.join([z[getrandbits(1)] for z in zip(string, string.swapcase())])

funcs = original, original_2, Samwise, theCoder, superb_rain, Peter_Wood, based_on_Olvin_Roght, superb_Peter_Olvin, superb_Peter_Olvin_2

string = original('a' * 1000)
number = 1000

tss = [[] for _ in funcs]
for _ in range(4):
    for func, ts in zip(funcs, tss):
        t = min(repeat(lambda: func(string), number=number)) / number
        ts.append(t)
        print(*('%d μs ' % (1e6 * t) for t in ts[1:]), func.__name__)
    print()
superb rain
  • 5,300
  • 2
  • 11
  • 25
3

Mastering generator expressions is a great way to make code like this shorter:

from random import randrange

def rand_upper(string: str) -> str:
    return "".join(
        c.upper() if randrange(2) else c.lower() 
        for c in string
    )
>>> rand_upper("Sphinx of black quartz, witness my vow!")
'sPhiNx of BlacK qUARTz, wiTnEsS mY VOw!'

The general trick is that any time you're building a list by appending one element at a time, there's probably a way that you could do it more simply as a list comprehension, by writing an expression that generates each element of the list.

If you're not actually returning the list, and are instead passing it to a function that accepts any iterable (e.g. str.join), you can leave out the list part (the []) and just pass the generator expression directly to that function.

Samwise
  • 68,105
  • 3
  • 30
  • 44
1

Efficient one(may be)

from random import choice
def rand_upper(string):
    return ''.join(choice((str.upper, str.lower))(c) for c in string)

output in my pc:

>>print(rand_upper('ajskdlfhkalsdfhlkasjdfkajsdfhklasjdfkhaksjhdjkkaljsdf'))
theCoder
  • 34
  • 5
1

You can generate bit mask using random.getrandbits() and apply str.swapcase() only on 1 bit:

from random import getrandbits

s = "teststring"
mask = getrandbits(len(s))
res = "".join(1 << i & mask and c.swapcase() or c for i, c in enumerate(s))
Olvin Roght
  • 7,677
  • 2
  • 16
  • 35
0

Here is how:

from random import choice

def rand_upper(string):
    return ''.join(choice([i.upper(), i]) for i in string.lower())

print(rand_upper("Hello World!"))

Explanation:

This line:

''.join(choice([i.upper(), i]) for i in string.lower())

is the conversion of the list [choice([i.upper(), i]) for i in string.lower()] into a string. [choice([i.upper(), i]) returns randomly the uppercase of i, or just i (which is lower-cased).

Red
  • 26,798
  • 7
  • 36
  • 58