added literals
This commit is contained in:
parent
8699d8f7ab
commit
df9b2b5f29
@ -10,7 +10,7 @@ source venv/bin/activate
|
|||||||
pip install -e .
|
pip install -e .
|
||||||
```
|
```
|
||||||
|
|
||||||
Create digit files anywhere you like (the examples below use `instance/log`), then construct `StreamNumber` objects, and call the helpers:
|
Create digit files anywhere you like (the examples below use `instance/log`), or supply ad-hoc literals, then construct `StreamNumber` objects and call the helpers:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from mathstream import (
|
from mathstream import (
|
||||||
@ -26,8 +26,8 @@ from mathstream import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
a = StreamNumber("instance/log/huge.txt")
|
a = StreamNumber("instance/log/huge.txt")
|
||||||
b = StreamNumber("instance/log/tiny.txt")
|
b = StreamNumber(literal="34567")
|
||||||
e = StreamNumber("instance/log/exponent.txt")
|
e = StreamNumber(literal="3")
|
||||||
|
|
||||||
print("sum =", "".join(add(a, b).stream()))
|
print("sum =", "".join(add(a, b).stream()))
|
||||||
print("difference =", "".join(sub(a, b).stream()))
|
print("difference =", "".join(sub(a, b).stream()))
|
||||||
@ -43,7 +43,7 @@ Each arithmetic call writes its result back into `instance/log` (configurable vi
|
|||||||
|
|
||||||
## Core Concepts
|
## Core Concepts
|
||||||
|
|
||||||
- **StreamNumber(path)** – Wraps a digit text file. The file may include an optional leading sign (`+` or `-`). Whitespace is ignored; digits must otherwise be contiguous.
|
- **StreamNumber(path | literal=...)** – Wraps a digit text file or creates one for an integer literal inside `LOG_DIR`. Literal operands are persisted as `literal_<hash>.txt`, so repeated runs reuse the same staged file (note that `clear_logs()` removes these cache files too).
|
||||||
- **`.stream(chunk_size)`** – Yields strings of digits with the provided chunk size. Operations in `mathstream.engine` consume these streams to avoid loading the entire number at once.
|
- **`.stream(chunk_size)`** – Yields strings of digits with the provided chunk size. Operations in `mathstream.engine` consume these streams to avoid loading the entire number at once.
|
||||||
- **Automatic staging** – Outputs are stored under `LOG_DIR` with hashes based on input file paths, letting you compose operations without manual bookkeeping.
|
- **Automatic staging** – Outputs are stored under `LOG_DIR` with hashes based on input file paths, letting you compose operations without manual bookkeeping.
|
||||||
- **Sign-aware** – Addition, subtraction, multiplication, division (`//` behavior), modulo, and exponentiation (non-negative exponents) all respect operand sign. Division/modulo follow Python’s floor-division rules.
|
- **Sign-aware** – Addition, subtraction, multiplication, division (`//` behavior), modulo, and exponentiation (non-negative exponents) all respect operand sign. Division/modulo follow Python’s floor-division rules.
|
||||||
|
|||||||
@ -1,13 +1,55 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Optional, Union
|
||||||
|
|
||||||
LOG_DIR = Path("./instance/log")
|
LOG_DIR = Path("./instance/log")
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_log_dir() -> None:
|
||||||
|
LOG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _canonicalize_literal(value: str) -> str:
|
||||||
|
raw = value.strip()
|
||||||
|
if not raw:
|
||||||
|
raise ValueError("Literal value cannot be empty.")
|
||||||
|
|
||||||
|
sign = ""
|
||||||
|
digits = raw
|
||||||
|
if raw[0] in "+-":
|
||||||
|
sign = "-" if raw[0] == "-" else ""
|
||||||
|
digits = raw[1:]
|
||||||
|
|
||||||
|
if not digits or not digits.isdigit():
|
||||||
|
raise ValueError(f"Literal must be an integer string, got: {value!r}")
|
||||||
|
|
||||||
|
digits = digits.lstrip("0") or "0"
|
||||||
|
if digits == "0":
|
||||||
|
sign = ""
|
||||||
|
return f"{sign}{digits}"
|
||||||
|
|
||||||
|
|
||||||
class StreamNumber:
|
class StreamNumber:
|
||||||
def __init__(self, file_path):
|
def __init__(
|
||||||
self.path = Path(file_path)
|
self,
|
||||||
if not self.path.exists():
|
file_path: Optional[Union[str, Path]] = None,
|
||||||
raise FileNotFoundError(self.path)
|
*,
|
||||||
|
literal: Optional[str] = None,
|
||||||
|
):
|
||||||
|
if (file_path is None) == (literal is None):
|
||||||
|
raise ValueError("Provide exactly one of file_path or literal.")
|
||||||
|
|
||||||
|
if literal is not None:
|
||||||
|
normalized = _canonicalize_literal(literal)
|
||||||
|
_ensure_log_dir()
|
||||||
|
literal_hash = hashlib.sha1(normalized.encode()).hexdigest()[:10]
|
||||||
|
self.path = LOG_DIR / f"literal_{literal_hash}.txt"
|
||||||
|
self.path.write_text(normalized, encoding="utf-8")
|
||||||
|
else:
|
||||||
|
self.path = Path(file_path)
|
||||||
|
if not self.path.exists():
|
||||||
|
raise FileNotFoundError(self.path)
|
||||||
|
|
||||||
self.hash = hashlib.sha1(str(self.path).encode()).hexdigest()[:10]
|
self.hash = hashlib.sha1(str(self.path).encode()).hexdigest()[:10]
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -21,6 +63,7 @@ class StreamNumber:
|
|||||||
|
|
||||||
def write_stage(self, stage, data: str):
|
def write_stage(self, stage, data: str):
|
||||||
"""Write intermediate stage result."""
|
"""Write intermediate stage result."""
|
||||||
|
_ensure_log_dir()
|
||||||
stage_file = LOG_DIR / f"{self.hash}_stage_{stage}.bin"
|
stage_file = LOG_DIR / f"{self.hash}_stage_{stage}.bin"
|
||||||
with open(stage_file, "wb") as f:
|
with open(stage_file, "wb") as f:
|
||||||
f.write(data.encode())
|
f.write(data.encode())
|
||||||
|
|||||||
6
test.py
6
test.py
@ -52,6 +52,8 @@ def main() -> None:
|
|||||||
negative = write_number("negative", "-1200")
|
negative = write_number("negative", "-1200")
|
||||||
exponent = write_number("power", "5")
|
exponent = write_number("power", "5")
|
||||||
negative_divisor = write_number("neg_divisor", "-34567")
|
negative_divisor = write_number("neg_divisor", "-34567")
|
||||||
|
literal_even = StreamNumber(literal="2000")
|
||||||
|
literal_odd = StreamNumber(literal="-3")
|
||||||
|
|
||||||
# Showcase the core operations.
|
# Showcase the core operations.
|
||||||
total = add(big, small)
|
total = add(big, small)
|
||||||
@ -63,6 +65,7 @@ def main() -> None:
|
|||||||
neg_mod_pos = mod(negative, small)
|
neg_mod_pos = mod(negative, small)
|
||||||
pos_mod_neg = mod(small, negative)
|
pos_mod_neg = mod(small, negative)
|
||||||
neg_mod_neg = mod(negative, negative_divisor)
|
neg_mod_neg = mod(negative, negative_divisor)
|
||||||
|
literal_combo = add(literal_even, literal_odd)
|
||||||
|
|
||||||
print("Operands stored under:", NUMBERS_DIR)
|
print("Operands stored under:", NUMBERS_DIR)
|
||||||
check("huge + tiny", total, "98765432123491356")
|
check("huge + tiny", total, "98765432123491356")
|
||||||
@ -74,10 +77,13 @@ def main() -> None:
|
|||||||
check("negative % tiny", neg_mod_pos, "33367")
|
check("negative % tiny", neg_mod_pos, "33367")
|
||||||
check("tiny % negative", pos_mod_neg, "-233")
|
check("tiny % negative", pos_mod_neg, "-233")
|
||||||
check("negative % neg_divisor", neg_mod_neg, "-1200")
|
check("negative % neg_divisor", neg_mod_neg, "-1200")
|
||||||
|
check("literal_even + literal_odd", literal_combo, "1997")
|
||||||
check_bool("is_even(negative)", is_even(negative), True)
|
check_bool("is_even(negative)", is_even(negative), True)
|
||||||
check_bool("is_even(tiny)", is_even(small), False)
|
check_bool("is_even(tiny)", is_even(small), False)
|
||||||
check_bool("is_odd(tiny)", is_odd(small), True)
|
check_bool("is_odd(tiny)", is_odd(small), True)
|
||||||
check_bool("is_odd(negative)", is_odd(negative), False)
|
check_bool("is_odd(negative)", is_odd(negative), False)
|
||||||
|
check_bool("is_even(literal_even)", is_even(literal_even), True)
|
||||||
|
check_bool("is_odd(literal_odd)", is_odd(literal_odd), True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user