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.
@@ -0,0 +1,16 @@
1
+ .DS_Store
2
+ .idea
3
+ node_modules
4
+ dist
5
+ .pnpm-debug.log
6
+ npm-debug.log*
7
+ .pytest_cache
8
+ .ruff_cache
9
+ .mypy_cache
10
+ __pycache__
11
+ *.egg-info
12
+ python/dist
13
+ python/build
14
+ python/.venv
15
+ go/testdata/*.tmp
16
+ go/.gocache
@@ -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,2 @@
1
+ MIN_REGEN_SUPPORTED_VERSION = "0.3.0"
2
+ SRK_SUPPORTED_VERSIONS = ">=0.3.0 <0.4.0"
@@ -0,0 +1,6 @@
1
+ from enum import Enum
2
+
3
+
4
+ class EnumTheme(str, Enum):
5
+ light = "light"
6
+ dark = "dark"
@@ -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