algoux-standard-ranklist-utils 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- algoux_standard_ranklist_utils-0.3.0/.gitignore +16 -0
- algoux_standard_ranklist_utils-0.3.0/PKG-INFO +76 -0
- algoux_standard_ranklist_utils-0.3.0/README.md +47 -0
- algoux_standard_ranklist_utils-0.3.0/pyproject.toml +53 -0
- algoux_standard_ranklist_utils-0.3.0/src/standard_ranklist_utils/__init__.py +72 -0
- algoux_standard_ranklist_utils-0.3.0/src/standard_ranklist_utils/constants.py +2 -0
- algoux_standard_ranklist_utils-0.3.0/src/standard_ranklist_utils/enums.py +6 -0
- algoux_standard_ranklist_utils-0.3.0/src/standard_ranklist_utils/formatters.py +99 -0
- algoux_standard_ranklist_utils-0.3.0/src/standard_ranklist_utils/py.typed +1 -0
- algoux_standard_ranklist_utils-0.3.0/src/standard_ranklist_utils/ranklist.py +501 -0
- algoux_standard_ranklist_utils-0.3.0/src/standard_ranklist_utils/resolvers.py +132 -0
- algoux_standard_ranklist_utils-0.3.0/src/standard_ranklist_utils/types.py +29 -0
- algoux_standard_ranklist_utils-0.3.0/tests/fixtures/contract-fixtures.json +948 -0
- algoux_standard_ranklist_utils-0.3.0/tests/test_contract.py +406 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: algoux-standard-ranklist-utils
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Utilities for Standard Ranklist (srk).
|
|
5
|
+
Project-URL: Homepage, https://github.com/algoux/standard-ranklist-utils
|
|
6
|
+
Project-URL: Repository, https://github.com/algoux/standard-ranklist-utils
|
|
7
|
+
Project-URL: Issues, https://github.com/algoux/standard-ranklist-utils/issues
|
|
8
|
+
Author: bLue
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: icpc,ranklist,srk,standard ranklist
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
27
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# algoux-standard-ranklist-utils
|
|
31
|
+
|
|
32
|
+
Python utilities for Standard Ranklist (srk).
|
|
33
|
+
|
|
34
|
+
Supported srk versions: `>=0.3.0 <0.4.0`.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```shell
|
|
39
|
+
pip install algoux-standard-ranklist-utils
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage Sample
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from standard_ranklist_utils import format_time_duration, resolve_text, sort_rows
|
|
46
|
+
|
|
47
|
+
format_time_duration([1.5, "h"], "min") # 90
|
|
48
|
+
resolve_text({"fallback": "English", "zh-CN": "中文"}, ["zh-CN"]) # 中文
|
|
49
|
+
sort_rows(ranklist["rows"], ranklist.get("sorter", {}).get("config"))
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Utilities
|
|
53
|
+
|
|
54
|
+
### formatters
|
|
55
|
+
|
|
56
|
+
- `format_time_duration`: Convert an srk `TimeDuration` between `ms`, `s`, `min`, `h`, and `d`.
|
|
57
|
+
- `pre_zero_fill`: Left-pad a number with zeroes for fixed-width display.
|
|
58
|
+
- `sec_to_time_str`: Format elapsed seconds as a ranklist time string such as `1:02:03` or `1D 1:02:03`.
|
|
59
|
+
- `number_to_alphabet`: Convert a zero-based problem index to an alphabetic alias such as `A`, `Z`, or `AA`.
|
|
60
|
+
- `alphabet_to_number`: Convert an alphabetic problem alias back to a zero-based index.
|
|
61
|
+
|
|
62
|
+
### resolvers
|
|
63
|
+
|
|
64
|
+
- `resolve_text`: Resolve plain or i18n srk text using explicit language preferences and fallback text.
|
|
65
|
+
- `resolve_contributor`: Parse a contributor string into `name`, optional `email`, and optional `url`.
|
|
66
|
+
- `resolve_color`: Normalize an srk color value to a CSS color string.
|
|
67
|
+
- `resolve_theme_color`: Expand a single color or theme color object into explicit light and dark colors.
|
|
68
|
+
- `resolve_style`: Resolve text/background style colors and auto-pick readable text color when needed.
|
|
69
|
+
- `resolve_user_markers`: Resolve a user's marker IDs to marker definitions from the ranklist config.
|
|
70
|
+
|
|
71
|
+
### ranklist
|
|
72
|
+
|
|
73
|
+
- `sort_rows`: Sort rows by ICPC solved count descending and penalty time ascending, with optional ranking-time precision.
|
|
74
|
+
- `regenerate_ranklist_by_solutions`: Rebuild rows, scores, sorting, and problem statistics from solution tetrads.
|
|
75
|
+
- `regenerate_rows_by_incremental_solutions`: Apply incremental solution tetrads to existing rows and re-sort them.
|
|
76
|
+
- `convert_to_static_ranklist`: Add precomputed per-series rank values and segment indexes to each row.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# algoux-standard-ranklist-utils
|
|
2
|
+
|
|
3
|
+
Python utilities for Standard Ranklist (srk).
|
|
4
|
+
|
|
5
|
+
Supported srk versions: `>=0.3.0 <0.4.0`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```shell
|
|
10
|
+
pip install algoux-standard-ranklist-utils
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage Sample
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from standard_ranklist_utils import format_time_duration, resolve_text, sort_rows
|
|
17
|
+
|
|
18
|
+
format_time_duration([1.5, "h"], "min") # 90
|
|
19
|
+
resolve_text({"fallback": "English", "zh-CN": "中文"}, ["zh-CN"]) # 中文
|
|
20
|
+
sort_rows(ranklist["rows"], ranklist.get("sorter", {}).get("config"))
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Utilities
|
|
24
|
+
|
|
25
|
+
### formatters
|
|
26
|
+
|
|
27
|
+
- `format_time_duration`: Convert an srk `TimeDuration` between `ms`, `s`, `min`, `h`, and `d`.
|
|
28
|
+
- `pre_zero_fill`: Left-pad a number with zeroes for fixed-width display.
|
|
29
|
+
- `sec_to_time_str`: Format elapsed seconds as a ranklist time string such as `1:02:03` or `1D 1:02:03`.
|
|
30
|
+
- `number_to_alphabet`: Convert a zero-based problem index to an alphabetic alias such as `A`, `Z`, or `AA`.
|
|
31
|
+
- `alphabet_to_number`: Convert an alphabetic problem alias back to a zero-based index.
|
|
32
|
+
|
|
33
|
+
### resolvers
|
|
34
|
+
|
|
35
|
+
- `resolve_text`: Resolve plain or i18n srk text using explicit language preferences and fallback text.
|
|
36
|
+
- `resolve_contributor`: Parse a contributor string into `name`, optional `email`, and optional `url`.
|
|
37
|
+
- `resolve_color`: Normalize an srk color value to a CSS color string.
|
|
38
|
+
- `resolve_theme_color`: Expand a single color or theme color object into explicit light and dark colors.
|
|
39
|
+
- `resolve_style`: Resolve text/background style colors and auto-pick readable text color when needed.
|
|
40
|
+
- `resolve_user_markers`: Resolve a user's marker IDs to marker definitions from the ranklist config.
|
|
41
|
+
|
|
42
|
+
### ranklist
|
|
43
|
+
|
|
44
|
+
- `sort_rows`: Sort rows by ICPC solved count descending and penalty time ascending, with optional ranking-time precision.
|
|
45
|
+
- `regenerate_ranklist_by_solutions`: Rebuild rows, scores, sorting, and problem statistics from solution tetrads.
|
|
46
|
+
- `regenerate_rows_by_incremental_solutions`: Apply incremental solution tetrads to existing rows and re-sort them.
|
|
47
|
+
- `convert_to_static_ranklist`: Add precomputed per-series rank values and segment indexes to each row.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "algoux-standard-ranklist-utils"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Utilities for Standard Ranklist (srk)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "bLue" }]
|
|
13
|
+
keywords = ["standard ranklist", "srk", "ranklist", "icpc"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/algoux/standard-ranklist-utils"
|
|
30
|
+
Repository = "https://github.com/algoux/standard-ranklist-utils"
|
|
31
|
+
Issues = "https://github.com/algoux/standard-ranklist-utils/issues"
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = [
|
|
35
|
+
"build>=1.2",
|
|
36
|
+
"pytest>=8.0",
|
|
37
|
+
"ruff>=0.5",
|
|
38
|
+
"twine>=5.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/standard_ranklist_utils"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
pythonpath = ["src"]
|
|
47
|
+
|
|
48
|
+
[tool.ruff]
|
|
49
|
+
line-length = 120
|
|
50
|
+
target-version = "py39"
|
|
51
|
+
|
|
52
|
+
[tool.ruff.lint]
|
|
53
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from .constants import MIN_REGEN_SUPPORTED_VERSION, SRK_SUPPORTED_VERSIONS
|
|
2
|
+
from .enums import EnumTheme
|
|
3
|
+
from .formatters import (
|
|
4
|
+
alphabet_to_number,
|
|
5
|
+
format_time_duration,
|
|
6
|
+
number_to_alphabet,
|
|
7
|
+
pre_zero_fill,
|
|
8
|
+
sec_to_time_str,
|
|
9
|
+
)
|
|
10
|
+
from .ranklist import (
|
|
11
|
+
convert_to_static_ranklist,
|
|
12
|
+
regenerate_ranklist_by_solutions,
|
|
13
|
+
regenerate_rows_by_incremental_solutions,
|
|
14
|
+
sort_rows,
|
|
15
|
+
)
|
|
16
|
+
from .resolvers import (
|
|
17
|
+
resolve_color,
|
|
18
|
+
resolve_contributor,
|
|
19
|
+
resolve_style,
|
|
20
|
+
resolve_text,
|
|
21
|
+
resolve_theme_color,
|
|
22
|
+
resolve_user_markers,
|
|
23
|
+
)
|
|
24
|
+
from .types import (
|
|
25
|
+
CalculatedSolutionTetrad,
|
|
26
|
+
Color,
|
|
27
|
+
I18NStringSet,
|
|
28
|
+
Marker,
|
|
29
|
+
Ranklist,
|
|
30
|
+
RanklistRow,
|
|
31
|
+
RankProblemStatus,
|
|
32
|
+
RankValue,
|
|
33
|
+
Text,
|
|
34
|
+
ThemeColor,
|
|
35
|
+
ThemeColorInput,
|
|
36
|
+
TimeDuration,
|
|
37
|
+
TimeUnit,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"MIN_REGEN_SUPPORTED_VERSION",
|
|
42
|
+
"SRK_SUPPORTED_VERSIONS",
|
|
43
|
+
"EnumTheme",
|
|
44
|
+
"TimeUnit",
|
|
45
|
+
"TimeDuration",
|
|
46
|
+
"I18NStringSet",
|
|
47
|
+
"Text",
|
|
48
|
+
"Color",
|
|
49
|
+
"ThemeColorInput",
|
|
50
|
+
"ThemeColor",
|
|
51
|
+
"RankValue",
|
|
52
|
+
"CalculatedSolutionTetrad",
|
|
53
|
+
"Ranklist",
|
|
54
|
+
"RanklistRow",
|
|
55
|
+
"RankProblemStatus",
|
|
56
|
+
"Marker",
|
|
57
|
+
"format_time_duration",
|
|
58
|
+
"pre_zero_fill",
|
|
59
|
+
"sec_to_time_str",
|
|
60
|
+
"number_to_alphabet",
|
|
61
|
+
"alphabet_to_number",
|
|
62
|
+
"resolve_text",
|
|
63
|
+
"resolve_contributor",
|
|
64
|
+
"resolve_color",
|
|
65
|
+
"resolve_theme_color",
|
|
66
|
+
"resolve_style",
|
|
67
|
+
"resolve_user_markers",
|
|
68
|
+
"sort_rows",
|
|
69
|
+
"regenerate_ranklist_by_solutions",
|
|
70
|
+
"regenerate_rows_by_incremental_solutions",
|
|
71
|
+
"convert_to_static_ranklist",
|
|
72
|
+
]
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from collections.abc import Sequence
|
|
3
|
+
from typing import Callable, Union
|
|
4
|
+
|
|
5
|
+
from .types import TimeUnit
|
|
6
|
+
|
|
7
|
+
NumberLike = Union[int, float, str]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _to_milliseconds(time: Sequence[Union[int, float, str]]) -> float:
|
|
11
|
+
value = time[0]
|
|
12
|
+
unit = time[1]
|
|
13
|
+
if not isinstance(value, (int, float)) or not math.isfinite(value) or value < 0:
|
|
14
|
+
raise ValueError(f"Invalid source time value {value}")
|
|
15
|
+
if unit == "ms":
|
|
16
|
+
return value
|
|
17
|
+
if unit == "s":
|
|
18
|
+
return value * 1000
|
|
19
|
+
if unit == "min":
|
|
20
|
+
return value * 1000 * 60
|
|
21
|
+
if unit == "h":
|
|
22
|
+
return value * 1000 * 60 * 60
|
|
23
|
+
if unit == "d":
|
|
24
|
+
return value * 1000 * 60 * 60 * 24
|
|
25
|
+
raise ValueError(f"Invalid source time unit {unit}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def format_time_duration(
|
|
29
|
+
time: Sequence[Union[int, float, str]],
|
|
30
|
+
target_unit: TimeUnit = "ms",
|
|
31
|
+
fmt: Callable[[float], float] = lambda number: number,
|
|
32
|
+
) -> float:
|
|
33
|
+
ms = _to_milliseconds(time)
|
|
34
|
+
if target_unit == "ms":
|
|
35
|
+
return ms
|
|
36
|
+
if target_unit == "s":
|
|
37
|
+
return fmt(ms / 1000)
|
|
38
|
+
if target_unit == "min":
|
|
39
|
+
return fmt(ms / 1000 / 60)
|
|
40
|
+
if target_unit == "h":
|
|
41
|
+
return fmt(ms / 1000 / 60 / 60)
|
|
42
|
+
if target_unit == "d":
|
|
43
|
+
return fmt(ms / 1000 / 60 / 60 / 24)
|
|
44
|
+
raise ValueError(f"Invalid target time unit {target_unit}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def pre_zero_fill(num: int, size: int) -> str:
|
|
48
|
+
if num >= 10**size:
|
|
49
|
+
return str(num)
|
|
50
|
+
text = ("0" * size) + str(num)
|
|
51
|
+
return text[len(text) - size :]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def sec_to_time_str(second: float, *, fill_hour: bool = False, show_day: bool = False) -> str:
|
|
55
|
+
if second < 0:
|
|
56
|
+
return "--"
|
|
57
|
+
sec = second
|
|
58
|
+
days = 0
|
|
59
|
+
if show_day:
|
|
60
|
+
days = math.floor(sec / 86400)
|
|
61
|
+
sec %= 86400
|
|
62
|
+
hours = math.floor(sec / 3600)
|
|
63
|
+
sec %= 3600
|
|
64
|
+
minutes = math.floor(sec / 60)
|
|
65
|
+
sec %= 60
|
|
66
|
+
seconds = math.floor(sec)
|
|
67
|
+
day_text = f"{days}D " if show_day and days >= 1 else ""
|
|
68
|
+
hour_text = pre_zero_fill(hours, 2) if fill_hour else str(hours)
|
|
69
|
+
return f"{day_text}{hour_text}:{pre_zero_fill(minutes, 2)}:{pre_zero_fill(seconds, 2)}"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def number_to_alphabet(number: NumberLike) -> str:
|
|
73
|
+
n = int(float(number))
|
|
74
|
+
radix = 26
|
|
75
|
+
count = 1
|
|
76
|
+
power = radix
|
|
77
|
+
while n >= power:
|
|
78
|
+
n -= power
|
|
79
|
+
count += 1
|
|
80
|
+
power *= radix
|
|
81
|
+
result = []
|
|
82
|
+
while count > 0:
|
|
83
|
+
result.append(chr((n % radix) + 65))
|
|
84
|
+
n = math.trunc(n / radix)
|
|
85
|
+
count -= 1
|
|
86
|
+
return "".join(reversed(result))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def alphabet_to_number(alphabet: str) -> int:
|
|
90
|
+
if not isinstance(alphabet, str) or not alphabet:
|
|
91
|
+
return -1
|
|
92
|
+
chars = list(reversed(alphabet.upper()))
|
|
93
|
+
radix = 26
|
|
94
|
+
power = 1
|
|
95
|
+
result = -1
|
|
96
|
+
for char in chars:
|
|
97
|
+
result += (ord(char) - 65) * power + power
|
|
98
|
+
power *= radix
|
|
99
|
+
return result
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|