Skip to content

Commit 815a20f

Browse files
committed
Updates
1 parent 53831de commit 815a20f

File tree

8 files changed

+140
-492
lines changed

8 files changed

+140
-492
lines changed

.github/workflows/ci.yml

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,27 @@ on:
99
jobs:
1010
test:
1111
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.10", "3.11", "3.12", "3.13"]
1215
steps:
13-
- uses: actions/checkout@v2
14-
15-
- uses: actions/setup-python@v2
16-
with:
17-
python-version: 3.11
18-
19-
- name: cache poetry install
20-
uses: actions/cache@v3
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-python@v5
2118
with:
22-
path: ~/.local
23-
key: poetry-0
19+
python-version: ${{ matrix.python-version }}
20+
- run: pip install uv
21+
- run: uv venv
22+
- run: uv sync
23+
- run: uv run pytest
2424

25-
- uses: snok/install-poetry@v1
26-
with:
27-
virtualenvs-create: true
28-
virtualenvs-in-project: true
29-
30-
- name: cache deps
31-
id: cache-deps
32-
uses: actions/cache@v3
33-
with:
34-
path: .venv
35-
key: pydeps-${{ hashFiles('**/poetry.lock') }}
36-
37-
- run: poetry install --no-interaction --no-root
38-
if: steps.cache-deps.outputs.cache-hit != 'true'
39-
40-
- run: poetry install --no-interaction
41-
42-
- run: poetry run pytest tests.py
43-
- run: poetry run black mudder.py tests.py
44-
- run: poetry run flake8 mudder.py
25+
lint:
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: actions/checkout@v4
29+
- uses: actions/setup-python@v5
30+
- run: pip install uv
31+
- run: uv venv
32+
- run: uv pip install --requirement pyproject.toml --all-extras
33+
- run: .venv/bin/ruff format --check .
34+
- run: .venv/bin/ruff check .
35+
- run: .venv/bin/mypy .

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ From the original readme:
77
> Generate lexicographically-spaced strings between two strings from
88
> pre-defined alphabets.
99
10-
[1]: https://github.com/fasiha/mudderjs
10+
This technique is also known as _fractional indexing_.
1111

12+
[1]: https://github.com/fasiha/mudderjs
1213

1314
## Example
1415

mudder.py renamed to mudder/__init__.py

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import math
2-
from decimal import Decimal, ROUND_HALF_UP
2+
from collections.abc import Iterable, Reversible
3+
from decimal import ROUND_HALF_UP, Decimal
34
from functools import partial
45
from itertools import chain, cycle
56
from operator import add
6-
from typing import Dict, Iterable, List, Optional, Reversible, Tuple, Union
77

88
__all__ = [
99
"SymbolTable",
10-
"decimal",
10+
"alphabet",
1111
"base36",
1212
"base62",
13-
"alphabet",
13+
"decimal",
1414
]
1515

1616

@@ -29,27 +29,26 @@ def is_prefix_code(strings: Iterable[str]) -> bool:
2929

3030
class SymbolTable:
3131
def __init__(
32-
self, symbols: Iterable[str], symbol_map: Optional[Dict[str, int]] = None
33-
):
32+
self, symbols: Iterable[str], symbol_map: dict[str, int] | None = None
33+
) -> None:
3434
symbols = list(symbols)
3535
if not symbol_map:
3636
symbol_map = dict((c, i) for i, c in enumerate(symbols))
3737

3838
symbol_values = set(symbol_map.values())
3939
for i in range(len(symbols)):
4040
if i not in symbol_values:
41-
raise ValueError(
42-
f"{len(symbols)} symbols given but {i} not found in symbol table"
43-
)
41+
msg = f"{len(symbols)} symbols given but {i} not found in symbol table"
42+
raise ValueError(msg)
4443

4544
self.num2sym = symbols
4645
self.sym2num = symbol_map
4746
self.max_base = len(symbols)
4847
self.is_prefix_code = is_prefix_code(symbols)
4948

