Skip to content

Commit 6f8a2f2

Browse files
authored
Merge branch 'master' into feat/pancake-sort-comparable
2 parents eb62b14 + 9cd7ee0 commit 6f8a2f2

4 files changed

Lines changed: 203 additions & 20 deletions

File tree

data_structures/linked_list/partition_linked_list.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ def add(self, item: Any, position: int = 0) -> None:
103103

104104
def partition_liked_list(self, value: int) -> None:
105105
"""
106-
Partition Linked List based on node elements in-order.
107-
All nodes with elements less than value should occur in the left,
108-
while those greater than to value, in the right.
106+
Partition the linked list based on node elements in order.
107+
All nodes with elements less than value should occur on the left,
108+
while those greater than or equal to value should occur on the right.
109109
110110
>>> linked_list = LinkedList()
111111
>>> linked_list.add(1)
@@ -156,7 +156,7 @@ def partition_liked_list(self, value: int) -> None:
156156
1
157157
"""
158158
if self.head is None:
159-
return None
159+
return
160160

161161
less_nodes, greater_nodes = Node(0), Node(0)
162162
current, current_less, current_greater = self.head, less_nodes, greater_nodes

maths/softmax.py

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,43 +14,105 @@
1414
from numpy.exceptions import AxisError
1515

1616

17-
def softmax(vector: np.ndarray, axis: int = -1) -> np.ndarray:
17+
def softmax(vector: np.ndarray, axis: int | None = -1) -> np.ndarray:
1818
"""
19-
Implements the softmax function.
19+
Compute the softmax of ``vector`` along ``axis`` in a numerically-stable way.
2020
2121
Parameters:
22-
vector (np.ndarray | list | tuple): A numpy array of shape (1, n)
23-
consisting of real values or a similar list/tuple.
24-
axis (int, optional): Axis along which to compute softmax.
25-
Default is -1.
22+
vector (np.ndarray | list | tuple): Input data (vector, matrix or
23+
higher-rank tensor). It is converted to a float ``np.ndarray``,
24+
so lists, tuples and integers are accepted too.
25+
axis (int | None, optional): Axis along which softmax is computed so
26+
that the probabilities sum to 1 along that axis. If ``None``, the
27+
softmax is computed over the flattened array (a single
28+
distribution). Default is ``-1`` (the last axis).
2629
2730
Returns:
28-
np.ndarray: The input numpy array after applying softmax.
31+
np.ndarray: An array with the same shape as ``vector`` whose values
32+
along ``axis`` (or over the whole array when ``axis is None``) form a
33+
probability distribution that sums to 1.
34+
35+
Raises:
36+
ValueError: If ``vector`` is empty or cannot be converted to a numeric
37+
float array (for example a string or a dict).
38+
numpy.exceptions.AxisError: If ``axis`` is out of bounds for the input.
39+
40+
Note:
41+
If the input contains ``NaN`` or ``inf`` the result will contain
42+
``NaN`` along the affected axis; softmax is only meaningful for finite
43+
real inputs.
2944
3045
The softmax vector adds up to one. We need to ceil to mitigate precision.
3146
3247
>>> float(np.ceil(np.sum(softmax([1, 2, 3, 4]))))
3348
1.0
3449
35-
>>> vec = np.array([5, 5])
36-
>>> softmax(vec)
50+
Identical logits map to a uniform distribution:
51+
52+
>>> softmax(np.array([5, 5]))
3753
array([0.5, 0.5])
3854
55+
A single element always maps to 1:
56+
3957
>>> softmax([0])
4058
array([1.])
59+
60+
It is numerically stable for large logits (no overflow):
61+
62+
>>> softmax([1000.0, 1001.0, 1002.0])
63+
array([0.09003057, 0.24472847, 0.66524096])
64+
65+
For a 2-D array the ``axis`` selects where probabilities sum to 1:
66+
67+
>>> mat = np.array([[1.0, 2.0, 3.0], [1.0, 2.0, 3.0]])
68+
>>> np.round(softmax(mat, axis=-1), 3)
69+
array([[0.09 , 0.245, 0.665],
70+
[0.09 , 0.245, 0.665]])
71+
>>> np.round(softmax(mat, axis=0), 3)
72+
array([[0.5, 0.5, 0.5],
73+
[0.5, 0.5, 0.5]])
74+
75+
With ``axis=None`` the whole array becomes one distribution that sums to 1:
76+
77+
>>> float(np.round(np.sum(softmax(mat, axis=None)), 6))
78+
1.0
79+
80+
Empty, non-numeric and out-of-bounds inputs raise clear errors:
81+
82+
>>> softmax([])
83+
Traceback (most recent call last):
84+
...
85+
ValueError: softmax input must be non-empty
86+
>>> softmax("not a number")
87+
Traceback (most recent call last):
88+
...
89+
ValueError: softmax input must be numeric, got str
90+
>>> softmax([1, 2, 3], axis=3)
91+
Traceback (most recent call last):
92+
...
93+
numpy.exceptions.AxisError: axis 3 is out of bounds for array of dimension 1
4194
"""
42-
# Convert input to numpy array of floats
43-
vector = np.asarray(vector, dtype=float)
95+
# Convert input to a float numpy array, turning numpy's terse conversion
96+
# errors into a clear message about the unsupported input type.
97+
try:
98+
vector = np.asarray(vector, dtype=float)
99+
except (ValueError, TypeError) as exc:
100+
error_message = f"softmax input must be numeric, got {type(vector).__name__}"
101+
raise ValueError(error_message) from exc
44102

