fibonacci-tdd-kataa 0.1.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.
- fibonacci_tdd_kataa-0.1.0/.github/workflows/publish.yml +28 -0
- fibonacci_tdd_kataa-0.1.0/.github/workflows/tests.yml +33 -0
- fibonacci_tdd_kataa-0.1.0/.gitignore +5 -0
- fibonacci_tdd_kataa-0.1.0/.python-version +1 -0
- fibonacci_tdd_kataa-0.1.0/PKG-INFO +6 -0
- fibonacci_tdd_kataa-0.1.0/README.md +0 -0
- fibonacci_tdd_kataa-0.1.0/notebooks/notebook.py +157 -0
- fibonacci_tdd_kataa-0.1.0/pyproject.toml +33 -0
- fibonacci_tdd_kataa-0.1.0/src/fibonacci_tdd_kata/__init__.py +7 -0
- fibonacci_tdd_kataa-0.1.0/src/fibonacci_tdd_kata/core.py +22 -0
- fibonacci_tdd_kataa-0.1.0/tests/test_core.py +62 -0
- fibonacci_tdd_kataa-0.1.0/uv.lock +863 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
name: Publish package
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
release:
|
|
6
|
+
types: [published]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
uses: ./.github/workflows/tests.yml
|
|
14
|
+
|
|
15
|
+
build-and-publish:
|
|
16
|
+
needs: test
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v5
|
|
23
|
+
|
|
24
|
+
- name: Build package
|
|
25
|
+
run: uv build
|
|
26
|
+
|
|
27
|
+
- name: Publish to PyPI
|
|
28
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
name: Tests
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main]
|
|
9
|
+
workflow_call:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.12"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{ matrix.python-version }}
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: uv sync --all-groups
|
|
28
|
+
|
|
29
|
+
- name: Lint
|
|
30
|
+
run: uv run ruff check .
|
|
31
|
+
|
|
32
|
+
- name: Run tests
|
|
33
|
+
run: uv run pytest --cov=src --cov-report=term-missing
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
File without changes
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import marimo
|
|
2
|
+
|
|
3
|
+
__generated_with = "0.24.2"
|
|
4
|
+
app = marimo.App(width="medium")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@app.cell
|
|
8
|
+
def _():
|
|
9
|
+
import marimo as mo
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
return (mo,)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.cell
|
|
16
|
+
def _(mo):
|
|
17
|
+
mo.md("""
|
|
18
|
+
# Fibonacci TDD Kata
|
|
19
|
+
|
|
20
|
+
## Objective
|
|
21
|
+
|
|
22
|
+
This notebook demonstrates the Test-Driven Development workflow by implementing
|
|
23
|
+
the Fibonacci sequence step by step.
|
|
24
|
+
|
|
25
|
+
## Fibonacci sequence
|
|
26
|
+
|
|
27
|
+
The sequence is defined as:
|
|
28
|
+
|
|
29
|
+
- F(0) = 0
|
|
30
|
+
- F(1) = 1
|
|
31
|
+
- F(n) = F(n - 1) + F(n - 2) for n >= 2
|
|
32
|
+
|
|
33
|
+
## TDD workflow
|
|
34
|
+
|
|
35
|
+
The implementation follows the Red → Green → Refactor cycle:
|
|
36
|
+
|
|
37
|
+
1. Write tests that initially fail.
|
|
38
|
+
2. Implement the function until all tests pass.
|
|
39
|
+
3. Refactor the code while keeping the test suite green.
|
|
40
|
+
|
|
41
|
+
## Interactive demo
|
|
42
|
+
|
|
43
|
+
Use the widget below to select a value of `n` and display the corresponding
|
|
44
|
+
Fibonacci number.
|
|
45
|
+
""")
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@app.function
|
|
50
|
+
def fibonacci(n):
|
|
51
|
+
def fib_pair(k):
|
|
52
|
+
if k == 0:
|
|
53
|
+
return 0, 1
|
|
54
|
+
|
|
55
|
+
a, b = fib_pair(k // 2)
|
|
56
|
+
|
|
57
|
+
c = a * (2 * b - a)
|
|
58
|
+
d = a * a + b * b
|
|
59
|
+
|
|
60
|
+
if k % 2 == 0:
|
|
61
|
+
return c, d
|
|
62
|
+
return d, c + d
|
|
63
|
+
|
|
64
|
+
return fib_pair(n)[0]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.cell
|
|
68
|
+
def _():
|
|
69
|
+
def test_fibonacci_zero():
|
|
70
|
+
assert fibonacci(0) == 0
|
|
71
|
+
|
|
72
|
+
def test_fibonacci_one():
|
|
73
|
+
assert fibonacci(1) == 1
|
|
74
|
+
|
|
75
|
+
def test_fibonacci_two():
|
|
76
|
+
assert fibonacci(2) == 1
|
|
77
|
+
|
|
78
|
+
def test_fibonacci_three():
|
|
79
|
+
assert fibonacci(3) == 2
|
|
80
|
+
|
|
81
|
+
def test_fibonacci_four():
|
|
82
|
+
assert fibonacci(4) == 3
|
|
83
|
+
|
|
84
|
+
def test_fibonacci_five():
|
|
85
|
+
assert fibonacci(5) == 5
|
|
86
|
+
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@app.cell
|
|
91
|
+
def _(mo):
|
|
92
|
+
n_input = mo.ui.number(
|
|
93
|
+
start=0,
|
|
94
|
+
stop=30,
|
|
95
|
+
step=1,
|
|
96
|
+
value=10,
|
|
97
|
+
label="n"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
n_input
|
|
101
|
+
return (n_input,)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@app.cell
|
|
105
|
+
def _(mo, n_input):
|
|
106
|
+
mo.md(
|
|
107
|
+
f"**F({n_input.value}) = {fibonacci(n_input.value)}**"
|
|
108
|
+
)
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@app.cell
|
|
113
|
+
def _(mo):
|
|
114
|
+
mo.md("""
|
|
115
|
+
## Large-value tests
|
|
116
|
+
|
|
117
|
+
These tests serve two purposes:
|
|
118
|
+
|
|
119
|
+
1. **Correctness after optimization**
|
|
120
|
+
We verify that the function still returns known Fibonacci values for larger inputs.
|
|
121
|
+
|
|
122
|
+
2. **Performance motivation**
|
|
123
|
+
We use a much larger input to reveal the limitations of the naive recursive implementation
|
|
124
|
+
before refactoring it for better performance.
|
|
125
|
+
""")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@app.function
|
|
130
|
+
def test_fibonacci_large_values():
|
|
131
|
+
assert fibonacci(10) == 55
|
|
132
|
+
assert fibonacci(20) == 6765
|
|
133
|
+
assert fibonacci(30) == 832040
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@app.function
|
|
137
|
+
def test_fibonacci_large_recurrence():
|
|
138
|
+
n = 1000
|
|
139
|
+
assert fibonacci(n) == fibonacci(n - 1) + fibonacci(n - 2)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@app.function
|
|
143
|
+
# Large-scale performance and correctness test:
|
|
144
|
+
# Verifies that the optimized implementation can handle n = 10^7.
|
|
145
|
+
# Two consecutive Fibonacci numbers are always coprime.
|
|
146
|
+
def test_fibonacci_very_large():
|
|
147
|
+
import math
|
|
148
|
+
|
|
149
|
+
n = 10**7
|
|
150
|
+
fn = fibonacci(n)
|
|
151
|
+
fn_next = fibonacci(n + 1)
|
|
152
|
+
|
|
153
|
+
assert math.gcd(fn, fn_next) == 1
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
app.run()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
[project]
|
|
3
|
+
name = "fibonacci-tdd-kataa"
|
|
4
|
+
version = "0.1.0"
|
|
5
|
+
description = "A TDD kata: Fibonacci, from notebook exploration to a tested, published package"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "malak khalil", email = "malak.khalil@lau.edu" }
|
|
10
|
+
]
|
|
11
|
+
dependencies = []
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["hatchling"]
|
|
15
|
+
build-backend = "hatchling.build"
|
|
16
|
+
|
|
17
|
+
[tool.hatch.build.targets.wheel]
|
|
18
|
+
packages = ["src/fibonacci_tdd_kata"]
|
|
19
|
+
|
|
20
|
+
[dependency-groups]
|
|
21
|
+
dev = [
|
|
22
|
+
"pytest>=9.1.1",
|
|
23
|
+
"pytest-cov>=5.0",
|
|
24
|
+
"ruff>=0.16.8",
|
|
25
|
+
"marimo>=0.24.2",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[tool.pytest.ini_options]
|
|
29
|
+
pythonpath = ["src"]
|
|
30
|
+
testpaths = ["tests"]
|
|
31
|
+
|
|
32
|
+
[tool.ruff.lint.per-file-ignores]
|
|
33
|
+
"notebooks/*.py" = ["B018", "PLR1711", "F401"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
"""Core Fibonacci implementation."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def fibonacci(n: int) -> int:
|
|
6
|
+
"""Return F(n) using the fast-doubling algorithm."""
|
|
7
|
+
if not isinstance(n, int) or n < 0:
|
|
8
|
+
raise ValueError("n must be a non-negative integer")
|
|
9
|
+
|
|
10
|
+
def fib_pair(k: int) -> tuple[int, int]:
|
|
11
|
+
if k == 0:
|
|
12
|
+
return 0, 1
|
|
13
|
+
|
|
14
|
+
a, b = fib_pair(k // 2)
|
|
15
|
+
c = a * (2 * b - a)
|
|
16
|
+
d = a * a + b * b
|
|
17
|
+
|
|
18
|
+
if k % 2 == 0:
|
|
19
|
+
return c, d
|
|
20
|
+
return d, c + d
|
|
21
|
+
|
|
22
|
+
return fib_pair(n)[0]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
|
|
2
|
+
"""Tests for the Fibonacci implementation."""
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from fibonacci_tdd_kata.core import fibonacci
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_fibonacci_zero():
|
|
12
|
+
assert fibonacci(0) == 0
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_fibonacci_one():
|
|
16
|
+
assert fibonacci(1) == 1
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_fibonacci_two():
|
|
20
|
+
assert fibonacci(2) == 1
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_fibonacci_three():
|
|
24
|
+
assert fibonacci(3) == 2
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_fibonacci_four():
|
|
28
|
+
assert fibonacci(4) == 3
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_fibonacci_five():
|
|
32
|
+
assert fibonacci(5) == 5
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.mark.parametrize(
|
|
36
|
+
("n", "expected"),
|
|
37
|
+
[
|
|
38
|
+
(10, 55),
|
|
39
|
+
(20, 6765),
|
|
40
|
+
(30, 832040),
|
|
41
|
+
],
|
|
42
|
+
)
|
|
43
|
+
def test_fibonacci_large_values(n, expected):
|
|
44
|
+
assert fibonacci(n) == expected
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_fibonacci_large_recurrence():
|
|
48
|
+
n = 1000
|
|
49
|
+
assert fibonacci(n) == fibonacci(n - 1) + fibonacci(n - 2)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_fibonacci_very_large():
|
|
53
|
+
n = 10**7
|
|
54
|
+
fn = fibonacci(n)
|
|
55
|
+
fn_next = fibonacci(n + 1)
|
|
56
|
+
|
|
57
|
+
assert math.gcd(fn, fn_next) == 1
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_fibonacci_negative_input():
|
|
61
|
+
with pytest.raises(ValueError):
|
|
62
|
+
fibonacci(-1)
|