50-
def number_to_digits(self, num: int, base: Optional[int] = None) -> List[int]:
49+
def number_to_digits(self, num: int, base: int | None = None) -> list[int]:
5150
base = base or self.max_base
52-
digits: List[int] = []
51+
digits: list[int] = []
5352
while num >= 1:
5453
digits.append(num % base)
5554
num = num // base
@@ -61,20 +60,19 @@ def number_to_digits(self, num: int, base: Optional[int] = None) -> List[int]:
6160
def digits_to_string(self, digits: Iterable[int]) -> str:
6261
return "".join([self.num2sym[n] for n in digits])
6362

64-
def string_to_digits(self, string: Iterable[str]) -> List[int]:
63+
def string_to_digits(self, string: Iterable[str]) -> list[int]:
6564
if isinstance(string, str):
6665
if not self.is_prefix_code:
67-
raise ValueError(
66+
msg = (
6867
"Parsing without prefix code is unsupported. "
6968
"Pass in array of stringy symbols?"
7069
)
70+
raise ValueError(msg)
7171
string = (c for c in string if c in self.sym2num)
7272

7373
return [self.sym2num[c] for c in string]
7474

75-
def digits_to_number(
76-
self, digits: Reversible[int], base: Optional[int] = None
77-
) -> int:
75+
def digits_to_number(self, digits: Reversible[int], base: int | None = None) -> int:
7876
base = base or self.max_base
7977
current_base = 1
8078
accum = 0
@@ -83,15 +81,15 @@ def digits_to_number(
8381
current_base *= base
8482
return accum
8583

86-
def number_to_string(self, num: int, base: Optional[int] = None) -> str:
84+
def number_to_string(self, num: int, base: int | None = None) -> str:
8785
return self.digits_to_string(self.number_to_digits(num, base=base))
8886

89-
def string_to_number(self, num: Iterable[str], base: Optional[int] = None) -> int:
87+
def string_to_number(self, num: Iterable[str], base: int | None = None) -> int:
9088
return self.digits_to_number(self.string_to_digits(num), base=base)
9189

9290
def round_fraction(
93-
self, numerator: int, denominator: int, base: Optional[int] = None
94-
) -> List[int]:
91+
self, numerator: int, denominator: int, base: int | None = None
92+
) -> list[int]:
9593
base = base or self.max_base
9694
places = math.ceil(math.log(denominator) / math.log(base))
9795
scale = pow(base, places)
@@ -105,12 +103,12 @@ def round_fraction(
105103

106104
def mudder(
107105
self,
108-
a: Union[Iterable[str], int] = "",
106+
a: Iterable[str] | int = "",
109107
b: Iterable[str] = "",
110108
num_strings: int = 1,
111-
base: Optional[int] = None,
112-
num_divisions: Optional[int] = None,
113-
) -> List[str]:
109+
base: int | None = None,
110+
num_divisions: int | None = None,
111+
) -> list[str]:
114112
if isinstance(a, int):
115113
num_strings = a
116114
a = ""
@@ -135,8 +133,8 @@ def mudder(
135133

136134

137135
def long_div(
138-
numerator: List[int], denominator: int, base: int
139-
) -> Tuple[List[int], int]:
136+
numerator: list[int], denominator: int, base: int
137+
) -> tuple[list[int], int]:
140138
result = []
141139
remainder = 0
142140
for current in numerator:
@@ -146,15 +144,16 @@ def long_div(
146144
return result, remainder
147145

148146

149-
def long_sub_same_len( # noqa: C901
150-
a: List[int],
151-
b: List[int],
147+
def long_sub_same_len(
148+
a: list[int],
149+
b: list[int],
152150
base: int,
153-
remainder: Optional[Tuple[int, int]] = None,
154-
denominator=0,
155-
) -> Tuple[List[int], int]:
151+
remainder: tuple[int, int] | None = None,
152+
denominator: int = 0,
153+
) -> tuple[list[int], int]:
156154
if len(a) != len(b):
157-
raise ValueError("a and b should have same length")
155+
msg = "a and b should have same length"
156+
raise ValueError(msg)
158157

159158
a = a.copy() # pre-emptively copy
160159
if remainder:
@@ -169,7 +168,8 @@ def long_sub_same_len( # noqa: C901
169168
ret[i] = a[i] - b[i]
170169
continue
171170
if i == 0:
172-
raise ValueError("Cannot go negative")
171+
msg = "Cannot go negative"
172+
raise ValueError(msg)
173173
do_break = False
174174
# look for a digit to the left to borrow from
175175
for j in reversed(range(i)):
@@ -189,18 +189,20 @@ def long_sub_same_len( # noqa: C901
189189
break
190190
if do_break:
191191
continue
192-
raise ValueError("Failed to find digit to borrow from")
192+
msg = "Failed to find digit to borrow from"
193+
raise ValueError(msg)
193194
if remainder:
194195
# result, remainder
195196
return ret[:-1], ret[-1]
196197
return ret, 0
197198

198199

199200
def long_add_same_len(
200-
a: List[int], b: List[int], base: int, remainder: int, denominator: int
201-
) -> Tuple[List[int], bool, int, int]:
201+
a: list[int], b: list[int], base: int, remainder: int, denominator: int
202+
) -> tuple[list[int], bool, int, int]:
202203
if len(a) != len(b):
203-
raise ValueError("a and b should have same length")
204+
msg = "a and b should have same length"
205+
raise ValueError(msg)
204206

205207
carry = remainder >= denominator
206208
res = b.copy()
@@ -215,22 +217,23 @@ def long_add_same_len(
215217
return res, carry, remainder, denominator
216218

217219

218-
def right_pad(arr: List[int], to_length: int, val: int = 0) -> List[int]:
220+
def right_pad(arr: list[int], to_length: int, val: int = 0) -> list[int]:
219221
pad_len = to_length - len(arr)
220222
if pad_len > 0:
221223
return arr + [val] * pad_len
222224
return arr
223225

224226

225227
def long_linspace(
226-
a: List[int], b: List[int], base: int, n: int, m: int
227-
) -> List[Tuple[List[int], int, int]]:
228+
a: list[int], b: list[int], base: int, n: int, m: int
229+
) -> list[tuple[list[int], int, int]]:
228230
if len(a) < len(b):
229231
a = right_pad(a, len(b))
230232
elif len(b) < len(a):
231233
b = right_pad(b, len(a))
232234
if a == b:
233-
raise ValueError("Start and end strings are lexicographically inseperable")
235+
msg = "Start and end strings are lexicographically inseparable"
236+
raise ValueError(msg)
234237
a_div, a_div_rem = long_div(a, m, base)
235238
b_div, b_div_rem = long_div(b, m, base)
236239

@@ -252,21 +255,21 @@ def long_linspace(
252255
return ret
253256

254257

255-
def left_pad(arr: List[int], to_length: int, val: int = 0) -> List[int]:
258+
def left_pad(arr: list[int], to_length: int, val: int = 0) -> list[int]:
256259
pad_len = to_length - len(arr)
257260
if pad_len > 0:
258261
return [val] * pad_len + arr
259262
return arr
260263

261264

262-
def chop_digits(rock: List[int], water: List[int]) -> List[int]:
265+
def chop_digits(rock: list[int], water: list[int]) -> list[int]:
263266
for i in range(len(water)):
264267
if water[i] and (i >= len(rock) or rock[i] != water[i]):
265268
return water[: i + 1]
266269
return water
267270

268271

269-
def lexicographic_less_than_array(a: List[int], b: List[int]) -> bool:
272+
def lexicographic_less_than_array(a: list[int], b: list[int]) -> bool:
270273
n = min(len(a), len(b))
271274
for i in range(n):
272275
if a[i] == b[i]:
@@ -275,7 +278,7 @@ def lexicographic_less_than_array(a: List[int], b: List[int]) -> bool:
275278
return len(a) < len(b)
276279

277280

278-
def chop_successive_digits(strings: List[List[int]]) -> List[List[int]]:
281+
def chop_successive_digits(strings: list[list[int]]) -> list[list[int]]:
279282
reversed_ = not lexicographic_less_than_array(strings[0], strings[1])
280283
if reversed_:
281284
strings.reverse()
@@ -300,6 +303,7 @@ def chop_successive_digits(strings: List[List[int]]) -> List[List[int]]:
300303
digits + alpha_lower + alpha_upper,
301304
# 0-9, then 10-35 repeating (for upper and lower case)
302305
chain(range(10), cycle(map(partial(add, 10), range(26)))),
306+
strict=False,
303307
)
304308
),
305309
)

mudder/py.typed

Whitespace-only changes.

mypy.ini

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)