自定义排序

这道题的本质:自定义排序

这道题不是普通的“按字母表排序”,而是要求你按照题目给定的特殊规则排序字符串 S

排序后的顺序必须是:

  1. 小写字母在最前面,并且小写字母内部按字母顺序排序
  2. 大写字母排在小写字母后面,并且大写字母内部按字母顺序排序
  3. 奇数数字排在大写字母后面,并且奇数内部按从小到大排序
  4. 偶数数字排在最后,并且偶数内部按从小到大排序

所以它的排序优先级可以理解成:

小写字母 < 大写字母 < 奇数数字 < 偶数数字

题目给的例子是:

Sorting1234

我们先拆开看:

小写字母:o r t i n g
大写字母:S
奇数数字:1 3
偶数数字:2 4

分别排序后:

小写字母:g i n o r t
大写字母:S
奇数数字:1 3
偶数数字:2 4

拼起来就是:

ginortS1324

最容易理解的做法:分组,再排序,再拼接

初学者建议先用这种方法。因为它非常符合人的思考过程。

思路

看到一个字符,我们先判断它属于哪一类:

如果是小写字母,放进 lowercase
如果是大写字母,放进 uppercase
如果是数字:
    如果是奇数,放进 odd_digits
    如果是偶数,放进 even_digits

最后分别排序,再拼起来。


代码写法一:分组法,适合初学者

s = input()

lowercase = []
uppercase = []
odd_digits = []
even_digits = []

for ch in s:
    if ch.islower():
        lowercase.append(ch)
    elif ch.isupper():
        uppercase.append(ch)
    elif ch.isdigit():
        if int(ch) % 2 == 1:
            odd_digits.append(ch)
        else:
            even_digits.append(ch)

result = (
    sorted(lowercase)
    + sorted(uppercase)
    + sorted(odd_digits)
    + sorted(even_digits)
)

print("".join(result))

逐行解释

先读入字符串:

s = input()

比如用户输入:

Sorting1234

那么变量 s 里面保存的就是这个字符串。

然后准备四个列表,用来分别保存四类字符:

lowercase = []
uppercase = []
odd_digits = []
even_digits = []

接着遍历字符串中的每一个字符:

for ch in s:

比如 Sorting1234 会依次取出:

S
o
r
t
i
n
g
1
2
3
4

判断是否是小写字母:

if ch.islower():
    lowercase.append(ch)

例如 orting 都会进入 lowercase

判断是否是大写字母:

elif ch.isupper():
    uppercase.append(ch)

例如 S 会进入 uppercase

判断是否是数字:

elif ch.isdigit():

数字还要继续判断是奇数还是偶数:

if int(ch) % 2 == 1:
    odd_digits.append(ch)
else:
    even_digits.append(ch)

这里要注意,ch 是字符串字符,比如 "1",不是数字 1

所以要先写:

int(ch)

把字符串形式的数字转换成整数,然后才能判断奇偶。

例如:

int("3") % 2 == 1

说明 "3" 是奇数。


为什么最后要 sorted()?

题目要求每一类内部也要排序。

例如小写字母:

['o', 'r', 't', 'i', 'n', 'g']

排序后变成:

['g', 'i', 'n', 'o', 'r', 't']

所以最后写:

sorted(lowercase)

注意:sorted() 返回的是一个新的列表,不会直接变成字符串。

最后四部分拼起来:

result = (
    sorted(lowercase)
    + sorted(uppercase)
    + sorted(odd_digits)
    + sorted(even_digits)
)

这时候 result 还是一个列表,大概是:

['g', 'i', 'n', 'o', 'r', 't', 'S', '1', '3', '2', '4']

所以要用:

"".join(result)

把字符列表合成字符串。


代码写法二:使用 sorted 的 key 参数

这道题在 HackerRank 上常见的简洁写法是使用 sorted()key 参数。

s = input()

def sort_key(ch):
    if ch.islower():
        return (0, ch)
    elif ch.isupper():
        return (1, ch)
    elif ch.isdigit() and int(ch) % 2 == 1:
        return (2, ch)
    else:
        return (3, ch)

