0%

python排序……根据汉字拼音排序; 相邻元素比较排序

python排序……根据汉字拼音排序; 相邻元素比较排序

现在我们有个联系人名单, 想要对其按照拼音排序, 就像微信的通讯录一样, 应该怎么做, 如下是随便写的。

zh_names = ["李大", "刘老三", "王富贵", "牛二娃", "毕姥爷", "赵匡胤", "朱元璋", "铁木真", "李世民", "朱由检", "王莽", "嬴政", "刘邦", "项羽", "貂蝉", "赵飞燕", "曹操", "孙权", "司马懿", "杨坚", "武松", "宋江", "晁盖", "李逵", "胡汉三", "阿斌", "狄仁杰", "寇准", "包拯", "张居正", "霍去病", "蒙恬"]

这个时候首先想到的肯定是内置的sorted函数, 如下所示:

>>> sorted(zh_names)
# 结果
['刘老三', '刘邦', '包拯', '司马懿', '嬴政', '孙权', '宋江', '寇准', '张居正', '晁盖', '曹操', '朱元璋', '朱由检', '李世民', '李大', '李逵', '杨坚', '武松', '毕姥爷', '牛二娃', '狄仁杰', '王富贵', '王莽', '胡汉三', '蒙恬', '貂蝉', '赵匡胤', '赵飞燕', '铁木真', '阿斌', '霍去病', '项羽']
>>>

显然sorted函数默认按照字符编码来排序的, 如果给英文排序肯定没啥问题, 换成中文了排序失去了意义, 别说用户了, 向我们这些人也搞不清阿斌和赵匡胤在字符编码里的相对位置。

所以还是要按照汉字拼音的字母顺序来排列是最为合理的。

我们的第一步需要获取汉字拼音, 推荐使用pypinyin模块,

pypinyin模块

pypinyin模块基本上能满足你所有的汉字转拼音需求, 主要两个函数lazy_pinyin和pinyin, 两者本质上没有区别。

更多使用方法参考官方文档, 比较友好的是官方文档是用中文编写的, 所以直接dir; help结果就是中文。

接下来看一个简单演示,

# 导入的时候重命名, 提高可读性, 降低命名冲突
>>> from pypinyin import lazy_pinyin as pinyin, Style as PinyinStyle
>>> s = "世界你好"
>>> pinyin(s)
['shi', 'jie', 'ni', 'hao']
>>> pinyin(s, PinyinStyle.TONE)
['shì', 'jiè', 'nǐ', 'hǎo']
>>> pinyin(s, PinyinStyle.INITIALS)
['sh', 'j', 'n', 'h']
>>>

第二个参数是输出格式, 我们分别使用了纯字母格式, 声调格式和首拼格式, 更多格式查阅文档。

如下是Style类型的简要说明

NORMAL = 0 # 纯字母格式, lazy_pinyin默认使用该格式
TONE = 1 # 声调格式, pinyin默认使用该格式, 需要注意的是该函数一个汉字返回一个list, 而lazy_pinyin返回字符串, 原因和多音字有关
INITIALS = 3 # 首拼格式

sorted函数的第二个参数

知道如何获取汉字拼音后, 接下来的事情就是在sorted函数传入排序依据。

我们知道sorted第一个参数接受一个迭代器, 列表; 字典; 元组等等, 第二个关键字参数接受一个函数, 该函数需要一个参数。

sorted函数在迭代器的每一个元素上调用该函数, 根据该函数的返回值给迭代器排序。

语言描述不好理解, 看例子。

from pypinyin import lazy_pinyin

# 排序依据函数
def key_pinyin(zh_str):
return "".join(lazy_pinyin(zh_str))