45103
# Handle empty input
46104
if vector.size == 0:
47105
raise ValueError("softmax input must be non-empty")
48106

49-
# Validate axis
50-
ndim = vector.ndim
51-
if axis >= ndim or axis < -ndim:
52-
error_message = f"axis {axis} is out of bounds for array of dimension {ndim}"
53-
raise AxisError(error_message)
107+
# Validate axis (None means "treat the whole array as one distribution")
108+
if axis is not None:
109+
ndim = vector.ndim
110+
if axis >= ndim or axis < -ndim:
111+
error_message = (
112+
f"axis {axis} is out of bounds for array of dimension {ndim}"
113+
)
114+
raise AxisError(error_message)
115+
54116
# Subtract max for numerical stability
55117
vector_max = np.max(vector, axis=axis, keepdims=True)
56118
exponent_vector = np.exp(vector - vector_max)
@@ -73,3 +135,5 @@ def softmax(vector: np.ndarray, axis: int = -1) -> np.ndarray:
73135
print("Softmax along last axis:\n", softmax(mat))
74136
# Matrix along axis 0
75137
print("Softmax along axis 0:\n", softmax(mat, axis=0))
138+
# Whole-matrix distribution
139+
print("Softmax over the whole matrix:\n", softmax(mat, axis=None))

scripts/pr_file_map.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env python3
2+
"""
3+
pr_file_map.py
4+
5+
Lists all open pull requests in the current directory's git repo (via `gh`)
6+
and, for each file touched by any open PR, which PR number(s) touch it.
7+
8+
Output is GitHub-flavored Markdown: a sorted list of files that currently
9+
exist in the working directory, each with its modifying PR numbers, followed
10+
by a separate section for files referenced by open PRs but that do not exist
11+
in the working directory (e.g. deleted, renamed, or on a branch not checked
12+
out locally).
13+
14+
Requirements: gh (GitHub CLI), authenticated (`gh auth login`)
15+
16+
Usage:
17+
./pr_file_map.py
18+
./pr_file_map.py > report.md
19+
"""
20+
21+
import json
22+
import os
23+
import shutil
24+
import subprocess
25+
import sys
26+
from collections import defaultdict
27+
28+
29+
def run_gh(args: list[str]) -> str:
30+
try:
31+
result = subprocess.run( # noqa: S603
32+
["gh", *args], # noqa: S607
33+
capture_output=True,
34+
text=True,
35+
check=True,
36+
)
37+
except FileNotFoundError:
38+
sys.exit("Error: 'gh' (GitHub CLI) is not installed or not in PATH.")
39+
except subprocess.CalledProcessError as e:
40+
sys.exit(f"Error running 'gh {' '.join(args)}':\n{e.stderr.strip()}")
41+
return result.stdout
42+
43+
44+
def check_gh_auth() -> None:
45+
try:
46+
subprocess.run(
47+
["gh", "auth", "status"], # noqa: S607
48+
capture_output=True,
49+
text=True,
50+
check=True,
51+
)
52+
except subprocess.CalledProcessError:
53+
sys.exit("Error: gh is not authenticated. Run 'gh auth login' first.")
54+
55+
56+
def get_open_prs() -> list[dict]:
57+
raw = run_gh(
58+
["pr", "list", "--state", "open", "--limit", "1000", "--json", "number,title"]
59+
)
60+
return json.loads(raw)
61+
62+
63+
def get_pr_files(pr_number: int) -> list[str]:
64+
raw = run_gh(["pr", "view", str(pr_number), "--json", "files"])
65+
data = json.loads(raw)
66+
return [f["path"] for f in data.get("files", [])]
67+
68+
69+
def main() -> None:
70+
if shutil.which("gh") is None:
71+
sys.exit("Error: 'gh' (GitHub CLI) is not installed or not in PATH.")
72+
73+
check_gh_auth()
74+
75+
prs = get_open_prs()
76+
if not prs:
77+
print("No open pull requests found.")
78+
return
79+
80+
file_to_prs: dict[str, list[int]] = defaultdict(list)
81+
82+
for pr in prs:
83+
pr_number = pr["number"]
84+
for path in get_pr_files(pr_number):
85+
file_to_prs[path].append(pr_number)
86+
87+
existing: dict[str, list[int]] = {}
88+
missing: dict[str, list[int]] = {}
89+
90+
for path, pr_numbers in file_to_prs.items():
91+
target = existing if os.path.exists(path) else missing
92+
target[path] = sorted(set(pr_numbers))
93+
94+
# --- Render GitHub-flavored Markdown ---
95+
print("# Open Pull Request File Map\n")
96+
97+
print("## Existing files\n")
98+
if existing:
99+
for path in sorted(existing):
100+
pr_list = " ".join(f"#{n}" for n in existing[path])
101+
print(f"- `{path}`: {pr_list}")
102+
else:
103+
print("_None._")
104+
105+
print("\n## Files not present in the working directory\n")
106+
if missing:
107+
for path in sorted(missing):
108+
pr_list = " ".join(f"#{n}" for n in missing[path])
109+
print(f"- `{path}`: {pr_list}")
110+
else:
111+
print("_None._")
112+
113+
114+
if __name__ == "__main__":
115+
main()

sorts/bucket_sort.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ def bucket_sort(
7777
>>> data = [9, 2, 7, 1, 5]
7878
>>> bucket_sort(data) == sorted(data)
7979
True
80+
>>> bucket_sort(data, 3.5)
81+
Traceback (most recent call last):
82+
...
83+
TypeError: bucket_count must be an integer
8084
"""
8185

8286
if not isinstance(bucket_count, int):

0 commit comments

Comments
 (0)