print("".join(sorted(s, key=sort_key)))

key 的核心思想

sorted() 默认只知道普通排序,但是这道题需要特殊规则。

所以我们告诉 Python:

每个字符排序之前,先给它一个排序标签

比如:

小写字母 -> 第 0 类
大写字母 -> 第 1 类
奇数数字 -> 第 2 类
偶数数字 -> 第 3 类

所以:

return (0, ch)

意思是:

先按照第 0 类排序
如果类别一样,再按照 ch 自身排序

例如:

'g' -> (0, 'g')
'i' -> (0, 'i')
'S' -> (1, 'S')
'1' -> (2, '1')
'2' -> (3, '2')

Python 排序元组时,会先比较第一个元素,再比较第二个元素。

所以:

(0, 'g') 会排在 (1, 'S') 前面

因为 0 < 1

如果都是小写字母:

(0, 'g')
(0, 'i')
(0, 'n')

第一项都是 0,Python 就继续比较第二项,也就是字母本身,因此得到字母顺序。


更短的 lambda 写法

熟练以后,这道题可以写成:

s = input()

print("".join(sorted(s, key=lambda ch: (
    ch.isdigit(),
    ch.isdigit() and int(ch) % 2 == 0,
    ch.isupper(),
    ch
))))

但是这个写法对初学者不太友好,因为它利用了布尔值排序:

False < True

所以现阶段不建议你优先记这个写法。

你真正应该先掌握的是前面两个版本:

分组法
自定义 key 函数法

常见错误与易混点

1. 以为直接 sorted(s) 就可以

不可以。

print("".join(sorted(s)))

这是普通排序,不符合题目的特殊顺序。

普通排序可能会按照字符编码顺序排,不会自动满足:

小写字母 -> 大写字母 -> 奇数 -> 偶数

2. 忘记把数字字符转换成 int

错误写法:

if ch % 2 == 1:

这里会报错,因为 ch 是字符串,不是整数。

正确写法:

if int(ch) % 2 == 1:

3. 忘记 join

sorted() 得到的是列表。

例如:

['g', 'i', 'n', 'o', 'r', 't']

题目要求输出字符串,所以必须写:

"".join(result)

4. 分组顺序写错

题目要求是:

小写字母 -> 大写字母 -> 奇数数字 -> 偶数数字

不是:

小写字母 -> 大写字母 -> 所有数字

也不是:

字母 -> 数字

数字还要再分成奇数和偶数。


本题推荐你记住的解题套路

遇到这种题,可以按这个思路想:

第一步:题目是不是普通排序?
第二步:如果不是普通排序,有哪些分类?
第三步:每一类内部是否还要排序?
第四步:分类后排序,最后拼接输出。

这道题对应就是:

分类:
小写字母、大写字母、奇数数字、偶数数字

每类内部:
都要排序

最后:
小写 + 大写 + 奇数 + 偶数

推荐答案

初学阶段建议提交这个版本:

s = input()

lowercase = []
uppercase = []
odd_digits = []
even_digits = []

for ch in s:
    if ch.islower():
        lowercase.append(ch)
    elif ch.isupper():
        uppercase.append(ch)
    elif ch.isdigit():
        if int(ch) % 2 == 1:
            odd_digits.append(ch)
        else:
            even_digits.append(ch)

result = (
    sorted(lowercase)
    + sorted(uppercase)
    + sorted(odd_digits)
    + sorted(even_digits)
)

print("".join(result))

等你熟悉 sorted(..., key=...) 以后,可以再使用这个版本:

s = input()

def sort_key(ch):
    if ch.islower():
        return (0, ch)
    elif ch.isupper():
        return (1, ch)
    elif int(ch) % 2 == 1:
        return (2, ch)
    else:
        return (3, ch)

print("".join(sorted(s, key=sort_key)))

练习

题目:

输入一个只包含大小写字母和数字的字符串,要求按照下面规则排序:

数字排最前面
小写字母排第二
大写字母排最后
每一类内部都按升序排序

