Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ The architectural choices, trade-offs, and design patterns for each algorithm ar
* [ADR 0016: Bottom-Up Tabulation with Backtracking for 0/1 Knapsack](docs/adr/0016-use-bottom-up-tabulation-with-backtracking-for-01-knapsack.md)
* [ADR 0017: Bottom-Up Tabulation with Diagonal Backtracking for LCS](docs/adr/0017-use-bottom-up-tabulation-with-diagonal-backtracking-for-lcs.md)
* [ADR 0018: Binary Max-Heap for Heap Sort](docs/adr/0018-use-binary-max-heap-for-heap-sort.md)
* [ADR 0019: Iterative Midpoint Bisection for Binary Search](docs/adr/0019-use-iterative-midpoint-bisection-for-binary-search.md)

---

Expand Down Expand Up @@ -122,4 +123,5 @@ To ensure uniformity, this repository follows strict standards derived from **PE
14. **Bounded Traversal Footprint:** The Singly Linked List's `search`/`delete`/`reverse` operations are strictly O(n) iterative walks with no recursion, preventing stack-depth exhaustion on very large untrusted input lists.
15. **Iterative DP, No Recursion Limits:** The 0/1 Knapsack solver uses bottom-up tabulation rather than top-down recursion, avoiding Python's `RecursionError` on large item counts.
16. **Quadratic Complexity Awareness:** LCS runs in $O(n \times m)$ time and space; callers should bound input string lengths when comparing untrusted, attacker-controlled text to avoid excessive memory allocation on very large inputs.
17. **In-Place Worst-Case Guarantee:** Heap Sort provides the same $O(n \log n)$ worst-case guarantee as Merge Sort but with $O(1)$ auxiliary space, useful when both adversarial-input resilience and memory constraints matter simultaneously.
17. **In-Place Worst-Case Guarantee:** Heap Sort provides the same $O(n \log n)$ worst-case guarantee as Merge Sort but with $O(1)$ auxiliary space, useful when both adversarial-input resilience and memory constraints matter simultaneously.
18. **Precondition Responsibility:** Binary Search assumes sorted input and does not validate it; callers must guarantee sortedness themselves, since verifying it would negate the algorithm's logarithmic performance advantage.
10 changes: 10 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ This document serves as the long-term architectural roadmap for this learning re
* **Heap Sort**: In-place comparison sort built atop a binary max-heap, contrasting Quicksort/Merge Sort's partitioning and merging strategies. (Completed)
* **Euclidean Algorithm (GCD)**: Iterative remainder-based reduction resolving the greatest common divisor between two integers.
* **Valid Parentheses (Stack-Based Matching)**: Stack-tracked bracket balancing validating correctly nested and closed symbol pairs.
* **Binary Search**: Divide-and-conquer $O(\log n)$ lookup resolving a target's position within a sorted array. (Completed)

### Phase 7: Graph Traversal & Minimum Spanning Trees
* **Breadth-First Search (BFS)**: Queue-driven level-order graph traversal resolving shortest unweighted paths and reachability.
* **Depth-First Search (DFS)**: Stack/recursion-driven graph traversal resolving connectivity, cycle detection, and ordering.
* **Kruskal's Algorithm**: Greedy edge-sorted minimum spanning tree construction built atop the existing Union-Find structure.

### Phase 8: Numerical Methods & Ranking Algorithms
* **PageRank (Power Iteration)**: Iterative eigenvector approximation ranking nodes by weighted incoming link importance.
* **Fast Inverse Square Root**: Bit-level floating-point approximation technique accelerating $1/\sqrt{x}$ via a single Newton-Raphson refinement step.

---

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# 19. Use Iterative Midpoint Bisection for Binary Search

