Python 用排序构建映射,如果不考虑Counter
类,可以用更函数式的方式进行排序和分组。常用实现如下:
from typing import Dict, Any, Iterable, Tuple, List, TypeVar
Leg = Tuple[Any, Any, float]
T_ = TypeVar("T_")
def group_sort1(trip: Iterable[Leg]) -> Dict[int, int]:
def group(
data: Iterable[T_]
) -> Iterable[Tuple[T_, int]]:
previous, count = None, 0
for d in sorted(data):
if d == previous:
count += 1
elif previous is not None: # and d != previous
yield previous, count
previous, count = d, 1
elif previous is None:
previous, count = d, 1
else:
raise Exception("Bad bad design problem.")
yield previous, count
quantized = (int(5 * (dist // 5)) for beg, end, dist in trip)
return dict(group(quantized))
内部的group()
函数遍历排序后的数据集合,如果当前值与前次循环值一致(等于previous
中保存的值),给计数器加1,如果不一致,并且前次循环值不是None
,说明新的键值出现了。这时首先返回前次循环值及其计数,然后创建新的键值对,键是当前值,值是计数器初始值。第三种情况只会出现一次:未设置前次循环值,这是循环的开始状态,应保存初始值。
group()
函数包含两个重要的类型标示,数据源是基于某种数据类型(用T_
表示)的可迭代对象,在这个例子中是整型,但适用于任何Python类型。由于返回结果中同样使用了类型变量T_
,可知group()
函数的返回结果中包含的数据类型与数据源的类型一致。
group_sort1()
函数的最后一行基于分组后的数据创建了一个字典。与Counter
创建的字典类似,主要区别在于Counter
对象有most_common()
方法,而普通的字典对象没有。
条件分支elif previous is None
是比较笨拙的设计,去掉它难度不是太大(并且性能会略有提升)。
为了去掉该elif
分支,需要调整一下group()
函数的初始化方法。
def group(data: Iterable[T_]) -> Iterable[Tuple[T_, int]]:
sorted_data = iter(sorted(data))
previous, count = next(sorted_data), 1
for d in sorted_data:
if d == previous:
count += 1
elif previous is not None: # and d != previous
yield previous, count
previous, count = d, 1
else:
raise Exception("Bad bad design problem.")
yield previous, count
首先从排序后的数据中取出第一个数据,作为previous的初始值,在循环中处理剩余的值。从这一设计思路中可以看到递归方法的使用:用序列的第一个值做递归的初始值,每次递归处理一个值,如果不是None
则继续递归处理,否则说明数据已经处理完毕。
除此之外,用itertools.groupby()
也可以达到相同的效果。