Skip to content

Commit 475bf57

Browse files
authored
Merge pull request #77 from tushushu/wip-select
Wip select
2 parents 7f2a8cc + 63eb248 commit 475bf57

File tree

7 files changed

+260
-5
lines changed

7 files changed

+260
-5
lines changed
File renamed without changes.

tests/test_control_flow.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from typing import List, Union
2+
3+
import pytest
4+
import ulist as ul
5+
import operator as op
6+
from ulist.utils import check_test_result
7+
8+
LIST_TYPE = Union[List[float], List[int], List[bool]]
9+
10+
11+
@pytest.mark.parametrize(
12+
"dtype, nums, kwargs, expected_value",
13+
[
14+
(
15+
"float",
16+
[0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
17+
{
18+
"conditions": [(op.lt, 2.0), (op.lt, 4.0)],
19+
"choices": [False, True],
20+
"default": False,
21+
},
22+
[False, False, True, True, False, False],
23+
),
24+
(
25+
"float",
26+
[0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
27+
{
28+
"conditions": [(op.lt, 2.0), (op.lt, 4.0)],
29+
"choices": [0.0, 1.0],
30+
"default": 2.0,
31+
},
32+
[0.0, 0.0, 1.0, 1.0, 2.0, 2.0],
33+
),
34+
(
35+
"float",
36+
[0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
37+
{
38+
"conditions": [(op.lt, 2.0), (op.lt, 4.0)],
39+
"choices": [0, 1],
40+
"default": 2,
41+
},
42+
[0, 0, 1, 1, 2, 2],
43+
),
44+
45+
(
46+
"int",
47+
[0, 1, 2, 3, 4, 5],
48+
{
49+
"conditions": [(op.lt, 2), (op.lt, 4)],
50+
"choices": [False, True],
51+
"default": False,
52+
},
53+
[False, False, True, True, False, False],
54+
),
55+
(
56+
"int",
57+
[0, 1, 2, 3, 4, 5],
58+
{
59+
"conditions": [(op.lt, 2), (op.lt, 4)],
60+
"choices": [0.0, 1.0],
61+
"default": 2.0,
62+
},
63+
[0.0, 0.0, 1.0, 1.0, 2.0, 2.0],
64+
),
65+
(
66+
"int",
67+
[0, 1, 2, 3, 4, 5],
68+
{
69+
"conditions": [(op.lt, 2), (op.lt, 4)],
70+
"choices": [0, 1],
71+
"default": 2,
72+
},
73+
[0, 0, 1, 1, 2, 2],
74+
),
75+
],
76+
)
77+
def test_select(
78+
dtype: str,
79+
nums: LIST_TYPE,
80+
kwargs: dict,
81+
expected_value: List[bool],
82+
) -> None:
83+
arr = ul.from_seq(nums, dtype)
84+
choices = kwargs["choices"]
85+
default = kwargs["default"]
86+
conditions = [f(arr, v) for f, v in kwargs["conditions"]]
87+
result = ul.select(conditions, choices=choices, default=default)
88+
if type(expected_value[0]) == int:
89+
dtype = "int"
90+
elif type(expected_value[0]) == float:
91+
dtype = "float"
92+
elif type(expected_value[0]) == bool:
93+
dtype = "bool"
94+
else:
95+
raise TypeError(f"Unexpected type {type(expected_value[0])}!")
96+
check_test_result(dtype, "ul.select", result, expected_value)

ulist/python/ulist/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
from .control_flow import select # noqa:F401
12
from .constructor import arange, cycle, from_seq, repeat # noqa:F401
23
from .core import UltraFastList # noqa:F401

ulist/python/ulist/control_flow.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from typing import Callable, List
2+
3+
from .core import BooleanList, UltraFastList
4+
from .typedef import ELEM, LIST_PY
5+
from .ulist import select_bool as _select_bool
6+
from .ulist import select_float as _select_float
7+
from .ulist import select_int as _select_int
8+
9+
10+
def select(
11+
conditions: List[UltraFastList],
12+
choices: LIST_PY,
13+
default: ELEM,
14+
) -> UltraFastList:
15+
"""Return a ulist drawn from elements in `choices`, depending on`conditions`.
16+
17+
Args:
18+
conditions (List[UltraFastList]):
19+
The list of conditions which determine from which array in
20+
`choices` the output elements are taken. When multiple conditions
21+
are satisfied, the first one encountered in `conditions` is used.
22+
choices (LIST_PY):
23+
The list of ulist from which the output elements are taken.
24+
It has to be of the same length as `conditions`.
25+
default (ELEM):
26+
The element inserted in output when all conditions evaluate
27+
to False.
28+
29+
Raises:
30+
TypeError:
31+
The type of parameter `default` should be bool, float or int!
32+
33+
Returns:
34+
UltraFastList: A ulist object.
35+
36+
Examples
37+
--------
38+
>>> import ulist as ul
39+
>>> arr = ul.arange(6)
40+
>>> arr
41+
UltraFastList([0, 1, 2, 3, 4, 5])
42+
43+
>>> conditions = [arr < 2, arr < 4]
44+
>>> conditions
45+
[
46+
UltraFastList([True, True, False, False, False, False]),
47+
UltraFastList([True, True, True, True, False, False])
48+
]
49+
50+
>>> result = ul.select(conditions, choices=[0, 1], default=2)
51+
>>> result
52+
UltraFastList([0, 0, 1, 1, 2, 2])
53+
"""
54+
assert len(conditions) == len(choices)
55+
56+
if type(default) is bool:
57+
fn: Callable = _select_bool
58+
elif type(default) is float:
59+
fn = _select_float
60+
elif type(default) is int:
61+
fn = _select_int
62+
else:
63+
raise TypeError(
64+
"The type of parameter `default` should be bool, float or int!"
65+
)
66+
67+
_conditions = []
68+
for cond in conditions:
69+
assert isinstance(cond._values, BooleanList)
70+
_conditions.append(cond._values)
71+
result = fn(_conditions, choices, default)
72+
return UltraFastList(result)

ulist/python/ulist/ulist.pyi

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from typing import Sequence, List
1+
from typing import List, Sequence
22

3-
from .typedef import ELEM, NUM, NUM_LIST_RS
3+
from .typedef import ELEM, LIST_PY, NUM, NUM_LIST_RS
44

55

66
class BooleanList:
@@ -115,3 +115,24 @@ class IntegerList:
115115

116116

117117
def arange(start: int, stop: int, step: int) -> IntegerList: ...
118+
119+
120+
def select_bool(
121+
conditions: List[BooleanList],
122+
choices: LIST_PY,
123+
default: bool,
124+
) -> BooleanList: ...
125+
126+
127+
def select_float(
128+
conditions: List[BooleanList],
129+
choices: LIST_PY,
130+
default: float,
131+
) -> FloatList: ...
132+
133+
134+
def select_int(
135+
conditions: List[BooleanList],
136+
choices: LIST_PY,
137+
default: int,
138+
) -> IntegerList: ...

ulist/src/control_flow.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use crate::base::List;
2+
use crate::boolean::BooleanList;
3+
use crate::float::FloatList;
4+
use crate::integer::IntegerList;
5+
use pyo3::prelude::*;
6+
use pyo3::Py;
7+
use std::cmp::PartialEq;
8+
use std::marker::Copy;
9+
10+
unsafe fn select<T, U>(
11+
py: Python,
12+
conditions: &Vec<Py<BooleanList>>,
13+
choices: &Vec<T>,
14+
default: T,
15+
) -> U
16+
where
17+
T: PartialEq + Copy,
18+
U: List<T>,
19+
{
20+
let cond: Vec<PyRef<BooleanList>> = conditions.iter().map(|x| x.borrow(py)).collect();
21+
let mut vec = vec![default; cond[0].size()];
22+
for j in 0..cond[0].size() {
23+
for i in 0..cond.len() {
24+
if cond[i].get(j) {
25+
vec[j] = choices[i];
26+
break;
27+
}
28+
}
29+
}
30+
U::_new(vec)
31+
}
32+
33+
#[pyfunction]
34+
pub unsafe fn select_bool(
35+
py: Python,
36+
conditions: Vec<Py<BooleanList>>,
37+
choices: Vec<bool>,
38+
default: bool,
39+
) -> BooleanList {
40+
select::<bool, BooleanList>(py, &conditions, &choices, default)
41+
}
42+
43+
#[pyfunction]
44+
pub unsafe fn select_float(
45+
py: Python,
46+
conditions: Vec<Py<BooleanList>>,
47+
choices: Vec<f32>,
48+
default: f32,
49+
) -> FloatList {
50+
select::<f32, FloatList>(py, &conditions, &choices, default)
51+
}
52+
53+
#[pyfunction]
54+
pub unsafe fn select_int(
55+
py: Python,
56+
conditions: Vec<Py<BooleanList>>,
57+
choices: Vec<i32>,
58+
default: i32,
59+
) -> IntegerList {
60+
select::<i32, IntegerList>(py, &conditions, &choices, default)
61+
}

ulist/src/lib.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
mod base;
22
mod boolean;
3+
mod control_flow;
34
mod float;
45
mod integer;
56
mod numerical;
67
mod types;
7-
use integer::IntegerList;
8+
use control_flow::*;
89
use pyo3::prelude::*;
910

1011
// Cannot find a way to put this function in another file.
1112
#[pyfunction]
12-
pub fn arange(start: i32, stop: i32, step: usize) -> IntegerList {
13+
pub fn arange(start: i32, stop: i32, step: usize) -> integer::IntegerList {
1314
let vec = (start..stop).step_by(step).collect();
14-
IntegerList::new(vec)
15+
integer::IntegerList::new(vec)
1516
}
1617

1718
/// A Python module implemented in Rust. The name of this function must match
@@ -23,6 +24,9 @@ fn ulist(_py: Python, m: &PyModule) -> PyResult<()> {
2324
m.add_class::<float::FloatList>()?;
2425
m.add_class::<integer::IntegerList>()?;
2526
m.add_function(wrap_pyfunction!(arange, m)?)?;
27+
m.add_function(wrap_pyfunction!(select_bool, m)?)?;
28+
m.add_function(wrap_pyfunction!(select_float, m)?)?;
29+
m.add_function(wrap_pyfunction!(select_int, m)?)?;
2630

2731
Ok(())
2832
}

0 commit comments

Comments
 (0)