* **Status:** Approved
* **Context:** Phase 6 required a foundational $O(\log n)$ lookup mechanism as a baseline searching primitive, applicable whenever data is already sorted (as opposed to linear O(n) scanning).
* **Decision:** We implemented **Binary Search** iteratively, using `low + (high - low) // 2` to compute the midpoint (rather than `(low + high) // 2`) and narrowing the search window each iteration based on comparison against the target.
* **Consequences:**
* Achieves $O(\log n)$ time and $O(1)$ space, since the iterative approach avoids any recursive call-stack growth.
* The `low + (high - low) // 2` midpoint formula is a defensive habit carried over from languages with fixed-width integers (where `low + high` can overflow); it is unnecessary in Python's arbitrary-precision integers but costs nothing and keeps the implementation portable as a reference pattern.
* *Trade-off:* The function assumes its input is already sorted and provides no validation of that precondition — enforcing sortedness would cost an extra $O(n)$ pass, defeating the purpose of using binary search in the first place. Callers are responsible for ensuring sorted input.

11 changes: 11 additions & 0 deletions service/http_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ class SortRequest(BaseModel):
values: List[int]


class BinarySearchRequest(BaseModel):
sorted_values: List[int]
target: int


class KmpSearchRequest(BaseModel):
text: str
pattern: str
Expand Down Expand Up @@ -122,6 +127,12 @@ def sort_heap_sort(request: SortRequest) -> List[int]:
return _call(tools.sort_heap_sort, request.values)


@app.post("/searching/binary-search")
def search_binary_search(request: BinarySearchRequest) -> Dict:
"""Returns the index of a target within a sorted array, or -1 if absent."""
return _call(tools.search_binary_search, request.sorted_values, request.target)


@app.post("/string-matching/kmp")
def search_kmp(request: KmpSearchRequest) -> List[int]:
"""Finds every 0-indexed starting position of a pattern within a text."""
Expand Down
6 changes: 6 additions & 0 deletions service/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ def sort_heap_sort(values: List[int]) -> List[int]:
return tools.sort_heap_sort(values)


@mcp.tool()
def search_binary_search(sorted_values: List[int], target: int) -> Dict:
"""Returns the index of `target` within a sorted array, or -1 if absent."""
return tools.search_binary_search(sorted_values, target)


@mcp.tool()
def search_kmp(text: str, pattern: str) -> List[int]:
"""Finds every 0-indexed starting position of `pattern` within `text`."""
Expand Down
6 changes: 6 additions & 0 deletions service/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from src.machine_learning.kmeans import KMeans
from src.machine_learning.pca import PCA
from src.numeric.sieve import sieve_of_eratosthenes
from src.searching.binary_search import binary_search
from src.sorting.heap_sort import heap_sort
from src.sorting.merge_sort import merge_sort
from src.sorting.quicksort import quicksort
Expand Down Expand Up @@ -59,6 +60,11 @@ def sort_heap_sort(values: List[int]) -> List[int]:
return heap_sort(values)


def search_binary_search(sorted_values: List[int], target: int) -> Dict:
"""Returns the index of `target` within a sorted array, or -1 if absent."""
return {"index": binary_search(sorted_values, target)}


def search_kmp(text: str, pattern: str) -> List[int]:
"""Finds every 0-indexed starting position of `pattern` within `text`."""
return kmp_search(text, pattern)
Expand Down
Empty file added src/searching/__init__.py
Empty file.
29 changes: 29 additions & 0 deletions src/searching/binary_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Binary Search module implementing iterative divide-and-conquer lookups on sorted arrays."""

from typing import List


def binary_search(sorted_array: List[int], target: int) -> int:
"""Returns the index of `target` within a sorted array, or -1 if absent.

The input array must already be sorted in ascending order; behavior on an
unsorted array is undefined.