zh_names = ["李大", "刘老三", "王富贵", "牛二娃", "毕姥爷", "赵匡胤", "朱元璋", "铁木真", "李世民", "朱由检", "王莽", "嬴政", "刘邦", "项羽", "貂蝉", "赵飞燕", "曹操", "孙权", "司马懿", "杨坚", "武松", "宋江", "晁盖", "李逵", "胡汉三", "阿斌", "狄仁杰", "寇准", "包拯", "张居正", "霍去病", "蒙恬"]

# 第二个参数只要传入函数名就可以了, sorted函数内部自己调用它
result = sorted(zh_names, key=key_pinyin)
print("\n".join(result))

实际上我们不需要单独定义key_pinyin函数, 调用lambda表达式就可以了。

result = sorted(zh_names, key=lambda e: "".join(lazy_pinyin(e)))

如果排序依据函数代码比较长或者在多个地方调用这段代码, 那么定义单独函数, 如果是一次性的而且处理代码比较短, 那么使用lambda表达式。

排序依据函数太长, 我们简称key函数吧!

sorted函数在每一个迭代器元素上调用key函数, 参照其返回值对迭代器排序。

key函数返回每个元素的拼音首拼, 之后sorted根据这些拼音给整个中文姓名排序。

cmp_to_key

现在我们遇到这样一个场景, 给学生成绩排序, 排序依据是主修课总成绩, 如果两个学生的主修课总成绩相同的话根据选修课总成绩来排序。

scores = [{"major": 85, "elective": 70}, 
{"major": 82, "elective": 90},
{"major": 60, "elective": 99},
{"major": 90, "elective": 68},
{"major": 60, "elective": 70},
{"major": 60, "elective": 75}]

# reverse参数False升序, True降序, 默认False
result = sorted(scores, key=lambda e: e["major"], reverse=True)
for r in result[: -1]:
print("{}, {}".format(r["major"], r["elective"]))

一共六个考生但是只能录取五个名额, 其中三名考生主修课总成绩相同。 看运行结果:

90, 68
85, 70
82, 90
60, 99
60, 70

正确结果是录取选修成绩为75分的学生, 但是我们的程序录取了选修成绩为70分的学生, 显然不公平, 引起了考生不满。

这个时候很自然的想到给key函数增加第二个参数, 可惜报错了。

.....
# 函数比较长而且为了说明问题单独定义函数
def cmp(first, second):
if first["major"] != second["major"]:
return first["major"] - second["major"]
else:
return first["elective"] - second["elective"]

result = sorted(scores, key=cmp, reverse=True)
.....

错误输出如下:

Traceback (most recent call last):
File "", line 14, in <module>
result = sorted(scores, key=cmp, reverse=True)
TypeError: cmp() missing 1 required positional argument: 'second'

所以这里我们需要一个转换器, 在functools模块里, 正确代码如下。

from functools import cmp_to_key
.....
result = sorted(scores, key=cmp_to_key(cmp), reverse=True)
.....

顾名思义cmp_to_key函数把cmp函数转换成了key函数。

当然cmp函数也可以使用lambda表达式, 也要看具体场景。

cmp函数和key函数有一些区别, 需要格外注意。

  • key函数接受一个参数, 而cmp函数接受两个参数, 是相邻的迭代器元素

  • key函数返回处理元素后的结果, 而cmp函数返回相邻两个元素的比较结果

    最后看下输出结果:

90, 68
85, 70
82, 90
60, 99
60, 75

python排序总结

最后给python排序做个总结, 基本上能解决所有的排序需求。

  • sorted函数对迭代器排序, 结果返回新副本, 列表的sort方法原地排序, 结果修改当前列表

  • key函数处理元素返回排序依据

  • cmp函数比较相邻的两个元素返回比较结果, sorted根据比较结果对迭代器排序

  • cmp需要做转换, 使用functools模块的cmp_to_key函数

    总体上没有特殊需要使用key函数就可以了, 而其他语言肯定是cmp函数形式,

所以如果有其他语言的经验还是优先使用key函数吧, 不要先入为主, 刚开始我就是先入为主不太理解key函数。