Skip to content

Commit 95ce95c

Browse files
committed
Add Rust accelerator module for bisect.
1 parent 658d175 commit 95ce95c

File tree

4 files changed

+169
-18
lines changed

4 files changed

+169
-18
lines changed

Lib/bisect.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ def insort_right(a, x, lo=0, hi=None):
1919
else: lo = mid+1
2020
a.insert(lo, x)
2121

22-
insort = insort_right # backward compatibility
23-
2422
def bisect_right(a, x, lo=0, hi=None):
2523
"""Return the index where to insert item x in list a, assuming a is sorted.
2624
@@ -42,8 +40,6 @@ def bisect_right(a, x, lo=0, hi=None):
4240
else: lo = mid+1
4341
return lo
4442

45-
bisect = bisect_right # backward compatibility
46-
4743
def insort_left(a, x, lo=0, hi=None):
4844
"""Insert item x in list a, and keep it sorted assuming a is sorted.
4945
@@ -85,8 +81,11 @@ def bisect_left(a, x, lo=0, hi=None):
8581
else: hi = mid
8682
return lo
8783

88-
# Overwrite above definitions with a fast C implementation
84+
# Overwrite above definitions with a fast Rust implementation
8985
try:
9086
from _bisect import *
9187
except ImportError:
9288
pass
89+
90+
bisect = bisect_right # backward compatibility
91+
insort = insort_right # backward compatibility

Lib/test/test_bisect.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from collections import UserList
55

66
py_bisect = support.import_fresh_module('bisect', blocked=['_bisect'])
7-
c_bisect = None
7+
rust_bisect = support.import_fresh_module('bisect', fresh=['bisect'])
88

99
class Range(object):
1010
"""A trivial range()-like object that has an insert() method."""
@@ -202,9 +202,8 @@ def test_keyword_args(self):
202202
class TestBisectPython(TestBisect, unittest.TestCase):
203203
module = py_bisect
204204

205-
@unittest.skip("TODO: RUSTPYTHON, _bisect module")
206-
class TestBisectC(TestBisect, unittest.TestCase):
207-
module = c_bisect
205+
class TestBisectRust(TestBisect, unittest.TestCase):
206+
module = rust_bisect
208207

209208
#==============================================================================
210209

@@ -238,9 +237,8 @@ def insert(self, index, item):
238237
class TestInsortPython(TestInsort, unittest.TestCase):
239238
module = py_bisect
240239

241-
@unittest.skip("TODO: RUSTPYTHON, _bisect module")
242-
class TestInsortC(TestInsort, unittest.TestCase):
243-
module = c_bisect
240+
class TestInsortRust(TestInsort, unittest.TestCase):
241+
module = rust_bisect
244242

245243
#==============================================================================
246244

@@ -294,9 +292,8 @@ def test_arg_parsing(self):
294292
class TestErrorHandlingPython(TestErrorHandling, unittest.TestCase):
295293
module = py_bisect
296294

297-
@unittest.skip("TODO: RUSTPYTHON, _bisect module")
298-
class TestErrorHandlingC(TestErrorHandling, unittest.TestCase):
299-
module = c_bisect
295+
class TestErrorHandlingRust(TestErrorHandling, unittest.TestCase):
296+
module = rust_bisect
300297

301298
#==============================================================================
302299

@@ -322,9 +319,8 @@ def test_colors(self):
322319
class TestDocExamplePython(TestDocExample, unittest.TestCase):
323320
module = py_bisect
324321

325-
@unittest.skip("TODO: RUSTPYTHON, _bisect module")
326-
class TestDocExampleC(TestDocExample, unittest.TestCase):
327-
module = c_bisect
322+
class TestDocExampleRust(TestDocExample, unittest.TestCase):
323+
module = rust_bisect
328324

329325
#------------------------------------------------------------------------------
330326

