8

I need to return a list of all possible case permutations of a string in python.

For example, input "ar" should return:

[ 'ar','Ar','aR','AR']  

or "arc":

[ 'arc','Arc','ARc','aRc','aRC','ARC']
iacob
  • 20,084
  • 6
  • 92
  • 119
KatGaea
  • 996
  • 1
  • 12
  • 31
  • What you're asking for is basically an algorithm, if you know the algorithm you can implement it in whatever language you want (Python). All i can help you with that is: recursion. (trying some things on paper may help) – Aerus Jul 22 '11 at 16:10
  • @nekosune: Out of curiosity, what are you using this feature for? I'm concerned that maybe you're using this to compare a string of unknown case to see if it matches a word. If so, there are better ways to do that. – Bryan Oakley Jul 22 '11 at 16:28
  • @Bryan No, if I was doing that would use tolower, I am using app engine and wish to return a query which is insensitive to case, using the in part of GQL to do such, as it is garenteed to be short words. – KatGaea Jul 22 '11 at 16:31
  • 2
    The easiest way would be `itertools.product(*zip(string.lower(), string.upper()))`. – agf Apr 11 '12 at 04:36

2 Answers2

21
def all_casings(input_string):
    if not input_string:
        yield ""
    else:
        first = input_string[:1]
        if first.lower() == first.upper():
            for sub_casing in all_casings(input_string[1:]):
                yield first + sub_casing
        else:
            for sub_casing in all_casings(input_string[1:]):
                yield first.lower() + sub_casing
                yield first.upper() + sub_casing

>>> [x for x in all_casings("foo")]
['foo', 'Foo', 'fOo', 'FOo', 'foO', 'FoO', 'fOO', 'FOO']
>>> list(all_casings("foo"))
['foo', 'Foo', 'fOo', 'FOo', 'foO', 'FoO', 'fOO', 'FOO']
Amber
  • 507,862
  • 82
  • 626
  • 550
  • Thankyou very much, this was perfect. Algorithms especially recursive have allways been my weak point. – KatGaea Jul 22 '11 at 16:26
  • I'm a little confused. What's the point of comparing the uppercase/lowercase letter (as `'a'` should never equal `'A'`) and slicing the first character (versus `input_string[0]`)? – Maximilian Burszley Sep 02 '18 at 17:43
  • 1
    To avoid returning two results when there's a character without upper/lower distinction, e,g, `'.'.upper() == '.'.lower()` – Paul McMillan Jul 02 '19 at 19:57
4

You can achieve this by zipping the upper and lower case letters and taking their cartesian product:

import itertools

chars = "abc"
results = list(map(''.join, itertools.product(*zip(chars.upper(), chars.lower()))))

print(results)
>>>['ABC', 'ABc', 'AbC', 'Abc', 'aBC', 'aBc', 'abC', 'abc']

To visualise how this works:

  1. zip is creating our 3 'axes' for us, each with 2 points (the upper / lower cases):
    [('A', 'a'), 
     ('B', 'b'), 
     ('C', 'c')]
    
  2. product takes the cartesian product of these axes, i.e. the 8 possible coordinates corresponding to the corners of the unit cube it creates:
enter image description here enter image description here
1. Create axes 2. Take Cartesian product
  1. ''.join concatenates the tuples to output a string: ('A','B','C') -> 'ABC'
iacob
  • 20,084
  • 6
  • 92
  • 119
  • 1
    While this is a great idea, you need to be careful about characters whose upper and lower cases are the same (e.g. '7'). In which case you need something like: (''.join(prod) for prod in itertools.product(*((letter, letter.swapcase()) if letter != letter.swapcase() else (letter,) for letter in word))) – Mark Bell Mar 04 '22 at 21:37