Complexity Analysis:
Time Complexity: O(log n) where n = len(sorted_array).
Space Complexity: O(1) — iterative, no recursive call stack growth.
"""
low, high = 0, len(sorted_array) - 1

while low <= high:
# Midpoint computed this way avoids integer overflow in lower-level languages
mid = low + (high - low) // 2

if sorted_array[mid] == target:
return mid
if sorted_array[mid] < target:
low = mid + 1
else:
high = mid - 1

return -1
32 changes: 32 additions & 0 deletions tests/test_searching.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Comprehensive evaluation suite tracking Binary Search lookup operations."""

from src.searching.binary_search import binary_search


def test_binary_search_finds_present_values():
"""Verifies correct index resolution across first, middle, and last positions."""
sorted_array = [1, 3, 5, 7, 9, 11, 13]

assert binary_search(sorted_array, 1) == 0
assert binary_search(sorted_array, 7) == 3
assert binary_search(sorted_array, 13) == 6


def test_binary_search_absent_value_returns_negative_one():
"""Ensures a target not present in the array resolves to -1."""
sorted_array = [2, 4, 6, 8, 10]

assert binary_search(sorted_array, 5) == -1
assert binary_search(sorted_array, -1) == -1
assert binary_search(sorted_array, 100) == -1


def test_binary_search_empty_array():
"""Ensures searching an empty array returns -1 without error."""
assert binary_search([], 5) == -1


def test_binary_search_single_element_array():
"""Ensures single-element arrays resolve correctly for both match and mismatch."""
assert binary_search([42], 42) == 0
assert binary_search([42], 7) == -1
17 changes: 17 additions & 0 deletions tests/test_service_http_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ def test_http_sort_heap_sort():
assert response.json() == [1, 2, 3, 4, 5]


def test_http_search_binary_search():
"""Verifies the Binary Search endpoint returns the correct index, or -1 if absent."""
response = client.post(
"/searching/binary-search",
json={"sorted_values": [1, 3, 5, 7, 9], "target": 7},
)
assert response.status_code == 200
assert response.json() == {"index": 3}

response = client.post(
"/searching/binary-search",
json={"sorted_values": [1, 3, 5, 7, 9], "target": 4},
)
assert response.status_code == 200
assert response.json() == {"index": -1}


def test_http_search_kmp():
"""Verifies the KMP search endpoint returns matching start indices."""
response = client.post(
Expand Down
7 changes: 7 additions & 0 deletions tests/test_service_mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def test_mcp_tool_registry_contains_all_algorithms():
"sort_quicksort",
"sort_merge_sort",
"sort_heap_sort",
"search_binary_search",
"search_kmp",
"build_and_query_bst",
"build_and_query_avl_tree",
Expand Down Expand Up @@ -52,6 +53,12 @@ def test_mcp_sort_heap_sort():
assert mcp_server.sort_heap_sort([5, 2, 4, 1, 3]) == [1, 2, 3, 4, 5]


def test_mcp_search_binary_search():
"""Verifies the Binary Search tool returns the correct index, or -1 if absent."""
assert mcp_server.search_binary_search([1, 3, 5, 7, 9], target=7) == {"index": 3}
assert mcp_server.search_binary_search([1, 3, 5, 7, 9], target=4) == {"index": -1}


def test_mcp_search_kmp():
"""Verifies the KMP search tool returns matching start indices."""
assert mcp_server.search_kmp("ababcababc", "abc") == [2, 7]
Expand Down
6 changes: 6 additions & 0 deletions tests/test_service_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ def test_sort_heap_sort():
assert tools.sort_heap_sort([5, 2, 4, 1, 3]) == [1, 2, 3, 4, 5]


def test_search_binary_search():
"""Verifies Binary Search wrapper returns the correct index, or -1 if absent."""
assert tools.search_binary_search([1, 3, 5, 7, 9], target=7) == {"index": 3}
assert tools.search_binary_search([1, 3, 5, 7, 9], target=4) == {"index": -1}


def test_search_kmp():
"""Verifies KMP wrapper returns matching start indices."""
assert tools.search_kmp("ababcababc", "abc") == [2, 7]
Expand Down
Loading