From 3c1e813ac06ca3d6705528f8c036c2d5c5471cdb Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Tue, 4 Jun 2024 16:54:09 +0000 Subject: [PATCH 01/29] RED: one test changed --- rust/fizzbuzzo3/src/lib.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index 707f26b..5051baf 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -1,6 +1,6 @@ use std::ops::Neg; -use fizzbuzz::{FizzBuzz, MultiFizzBuzz}; +use fizzbuzz::{FizzBuzz, FizzBuzzAnswer, MultiFizzBuzz}; use pyo3::{exceptions::PyValueError, prelude::*, types::PySlice}; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; @@ -141,8 +141,15 @@ mod tests { #[pyo3import(py_fizzbuzzo3: from fizzbuzzo3 import fizzbuzz)] fn test_fizzbuzz_vec() { let input = vec![1, 2, 3, 4, 5]; - let result: String = fizzbuzz!(input); - assert_eq!(result, "1, 2, fizz, 4, buzz"); + let expected = vec![ + "1".to_string(), + "2".to_string(), + "fizz".to_string(), + "4".to_string(), + "buzz".to_string(), + ]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] From 5ddac71a960957ad17e3ba73aec75494bdc1c76b Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Tue, 4 Jun 2024 16:54:18 +0000 Subject: [PATCH 02/29] change return type of fizzbuzz --- rust/fizzbuzzo3/src/lib.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index 5051baf..88698a5 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -25,6 +25,16 @@ impl IntoPy> for MySlice { } } +struct FizzBuzzReturn { + answer: FizzBuzzAnswer +} + +impl From for FizzBuzzReturn { + fn from(value: FizzBuzzAnswer) -> Self { + FizzBuzzReturn { answer: value } + } +} + /// Returns the correct fizzbuzz answer for any number or list/range of numbers. /// /// This is an optimised algorithm compiled in rust. Large lists will utilise multiple CPU cores for processing. @@ -66,7 +76,7 @@ impl IntoPy> for MySlice { /// Negative steps require start > stop. #[pyfunction] #[pyo3(name = "fizzbuzz", text_signature = "(n)")] -fn py_fizzbuzz(num: FizzBuzzable) -> PyResult { +fn py_fizzbuzz(num: FizzBuzzable) -> PyResult { match num { FizzBuzzable::Int(n) => Ok(n.fizzbuzz().into()), FizzBuzzable::Float(n) => Ok(n.fizzbuzz().into()), From 4e396de6772610d66fc00b57cda81cb135f9b47d Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Tue, 4 Jun 2024 16:58:24 +0000 Subject: [PATCH 03/29] ONE GREEN: impl IntoPy for FizzBuzzReturn --- rust/fizzbuzzo3/src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index 88698a5..eae5dd7 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -1,7 +1,7 @@ use std::ops::Neg; use fizzbuzz::{FizzBuzz, FizzBuzzAnswer, MultiFizzBuzz}; -use pyo3::{exceptions::PyValueError, prelude::*, types::PySlice}; +use pyo3::{exceptions::PyValueError, prelude::*, types::{PyList, PySlice, PyString}}; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; #[derive(FromPyObject)] @@ -35,6 +35,15 @@ impl From for FizzBuzzReturn { } } +impl IntoPy> for FizzBuzzReturn { + fn into_py(self, py: Python<'_>) -> Py { + match self.answer { + FizzBuzzAnswer::One(string) => PyString::new_bound(py, &string).into_py(py), + FizzBuzzAnswer::Many(list) => PyList::new_bound(py, list).into_py(py) + } + } +} + /// Returns the correct fizzbuzz answer for any number or list/range of numbers. /// /// This is an optimised algorithm compiled in rust. Large lists will utilise multiple CPU cores for processing. From b1787fd5756d59584976a85ec3103b456d092aa6 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Tue, 4 Jun 2024 17:06:06 +0000 Subject: [PATCH 04/29] almost GREEN: all pyo3 tests updated and correct EXCEPT empty list return for invalid slice --- rust/fizzbuzzo3/src/lib.rs | 69 ++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index eae5dd7..eb89a89 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -186,8 +186,15 @@ mod tests { stop: 6, step: Some(1), }; - let result: String = fizzbuzz!(input); - assert_eq!(result, "1, 2, fizz, 4, buzz"); + let expected = vec![ + "1".to_string(), + "2".to_string(), + "fizz".to_string(), + "4".to_string(), + "buzz".to_string(), + ]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] @@ -198,8 +205,15 @@ mod tests { stop: 6, step: None, }; - let result: String = fizzbuzz!(input); - assert_eq!(result, "1, 2, fizz, 4, buzz"); + let expected = vec![ + "1".to_string(), + "2".to_string(), + "fizz".to_string(), + "4".to_string(), + "buzz".to_string(), + ]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] @@ -210,8 +224,13 @@ mod tests { stop: 6, step: Some(2), }; - let result: String = fizzbuzz!(input); - assert_eq!(result, "1, fizz, buzz"); + let expected = vec![ + "1".to_string(), + "fizz".to_string(), + "buzz".to_string(), + ]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] @@ -222,8 +241,8 @@ mod tests { stop: 0, step: Some(1), }; - let result: String = fizzbuzz!(input); - assert_eq!(result, ""); + let result: Vec<> = fizzbuzz!(input); + assert_eq!(result, vec![]); } #[pyo3test] @@ -234,8 +253,15 @@ mod tests { stop: 0, step: Some(-2), }; - let result: String = fizzbuzz!(input); - assert_eq!(result, "buzz, fizz, 1"); + let expected = vec![ + "buzz".to_string(), + + "fizz".to_string(), + "1".to_string(), + + ]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] @@ -246,8 +272,15 @@ mod tests { stop: 1, step: Some(-1), }; - let result: String = fizzbuzz!(input); - assert_eq!(result, "buzz, 4, fizz, 2"); + let expected = vec![ + "buzz".to_string(), + "4".to_string(), + "fizz".to_string(), + "2".to_string(), + + ]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] @@ -258,8 +291,16 @@ mod tests { stop: 0, step: Some(-2), }; - let result: String = fizzbuzz!(input); - assert_eq!(result, "fizz, 4, 2"); + + let expected = vec![ + + "fizz".to_string(), + "4".to_string(), + "2".to_string(), + + ]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] #[allow(unused_macros)] From ff91bfc06793b67a69c105f1a812c4d74309bb79 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Tue, 4 Jun 2024 18:03:41 +0000 Subject: [PATCH 05/29] all pyo3 tests passing --- rust/fizzbuzzo3/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index eb89a89..7f61f71 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -241,8 +241,9 @@ mod tests { stop: 0, step: Some(1), }; - let result: Vec<> = fizzbuzz!(input); - assert_eq!(result, vec![]); + let result: Vec = fizzbuzz!(input); + let expected: Vec = vec![]; + assert_eq!(result, expected); } #[pyo3test] From b39797d82ccb465a21103de05167edd63ce0d9d0 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Tue, 4 Jun 2024 18:09:16 +0000 Subject: [PATCH 06/29] pytests passing --- tests/test_fizzbuzzo3.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/test_fizzbuzzo3.py b/tests/test_fizzbuzzo3.py index 167ae26..7d0d686 100644 --- a/tests/test_fizzbuzzo3.py +++ b/tests/test_fizzbuzzo3.py @@ -11,17 +11,23 @@ def test_lazy(): assert fizzbuzz(6) == "fizz" assert fizzbuzz(15) == "fizzbuzz" + def test_float(): assert fizzbuzz(1.0) == "1" assert fizzbuzz(3.0) == "fizz" + def test_list(): - assert fizzbuzz([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15]) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, fizzbuzz" + assert fizzbuzz( + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15], + ) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, fizzbuzz".split(", ") + def test_string(): with pytest.raises(TypeError): fizzbuzz("1") + def test_1_to_100(): results = [fizzbuzz(i) for i in range(1, 101)] every_3rd_has_fizz = all("fizz" in r for r in results[2::3]) @@ -39,16 +45,24 @@ def test_1_to_100(): all_numbers_correct = all(r == str(i + 1) for i, r in enumerate(results) if r not in ("fizz", "buzz", "fizzbuzz")) assert all_numbers_correct + def test_slice(): - assert fizzbuzz(slice(1,16,1)) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz" + assert fizzbuzz( + slice(1, 16, 1), + ) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz".split(", ") + # This case is REALLY IMPORTANT as it cannot be tested via rust unit tests... def test_slice_no_step(): - assert fizzbuzz(slice(1,16)) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz" + assert fizzbuzz( + slice(1, 16), + ) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz".split(", ") + def test_slice_negative_step(): - assert fizzbuzz(slice(15,0,-3)) == "fizzbuzz, fizz, fizz, fizz, fizz" + assert fizzbuzz(slice(15, 0, -3)) == "fizzbuzz, fizz, fizz, fizz, fizz".split(", ") + def test_slice_zero_step(): with pytest.raises(ValueError, match="step cannot be zero"): - fizzbuzz(slice(1,16,0)) \ No newline at end of file + fizzbuzz(slice(1, 16, 0)) From 01933b81e56875504ee12d3f8cf4c456322a3721 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Tue, 4 Jun 2024 18:14:23 +0000 Subject: [PATCH 07/29] GREEN: update docs & examples --- python/fizzbuzz/fizzbuzzo3.pyi | 16 +++++++++------- rust/fizzbuzzo3/src/lib.rs | 16 +++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/python/fizzbuzz/fizzbuzzo3.pyi b/python/fizzbuzz/fizzbuzzo3.pyi index 25627d9..82671f5 100644 --- a/python/fizzbuzz/fizzbuzzo3.pyi +++ b/python/fizzbuzz/fizzbuzzo3.pyi @@ -21,8 +21,8 @@ def fizzbuzz(n: int | list[int] | slice) -> str: n: the number(s) to fizzbuzz Returns: - A `str` with the correct fizzbuzz answer(s). - In the case of a list or range of inputs the answers will be separated by `, ` + In the case of a sinlge number: a `str` with the correct fizzbuzz answer. + In the case of a list or range of inputs: a `list` of `str` with the correct fizzbuzz answers. Examples: a single `int`: @@ -37,18 +37,20 @@ def fizzbuzz(n: int | list[int] | slice) -> str: ``` from fizzbuzz.fizzbuzzo3 import fizzbuzz >>> fizzbuzz([1,3]) - '1, fizz' + ['1', 'fizz'] ``` a `slice` representing a range: ``` from fizzbuzz.fizzbuzzo3 import fizzbuzz >>> fizzbuzz(slice(1,4,2)) - '1, fizz' + ['1', 'fizz'] >>> fizzbuzz(slice(1,4)) - '1, 2, fizz' + ['1', '2', 'fizz'] >>> fizzbuzz(slice(4,1,-1)) - '4, fizz, 2' + ['4', 'fizz', '2'] + >>> fizzbuzz(slice(1,5,-1)) + [] ``` Note: Slices are inclusive on the left, exclusive on the right and can contain an optional step. - Negative steps require start > stop. + Negative steps require start > stop, positive stop > start or else will return an empty list. """ diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index 7f61f71..f479256 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -53,8 +53,8 @@ impl IntoPy> for FizzBuzzReturn { /// n: the number(s) to fizzbuzz /// /// Returns: -/// A `str` with the correct fizzbuzz answer(s). -/// In the case of a list or range of inputs the answers will be separated by `, ` +/// In the case of a sinlge number: a `str` with the correct fizzbuzz answer. +/// In the case of a list or range of inputs: a `list` of `str` with the correct fizzbuzz answers. /// /// Examples: /// a single `int`: @@ -69,20 +69,22 @@ impl IntoPy> for FizzBuzzReturn { /// ``` /// from fizzbuzz.fizzbuzzo3 import fizzbuzz /// >>> fizzbuzz([1,3]) -/// '1, fizz' +/// ['1', 'fizz'] /// ``` /// a `slice` representing a range: /// ``` /// from fizzbuzz.fizzbuzzo3 import fizzbuzz /// >>> fizzbuzz(slice(1,4,2)) -/// '1, fizz' +/// ['1', 'fizz'] /// >>> fizzbuzz(slice(1,4)) -/// '1, 2, fizz' +/// ['1', '2', 'fizz'] /// >>> fizzbuzz(slice(4,1,-1)) -/// '4, fizz, 2' +/// ['4', 'fizz', '2'] +/// >>> fizzbuzz(slice(1,5,-1)) +/// [] /// ``` /// Note: Slices are inclusive on the left, exclusive on the right and can contain an optional step. -/// Negative steps require start > stop. +/// Negative steps require start > stop, positive stop > start or else will return an empty list. #[pyfunction] #[pyo3(name = "fizzbuzz", text_signature = "(n)")] fn py_fizzbuzz(num: FizzBuzzable) -> PyResult { From fad959b15149236ae7dc528bd6392ba8fb8f3f8c Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Tue, 4 Jun 2024 18:15:45 +0000 Subject: [PATCH 08/29] fmt --- rust/fizzbuzzo3/src/lib.rs | 41 +++++++++++++------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index f479256..811ab94 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -1,7 +1,11 @@ use std::ops::Neg; use fizzbuzz::{FizzBuzz, FizzBuzzAnswer, MultiFizzBuzz}; -use pyo3::{exceptions::PyValueError, prelude::*, types::{PyList, PySlice, PyString}}; +use pyo3::{ + exceptions::PyValueError, + prelude::*, + types::{PyList, PySlice, PyString}, +}; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; #[derive(FromPyObject)] @@ -26,7 +30,7 @@ impl IntoPy> for MySlice { } struct FizzBuzzReturn { - answer: FizzBuzzAnswer + answer: FizzBuzzAnswer, } impl From for FizzBuzzReturn { @@ -39,7 +43,7 @@ impl IntoPy> for FizzBuzzReturn { fn into_py(self, py: Python<'_>) -> Py { match self.answer { FizzBuzzAnswer::One(string) => PyString::new_bound(py, &string).into_py(py), - FizzBuzzAnswer::Many(list) => PyList::new_bound(py, list).into_py(py) + FizzBuzzAnswer::Many(list) => PyList::new_bound(py, list).into_py(py), } } } @@ -166,7 +170,7 @@ mod tests { "1".to_string(), "2".to_string(), "fizz".to_string(), - "4".to_string(), + "4".to_string(), "buzz".to_string(), ]; let result: Vec = fizzbuzz!(input); @@ -192,7 +196,7 @@ mod tests { "1".to_string(), "2".to_string(), "fizz".to_string(), - "4".to_string(), + "4".to_string(), "buzz".to_string(), ]; let result: Vec = fizzbuzz!(input); @@ -211,7 +215,7 @@ mod tests { "1".to_string(), "2".to_string(), "fizz".to_string(), - "4".to_string(), + "4".to_string(), "buzz".to_string(), ]; let result: Vec = fizzbuzz!(input); @@ -226,11 +230,7 @@ mod tests { stop: 6, step: Some(2), }; - let expected = vec![ - "1".to_string(), - "fizz".to_string(), - "buzz".to_string(), - ]; + let expected = vec!["1".to_string(), "fizz".to_string(), "buzz".to_string()]; let result: Vec = fizzbuzz!(input); assert_eq!(result, expected); } @@ -256,13 +256,7 @@ mod tests { stop: 0, step: Some(-2), }; - let expected = vec![ - "buzz".to_string(), - - "fizz".to_string(), - "1".to_string(), - - ]; + let expected = vec!["buzz".to_string(), "fizz".to_string(), "1".to_string()]; let result: Vec = fizzbuzz!(input); assert_eq!(result, expected); } @@ -280,7 +274,6 @@ mod tests { "4".to_string(), "fizz".to_string(), "2".to_string(), - ]; let result: Vec = fizzbuzz!(input); assert_eq!(result, expected); @@ -294,14 +287,8 @@ mod tests { stop: 0, step: Some(-2), }; - - let expected = vec![ - - "fizz".to_string(), - "4".to_string(), - "2".to_string(), - - ]; + + let expected = vec!["fizz".to_string(), "4".to_string(), "2".to_string()]; let result: Vec = fizzbuzz!(input); assert_eq!(result, expected); } From 69702248f0cc159062fe69db84da4f8da68ba0d1 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Tue, 4 Jun 2024 18:31:39 +0000 Subject: [PATCH 09/29] add overload type hints --- python/fizzbuzz/fizzbuzzo3.pyi | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/python/fizzbuzz/fizzbuzzo3.pyi b/python/fizzbuzz/fizzbuzzo3.pyi index 82671f5..89ff23d 100644 --- a/python/fizzbuzz/fizzbuzzo3.pyi +++ b/python/fizzbuzz/fizzbuzzo3.pyi @@ -10,7 +10,17 @@ Usage: ``` """ -def fizzbuzz(n: int | list[int] | slice) -> str: +from typing import overload + +@overload +def fizzbuzz(n: int) -> str: + ... + +@overload +def fizzbuzz(n: list[int] | slice) -> list[str]: + ... + +def fizzbuzz(n): """ Returns the correct fizzbuzz answer for any number or list/range of numbers. From 4de884c739d7d0806aa434ff1e9a9c3f9a36f8e1 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Wed, 5 Jun 2024 05:25:26 +0000 Subject: [PATCH 10/29] performance really is 2x worse with variable return types --- tests/perf_results.md | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/perf_results.md b/tests/perf_results.md index 1065fa8..b0fe6ad 100644 --- a/tests/perf_results.md +++ b/tests/perf_results.md @@ -92,3 +92,52 @@ Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuz Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] [0.5420241989995702] ``` + +## Comparing return types (-> str | list[str] vs. -> str) + +```text +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ echo "No LTO, Union" +No LTO, Union +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ /workspaces/FizzBuzz/.venv/bin/python /workspaces/FizzBuzz/tests/perftest.py +Rust: [1 calls of 10 runs fizzbuzzing up to 1000000] +[2.7247621990100015] +Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 1000000] +[1.4409539260086603] +Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 1000000] +[1.748141026997473] +Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] +[1.140464444004465] +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ echo "thin LTO, Union" +thin LTO, Union +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ /workspaces/FizzBuzz/.venv/bin/python /workspaces/FizzBuzz/tests/perftest.py +Rust: [1 calls of 10 runs fizzbuzzing up to 1000000] +[2.573878561001038] +Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 1000000] +[1.5258527039986802] +Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 1000000] +[1.7503311760083307] +Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] +[1.1543225019995589] +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ echo "fat LTO, Union" +fat LTO, Union +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ /workspaces/FizzBuzz/.venv/bin/python /workspaces/FizzBuzz/tests/perftest.py +Rust: [1 calls of 10 runs fizzbuzzing up to 1000000] +[2.659256437997101] +Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 1000000] +[1.4467686470015906] +Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 1000000] +[1.6921475639974233] +Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] +[1.1023815070075216] +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ echo "no LTO, String" +no LTO, String +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ /workspaces/FizzBuzz/.venv/bin/python /workspaces/FizzBuzz/tests/perftest.py +Rust: [1 calls of 10 runs fizzbuzzing up to 1000000] +[2.6100861899903975] +Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 1000000] +[0.8597368839982664] +Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 1000000] +[1.1903306849999353] +Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] +[0.6246530729986262] +``` From b1c0c7315ab91711abaa685ef5130f1c20794baa Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Wed, 5 Jun 2024 05:46:46 +0000 Subject: [PATCH 11/29] only perftest rust --- tests/perftest.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/perftest.py b/tests/perftest.py index c04ee1c..0564ae0 100644 --- a/tests/perftest.py +++ b/tests/perftest.py @@ -18,10 +18,10 @@ def main(): fbo3times = fbo3timer.repeat(repeat=REPEAT, number=NUMBER) print(fbo3times) - print(f"Python: [{REPEAT} calls of {NUMBER} runs fizzbuzzing up to {FIZZBUZZES}]") - fbpytimer = timeit.Timer(stmt="[fbpy(i) for i in range(1,FIZZBUZZES)]", globals=globals()) - fbpytimes = fbpytimer.repeat(repeat=REPEAT, number=NUMBER) - print(fbpytimes) + # print(f"Python: [{REPEAT} calls of {NUMBER} runs fizzbuzzing up to {FIZZBUZZES}]") + # fbpytimer = timeit.Timer(stmt="[fbpy(i) for i in range(1,FIZZBUZZES)]", globals=globals()) + # fbpytimes = fbpytimer.repeat(repeat=REPEAT, number=NUMBER) + # print(fbpytimes) print(f"Rust vector: [{REPEAT} calls of {NUMBER} runs fizzbuzzing a list of numbers up to {FIZZBUZZES}]") fbo3vectimer = timeit.Timer(stmt="[fbo3(LISTOFNUMBERS)]", globals=globals()) From e95e0657a8fae012add2aff78edaed3ef36933ad Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Wed, 5 Jun 2024 05:47:08 +0000 Subject: [PATCH 12/29] simplify FizzBuzzReturn::IntoPy --- rust/fizzbuzzo3/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index 811ab94..43eeb78 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -42,8 +42,8 @@ impl From for FizzBuzzReturn { impl IntoPy> for FizzBuzzReturn { fn into_py(self, py: Python<'_>) -> Py { match self.answer { - FizzBuzzAnswer::One(string) => PyString::new_bound(py, &string).into_py(py), - FizzBuzzAnswer::Many(list) => PyList::new_bound(py, list).into_py(py), + FizzBuzzAnswer::One(string) => string.into_py(py), + FizzBuzzAnswer::Many(list) => list.into_py(py), } } } From ac7387d4ddecb8bc3600d796e881f9cccedde97a Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Wed, 5 Jun 2024 05:49:50 +0000 Subject: [PATCH 13/29] tidy imports --- rust/fizzbuzzo3/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index 43eeb78..e7da7ea 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -4,7 +4,7 @@ use fizzbuzz::{FizzBuzz, FizzBuzzAnswer, MultiFizzBuzz}; use pyo3::{ exceptions::PyValueError, prelude::*, - types::{PyList, PySlice, PyString}, + types::PySlice, }; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; From 1b981de03361b8beff32fa21eef598a21866e1b3 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Wed, 5 Jun 2024 05:50:00 +0000 Subject: [PATCH 14/29] simplify FizzBuzzReturn --- rust/fizzbuzzo3/src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index e7da7ea..17a6897 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -29,19 +29,17 @@ impl IntoPy> for MySlice { } } -struct FizzBuzzReturn { - answer: FizzBuzzAnswer, -} +struct FizzBuzzReturn(FizzBuzzAnswer); impl From for FizzBuzzReturn { fn from(value: FizzBuzzAnswer) -> Self { - FizzBuzzReturn { answer: value } + FizzBuzzReturn(value) } } impl IntoPy> for FizzBuzzReturn { fn into_py(self, py: Python<'_>) -> Py { - match self.answer { + match self.0 { FizzBuzzAnswer::One(string) => string.into_py(py), FizzBuzzAnswer::Many(list) => list.into_py(py), } From 7c031d426bca5441adc5fdf5a899908f903b94f5 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Wed, 5 Jun 2024 18:11:18 +0000 Subject: [PATCH 15/29] reset to: Fix: MacOS14 CIBW bug (won't move wheel after test) (#50) This commit resets the branch to: a0c607357c8c614a88a30180f63b97c349e858b8 --- python/fizzbuzz/fizzbuzzo3.pyi | 28 +++------ rust/fizzbuzzo3/src/lib.rs | 107 ++++++++------------------------- tests/perf_results.md | 49 --------------- tests/perftest.py | 8 +-- tests/test_fizzbuzzo3.py | 24 ++------ 5 files changed, 43 insertions(+), 173 deletions(-) diff --git a/python/fizzbuzz/fizzbuzzo3.pyi b/python/fizzbuzz/fizzbuzzo3.pyi index 89ff23d..25627d9 100644 --- a/python/fizzbuzz/fizzbuzzo3.pyi +++ b/python/fizzbuzz/fizzbuzzo3.pyi @@ -10,17 +10,7 @@ Usage: ``` """ -from typing import overload - -@overload -def fizzbuzz(n: int) -> str: - ... - -@overload -def fizzbuzz(n: list[int] | slice) -> list[str]: - ... - -def fizzbuzz(n): +def fizzbuzz(n: int | list[int] | slice) -> str: """ Returns the correct fizzbuzz answer for any number or list/range of numbers. @@ -31,8 +21,8 @@ def fizzbuzz(n): n: the number(s) to fizzbuzz Returns: - In the case of a sinlge number: a `str` with the correct fizzbuzz answer. - In the case of a list or range of inputs: a `list` of `str` with the correct fizzbuzz answers. + A `str` with the correct fizzbuzz answer(s). + In the case of a list or range of inputs the answers will be separated by `, ` Examples: a single `int`: @@ -47,20 +37,18 @@ def fizzbuzz(n): ``` from fizzbuzz.fizzbuzzo3 import fizzbuzz >>> fizzbuzz([1,3]) - ['1', 'fizz'] + '1, fizz' ``` a `slice` representing a range: ``` from fizzbuzz.fizzbuzzo3 import fizzbuzz >>> fizzbuzz(slice(1,4,2)) - ['1', 'fizz'] + '1, fizz' >>> fizzbuzz(slice(1,4)) - ['1', '2', 'fizz'] + '1, 2, fizz' >>> fizzbuzz(slice(4,1,-1)) - ['4', 'fizz', '2'] - >>> fizzbuzz(slice(1,5,-1)) - [] + '4, fizz, 2' ``` Note: Slices are inclusive on the left, exclusive on the right and can contain an optional step. - Negative steps require start > stop, positive stop > start or else will return an empty list. + Negative steps require start > stop. """ diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index 17a6897..707f26b 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -1,11 +1,7 @@ use std::ops::Neg; -use fizzbuzz::{FizzBuzz, FizzBuzzAnswer, MultiFizzBuzz}; -use pyo3::{ - exceptions::PyValueError, - prelude::*, - types::PySlice, -}; +use fizzbuzz::{FizzBuzz, MultiFizzBuzz}; +use pyo3::{exceptions::PyValueError, prelude::*, types::PySlice}; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; #[derive(FromPyObject)] @@ -29,23 +25,6 @@ impl IntoPy> for MySlice { } } -struct FizzBuzzReturn(FizzBuzzAnswer); - -impl From for FizzBuzzReturn { - fn from(value: FizzBuzzAnswer) -> Self { - FizzBuzzReturn(value) - } -} - -impl IntoPy> for FizzBuzzReturn { - fn into_py(self, py: Python<'_>) -> Py { - match self.0 { - FizzBuzzAnswer::One(string) => string.into_py(py), - FizzBuzzAnswer::Many(list) => list.into_py(py), - } - } -} - /// Returns the correct fizzbuzz answer for any number or list/range of numbers. /// /// This is an optimised algorithm compiled in rust. Large lists will utilise multiple CPU cores for processing. @@ -55,8 +34,8 @@ impl IntoPy> for FizzBuzzReturn { /// n: the number(s) to fizzbuzz /// /// Returns: -/// In the case of a sinlge number: a `str` with the correct fizzbuzz answer. -/// In the case of a list or range of inputs: a `list` of `str` with the correct fizzbuzz answers. +/// A `str` with the correct fizzbuzz answer(s). +/// In the case of a list or range of inputs the answers will be separated by `, ` /// /// Examples: /// a single `int`: @@ -71,25 +50,23 @@ impl IntoPy> for FizzBuzzReturn { /// ``` /// from fizzbuzz.fizzbuzzo3 import fizzbuzz /// >>> fizzbuzz([1,3]) -/// ['1', 'fizz'] +/// '1, fizz' /// ``` /// a `slice` representing a range: /// ``` /// from fizzbuzz.fizzbuzzo3 import fizzbuzz /// >>> fizzbuzz(slice(1,4,2)) -/// ['1', 'fizz'] +/// '1, fizz' /// >>> fizzbuzz(slice(1,4)) -/// ['1', '2', 'fizz'] +/// '1, 2, fizz' /// >>> fizzbuzz(slice(4,1,-1)) -/// ['4', 'fizz', '2'] -/// >>> fizzbuzz(slice(1,5,-1)) -/// [] +/// '4, fizz, 2' /// ``` /// Note: Slices are inclusive on the left, exclusive on the right and can contain an optional step. -/// Negative steps require start > stop, positive stop > start or else will return an empty list. +/// Negative steps require start > stop. #[pyfunction] #[pyo3(name = "fizzbuzz", text_signature = "(n)")] -fn py_fizzbuzz(num: FizzBuzzable) -> PyResult { +fn py_fizzbuzz(num: FizzBuzzable) -> PyResult { match num { FizzBuzzable::Int(n) => Ok(n.fizzbuzz().into()), FizzBuzzable::Float(n) => Ok(n.fizzbuzz().into()), @@ -164,15 +141,8 @@ mod tests { #[pyo3import(py_fizzbuzzo3: from fizzbuzzo3 import fizzbuzz)] fn test_fizzbuzz_vec() { let input = vec![1, 2, 3, 4, 5]; - let expected = vec![ - "1".to_string(), - "2".to_string(), - "fizz".to_string(), - "4".to_string(), - "buzz".to_string(), - ]; - let result: Vec = fizzbuzz!(input); - assert_eq!(result, expected); + let result: String = fizzbuzz!(input); + assert_eq!(result, "1, 2, fizz, 4, buzz"); } #[pyo3test] @@ -190,15 +160,8 @@ mod tests { stop: 6, step: Some(1), }; - let expected = vec![ - "1".to_string(), - "2".to_string(), - "fizz".to_string(), - "4".to_string(), - "buzz".to_string(), - ]; - let result: Vec = fizzbuzz!(input); - assert_eq!(result, expected); + let result: String = fizzbuzz!(input); + assert_eq!(result, "1, 2, fizz, 4, buzz"); } #[pyo3test] @@ -209,15 +172,8 @@ mod tests { stop: 6, step: None, }; - let expected = vec![ - "1".to_string(), - "2".to_string(), - "fizz".to_string(), - "4".to_string(), - "buzz".to_string(), - ]; - let result: Vec = fizzbuzz!(input); - assert_eq!(result, expected); + let result: String = fizzbuzz!(input); + assert_eq!(result, "1, 2, fizz, 4, buzz"); } #[pyo3test] @@ -228,9 +184,8 @@ mod tests { stop: 6, step: Some(2), }; - let expected = vec!["1".to_string(), "fizz".to_string(), "buzz".to_string()]; - let result: Vec = fizzbuzz!(input); - assert_eq!(result, expected); + let result: String = fizzbuzz!(input); + assert_eq!(result, "1, fizz, buzz"); } #[pyo3test] @@ -241,9 +196,8 @@ mod tests { stop: 0, step: Some(1), }; - let result: Vec = fizzbuzz!(input); - let expected: Vec = vec![]; - assert_eq!(result, expected); + let result: String = fizzbuzz!(input); + assert_eq!(result, ""); } #[pyo3test] @@ -254,9 +208,8 @@ mod tests { stop: 0, step: Some(-2), }; - let expected = vec!["buzz".to_string(), "fizz".to_string(), "1".to_string()]; - let result: Vec = fizzbuzz!(input); - assert_eq!(result, expected); + let result: String = fizzbuzz!(input); + assert_eq!(result, "buzz, fizz, 1"); } #[pyo3test] @@ -267,14 +220,8 @@ mod tests { stop: 1, step: Some(-1), }; - let expected = vec![ - "buzz".to_string(), - "4".to_string(), - "fizz".to_string(), - "2".to_string(), - ]; - let result: Vec = fizzbuzz!(input); - assert_eq!(result, expected); + let result: String = fizzbuzz!(input); + assert_eq!(result, "buzz, 4, fizz, 2"); } #[pyo3test] @@ -285,10 +232,8 @@ mod tests { stop: 0, step: Some(-2), }; - - let expected = vec!["fizz".to_string(), "4".to_string(), "2".to_string()]; - let result: Vec = fizzbuzz!(input); - assert_eq!(result, expected); + let result: String = fizzbuzz!(input); + assert_eq!(result, "fizz, 4, 2"); } #[pyo3test] #[allow(unused_macros)] diff --git a/tests/perf_results.md b/tests/perf_results.md index b0fe6ad..1065fa8 100644 --- a/tests/perf_results.md +++ b/tests/perf_results.md @@ -92,52 +92,3 @@ Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuz Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] [0.5420241989995702] ``` - -## Comparing return types (-> str | list[str] vs. -> str) - -```text -(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ echo "No LTO, Union" -No LTO, Union -(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ /workspaces/FizzBuzz/.venv/bin/python /workspaces/FizzBuzz/tests/perftest.py -Rust: [1 calls of 10 runs fizzbuzzing up to 1000000] -[2.7247621990100015] -Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 1000000] -[1.4409539260086603] -Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 1000000] -[1.748141026997473] -Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] -[1.140464444004465] -(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ echo "thin LTO, Union" -thin LTO, Union -(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ /workspaces/FizzBuzz/.venv/bin/python /workspaces/FizzBuzz/tests/perftest.py -Rust: [1 calls of 10 runs fizzbuzzing up to 1000000] -[2.573878561001038] -Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 1000000] -[1.5258527039986802] -Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 1000000] -[1.7503311760083307] -Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] -[1.1543225019995589] -(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ echo "fat LTO, Union" -fat LTO, Union -(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ /workspaces/FizzBuzz/.venv/bin/python /workspaces/FizzBuzz/tests/perftest.py -Rust: [1 calls of 10 runs fizzbuzzing up to 1000000] -[2.659256437997101] -Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 1000000] -[1.4467686470015906] -Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 1000000] -[1.6921475639974233] -Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] -[1.1023815070075216] -(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ echo "no LTO, String" -no LTO, String -(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ /workspaces/FizzBuzz/.venv/bin/python /workspaces/FizzBuzz/tests/perftest.py -Rust: [1 calls of 10 runs fizzbuzzing up to 1000000] -[2.6100861899903975] -Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 1000000] -[0.8597368839982664] -Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 1000000] -[1.1903306849999353] -Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] -[0.6246530729986262] -``` diff --git a/tests/perftest.py b/tests/perftest.py index 0564ae0..c04ee1c 100644 --- a/tests/perftest.py +++ b/tests/perftest.py @@ -18,10 +18,10 @@ def main(): fbo3times = fbo3timer.repeat(repeat=REPEAT, number=NUMBER) print(fbo3times) - # print(f"Python: [{REPEAT} calls of {NUMBER} runs fizzbuzzing up to {FIZZBUZZES}]") - # fbpytimer = timeit.Timer(stmt="[fbpy(i) for i in range(1,FIZZBUZZES)]", globals=globals()) - # fbpytimes = fbpytimer.repeat(repeat=REPEAT, number=NUMBER) - # print(fbpytimes) + print(f"Python: [{REPEAT} calls of {NUMBER} runs fizzbuzzing up to {FIZZBUZZES}]") + fbpytimer = timeit.Timer(stmt="[fbpy(i) for i in range(1,FIZZBUZZES)]", globals=globals()) + fbpytimes = fbpytimer.repeat(repeat=REPEAT, number=NUMBER) + print(fbpytimes) print(f"Rust vector: [{REPEAT} calls of {NUMBER} runs fizzbuzzing a list of numbers up to {FIZZBUZZES}]") fbo3vectimer = timeit.Timer(stmt="[fbo3(LISTOFNUMBERS)]", globals=globals()) diff --git a/tests/test_fizzbuzzo3.py b/tests/test_fizzbuzzo3.py index 7d0d686..167ae26 100644 --- a/tests/test_fizzbuzzo3.py +++ b/tests/test_fizzbuzzo3.py @@ -11,23 +11,17 @@ def test_lazy(): assert fizzbuzz(6) == "fizz" assert fizzbuzz(15) == "fizzbuzz" - def test_float(): assert fizzbuzz(1.0) == "1" assert fizzbuzz(3.0) == "fizz" - def test_list(): - assert fizzbuzz( - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15], - ) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, fizzbuzz".split(", ") - + assert fizzbuzz([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15]) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, fizzbuzz" def test_string(): with pytest.raises(TypeError): fizzbuzz("1") - def test_1_to_100(): results = [fizzbuzz(i) for i in range(1, 101)] every_3rd_has_fizz = all("fizz" in r for r in results[2::3]) @@ -45,24 +39,16 @@ def test_1_to_100(): all_numbers_correct = all(r == str(i + 1) for i, r in enumerate(results) if r not in ("fizz", "buzz", "fizzbuzz")) assert all_numbers_correct - def test_slice(): - assert fizzbuzz( - slice(1, 16, 1), - ) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz".split(", ") - + assert fizzbuzz(slice(1,16,1)) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz" # This case is REALLY IMPORTANT as it cannot be tested via rust unit tests... def test_slice_no_step(): - assert fizzbuzz( - slice(1, 16), - ) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz".split(", ") - + assert fizzbuzz(slice(1,16)) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz" def test_slice_negative_step(): - assert fizzbuzz(slice(15, 0, -3)) == "fizzbuzz, fizz, fizz, fizz, fizz".split(", ") - + assert fizzbuzz(slice(15,0,-3)) == "fizzbuzz, fizz, fizz, fizz, fizz" def test_slice_zero_step(): with pytest.raises(ValueError, match="step cannot be zero"): - fizzbuzz(slice(1, 16, 0)) + fizzbuzz(slice(1,16,0)) \ No newline at end of file From 46f61e40b51b4d952554f9d31c696f6ce503dca8 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Wed, 5 Jun 2024 18:13:33 +0000 Subject: [PATCH 16/29] Improve docstring re steps --- python/fizzbuzz/fizzbuzzo3.pyi | 2 +- rust/fizzbuzzo3/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/fizzbuzz/fizzbuzzo3.pyi b/python/fizzbuzz/fizzbuzzo3.pyi index 25627d9..c1379ef 100644 --- a/python/fizzbuzz/fizzbuzzo3.pyi +++ b/python/fizzbuzz/fizzbuzzo3.pyi @@ -50,5 +50,5 @@ def fizzbuzz(n: int | list[int] | slice) -> str: '4, fizz, 2' ``` Note: Slices are inclusive on the left, exclusive on the right and can contain an optional step. - Negative steps require start > stop. + /// Negative steps require start > stop, positive steps require stop > start; or else will return an empty list. """ diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index 707f26b..9619bea 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -63,7 +63,7 @@ impl IntoPy> for MySlice { /// '4, fizz, 2' /// ``` /// Note: Slices are inclusive on the left, exclusive on the right and can contain an optional step. -/// Negative steps require start > stop. +/// Negative steps require start > stop, positive steps require stop > start; or else will return an empty list. #[pyfunction] #[pyo3(name = "fizzbuzz", text_signature = "(n)")] fn py_fizzbuzz(num: FizzBuzzable) -> PyResult { From 67824ddf3547ae1b3d0d1632ffd3410fae93417f Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Wed, 5 Jun 2024 18:14:03 +0000 Subject: [PATCH 17/29] performance test results from Union return --- tests/perf_results.md | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/perf_results.md b/tests/perf_results.md index 1065fa8..b0fe6ad 100644 --- a/tests/perf_results.md +++ b/tests/perf_results.md @@ -92,3 +92,52 @@ Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuz Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] [0.5420241989995702] ``` + +## Comparing return types (-> str | list[str] vs. -> str) + +```text +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ echo "No LTO, Union" +No LTO, Union +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ /workspaces/FizzBuzz/.venv/bin/python /workspaces/FizzBuzz/tests/perftest.py +Rust: [1 calls of 10 runs fizzbuzzing up to 1000000] +[2.7247621990100015] +Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 1000000] +[1.4409539260086603] +Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 1000000] +[1.748141026997473] +Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] +[1.140464444004465] +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ echo "thin LTO, Union" +thin LTO, Union +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ /workspaces/FizzBuzz/.venv/bin/python /workspaces/FizzBuzz/tests/perftest.py +Rust: [1 calls of 10 runs fizzbuzzing up to 1000000] +[2.573878561001038] +Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 1000000] +[1.5258527039986802] +Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 1000000] +[1.7503311760083307] +Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] +[1.1543225019995589] +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ echo "fat LTO, Union" +fat LTO, Union +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ /workspaces/FizzBuzz/.venv/bin/python /workspaces/FizzBuzz/tests/perftest.py +Rust: [1 calls of 10 runs fizzbuzzing up to 1000000] +[2.659256437997101] +Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 1000000] +[1.4467686470015906] +Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 1000000] +[1.6921475639974233] +Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] +[1.1023815070075216] +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ echo "no LTO, String" +no LTO, String +(.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ /workspaces/FizzBuzz/.venv/bin/python /workspaces/FizzBuzz/tests/perftest.py +Rust: [1 calls of 10 runs fizzbuzzing up to 1000000] +[2.6100861899903975] +Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 1000000] +[0.8597368839982664] +Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 1000000] +[1.1903306849999353] +Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] +[0.6246530729986262] +``` From 285385204c227341cf0bec2cba59ee76dd9c4923 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Wed, 5 Jun 2024 18:32:50 +0000 Subject: [PATCH 18/29] perftest returning a list vs union vs string (with 10M elements) --- rust/fizzbuzzo3/src/lib.rs | 2 +- tests/perf_results.md | 45 +++++++++++++++++++++++++++++++++++++- tests/perftest.py | 10 ++++----- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index 9619bea..8f7fbef 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -66,7 +66,7 @@ impl IntoPy> for MySlice { /// Negative steps require start > stop, positive steps require stop > start; or else will return an empty list. #[pyfunction] #[pyo3(name = "fizzbuzz", text_signature = "(n)")] -fn py_fizzbuzz(num: FizzBuzzable) -> PyResult { +fn py_fizzbuzz(num: FizzBuzzable) -> PyResult> { match num { FizzBuzzable::Int(n) => Ok(n.fizzbuzz().into()), FizzBuzzable::Float(n) => Ok(n.fizzbuzz().into()), diff --git a/tests/perf_results.md b/tests/perf_results.md index b0fe6ad..4769fa0 100644 --- a/tests/perf_results.md +++ b/tests/perf_results.md @@ -93,7 +93,7 @@ Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] [0.5420241989995702] ``` -## Comparing return types (-> str | list[str] vs. -> str) +## Comparing return types (`-> str | list[str]` vs. `-> str`) ```text (.venv) pyo3@6195c4a7706f:/workspaces/FizzBuzz$ echo "No LTO, Union" @@ -141,3 +141,46 @@ Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuz Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 1000000] [0.6246530729986262] ``` + +## Comparing return types in general (1..10_000_000) + +`Str`ings are 2x faster than `list`s created from `Vec` + +### `-> str` + +```text +Rust: [1 calls of 10 runs fizzbuzzing up to 10000000] +[27.814233318000333] +Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 10000000] +[7.261143727999297] +Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 10000000] +[10.321640708003542] +Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 10000000] +[4.721871253001154] +``` + +### `-> str | list[str]` + +```text +Rust: [1 calls of 10 runs fizzbuzzing up to 10000000] +[25.37807360100851] +Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 10000000] +[12.790041114989435] +Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 10000000] +[16.75132880899764] +Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 10000000] +[9.89638055099931] +``` + +### `-> list[str]` + +```text +Rust: [1 calls of 10 runs fizzbuzzing up to 10000000] +[47.682113279995974] +Rust vector: [1 calls of 10 runs fizzbuzzing a list of numbers up to 10000000] +[12.776051424996695] +Rust vector, with python list overhead: [1 calls of 10 runs creating and fizzbuzzing a list of numbers up to 10000000] +[16.503915808003512] +Rust range: [1 calls of 10 runs fizzbuzzing a range of numbers up to 10000000] +[9.859676376989228] +``` diff --git a/tests/perftest.py b/tests/perftest.py index c04ee1c..894abe8 100644 --- a/tests/perftest.py +++ b/tests/perftest.py @@ -7,7 +7,7 @@ REPEAT = 1 NUMBER = 10 -FIZZBUZZES = 1_000_000 +FIZZBUZZES = 10_000_000 LISTOFNUMBERS = list(range(1, FIZZBUZZES)) @@ -18,10 +18,10 @@ def main(): fbo3times = fbo3timer.repeat(repeat=REPEAT, number=NUMBER) print(fbo3times) - print(f"Python: [{REPEAT} calls of {NUMBER} runs fizzbuzzing up to {FIZZBUZZES}]") - fbpytimer = timeit.Timer(stmt="[fbpy(i) for i in range(1,FIZZBUZZES)]", globals=globals()) - fbpytimes = fbpytimer.repeat(repeat=REPEAT, number=NUMBER) - print(fbpytimes) + # print(f"Python: [{REPEAT} calls of {NUMBER} runs fizzbuzzing up to {FIZZBUZZES}]") + # fbpytimer = timeit.Timer(stmt="[fbpy(i) for i in range(1,FIZZBUZZES)]", globals=globals()) + # fbpytimes = fbpytimer.repeat(repeat=REPEAT, number=NUMBER) + # print(fbpytimes) print(f"Rust vector: [{REPEAT} calls of {NUMBER} runs fizzbuzzing a list of numbers up to {FIZZBUZZES}]") fbo3vectimer = timeit.Timer(stmt="[fbo3(LISTOFNUMBERS)]", globals=globals()) From f7f1a2e4596f753306ca6e649771b6d11ba19d43 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Wed, 5 Jun 2024 18:34:04 +0000 Subject: [PATCH 19/29] revert to returning an str --- rust/fizzbuzzo3/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index 8f7fbef..9619bea 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -66,7 +66,7 @@ impl IntoPy> for MySlice { /// Negative steps require start > stop, positive steps require stop > start; or else will return an empty list. #[pyfunction] #[pyo3(name = "fizzbuzz", text_signature = "(n)")] -fn py_fizzbuzz(num: FizzBuzzable) -> PyResult> { +fn py_fizzbuzz(num: FizzBuzzable) -> PyResult { match num { FizzBuzzable::Int(n) => Ok(n.fizzbuzz().into()), FizzBuzzable::Float(n) => Ok(n.fizzbuzz().into()), From 9f449f7f236714cfc1503d354ba7b331ed2c0dc9 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Wed, 5 Jun 2024 19:26:58 +0000 Subject: [PATCH 20/29] simplify docstring re slices --- python/fizzbuzz/fizzbuzzo3.pyi | 2 +- rust/fizzbuzzo3/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/fizzbuzz/fizzbuzzo3.pyi b/python/fizzbuzz/fizzbuzzo3.pyi index c1379ef..8814bf0 100644 --- a/python/fizzbuzz/fizzbuzzo3.pyi +++ b/python/fizzbuzz/fizzbuzzo3.pyi @@ -50,5 +50,5 @@ def fizzbuzz(n: int | list[int] | slice) -> str: '4, fizz, 2' ``` Note: Slices are inclusive on the left, exclusive on the right and can contain an optional step. - /// Negative steps require start > stop, positive steps require stop > start; or else will return an empty list. + Negative steps require start > stop, positive steps require stop > start; other combinations return `[]`. """ diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index 9619bea..2b8d9c4 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -63,7 +63,7 @@ impl IntoPy> for MySlice { /// '4, fizz, 2' /// ``` /// Note: Slices are inclusive on the left, exclusive on the right and can contain an optional step. -/// Negative steps require start > stop, positive steps require stop > start; or else will return an empty list. +/// Negative steps require start > stop, positive steps require stop > start; other combinations return `[]`. #[pyfunction] #[pyo3(name = "fizzbuzz", text_signature = "(n)")] fn py_fizzbuzz(num: FizzBuzzable) -> PyResult { From ac45a875dbc0431f2219ee4466deb245b4e18e68 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Wed, 5 Jun 2024 19:31:43 +0000 Subject: [PATCH 21/29] reimplement Union type returns --- python/fizzbuzz/fizzbuzzo3.pyi | 26 +++++--- rust/fizzbuzzo3/src/lib.rs | 105 +++++++++++++++++++++++++-------- tests/perftest.py | 2 +- tests/test_fizzbuzzo3.py | 24 ++++++-- 4 files changed, 119 insertions(+), 38 deletions(-) diff --git a/python/fizzbuzz/fizzbuzzo3.pyi b/python/fizzbuzz/fizzbuzzo3.pyi index 8814bf0..27d153b 100644 --- a/python/fizzbuzz/fizzbuzzo3.pyi +++ b/python/fizzbuzz/fizzbuzzo3.pyi @@ -10,7 +10,17 @@ Usage: ``` """ -def fizzbuzz(n: int | list[int] | slice) -> str: +from typing import overload + +@overload +def fizzbuzz(n: int) -> str: + ... + +@overload +def fizzbuzz(n: list[int] | slice) -> list[str]: + ... + +def fizzbuzz(n): """ Returns the correct fizzbuzz answer for any number or list/range of numbers. @@ -21,8 +31,8 @@ def fizzbuzz(n: int | list[int] | slice) -> str: n: the number(s) to fizzbuzz Returns: - A `str` with the correct fizzbuzz answer(s). - In the case of a list or range of inputs the answers will be separated by `, ` + In the case of a sinlge number: a `str` with the correct fizzbuzz answer. + In the case of a list or range of inputs: a `list` of `str` with the correct fizzbuzz answers. Examples: a single `int`: @@ -37,17 +47,19 @@ def fizzbuzz(n: int | list[int] | slice) -> str: ``` from fizzbuzz.fizzbuzzo3 import fizzbuzz >>> fizzbuzz([1,3]) - '1, fizz' + ['1', 'fizz'] ``` a `slice` representing a range: ``` from fizzbuzz.fizzbuzzo3 import fizzbuzz >>> fizzbuzz(slice(1,4,2)) - '1, fizz' + ['1', 'fizz'] >>> fizzbuzz(slice(1,4)) - '1, 2, fizz' + ['1', '2', 'fizz'] >>> fizzbuzz(slice(4,1,-1)) - '4, fizz, 2' + ['4', 'fizz', '2'] + >>> fizzbuzz(slice(1,5,-1)) + [] ``` Note: Slices are inclusive on the left, exclusive on the right and can contain an optional step. Negative steps require start > stop, positive steps require stop > start; other combinations return `[]`. diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index 2b8d9c4..bafed91 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -1,7 +1,11 @@ use std::ops::Neg; -use fizzbuzz::{FizzBuzz, MultiFizzBuzz}; -use pyo3::{exceptions::PyValueError, prelude::*, types::PySlice}; +use fizzbuzz::{FizzBuzz, FizzBuzzAnswer, MultiFizzBuzz}; +use pyo3::{ + exceptions::PyValueError, + prelude::*, + types::PySlice, +}; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; #[derive(FromPyObject)] @@ -25,6 +29,23 @@ impl IntoPy> for MySlice { } } +struct FizzBuzzReturn(FizzBuzzAnswer); + +impl From for FizzBuzzReturn { + fn from(value: FizzBuzzAnswer) -> Self { + FizzBuzzReturn(value) + } +} + +impl IntoPy> for FizzBuzzReturn { + fn into_py(self, py: Python<'_>) -> Py { + match self.0 { + FizzBuzzAnswer::One(string) => string.into_py(py), + FizzBuzzAnswer::Many(list) => list.into_py(py), + } + } +} + /// Returns the correct fizzbuzz answer for any number or list/range of numbers. /// /// This is an optimised algorithm compiled in rust. Large lists will utilise multiple CPU cores for processing. @@ -34,8 +55,8 @@ impl IntoPy> for MySlice { /// n: the number(s) to fizzbuzz /// /// Returns: -/// A `str` with the correct fizzbuzz answer(s). -/// In the case of a list or range of inputs the answers will be separated by `, ` +/// In the case of a sinlge number: a `str` with the correct fizzbuzz answer. +/// In the case of a list or range of inputs: a `list` of `str` with the correct fizzbuzz answers. /// /// Examples: /// a single `int`: @@ -50,23 +71,25 @@ impl IntoPy> for MySlice { /// ``` /// from fizzbuzz.fizzbuzzo3 import fizzbuzz /// >>> fizzbuzz([1,3]) -/// '1, fizz' +/// ['1', 'fizz'] /// ``` /// a `slice` representing a range: /// ``` /// from fizzbuzz.fizzbuzzo3 import fizzbuzz /// >>> fizzbuzz(slice(1,4,2)) -/// '1, fizz' +/// ['1', 'fizz'] /// >>> fizzbuzz(slice(1,4)) -/// '1, 2, fizz' +/// ['1', '2', 'fizz'] /// >>> fizzbuzz(slice(4,1,-1)) -/// '4, fizz, 2' +/// ['4', 'fizz', '2'] +/// >>> fizzbuzz(slice(1,5,-1)) +/// [] /// ``` /// Note: Slices are inclusive on the left, exclusive on the right and can contain an optional step. /// Negative steps require start > stop, positive steps require stop > start; other combinations return `[]`. #[pyfunction] #[pyo3(name = "fizzbuzz", text_signature = "(n)")] -fn py_fizzbuzz(num: FizzBuzzable) -> PyResult { +fn py_fizzbuzz(num: FizzBuzzable) -> PyResult { match num { FizzBuzzable::Int(n) => Ok(n.fizzbuzz().into()), FizzBuzzable::Float(n) => Ok(n.fizzbuzz().into()), @@ -141,8 +164,15 @@ mod tests { #[pyo3import(py_fizzbuzzo3: from fizzbuzzo3 import fizzbuzz)] fn test_fizzbuzz_vec() { let input = vec![1, 2, 3, 4, 5]; - let result: String = fizzbuzz!(input); - assert_eq!(result, "1, 2, fizz, 4, buzz"); + let expected = vec![ + "1".to_string(), + "2".to_string(), + "fizz".to_string(), + "4".to_string(), + "buzz".to_string(), + ]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] @@ -160,8 +190,15 @@ mod tests { stop: 6, step: Some(1), }; - let result: String = fizzbuzz!(input); - assert_eq!(result, "1, 2, fizz, 4, buzz"); + let expected = vec![ + "1".to_string(), + "2".to_string(), + "fizz".to_string(), + "4".to_string(), + "buzz".to_string(), + ]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] @@ -172,8 +209,15 @@ mod tests { stop: 6, step: None, }; - let result: String = fizzbuzz!(input); - assert_eq!(result, "1, 2, fizz, 4, buzz"); + let expected = vec![ + "1".to_string(), + "2".to_string(), + "fizz".to_string(), + "4".to_string(), + "buzz".to_string(), + ]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] @@ -184,8 +228,9 @@ mod tests { stop: 6, step: Some(2), }; - let result: String = fizzbuzz!(input); - assert_eq!(result, "1, fizz, buzz"); + let expected = vec!["1".to_string(), "fizz".to_string(), "buzz".to_string()]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] @@ -196,8 +241,9 @@ mod tests { stop: 0, step: Some(1), }; - let result: String = fizzbuzz!(input); - assert_eq!(result, ""); + let result: Vec = fizzbuzz!(input); + let expected: Vec = vec![]; + assert_eq!(result, expected); } #[pyo3test] @@ -208,8 +254,9 @@ mod tests { stop: 0, step: Some(-2), }; - let result: String = fizzbuzz!(input); - assert_eq!(result, "buzz, fizz, 1"); + let expected = vec!["buzz".to_string(), "fizz".to_string(), "1".to_string()]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] @@ -220,8 +267,14 @@ mod tests { stop: 1, step: Some(-1), }; - let result: String = fizzbuzz!(input); - assert_eq!(result, "buzz, 4, fizz, 2"); + let expected = vec![ + "buzz".to_string(), + "4".to_string(), + "fizz".to_string(), + "2".to_string(), + ]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] @@ -232,8 +285,10 @@ mod tests { stop: 0, step: Some(-2), }; - let result: String = fizzbuzz!(input); - assert_eq!(result, "fizz, 4, 2"); + + let expected = vec!["fizz".to_string(), "4".to_string(), "2".to_string()]; + let result: Vec = fizzbuzz!(input); + assert_eq!(result, expected); } #[pyo3test] #[allow(unused_macros)] diff --git a/tests/perftest.py b/tests/perftest.py index 894abe8..0564ae0 100644 --- a/tests/perftest.py +++ b/tests/perftest.py @@ -7,7 +7,7 @@ REPEAT = 1 NUMBER = 10 -FIZZBUZZES = 10_000_000 +FIZZBUZZES = 1_000_000 LISTOFNUMBERS = list(range(1, FIZZBUZZES)) diff --git a/tests/test_fizzbuzzo3.py b/tests/test_fizzbuzzo3.py index 167ae26..7d0d686 100644 --- a/tests/test_fizzbuzzo3.py +++ b/tests/test_fizzbuzzo3.py @@ -11,17 +11,23 @@ def test_lazy(): assert fizzbuzz(6) == "fizz" assert fizzbuzz(15) == "fizzbuzz" + def test_float(): assert fizzbuzz(1.0) == "1" assert fizzbuzz(3.0) == "fizz" + def test_list(): - assert fizzbuzz([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15]) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, fizzbuzz" + assert fizzbuzz( + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15], + ) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, fizzbuzz".split(", ") + def test_string(): with pytest.raises(TypeError): fizzbuzz("1") + def test_1_to_100(): results = [fizzbuzz(i) for i in range(1, 101)] every_3rd_has_fizz = all("fizz" in r for r in results[2::3]) @@ -39,16 +45,24 @@ def test_1_to_100(): all_numbers_correct = all(r == str(i + 1) for i, r in enumerate(results) if r not in ("fizz", "buzz", "fizzbuzz")) assert all_numbers_correct + def test_slice(): - assert fizzbuzz(slice(1,16,1)) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz" + assert fizzbuzz( + slice(1, 16, 1), + ) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz".split(", ") + # This case is REALLY IMPORTANT as it cannot be tested via rust unit tests... def test_slice_no_step(): - assert fizzbuzz(slice(1,16)) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz" + assert fizzbuzz( + slice(1, 16), + ) == "1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz, 11, fizz, 13, 14, fizzbuzz".split(", ") + def test_slice_negative_step(): - assert fizzbuzz(slice(15,0,-3)) == "fizzbuzz, fizz, fizz, fizz, fizz" + assert fizzbuzz(slice(15, 0, -3)) == "fizzbuzz, fizz, fizz, fizz, fizz".split(", ") + def test_slice_zero_step(): with pytest.raises(ValueError, match="step cannot be zero"): - fizzbuzz(slice(1,16,0)) \ No newline at end of file + fizzbuzz(slice(1, 16, 0)) From 60f04a72fef6cbf046a8b2877ee82232c46d1139 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Thu, 6 Jun 2024 05:04:23 +0000 Subject: [PATCH 22/29] From FizzBuzz for Cow etc. --- rust/fizzbuzz/src/lib.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/rust/fizzbuzz/src/lib.rs b/rust/fizzbuzz/src/lib.rs index 896988b..b0f7f10 100644 --- a/rust/fizzbuzz/src/lib.rs +++ b/rust/fizzbuzz/src/lib.rs @@ -13,6 +13,8 @@ //! assert_eq!(three, "fizz".to_string()); //! ``` +use std::borrow::Cow; + use rayon::prelude::*; static BIG_VECTOR: usize = 300_000; // Size from which parallelisation makes sense @@ -21,21 +23,22 @@ static BIG_VECTOR: usize = 300_000; // Size from which parallelisation makes sen /// ::From() etc. pub enum FizzBuzzAnswer { /// Stores a single FizzBuzz value - One(String), + One(Cow<'static, str>), /// Stores a series of FizzBuzz values - Many(Vec), + Many(Vec>), } -impl From for String { +impl From for Cow<'_, str> { fn from(value: FizzBuzzAnswer) -> Self { match value { FizzBuzzAnswer::One(s) => s, - FizzBuzzAnswer::Many(v) => v.join(", "), + FizzBuzzAnswer::Many(v) => v.join(", ").into(), } } } -impl From for Vec { +impl From for Vec> +{ fn from(value: FizzBuzzAnswer) -> Self { match value { FizzBuzzAnswer::One(s) => vec![s], @@ -76,21 +79,21 @@ where fn fizzbuzz(&self) -> FizzBuzzAnswer { let three = match ::try_from(3_u8) { Ok(three) => three, - Err(_) => return FizzBuzzAnswer::One(self.to_string()), + Err(_) => return FizzBuzzAnswer::One(self.to_string().into()), }; let five = match ::try_from(5_u8) { Ok(five) => five, - Err(_) => return FizzBuzzAnswer::One(self.to_string()), + Err(_) => return FizzBuzzAnswer::One(self.to_string().into()), }; let zero = match ::try_from(0_u8) { Ok(zero) => zero, - Err(_) => return FizzBuzzAnswer::One(self.to_string()), + Err(_) => return FizzBuzzAnswer::One(self.to_string().into()), }; match (self % three == zero, self % five == zero) { - (true, true) => FizzBuzzAnswer::One("fizzbuzz".to_string()), - (true, false) => FizzBuzzAnswer::One("fizz".to_string()), - (false, true) => FizzBuzzAnswer::One("buzz".to_string()), - _ => FizzBuzzAnswer::One(self.to_string()), + (true, true) => FizzBuzzAnswer::One("fizzbuzz".into()), + (true, false) => FizzBuzzAnswer::One("fizz".into()), + (false, true) => FizzBuzzAnswer::One("buzz".into()), + _ => FizzBuzzAnswer::One(self.to_string().into()), } } } From 4fdc3d0f6107be1b21824b428f9c94e2173240ca Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Thu, 6 Jun 2024 05:13:45 +0000 Subject: [PATCH 23/29] static cow for huge performance boosts - many tests disabled --- rust/fizzbuzz/benches/bench_fizzbuzz.rs | 4 +- rust/fizzbuzz/benches/results.md | 83 +++++++++++++++++++++++ rust/fizzbuzz/src/lib.rs | 13 +++- rust/fizzbuzz/tests/test_multifizzbuzz.rs | 2 + 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/rust/fizzbuzz/benches/bench_fizzbuzz.rs b/rust/fizzbuzz/benches/bench_fizzbuzz.rs index dc15907..1e54e8c 100644 --- a/rust/fizzbuzz/benches/bench_fizzbuzz.rs +++ b/rust/fizzbuzz/benches/bench_fizzbuzz.rs @@ -1,4 +1,6 @@ #![allow(dead_code)] +use std::borrow::Cow; + use criterion::{criterion_group, criterion_main, Criterion}; use fizzbuzz::{self, FizzBuzz, MultiFizzBuzz}; use rayon::prelude::*; @@ -45,7 +47,7 @@ fn vec_pariter() { #[inline] fn multifizzbuzz_trait() { let inputs: Vec<_> = (1..TEST_SIZE).collect(); - let _: Vec = inputs.fizzbuzz().into(); + let _: Vec> = inputs.fizzbuzz().into(); } #[inline] diff --git a/rust/fizzbuzz/benches/results.md b/rust/fizzbuzz/benches/results.md index 86a3b33..2f488e6 100644 --- a/rust/fizzbuzz/benches/results.md +++ b/rust/fizzbuzz/benches/results.md @@ -570,3 +570,86 @@ multifizzbuzz_trait time: [56.362 ms 57.859 ms 59.375 ms] change: [+590.60% +617.47% +644.82%] (p = 0.00 < 0.05) Performance has regressed. ``` + +## Switching to static Cow + +```text + Running benches/bench_fizzbuzz.rs (target/release/deps/bench_fizzbuzz-2663fda578b6ea26) +Gnuplot not found, using plotters backend +Benchmarking vec_iter: Warming up for 3.0000 s +Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 8.4s, or reduce sample count to 50. +vec_iter time: [80.584 ms 82.063 ms 83.705 ms] + change: [-4.6983% -1.9428% +0.7953%] (p = 0.19 > 0.05) + No change in performance detected. +Found 9 outliers among 100 measurements (9.00%) + 6 (6.00%) high mild + 3 (3.00%) high severe + +vec_pariter time: [56.315 ms 57.821 ms 59.357 ms] + change: [-5.8404% -2.4611% +1.1119%] (p = 0.18 > 0.05) + No change in performance detected. +Found 1 outliers among 100 measurements (1.00%) + 1 (1.00%) high mild + +multifizzbuzz_trait time: [43.466 ms 44.692 ms 45.949 ms] + change: [-27.996% -25.200% -22.244%] (p = 0.00 < 0.05) + Performance has improved. +Found 1 outliers among 100 measurements (1.00%) + 1 (1.00%) high mild + +multifizzbuzz_trait_as_string + time: [53.799 ms 55.400 ms 57.030 ms] + change: [-11.115% -7.8360% -4.5341%] (p = 0.00 < 0.05) + Performance has improved. +Found 1 outliers among 100 measurements (1.00%) + 1 (1.00%) high mild + +multifizzbuzz_trait_from_vec_as_answer + time: [41.851 ms 43.389 ms 44.938 ms] + change: [-26.894% -23.699% -20.349%] (p = 0.00 < 0.05) + Performance has improved. + +multifizzbuzz_trait_from_range_as_answer + time: [43.257 ms 44.830 ms 46.505 ms] + change: [-23.233% -19.874% -16.221%] (p = 0.00 < 0.05) + Performance has improved. +Found 6 outliers among 100 measurements (6.00%) + 4 (4.00%) high mild + 2 (2.00%) high severe + + Running benches/bench_sizes.rs (target/release/deps/bench_sizes-f51040c11ca30db3) +Gnuplot not found, using plotters backend +20 time: [834.50 ns 854.19 ns 877.63 ns] + change: [-28.854% -26.416% -23.758%] (p = 0.00 < 0.05) + Performance has improved. +Found 5 outliers among 100 measurements (5.00%) + 5 (5.00%) high mild + +200_000 time: [10.224 ms 10.411 ms 10.618 ms] + change: [-49.384% -47.551% -45.638%] (p = 0.00 < 0.05) + Performance has improved. +Found 12 outliers among 100 measurements (12.00%) + 11 (11.00%) high mild + 1 (1.00%) high severe + +300_000 time: [15.309 ms 15.592 ms 15.901 ms] + change: [-44.999% -43.536% -41.988%] (p = 0.00 < 0.05) + Performance has improved. +Found 9 outliers among 100 measurements (9.00%) + 4 (4.00%) high mild + 5 (5.00%) high severe + +1_000_000 time: [39.580 ms 40.862 ms 42.196 ms] + change: [-33.525% -30.475% -27.118%] (p = 0.00 < 0.05) + Performance has improved. +Found 1 outliers among 100 measurements (1.00%) + 1 (1.00%) high mild + +Benchmarking 10_000_000: Warming up for 3.0000 s +Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 26.5s, or reduce sample count to 10. +10_000_000 time: [242.49 ms 246.69 ms 250.73 ms] + change: [-33.151% -31.683% -30.155%] (p = 0.00 < 0.05) + Performance has improved. +Found 1 outliers among 100 measurements (1.00%) + 1 (1.00%) low mild +``` diff --git a/rust/fizzbuzz/src/lib.rs b/rust/fizzbuzz/src/lib.rs index b0f7f10..550ee0c 100644 --- a/rust/fizzbuzz/src/lib.rs +++ b/rust/fizzbuzz/src/lib.rs @@ -28,7 +28,16 @@ pub enum FizzBuzzAnswer { Many(Vec>), } -impl From for Cow<'_, str> { +impl From for String { + fn from(value: FizzBuzzAnswer) -> Self { + match value { + FizzBuzzAnswer::One(s) => s.into(), + FizzBuzzAnswer::Many(v) => v.join(", "), + } + } +} + +impl From for Cow<'static, str> { fn from(value: FizzBuzzAnswer) -> Self { match value { FizzBuzzAnswer::One(s) => s, @@ -127,7 +136,7 @@ where } } -#[cfg(test)] +#[cfg(testx)] mod test { use super::*; diff --git a/rust/fizzbuzz/tests/test_multifizzbuzz.rs b/rust/fizzbuzz/tests/test_multifizzbuzz.rs index 57ffa60..bf8b0eb 100644 --- a/rust/fizzbuzz/tests/test_multifizzbuzz.rs +++ b/rust/fizzbuzz/tests/test_multifizzbuzz.rs @@ -1,3 +1,5 @@ +#![cfg(x)] + use fizzbuzz::MultiFizzBuzz; mod vectors { From f3094f90a7fb6d94b82e88e1d64c71e98643c222 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Fri, 7 Jun 2024 05:04:28 +0000 Subject: [PATCH 24/29] reset Cow implementations ... this should be a separate piece of work this commit resets to: ac45a875dbc0431f2219ee4466deb245b4e18e68 ("reimplement Union type returns") --- rust/fizzbuzz/benches/bench_fizzbuzz.rs | 4 +- rust/fizzbuzz/benches/results.md | 83 ----------------------- rust/fizzbuzz/src/lib.rs | 36 ++++------ rust/fizzbuzz/tests/test_multifizzbuzz.rs | 2 - 4 files changed, 13 insertions(+), 112 deletions(-) diff --git a/rust/fizzbuzz/benches/bench_fizzbuzz.rs b/rust/fizzbuzz/benches/bench_fizzbuzz.rs index 1e54e8c..dc15907 100644 --- a/rust/fizzbuzz/benches/bench_fizzbuzz.rs +++ b/rust/fizzbuzz/benches/bench_fizzbuzz.rs @@ -1,6 +1,4 @@ #![allow(dead_code)] -use std::borrow::Cow; - use criterion::{criterion_group, criterion_main, Criterion}; use fizzbuzz::{self, FizzBuzz, MultiFizzBuzz}; use rayon::prelude::*; @@ -47,7 +45,7 @@ fn vec_pariter() { #[inline] fn multifizzbuzz_trait() { let inputs: Vec<_> = (1..TEST_SIZE).collect(); - let _: Vec> = inputs.fizzbuzz().into(); + let _: Vec = inputs.fizzbuzz().into(); } #[inline] diff --git a/rust/fizzbuzz/benches/results.md b/rust/fizzbuzz/benches/results.md index 2f488e6..86a3b33 100644 --- a/rust/fizzbuzz/benches/results.md +++ b/rust/fizzbuzz/benches/results.md @@ -570,86 +570,3 @@ multifizzbuzz_trait time: [56.362 ms 57.859 ms 59.375 ms] change: [+590.60% +617.47% +644.82%] (p = 0.00 < 0.05) Performance has regressed. ``` - -## Switching to static Cow - -```text - Running benches/bench_fizzbuzz.rs (target/release/deps/bench_fizzbuzz-2663fda578b6ea26) -Gnuplot not found, using plotters backend -Benchmarking vec_iter: Warming up for 3.0000 s -Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 8.4s, or reduce sample count to 50. -vec_iter time: [80.584 ms 82.063 ms 83.705 ms] - change: [-4.6983% -1.9428% +0.7953%] (p = 0.19 > 0.05) - No change in performance detected. -Found 9 outliers among 100 measurements (9.00%) - 6 (6.00%) high mild - 3 (3.00%) high severe - -vec_pariter time: [56.315 ms 57.821 ms 59.357 ms] - change: [-5.8404% -2.4611% +1.1119%] (p = 0.18 > 0.05) - No change in performance detected. -Found 1 outliers among 100 measurements (1.00%) - 1 (1.00%) high mild - -multifizzbuzz_trait time: [43.466 ms 44.692 ms 45.949 ms] - change: [-27.996% -25.200% -22.244%] (p = 0.00 < 0.05) - Performance has improved. -Found 1 outliers among 100 measurements (1.00%) - 1 (1.00%) high mild - -multifizzbuzz_trait_as_string - time: [53.799 ms 55.400 ms 57.030 ms] - change: [-11.115% -7.8360% -4.5341%] (p = 0.00 < 0.05) - Performance has improved. -Found 1 outliers among 100 measurements (1.00%) - 1 (1.00%) high mild - -multifizzbuzz_trait_from_vec_as_answer - time: [41.851 ms 43.389 ms 44.938 ms] - change: [-26.894% -23.699% -20.349%] (p = 0.00 < 0.05) - Performance has improved. - -multifizzbuzz_trait_from_range_as_answer - time: [43.257 ms 44.830 ms 46.505 ms] - change: [-23.233% -19.874% -16.221%] (p = 0.00 < 0.05) - Performance has improved. -Found 6 outliers among 100 measurements (6.00%) - 4 (4.00%) high mild - 2 (2.00%) high severe - - Running benches/bench_sizes.rs (target/release/deps/bench_sizes-f51040c11ca30db3) -Gnuplot not found, using plotters backend -20 time: [834.50 ns 854.19 ns 877.63 ns] - change: [-28.854% -26.416% -23.758%] (p = 0.00 < 0.05) - Performance has improved. -Found 5 outliers among 100 measurements (5.00%) - 5 (5.00%) high mild - -200_000 time: [10.224 ms 10.411 ms 10.618 ms] - change: [-49.384% -47.551% -45.638%] (p = 0.00 < 0.05) - Performance has improved. -Found 12 outliers among 100 measurements (12.00%) - 11 (11.00%) high mild - 1 (1.00%) high severe - -300_000 time: [15.309 ms 15.592 ms 15.901 ms] - change: [-44.999% -43.536% -41.988%] (p = 0.00 < 0.05) - Performance has improved. -Found 9 outliers among 100 measurements (9.00%) - 4 (4.00%) high mild - 5 (5.00%) high severe - -1_000_000 time: [39.580 ms 40.862 ms 42.196 ms] - change: [-33.525% -30.475% -27.118%] (p = 0.00 < 0.05) - Performance has improved. -Found 1 outliers among 100 measurements (1.00%) - 1 (1.00%) high mild - -Benchmarking 10_000_000: Warming up for 3.0000 s -Warning: Unable to complete 100 samples in 5.0s. You may wish to increase target time to 26.5s, or reduce sample count to 10. -10_000_000 time: [242.49 ms 246.69 ms 250.73 ms] - change: [-33.151% -31.683% -30.155%] (p = 0.00 < 0.05) - Performance has improved. -Found 1 outliers among 100 measurements (1.00%) - 1 (1.00%) low mild -``` diff --git a/rust/fizzbuzz/src/lib.rs b/rust/fizzbuzz/src/lib.rs index 550ee0c..896988b 100644 --- a/rust/fizzbuzz/src/lib.rs +++ b/rust/fizzbuzz/src/lib.rs @@ -13,8 +13,6 @@ //! assert_eq!(three, "fizz".to_string()); //! ``` -use std::borrow::Cow; - use rayon::prelude::*; static BIG_VECTOR: usize = 300_000; // Size from which parallelisation makes sense @@ -23,31 +21,21 @@ static BIG_VECTOR: usize = 300_000; // Size from which parallelisation makes sen /// ::From() etc. pub enum FizzBuzzAnswer { /// Stores a single FizzBuzz value - One(Cow<'static, str>), + One(String), /// Stores a series of FizzBuzz values - Many(Vec>), + Many(Vec), } impl From for String { - fn from(value: FizzBuzzAnswer) -> Self { - match value { - FizzBuzzAnswer::One(s) => s.into(), - FizzBuzzAnswer::Many(v) => v.join(", "), - } - } -} - -impl From for Cow<'static, str> { fn from(value: FizzBuzzAnswer) -> Self { match value { FizzBuzzAnswer::One(s) => s, - FizzBuzzAnswer::Many(v) => v.join(", ").into(), + FizzBuzzAnswer::Many(v) => v.join(", "), } } } -impl From for Vec> -{ +impl From for Vec { fn from(value: FizzBuzzAnswer) -> Self { match value { FizzBuzzAnswer::One(s) => vec![s], @@ -88,21 +76,21 @@ where fn fizzbuzz(&self) -> FizzBuzzAnswer { let three = match ::try_from(3_u8) { Ok(three) => three, - Err(_) => return FizzBuzzAnswer::One(self.to_string().into()), + Err(_) => return FizzBuzzAnswer::One(self.to_string()), }; let five = match ::try_from(5_u8) { Ok(five) => five, - Err(_) => return FizzBuzzAnswer::One(self.to_string().into()), + Err(_) => return FizzBuzzAnswer::One(self.to_string()), }; let zero = match ::try_from(0_u8) { Ok(zero) => zero, - Err(_) => return FizzBuzzAnswer::One(self.to_string().into()), + Err(_) => return FizzBuzzAnswer::One(self.to_string()), }; match (self % three == zero, self % five == zero) { - (true, true) => FizzBuzzAnswer::One("fizzbuzz".into()), - (true, false) => FizzBuzzAnswer::One("fizz".into()), - (false, true) => FizzBuzzAnswer::One("buzz".into()), - _ => FizzBuzzAnswer::One(self.to_string().into()), + (true, true) => FizzBuzzAnswer::One("fizzbuzz".to_string()), + (true, false) => FizzBuzzAnswer::One("fizz".to_string()), + (false, true) => FizzBuzzAnswer::One("buzz".to_string()), + _ => FizzBuzzAnswer::One(self.to_string()), } } } @@ -136,7 +124,7 @@ where } } -#[cfg(testx)] +#[cfg(test)] mod test { use super::*; diff --git a/rust/fizzbuzz/tests/test_multifizzbuzz.rs b/rust/fizzbuzz/tests/test_multifizzbuzz.rs index bf8b0eb..57ffa60 100644 --- a/rust/fizzbuzz/tests/test_multifizzbuzz.rs +++ b/rust/fizzbuzz/tests/test_multifizzbuzz.rs @@ -1,5 +1,3 @@ -#![cfg(x)] - use fizzbuzz::MultiFizzBuzz; mod vectors { From 1a5d0ea5f17ef8688026efac20943da1ec5d7092 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Fri, 7 Jun 2024 05:27:46 +0000 Subject: [PATCH 25/29] document how-to --- docs/dev-build.md | 68 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/docs/dev-build.md b/docs/dev-build.md index d34379a..993cac6 100644 --- a/docs/dev-build.md +++ b/docs/dev-build.md @@ -349,8 +349,72 @@ Python also often provides single functions which can receive multiple significa --8<-- "rust/fizzbuzzo3/src/lib.rs" ``` -!!! warning "`Union` type returns" - If you want to create something like: `#!python def fizzbuzz(n: int | list[int]) -> str | list[str]:` [Issue pyo3/#1637](https://github.com/PyO3/pyo3/issues/1637) suggests you may be able to do something with the `IntoPy` trait but I haven't tried (yet) +!!! pyo3 "`Union` type returns: `#!python def fizzbuzz(n: int | list[int]) -> str | list[str]`" + If you would like to provide different return types for different cases: + + 1. Implement an `enum` or a wrapper `struct` around an existing `enum` that holds the different types. + 1. Provide one or more conversion `From` traits to convert from the return of your core rust functions. + 1. Provide a conversion `IntoPy` trait to convert to the relevant PyO3 types. + 1. Use this new type as the return of your wrapped function. + + In **`/rust/fizzbuzzo3/src/lib.rs`**: + ```rust + ... + struct FizzBuzzReturn(FizzBuzzAnswer); + + impl From for FizzBuzzReturn { + fn from(value: FizzBuzzAnswer) -> Self { + FizzBuzzReturn(value) + } + } + + impl IntoPy> for FizzBuzzReturn { + fn into_py(self, py: Python<'_>) -> Py { + match self.0 { + FizzBuzzAnswer::One(string) => string.into_py(py), + FizzBuzzAnswer::Many(list) => list.into_py(py), + } + } + } + ... + #[pyfunction] + #[pyo3(name = "fizzbuzz", text_signature = "(n)")] + fn py_fizzbuzz(num: FizzBuzzable) -> PyResult { + ... + ``` + + Thanks to the comments in [Issue pyo3/#1637](https://github.com/PyO3/pyo3/issues/1637) for pointers on how to get this working. + + 1. Add `@overload` hints for your IDE (see [IDE type & doc hinting](#ide-type-doc-hinting)), so that it understands the relationships between input and output types: + + In **`/python/fizzbuzz/fizzbuzzo3.pyi`**: + ```python + ... + from typing import overload + + @overload + def fizzbuzz(n: int) -> str: + ... + + @overload + def fizzbuzz(n: list[int] | slice) -> list[str]: + ... + + def fizzbuzz(n): + """ + Returns the correct fizzbuzz answer for any number or list/range of numbers. + ... + ``` + + ??? pyo3 "**`rust/fizzbuzzo3/src/lib.rs`** - full source" + ```rust + --8<-- "rust/fizzbuzzo3/src/lib.rs" + ``` + + ??? python "**`python/fizzbuzz/fizzbuzzo3.pyi`** - full source" + ```python + --8<-- "python/fizzbuzz/fizzbuzzo3.pyi" + ``` ## IDE type & doc hinting From 92df97eee488336481729857636d3d80dea3e34a Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Fri, 7 Jun 2024 05:53:55 +0000 Subject: [PATCH 26/29] punctuation for legibility --- docs/dev-build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev-build.md b/docs/dev-build.md index 993cac6..11e5a29 100644 --- a/docs/dev-build.md +++ b/docs/dev-build.md @@ -352,7 +352,7 @@ Python also often provides single functions which can receive multiple significa !!! pyo3 "`Union` type returns: `#!python def fizzbuzz(n: int | list[int]) -> str | list[str]`" If you would like to provide different return types for different cases: - 1. Implement an `enum` or a wrapper `struct` around an existing `enum` that holds the different types. + 1. Implement an `enum`, or a wrapper `struct` around an existing `enum`, that holds the different types. 1. Provide one or more conversion `From` traits to convert from the return of your core rust functions. 1. Provide a conversion `IntoPy` trait to convert to the relevant PyO3 types. 1. Use this new type as the return of your wrapped function. From e0ee223cf550fbbe64108c080a325157be14c7ed Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Fri, 7 Jun 2024 08:39:55 +0000 Subject: [PATCH 27/29] fmt --- rust/fizzbuzzo3/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index bafed91..c39cfd3 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -1,11 +1,7 @@ use std::ops::Neg; use fizzbuzz::{FizzBuzz, FizzBuzzAnswer, MultiFizzBuzz}; -use pyo3::{ - exceptions::PyValueError, - prelude::*, - types::PySlice, -}; +use pyo3::{exceptions::PyValueError, prelude::*, types::PySlice}; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; #[derive(FromPyObject)] From add2264080f0de802587f5ceb17671c089e64a2b Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Fri, 7 Jun 2024 08:47:08 +0000 Subject: [PATCH 28/29] review suggestion: add test for empty list --- tests/test_fizzbuzzo3.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_fizzbuzzo3.py b/tests/test_fizzbuzzo3.py index 7d0d686..1f904ce 100644 --- a/tests/test_fizzbuzzo3.py +++ b/tests/test_fizzbuzzo3.py @@ -12,6 +12,10 @@ def test_lazy(): assert fizzbuzz(15) == "fizzbuzz" +def test_fizzbuzz_empty_list(): + assert fizzbuzz([]) == [] + + def test_float(): assert fizzbuzz(1.0) == "1" assert fizzbuzz(3.0) == "fizz" From 2cc0ba10a0d50c312c3e8c4819292af487171fb9 Mon Sep 17 00:00:00 2001 From: Mike Foster Date: Fri, 7 Jun 2024 08:47:26 +0000 Subject: [PATCH 29/29] review suggestion: documentation improvements --- docs/dev-build.md | 2 +- python/fizzbuzz/fizzbuzzo3.pyi | 3 ++- rust/fizzbuzzo3/src/lib.rs | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/dev-build.md b/docs/dev-build.md index 11e5a29..33ab1bf 100644 --- a/docs/dev-build.md +++ b/docs/dev-build.md @@ -354,7 +354,7 @@ Python also often provides single functions which can receive multiple significa 1. Implement an `enum`, or a wrapper `struct` around an existing `enum`, that holds the different types. 1. Provide one or more conversion `From` traits to convert from the return of your core rust functions. - 1. Provide a conversion `IntoPy` trait to convert to the relevant PyO3 types. + 1. Provide a conversion [`IntoPy` trait](https://docs.rs/pyo3/latest/pyo3/conversion/trait.IntoPy.html) to convert to the relevant PyO3 types. 1. Use this new type as the return of your wrapped function. In **`/rust/fizzbuzzo3/src/lib.rs`**: diff --git a/python/fizzbuzz/fizzbuzzo3.pyi b/python/fizzbuzz/fizzbuzzo3.pyi index 27d153b..0733095 100644 --- a/python/fizzbuzz/fizzbuzzo3.pyi +++ b/python/fizzbuzz/fizzbuzzo3.pyi @@ -31,7 +31,7 @@ def fizzbuzz(n): n: the number(s) to fizzbuzz Returns: - In the case of a sinlge number: a `str` with the correct fizzbuzz answer. + In the case of a single number: a `str` with the correct fizzbuzz answer. In the case of a list or range of inputs: a `list` of `str` with the correct fizzbuzz answers. Examples: @@ -63,4 +63,5 @@ def fizzbuzz(n): ``` Note: Slices are inclusive on the left, exclusive on the right and can contain an optional step. Negative steps require start > stop, positive steps require stop > start; other combinations return `[]`. + A step of zero is invalid and will raise a `ValueError`. """ diff --git a/rust/fizzbuzzo3/src/lib.rs b/rust/fizzbuzzo3/src/lib.rs index c39cfd3..8751459 100644 --- a/rust/fizzbuzzo3/src/lib.rs +++ b/rust/fizzbuzzo3/src/lib.rs @@ -25,6 +25,7 @@ impl IntoPy> for MySlice { } } +/// A wrapper struct for FizzBuzzAnswer to provide a custom implementation of `IntoPy`. struct FizzBuzzReturn(FizzBuzzAnswer); impl From for FizzBuzzReturn { @@ -51,7 +52,7 @@ impl IntoPy> for FizzBuzzReturn { /// n: the number(s) to fizzbuzz /// /// Returns: -/// In the case of a sinlge number: a `str` with the correct fizzbuzz answer. +/// In the case of a single number: a `str` with the correct fizzbuzz answer. /// In the case of a list or range of inputs: a `list` of `str` with the correct fizzbuzz answers. /// /// Examples: @@ -82,7 +83,9 @@ impl IntoPy> for FizzBuzzReturn { /// [] /// ``` /// Note: Slices are inclusive on the left, exclusive on the right and can contain an optional step. +/// Note: Slices are inclusive on the left, exclusive on the right and can contain an optional step. /// Negative steps require start > stop, positive steps require stop > start; other combinations return `[]`. +/// A step of zero is invalid and will raise a `ValueError`. #[pyfunction] #[pyo3(name = "fizzbuzz", text_signature = "(n)")] fn py_fizzbuzz(num: FizzBuzzable) -> PyResult {