ranking.py 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
  1. """跨业务模块复用的排名归一化工具。"""
  2. from __future__ import annotations
  3. from collections.abc import Hashable
  4. from typing import TypeVar
  5. KeyT = TypeVar("KeyT", bound=Hashable)
  6. def rank_with_scores(items: list[tuple[KeyT, float]]) -> dict[KeyT, dict[str, float | int]]:
  7. """按值降序排名,并返回同分平均名次与 ``(0, 1]`` 排名分。
  8. 不同量纲的数据应先分别调用本函数,不能先把原始值相加。排名分公式与
  9. ``category_tree_weight`` 的四维全局排名口径一致:
  10. ``score = (n - avg_rank + 1) / n``。
  11. """
  12. if not items:
  13. return {}
  14. sorted_items = sorted(items, key=lambda item: (-item[1], str(item[0])))
  15. total = len(sorted_items)
  16. result: dict[KeyT, dict[str, float | int]] = {}
  17. start = 0
  18. while start < total:
  19. end = start
  20. value = sorted_items[start][1]
  21. while end < total and sorted_items[end][1] == value:
  22. end += 1
  23. average_rank = (start + 1 + end) / 2.0
  24. normalized_score = (total - average_rank + 1) / total
  25. for index in range(start, end):
  26. result[sorted_items[index][0]] = {
  27. "rank": average_rank,
  28. "total": total,
  29. "normalized_score": normalized_score,
  30. }
  31. start = end
  32. return result
  33. def rank_to_scores(items: list[tuple[KeyT, float]]) -> dict[KeyT, float]:
  34. """兼容原调用方:只返回排名归一分。"""
  35. return {
  36. key: float(position["normalized_score"])
  37. for key, position in rank_with_scores(items).items()
  38. }