diff --git a/.gitignore b/.gitignore index b6e4761..46147bb 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ + +# VS Code +.vscode diff --git a/README.md b/README.md index 7c76f91..b25e5c9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ # ulist Ultra fast list - Python bindings to Rust Vector. + + +### Maturin +Build and publish crates with pyo3, rust-cpython and cffi bindings as well as rust binaries as python packages. +* `maturin publish` builds the crate into python packages and publishes them to pypi. +* `maturin build` builds the wheels and stores them in a folder (target/wheels by default), but doesn't upload them. It's possible to upload those with twine. +* `maturin develop` builds the crate and installs it as a python module directly in the current virtualenv. Note that while maturin develop is faster, it doesn't support all the feature that running pip install after `maturin build` supports. +* `maturin build --release` If we want to benchmark the package. diff --git a/test.py b/test.py new file mode 100644 index 0000000..c9bbe80 --- /dev/null +++ b/test.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +""" +@Author: tushushu +@Date: 2021-11-14 16:02:00 +""" +import pytest +from ulist import FloatList, IntegerList +from typing import Union, List, Optional + +LIST_TYPE = Union[FloatList, IntegerList] +NUM_TYPE = Union[float, int] + + +@pytest.mark.parametrize( + "test_class, nums", + [(FloatList, [1.0, 2.0, 3.0, 4.0, 5.0]), (IntegerList, [1, 2, 3, 4, 5])], +) +@pytest.mark.parametrize( + "test_method, expected_value, expected_type", + [ + ("max", 5.0, None), + ("mean", 3.0, float), + ("min", 1.0, None), + ("size", 5, int), + ("sum", 15.0, None), + ], +) +def test( + test_class: LIST_TYPE, + nums: List[NUM_TYPE], + test_method: str, + expected_value: NUM_TYPE, + expected_type: Optional[NUM_TYPE], +) -> None: + arr = test_class(nums) + result = getattr(arr, test_method)() + msg = ( + f"test_class - {test_class}" + + f" test_method - {test_method}" + + f" result - {result}" + + f" expected - {expected_value}" + ) + assert result == expected_value, msg + + if expected_type is None: + if test_class is FloatList: + expected_type = float + elif test_class is IntegerList: + expected_type = int + assert type(result) == expected_type, msg + + assert len(arr) == arr.size() diff --git a/ulist/Cargo.lock b/ulist/Cargo.lock new file mode 100644 index 0000000..a4d475b --- /dev/null +++ b/ulist/Cargo.lock @@ -0,0 +1,338 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "indoc" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47741a8bc60fb26eb8d6e0238bbb26d8575ff623fdc97b1a2c00c050b9684ed8" +dependencies = [ + "indoc-impl", + "proc-macro-hack", +] + +[[package]] +name = "indoc-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce046d161f000fffde5f432a0d034d0341dc152643b2598ed5bfce44c4f3a8f0" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", + "unindent", +] + +[[package]] +name = "instant" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "libc" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" + +[[package]] +name = "lock_api" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "paste" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +dependencies = [ + "paste-impl", + "proc-macro-hack", +] + +[[package]] +name = "paste-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" +dependencies = [ + "proc-macro-hack", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "pyo3" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35100f9347670a566a67aa623369293703322bb9db77d99d7df7313b575ae0c8" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "parking_lot", + "paste", + "pyo3-build-config", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d12961738cacbd7f91b7c43bc25cfeeaa2698ad07a04b3be0aa88b950865738f" +dependencies = [ + "once_cell", +] + +[[package]] +name = "pyo3-macros" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0bc5215d704824dfddddc03f93cb572e1155c68b6761c37005e1c288808ea8" +dependencies = [ + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71623fc593224afaab918aa3afcaf86ed2f43d34f6afde7f3922608f253240df" +dependencies = [ + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "smallvec" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" + +[[package]] +name = "syn" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "ulist" +version = "0.1.0" +dependencies = [ + "num", + "pyo3", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "unindent" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/ulist/Cargo.toml b/ulist/Cargo.toml new file mode 100644 index 0000000..28e8615 --- /dev/null +++ b/ulist/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "ulist" +version = "0.1.0" +authors = ["tushushu"] +edition = "2018" + +[lib] +name = "ulist" +# "cdylib" is necessary to produce a shared library for Python to import from. +# +# Downstream Rust code (including code in `bin/`, `examples/`, and `tests/`) will not be able +# to `use ulist;` unless the "rlib" or "lib" crate type is also included, e.g.: +# crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib"] + +[dependencies] +num = "0.4.0" + +[dependencies.pyo3] +version = "0.14.5" +features = ["extension-module"] \ No newline at end of file diff --git a/ulist/src/lib.rs b/ulist/src/lib.rs new file mode 100644 index 0000000..db3c8e0 --- /dev/null +++ b/ulist/src/lib.rs @@ -0,0 +1,154 @@ +use num::traits::AsPrimitive; +use pyo3::class::sequence::PySequenceProtocol; +use pyo3::prelude::*; +use std::iter::Sum; + +/// An abstract List. +trait List<'a, T> +where + T: AsPrimitive + Sum<&'a T>, +{ + fn values(&'a self) -> &'a Vec; + + // Arrange the following methods in alphabetical order. + fn max(&'a self) -> T; + + fn mean(&'a self) -> f32 { + let numeritor: f32 = self.sum().as_(); + let denominator: f32 = self.size().as_(); + numeritor / denominator + } + + fn min(&'a self) -> T; + + fn size(&'a self) -> usize { + self.values().len() + } + + fn sum(&'a self) -> T { + self.values().iter().sum() + } +} + +/// List for f32. +#[pyclass] +struct FloatList { + list: Vec, +} + +#[pymethods] +impl FloatList { + #[new] + fn new(list: Vec) -> Self { + FloatList { list } + } + + pub fn max(&self) -> f32 { + List::max(self) + } + + pub fn mean(&self) -> f32 { + List::mean(self) + } + + pub fn min(&self) -> f32 { + List::min(self) + } + + pub fn size(&self) -> usize { + List::size(self) + } + + pub fn sum(&self) -> f32 { + List::sum(self) + } +} + +impl<'a> List<'a, f32> for FloatList { + fn values(&'a self) -> &'a Vec { + &self.list + } + + fn max(&'a self) -> f32 { + self.values() + .iter() + .fold(f32::NEG_INFINITY, |x, &y| x.max(y)) + } + + fn min(&'a self) -> f32 { + self.values().iter().fold(f32::INFINITY, |x, &y| x.min(y)) + } +} + +#[pyproto] +impl PySequenceProtocol for FloatList { + fn __len__(&self) -> usize { + self.size() + } +} + +/// List for i32. +#[pyclass] +struct IntegerList { + list: Vec, +} + +#[pymethods] +impl IntegerList { + #[new] + fn new(list: Vec) -> Self { + IntegerList { list } + } + + pub fn max(&self) -> i32 { + List::max(self) + } + + pub fn mean(&self) -> f32 { + List::mean(self) + } + + pub fn min(&self) -> i32 { + List::min(self) + } + + pub fn size(&self) -> usize { + List::size(self) + } + + pub fn sum(&self) -> i32 { + List::sum(self) + } +} + +impl<'a> List<'a, i32> for IntegerList { + fn values(&'a self) -> &'a Vec { + &self.list + } + + fn max(&'a self) -> i32 { + *self.values().iter().max().unwrap() + } + + fn min(&'a self) -> i32 { + *self.values().iter().min().unwrap() + } +} + +#[pyproto] +impl PySequenceProtocol for IntegerList { + fn __len__(&self) -> usize { + self.size() + } +} + +/// A Python module implemented in Rust. The name of this function must match +/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to +/// import the module. +#[pymodule] +fn ulist(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + + Ok(()) +}