5

I am trying to order the words of a string in a particular way: In my code below the output is "MNWdeorwy" but i would like it to be "deMNorWwy" (so i need to keep the letters ordered despite being upper o lowercases) Could you please help me to understand where I am wrong and why? Thank you

wrd = "MyNewWord"

def order_word(s):
    if s == "":
        return "Invalid String!"
    else:
        c = sorted(s)
        d = ''.join(sorted(c))
        return d

print order_word(wrd)

I would like to precise that my question is different from the following: How to sort the letters in a string alphabetically in Python : in fact, the answers given in the link does not consider the difference between upper and lowercases in a string.

Community
  • 1
  • 1

3 Answers3

9

sorted() sorts based off of the ordinal of each character. Capital letters have ordinals that are lower than all lowercase letters. If you want different behavior, you'll need to define your own key:

c = sorted(s, key=lambda c: (c.lower(), c.islower()))

That way, c would be sorted by ('c', 1) and C is sorted by ('c', 0). Both come before ('d', ...) or ('e', ...) etc., but the capital C is earlier (lower) than the lowercase c.

By the way, you shouldn't say d = "".join(sorted(c)) because c has already been sorted. Just do d = "".join(c)

zondo
  • 19,901
  • 8
  • 44
  • 83
  • Thank You zondo, but I tried to change d = ''.join(sorted(c)) with d = "".join(c) but the output is the same "MNWdeorwy" .. – Marco Giuseppe de Pinto Mar 28 '16 at 13:04
  • Only if you use your code for `c`, right? My code works great on my computer. – zondo Mar 28 '16 at 13:06
  • This is very helpful and works for me. But can you please explain why you specified key=lambda c: (c.lower, c.islower()) ? – codeAligned Jul 14 '17 at 05:02
  • 2
    @user53008 Tuples are ordered by first item and then second item, third item, etc. Therefore 'a' will be lower than 'B' because the first item of each tuple (the lowercase version of each letter i.e. 'a' and 'b' compare as 'a' being greater. If the two letters are the same, the second item of the tuple is checked. For example, comparing 'a' with 'A' would be comparing ('a', True) and ('a', False). Since the first item of the tuple is the same, the second item is the tiebreaker. True is greater than False, so the lowercase version comes first. – zondo Jul 14 '17 at 05:21
3

If I understand correctly your requirements, you want to sort a string

  1. without changing the case of letters
  2. as if all the letters have the same case

this can be achieved, e.g.,

In [44]: a = 'zWea'

In [45]: sorted(a,key=lambda c:c.upper())
Out[45]: ['a', 'e', 'W', 'z']

In [46]: 

that works because you transform momentarily individual characters during a comparison.

Forgot to mention, you can mix non-alphabetical chars in your string, but a few characters are placed between upper and lower case alphabetical chars (e.g., the ^ caret), so what you get depends on using .lower() or .upper() method of strings,

In [56]: sorted('abCD^',key=lambda c:c.lower())
Out[56]: ['^', 'a', 'b', 'C', 'D']

In [57]: sorted('abCD^',key=lambda c:c.upper())
Out[57]: ['a', 'b', 'C', 'D', '^']

In [58]: 
gboffi
  • 22,939
  • 8
  • 54
  • 85
1

You can also try like this

import re

def natural_sort(wrd): 
    convert = lambda text: int(text) if text.isdigit() else text.lower() 
    final = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return ''.join(sorted(wrd, key = final))

Output:

>>> natural_sort(wrd)
'deMNorwWy'

OR

You can do with third party library for this on PyPI called natsort

https://pypi.python.org/pypi/natsort

Kamlesh
  • 2,032
  • 2
  • 19
  • 35