vm/src/stdlib/bisect.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
pub(crate) use _bisect::make_module;
2+
3+
#[pymodule]
4+
mod _bisect {
5+
use std::convert::TryFrom;
6+
7+
use crate::function::OptionalArg;
8+
use crate::slots::PyComparisonOp::Lt;
9+
use crate::vm::VirtualMachine;
10+
use crate::{ItemProtocol, PyObjectRef, PyResult};
11+
use num_traits::ToPrimitive;
12+
13+
#[derive(FromArgs)]
14+
struct BisectArgs {
15+
#[pyarg(any, name = "a")]
16+
a: PyObjectRef,
17+
#[pyarg(any, name = "x")]
18+
x: PyObjectRef,
19+
#[pyarg(any, optional, name = "lo")]
20+
lo: OptionalArg<PyObjectRef>,
21+
#[pyarg(any, optional, name = "hi")]
22+
hi: OptionalArg<PyObjectRef>,
23+
}
24+
25+
// Handles objects that implement __index__ and makes sure index fits in needed isize.
26+
#[inline]
27+
fn handle_default(
28+
arg: OptionalArg<PyObjectRef>,
29+
default: Option<isize>,
30+
vm: &VirtualMachine,
31+
) -> PyResult<Option<isize>> {
32+
Ok(match arg {
33+
OptionalArg::Present(v) => {
34+
Some(vm.to_index(&v)?.as_bigint().to_isize().ok_or_else(|| {
35+
vm.new_index_error("cannot fit 'int' into an index-sized integer".to_owned())
36+
})?)
37+
}
38+
OptionalArg::Missing => default,
39+
})
40+
}
41+
42+
// Handles defaults for lo, hi.
43+
//
44+
// - lo must be >= 0 with a default of 0.
45+
// - hi, while it could be negative, defaults to `0` and the same effect is achieved
46+
// (while loop isn't entered); this way we keep it a usize (and, if my understanding
47+
// is correct, issue 13496 is handled). Its default value is set to the length of the
48+
// input sequence.
49+
#[inline]
50+
fn as_usize(
51+
lo: OptionalArg<PyObjectRef>,
52+
hi: OptionalArg<PyObjectRef>,
53+
seq_len: usize,
54+
vm: &VirtualMachine,
55+
) -> PyResult<(usize, usize)> {
56+
// We only deal with positives for lo, try_from can't fail.
57+
// Default is always a Some so we can safely unwrap.
58+
let lo = handle_default(lo, Some(0), vm)?
59+
.map(|value| {
60+
if value < 0 {
61+
return Err(vm.new_value_error("lo must be non-negative".to_owned()));
62+
}
63+
Ok(usize::try_from(value).unwrap())
64+
})
65+
.unwrap()?;
66+
let hi = handle_default(hi, None, vm)?
67+
.map(|value| {
68+
if value < 0 {
69+
0
70+
} else {
71+
// Can't fail.
72+
usize::try_from(value).unwrap()
73+
}
74+
})
75+
.unwrap_or(seq_len);
76+
Ok((lo, hi))
77+
}
78+
79+
#[inline]
80+
fn bisect_left_impl(
81+
BisectArgs { a, x, lo, hi }: BisectArgs,
82+
vm: &VirtualMachine,
83+
) -> PyResult<usize> {
84+
let (mut lo, mut hi) = as_usize(lo, hi, vm.obj_len(&a)?, vm)?;
85+
86+
while lo < hi {
87+
// Handles issue 13496.
88+
let mid = (lo + hi) / 2;
89+
if vm.bool_cmp(&a.get_item(mid, vm)?, &x, Lt)? {
90+
lo = mid + 1;
91+
} else {
92+
hi = mid;
93+
}
94+
}
95+
Ok(lo)
96+
}
97+
98+
#[inline]
99+
fn bisect_right_impl(
100+
BisectArgs { a, x, lo, hi }: BisectArgs,
101+
vm: &VirtualMachine,
102+
) -> PyResult<usize> {
103+
let (mut lo, mut hi) = as_usize(lo, hi, vm.obj_len(&a)?, vm)?;
104+
105+
while lo < hi {
106+
// Handles issue 13496.
107+
let mid = (lo + hi) / 2;
108+
if vm.bool_cmp(&x, &a.get_item(mid, vm)?, Lt)? {
109+
hi = mid;
110+
} else {
111+
lo = mid + 1;
112+
}
113+
}
114+
Ok(lo)
115+
}
116+
117+
#[pyfunction]
118+
fn bisect_left(args: BisectArgs, vm: &VirtualMachine) -> PyResult<usize> {
119+
bisect_left_impl(args, vm)
120+
}
121+
122+
#[pyfunction]
123+
fn bisect_right(args: BisectArgs, vm: &VirtualMachine) -> PyResult<usize> {
124+
bisect_right_impl(args, vm)
125+
}
126+
127+
#[pyfunction]
128+
fn insort_left(BisectArgs { a, x, lo, hi }: BisectArgs, vm: &VirtualMachine) -> PyResult {
129+
let index = bisect_left_impl(
130+
BisectArgs {
131+
a: a.clone(),
132+
x: x.clone(),
133+
lo,
134+
hi,
135+
},
136+
vm,
137+
)?;
138+
vm.call_method(&a, "insert", (index, x))
139+
}
140+
141+
#[pyfunction]
142+
fn insort_right(BisectArgs { a, x, lo, hi }: BisectArgs, vm: &VirtualMachine) -> PyResult {
143+
let index = bisect_right_impl(
144+
BisectArgs {
145+
a: a.clone(),
146+
x: x.clone(),
147+
lo,
148+
hi,
149+
},
150+
vm,
151+
)?;
152+
vm.call_method(&a, "insert", (index, x))
153+
}
154+
}

vm/src/stdlib/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod array;
88
pub(crate) mod ast;
99
mod atexit;
1010
mod binascii;
11+
mod bisect;
1112
mod codecs;
1213
mod collections;
1314
mod csv;
@@ -104,6 +105,7 @@ pub fn get_module_inits() -> StdlibMap {
104105
"array" => array::make_module,
105106
"atexit" => atexit::make_module,
106107
"binascii" => binascii::make_module,
108+
"_bisect" => bisect::make_module,
107109
"_codecs" => codecs::make_module,
108110
"_collections" => collections::make_module,
109111
"_csv" => csv::make_module,

0 commit comments

Comments
 (0)