例如输入:

bA3aC1

应该输出:

13abAC

提示:

你可以准备三个列表:

digits = []
lowercase = []
uppercase = []

遍历字符串时,分别用:

ch.isdigit()
ch.islower()
ch.isupper()

判断字符属于哪一类。

补充:sorted(s, key=sort_key) 这一句整体是什么意思?

这一句可以拆成两部分看:

sorted(s, key=sort_key)

意思是:

把字符串 s 里面的每个字符拿出来排序,
但是排序时不要用 Python 默认规则,
而是用 sort_key 这个函数提供的规则来排序。

这里最关键的是:

key=sort_key

它表示:

排序之前,先把每个字符交给 sort_key 函数处理一下,
sort_key 返回什么,Python 就根据返回值来决定排序顺序。

注意:key 不是关键字本身的意思,而是“排序依据”。


不加 key 时,sorted 怎么排?

比如:

s = "Sorting1234"
print(sorted(s))

Python 会按照默认字符顺序排序。

但是默认排序不符合题目要求,因为题目要求的是:

小写字母 -> 大写字母 -> 奇数数字 -> 偶数数字

这不是 Python 默认知道的规则,所以我们要告诉 sorted() 一个新的排序规则。


key 参数的作用:给每个字符生成一个“排序标签”

假设我们写了这个函数:

def sort_key(ch):
    if ch.islower():
        return (0, ch)
    elif ch.isupper():
        return (1, ch)
    elif int(ch) % 2 == 1:
        return (2, ch)
    else:
        return (3, ch)

这个函数的作用不是直接排序,而是给每个字符生成一个“排序用的值”。

例如:

sort_key("g")

返回:

(0, "g")

表示:

"g" 是第 0 类,也就是小写字母
在小写字母内部,按照 "g" 自己排序

再比如:

sort_key("S")

返回:

(1, "S")

表示:

"S" 是第 1 类,也就是大写字母

再比如:

sort_key("3")

返回:

(2, "3")

表示:

"3" 是第 2 类,也就是奇数数字

再比如:

sort_key("2")

返回:

(3, "2")

表示:

"2" 是第 3 类,也就是偶数数字

sorted(s, key=sort_key) 的执行过程

假设:

s = "Sorting1234"

执行:

sorted(s, key=sort_key)

Python 大致会这样做:

拿到 "S" -> 调用 sort_key("S") -> 得到 (1, "S")
拿到 "o" -> 调用 sort_key("o") -> 得到 (0, "o")
拿到 "r" -> 调用 sort_key("r") -> 得到 (0, "r")
拿到 "t" -> 调用 sort_key("t") -> 得到 (0, "t")
拿到 "i" -> 调用 sort_key("i") -> 得到 (0, "i")
拿到 "n" -> 调用 sort_key("n") -> 得到 (0, "n")
拿到 "g" -> 调用 sort_key("g") -> 得到 (0, "g")
拿到 "1" -> 调用 sort_key("1") -> 得到 (2, "1")
拿到 "2" -> 调用 sort_key("2") -> 得到 (3, "2")
拿到 "3" -> 调用 sort_key("3") -> 得到 (2, "3")
拿到 "4" -> 调用 sort_key("4") -> 得到 (3, "4")

然后 Python 根据这些返回值来排序。

也就是说,它真正比较的不是原来的字符本身,而是这些元组:

"S" -> (1, "S")
"o" -> (0, "o")
"r" -> (0, "r")
"t" -> (0, "t")
"i" -> (0, "i")
"n" -> (0, "n")
"g" -> (0, "g")
"1" -> (2, "1")
"2" -> (3, "2")
"3" -> (2, "3")
"4" -> (3, "4")

为什么返回的是 (0, ch) 这种形式?

因为元组可以按顺序比较。

Python 比较两个元组时,会先比较第一个位置。

比如:

(0, "g") < (1, "S")

结果是 True,因为:

0 < 1

所以小写字母会排在大写字母前面。

再比如:

(2, "1") < (3, "2")

结果也是 True,因为:

2 < 3

所以奇数数字会排在偶数数字前面。

如果第一个位置一样,就继续比较第二个位置。

比如:

(0, "g") < (0, "i")

第一个位置都是 0,说明它们都是小写字母。

于是 Python 继续比较第二个位置:

"g" < "i"

所以 "g" 排在 "i" 前面。

因此:

return (0, ch)

可以理解成:

先按类别排,再按字符本身排。

这道题里的分类编号

我们人为规定:

0 表示小写字母
1 表示大写字母
2 表示奇数数字
3 表示偶数数字

所以:

def sort_key(ch):
    if ch.islower():
        return (0, ch)
    elif ch.isupper():
        return (1, ch)
    elif int(ch) % 2 == 1:
        return (2, ch)
    else:
        return (3, ch)

这段代码其实是在告诉 Python:

如果 ch 是小写字母,就让它排在第 0 组
如果 ch 是大写字母,就让它排在第 1 组
如果 ch 是奇数数字,就让它排在第 2 组
否则就是偶数数字,让它排在第 3 组

因为数字越小,排序越靠前,所以最终顺序就是:

小写字母 -> 大写字母 -> 奇数数字 -> 偶数数字

一个更小的例子

先看简单字符串:

s = "bA1a2"

def sort_key(ch):
    if ch.islower():
        return (0, ch)
    elif ch.isupper():
        return (1, ch)
    elif int(ch) % 2 == 1:
        return (2, ch)
    else:
        return (3, ch)

print(sorted(s, key=sort_key))

每个字符对应的排序依据是:

"b" -> (0, "b")
"A" -> (1, "A")
"1" -> (2, "1")
"a" -> (0, "a")
"2" -> (3, "2")

排序后:

"a" -> (0, "a")
"b" -> (0, "b")
"A" -> (1, "A")
"1" -> (2, "1")
"2" -> (3, "2")

所以结果是:

['a', 'b', 'A', '1', '2']

如果要输出字符串,还要用:

"".join(sorted(s, key=sort_key))

最终输出:

abA12

key=sort_key 为什么不写成 key=sort_key()?

这是一个非常重要的点。

正确写法是:

sorted(s, key=sort_key)

不是:

sorted(s, key=sort_key())

原因是:

key=sort_key

表示把函数本身交给 sorted()

然后 sorted() 会在排序过程中,自己拿每个字符去调用这个函数。

也就是 Python 内部会做类似这样的事情:

sort_key("S")
sort_key("o")
sort_key("r")
sort_key("1")

而如果你写:

key=sort_key()

意思就变成:

现在立刻调用 sort_key 函数

可是 sort_key(ch) 需要一个参数 ch,你没有给它参数,所以会报错。

所以要记住:

key=函数名

意思是:

把这个函数交给 sorted,让 sorted 自己去调用它。

完整代码再看一遍

s = input()

def sort_key(ch):
    if ch.islower():
        return (0, ch)
    elif ch.isupper():
        return (1, ch)
    elif int(ch) % 2 == 1:
        return (2, ch)
    else:
        return (3, ch)

print("".join(sorted(s, key=sort_key)))

这句:

sorted(s, key=sort_key)

可以翻译成:

对 s 里面的字符排序;
排序时,每个字符先交给 sort_key;
sort_key 返回的元组就是排序依据。

你可以这样记

key 参数的核心记法是:

key 用来指定“按什么排序”。

普通排序:

sorted(s)

意思是:

直接按元素本身排序。

自定义排序:

sorted(s, key=sort_key)

意思是:

先用 sort_key 给每个元素生成排序依据,
再按照这个依据排序。

小练习

不要急着写完整代码,先判断下面每个字符的 sort_key 返回值是什么。

def sort_key(ch):
    if ch.islower():
        return (0, ch)
    elif ch.isupper():
        return (1, ch)
    elif int(ch) % 2 == 1:
        return (2, ch)
    else:
        return (3, ch)

请你写出下面几个字符对应的返回值:

"a"
"B"
"7"
"8"

提示:

先判断它属于哪一类,再写出对应的编号。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