bisection-0.1.0/.cargo_vcs_info.json0000644000000001121372377377000131210ustar00{ "git": { "sha1": "e5550fd4a136c60ab41c3be81209dd5539aa392d" } } bisection-0.1.0/.gitignore010066400017500001750000000000231371713730700137060ustar0000000000000000/target Cargo.lock bisection-0.1.0/Cargo.lock0000644000000150151372377377000111040ustar00# This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "autocfg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "bisection" version = "0.1.0" dependencies = [ "proptest", ] [[package]] name = "bit-set" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getrandom" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" [[package]] name = "num-traits" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" dependencies = [ "autocfg", ] [[package]] name = "ppv-lite86" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] name = "proptest" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2520fe6373cf6a3a61e2d200e987c183778ade8d9248ac3e6614ab0edfe4a0c1" dependencies = [ "bit-set", "bitflags", "byteorder", "lazy_static", "num-traits", "quick-error", "rand", "rand_chacha", "rand_xorshift", "regex-syntax", "rusty-fork", "tempfile", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom", "libc", "rand_chacha", "rand_core", "rand_hc", ] [[package]] name = "rand_chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ "getrandom", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ "rand_core", ] [[package]] name = "rand_xorshift" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" dependencies = [ "rand_core", ] [[package]] name = "redox_syscall" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "regex-syntax" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" [[package]] name = "remove_dir_all" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ "winapi", ] [[package]] name = "rusty-fork" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", "quick-error", "tempfile", "wait-timeout", ] [[package]] name = "tempfile" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ "cfg-if", "libc", "rand", "redox_syscall", "remove_dir_all", "winapi", ] [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[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" bisection-0.1.0/Cargo.toml0000644000000017571372377377000111370ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "bisection" version = "0.1.0" authors = ["Ben Steadman "] description = "Rust implementation of the Python bisect module" homepage = "https://github.com/SteadBytes/bisection" readme = "README.md" keywords = ["bisect", "sorting", "searching", "binary-search"] categories = ["algorithms", "data-structures"] license = "MIT" repository = "https://github.com/SteadBytes/bisection" [dev-dependencies.proptest] version = "0.10.0" bisection-0.1.0/Cargo.toml.orig0000644000000007331372377377000120670ustar00[package] name = "bisection" version = "0.1.0" authors = ["Ben Steadman "] description = "Rust implementation of the Python bisect module" categories = ["algorithms", "data-structures"] keywords = ["bisect", "sorting", "searching", "binary-search"] repository = "https://github.com/SteadBytes/bisection" homepage = "https://github.com/SteadBytes/bisection" readme = "README.md" license = "MIT" edition = "2018" [dev-dependencies] proptest = "0.10.0" bisection-0.1.0/Cargo.toml.orig010066400017500001750000000007331372377345100146200ustar0000000000000000[package] name = "bisection" version = "0.1.0" authors = ["Ben Steadman "] description = "Rust implementation of the Python bisect module" categories = ["algorithms", "data-structures"] keywords = ["bisect", "sorting", "searching", "binary-search"] repository = "https://github.com/SteadBytes/bisection" homepage = "https://github.com/SteadBytes/bisection" readme = "README.md" license = "MIT" edition = "2018" [dev-dependencies] proptest = "0.10.0" bisection-0.1.0/README.md010066400017500001750000000004201372377364500132060ustar0000000000000000# bisection ![Crates.io](https://img.shields.io/crates/d/bisection) Rust implementation of the Python [`bisect` module](https://docs.python.org/3/library/bisect.html). ## Why `bisection`? `bisect` was [already taken](https://crates.io/crates/bisect) :man_shrugging: bisection-0.1.0/src/lib.rs010066400017500001750000000514511372377345100136370ustar0000000000000000pub use crate::bisect_right as bisect; pub use crate::insort_right as insort; use std::cmp::Ordering; use std::ops::{Bound::*, RangeBounds}; // TODO: Doctest examples /// Insert `x` in `a[within]`, keeping it sorted assuming `a` is sorted. /// /// If `a` contains `x`, insert it just *after* the *rightmost* occurence of `x`. /// /// # Panics /// /// Panics if `within` is out of bounds of `a`. pub fn insort_right_slice(a: &mut Vec, x: T, within: I) where I: RangeBounds, T: Ord, { insort_right_slice_by(a, x, within, T::cmp); } /// Insert `x` in `a`, keeping it sorted assuming `a` is sorted. /// If `a` contains `x`, insert it just *after* the *rightmost* occurence of `x`. pub fn insort_right(a: &mut Vec, x: T) where T: Ord, { insort_right_by(a, x, T::cmp); } /// Insert `x` in `a`, keeping it sorted, assuming `a` is sorted, according to a comparator /// function. /// /// The comparator function should implement an order consistent with the sort order of the /// underlying slice. /// /// If `a` contains `x`, insert it just *after* the *rightmost* occurence of `x`. pub fn insort_right_by(a: &mut Vec, x: T, f: F) where T: Ord, F: FnMut(&T, &T) -> Ordering, { insort_right_slice_by(a, x, .., f); } /// Insert x in `a[within]`, keeping it sorted, assuming `a` is sorted, according to a comparator /// function. /// /// The comparator function should implement an order consistent with the sort order of the /// underlying slice. /// /// If `a` contains `x`, insert it just *after* the *rightmost* occurence of `x`. /// /// # Panics /// /// Panics if `within` is out of bounds of `a`. pub fn insort_right_slice_by(a: &mut Vec, x: T, within: I, mut f: F) where I: RangeBounds, F: FnMut(&T, &T) -> Ordering, { let lo = bisect_right_slice_by(a, within, |p| f(&x, p)); a.insert(lo, x); } /// Return the index where `x` should be inserted in `a[within]`, assuming `a` /// is sorted. /// /// The return value `i` is such that all `e` in `a[..i]` have `e <= x`, and /// all `e` in `a[i..]` have `e > x`. /// - If `a` contains `x`, `a.insert(i, x)` will insert just *after* the /// *rightmost* `x`. /// /// # Panics /// /// Panics if `within` is out of bounds of `a`. pub fn bisect_right_slice(a: &[T], x: &T, within: I) -> usize where I: RangeBounds, T: Ord, { bisect_right_slice_by(a, within, |p| x.cmp(p)) } /// Return the index where `x` should be inserted in `a`, assuming `a` is sorted. /// /// The return value `i` is such that all `e` in `a[..i]` have `e <= x`, and /// all `e` in `a[i..]` have `e > x`. /// - If `a` contains `x`, `a.insert(i, x)` will insert just *after* the /// *rightmost* occurence of `x`. pub fn bisect_right(a: &[T], x: &T) -> usize where T: Ord, { bisect_right_slice(a, x, ..) } /// Return the index where `x` should be inserted in `a`, assuming `a` is sorted, according to /// a comparator function. /// /// The comparator function should implement an order consistent with the sort order of the /// underlying slice, returning an order code that indicates whethers its argument is `Less`, /// `Equal` or `Greater` that the **desired target**. /// /// The return value `i` is such that all `e` in `a[..i]` have `f(e) == Less | f(e) == Equal`, and /// all `e` in `a[i..]` have `f(e) == Greater`. /// - If `a` contains `x`, `a.insert(i, x)` will insert just *after* the /// *rightmost* occurence of `x`. pub fn bisect_right_by(a: &[T], f: F) -> usize where F: FnMut(&T) -> Ordering, { bisect_right_slice_by(a, .., f) } /// Return the index where a value should be inserted in `a[within]`, assuming it sorted, /// according to a comparator function. /// /// The comparator function should implement an order consistent with the sort order of the /// underlying slice, returning an order code that indicates whethers its argument is `Less`, /// `Equal` or `Greater` that the **desired target**. /// /// The return value `i` is such that all `e` in `a[..i]` have `f(e) == Less | f(e) == Equal`, and /// all `e` in `a[i..]` have `f(e) == Greater`. /// - If `a` contains `x`, `a.insert(i, x)` will insert just *after* the /// *rightmost* occurence of `x`. /// /// # Panics /// /// Panics if `within` is out of bounds of `a`. pub fn bisect_right_slice_by(a: &[T], within: I, mut f: F) -> usize where I: RangeBounds, F: FnMut(&T) -> Ordering, { let (mut lo, mut hi) = bounds_to_indices(a, within); while lo < hi { let mid = (lo + hi) / 2; if f(&a[mid]) == Ordering::Less { hi = mid; } else { lo = mid + 1; } } lo } /// Insert `x` in `a[within]`, keeping it sorted assuming `a` is sorted. /// /// If `a` contains `x`, insert it just *before* the *leftmost* occurence of `x`. /// /// # Panics /// /// Panics if `within` is out of bounds of `a`. pub fn insort_left_slice(a: &mut Vec, x: T, within: I) where I: RangeBounds, T: Ord, { insort_left_slice_by(a, x, within, T::cmp); } /// Insert `x` in `a`, keeping it sorted assuming `a` is sorted. /// /// If `a` contains `x`, insert it just *before* the *leftmost* occurence of `x`. pub fn insort_left(a: &mut Vec, x: T) where T: Ord, { insort_left_by(a, x, T::cmp); } /// Insert `x` in `a`, keeping it sorted, assuming `a` is sorted, according to a comparator /// function. /// /// The comparator function should implement an order consistent with the sort order of the /// underlying slice. /// /// If `a` contains `x`, insert it just *before* the *leftmost* occurence of `x`. pub fn insort_left_by(a: &mut Vec, x: T, f: F) where T: Ord, F: FnMut(&T, &T) -> Ordering, { insort_left_slice_by(a, x, .., f); } /// Insert x in `a[within]`, keeping it sorted, assuming `a` is sorted, according to a comparator /// function. /// /// The comparator function should implement an order consistent with the sort order of the /// underlying slice. /// /// If `a` contains `x`, insert it just *before* the *leftmost* occurence of `x`. /// /// # Panics /// /// Panics if `within` is out of bounds of `a`. pub fn insort_left_slice_by(a: &mut Vec, x: T, within: I, mut f: F) where I: RangeBounds, F: FnMut(&T, &T) -> Ordering, { let lo = bisect_right_slice_by(a, within, |p| f(&x, p)); a.insert(lo, x); } /// Return the index where `x` should be inserted in `a[within]`, assuming `a` /// is sorted. /// /// The return value `i` is such that all `e` in `a[..i]` have `e < x`, and /// all `e` in `a[i..]` have `e >= x`. /// - If `a` contains `x`, `a.insert(i, x)` will insert just *before* the /// *leftmost* `x`. /// /// # Panics /// /// Panics if `within` is out of bounds of `a`. pub fn bisect_left_slice(a: &[T], x: &T, within: I) -> usize where I: RangeBounds, T: Ord, { bisect_left_slice_by(a, within, |p| p.cmp(x)) } /// Return the index where `x` should be inserted in `a`, assuming `a` is sorted. /// /// The return value `i` is such that all `e` in `a[..i]` have `e < x`, and /// all `e` in `a[i..]` have `e >= x`. /// - If `a` contains `x`, `a.insert(i, x)` will insert just *before* the /// *leftmost* `x`. pub fn bisect_left(a: &[T], x: &T) -> usize where T: Ord, { bisect_left_slice(a, x, ..) } /// Return the index where a value should be inserted in `a`, assuming `a` is sorted, according to /// a comparator function. /// /// The comparator function should implement an order consistent with the sort order of the /// underlying slice, returning an order code that indicates whethers its argument is `Less`, /// `Equal` or `Greater` that the **desired target**. /// /// The return value `i` is such that all `e` in `a[..i]` have `f(e) == Less`, and /// all `e` in `a[i..]` have `f(e) == Greater | f(e) == Equal` /// - If `a` contains `x`, `a.insert(i, x)` will insert just *before* the /// *leftmost* `x`. pub fn bisect_left_by(a: &[T], f: F) -> usize where F: FnMut(&T) -> Ordering, { bisect_left_slice_by(a, .., f) } /// Return the index where a value should be inserted in `a[within]`, assuming it sorted, /// according to a comparator function. /// /// The comparator function should implement an order consistent with the sort order of the /// underlying slice, returning an order code that indicates whethers its argument is `Less`, /// `Equal` or `Greater` that the **desired target**. /// /// The return value `i` is such that all `e` in `a[..i]` have `f(e) == Less`, and /// all `e` in `a[i..]` have `f(e) == Greater | f(e) == Equal` /// - If `a` contains `x`, `a.insert(i, x)` will insert just *before* the /// *leftmost* `x`. /// # Panics /// /// Panics if `within` is out of bounds of `a`. pub fn bisect_left_slice_by(a: &[T], within: I, mut f: F) -> usize where I: RangeBounds, F: FnMut(&T) -> Ordering, { let (mut lo, mut hi) = bounds_to_indices(a, within); while lo < hi { let mid = (lo + hi) / 2; let cmp = f(&a[mid]); if cmp == Ordering::Less { lo = mid + 1; } else { hi = mid; } } lo } /// Convert bounds to a `(lo, hi)` pair for indexing into a slice of `a`. /// /// # Panics /// /// Panics if `within` is out of bounds of `a`. fn bounds_to_indices(a: &[T], within: I) -> (usize, usize) where I: RangeBounds, { // TODO: Panics let lo = match within.start_bound() { Unbounded => 0, Included(i) => *i, Excluded(i) => i + 1, }; let hi = match within.end_bound() { Unbounded => a.len(), Included(i) => i + 1, Excluded(i) => *i, }; if hi > a.len() { panic!("index out of bounds") } (lo, hi) } #[cfg(test)] mod tests { use super::*; use proptest::prelude::*; use std::collections::HashSet; use std::iter::FromIterator; #[derive(Clone, Debug)] struct BisectTest where T: Ord, { name: &'static str, a: &'static [T], x: T, expected_index: usize, } #[derive(Debug, Clone)] enum TestDirection { Left, Right, } type TestCollection = &'static [BisectTest]; macro_rules! t { ($name:ident, $a:expr, $x:expr, $expected_index:expr) => { BisectTest { name: stringify!($name), a: $a, x: $x, expected_index: $expected_index, } }; } const RIGHT_INT_CASES: TestCollection = &[ t!(ints_right_0, &[], 1, 0), t!(ints_right_1, &[1], 0, 0), t!(ints_right_2, &[1], 1, 1), t!(ints_right_3, &[1], 2, 1), t!(ints_right_4, &[1, 1], 0, 0), t!(ints_right_5, &[1, 1], 1, 2), t!(ints_right_6, &[1, 1], 2, 2), t!(ints_right_7, &[1, 1, 1], 0, 0), t!(ints_right_8, &[1, 1, 1], 1, 3), t!(ints_right_9, &[1, 1, 1], 2, 3), t!(ints_right_10, &[1, 1, 1, 1], 0, 0), t!(ints_right_11, &[1, 1, 1, 1], 1, 4), t!(ints_right_12, &[1, 1, 1, 1], 2, 4), t!(ints_right_13, &[1, 2], 0, 0), t!(ints_right_14, &[1, 2], 1, 1), t!(ints_right_15, &[1, 2], 2, 2), t!(ints_right_16, &[1, 2], 3, 2), t!(ints_right_17, &[1, 1, 2, 2], 0, 0), t!(ints_right_18, &[1, 1, 2, 2], 1, 2), t!(ints_right_19, &[1, 1, 2, 2], 2, 4), t!(ints_right_20, &[1, 1, 2, 2], 3, 4), t!(ints_right_21, &[1, 2, 3], 0, 0), t!(ints_right_22, &[1, 2, 3], 1, 1), t!(ints_right_23, &[1, 2, 3], 2, 2), t!(ints_right_24, &[1, 2, 3], 3, 3), t!(ints_right_25, &[1, 2, 3], 4, 3), t!(ints_right_26, &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 0, 0), t!(ints_right_27, &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 1, 1), t!(ints_right_28, &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 2, 3), t!(ints_right_29, &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 3, 6), t!(ints_right_30, &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 4, 10), t!(ints_right_31, &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 5, 10), ]; const LEFT_INT_CASES: TestCollection = &[ t!(ints_left_0, &[], 1, 0), t!(ints_left_1, &[1], 0, 0), t!(ints_left_2, &[1], 1, 0), t!(ints_left_3, &[1], 2, 1), t!(ints_left_4, &[1, 1], 0, 0), t!(ints_left_5, &[1, 1], 1, 0), t!(ints_left_6, &[1, 1], 2, 2), t!(ints_left_7, &[1, 1, 1], 0, 0), t!(ints_left_8, &[1, 1, 1], 1, 0), t!(ints_left_9, &[1, 1, 1], 2, 3), t!(ints_left_10, &[1, 1, 1, 1], 0, 0), t!(ints_left_11, &[1, 1, 1, 1], 1, 0), t!(ints_left_12, &[1, 1, 1, 1], 2, 4), t!(ints_left_13, &[1, 2], 0, 0), t!(ints_left_14, &[1, 2], 1, 0), t!(ints_left_15, &[1, 2], 2, 1), t!(ints_left_16, &[1, 2], 3, 2), t!(ints_left_17, &[1, 1, 2, 2], 0, 0), t!(ints_left_18, &[1, 1, 2, 2], 1, 0), t!(ints_left_19, &[1, 1, 2, 2], 2, 2), t!(ints_left_20, &[1, 1, 2, 2], 3, 4), t!(ints_left_21, &[1, 2, 3], 0, 0), t!(ints_left_22, &[1, 2, 3], 1, 0), t!(ints_left_23, &[1, 2, 3], 2, 1), t!(ints_left_24, &[1, 2, 3], 3, 2), t!(ints_left_25, &[1, 2, 3], 4, 3), t!(ints_left_26, &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 0, 0), t!(ints_left_27, &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 1, 0), t!(ints_left_28, &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 2, 1), t!(ints_left_29, &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 3, 3), t!(ints_left_30, &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 4, 6), t!(ints_left_31, &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 5, 10), ]; #[test] fn bisect_right_precomputed() { run_bisect_tests(TestDirection::Right, RIGHT_INT_CASES); } #[test] fn bisect_left_precomputed() { run_bisect_tests(TestDirection::Left, LEFT_INT_CASES); } #[test] fn bisect_right_slice_precomputed() { run_bisect_slice_tests(TestDirection::Right, RIGHT_INT_CASES); } #[test] fn bisect_left_slice_precomputed() { run_bisect_slice_tests(TestDirection::Left, LEFT_INT_CASES); } #[test] #[should_panic] fn right_slice_index_out_of_bounds() { let a: Vec = (0..10).collect(); // 10 does not fit within 5..10 so the search goes into the out of bounds range bisect_right_slice(&a, &10, 5..15); } #[test] #[should_panic] fn left_slice_index_out_of_bounds() { let a: Vec = (0..10).collect(); // 10 does not fit within 5..10 so the search goes into the out of bounds range bisect_left_slice(&a, &10, 5..15); } #[test] #[should_panic] fn right_slice_index_out_of_bounds_when_not_searched() { let a: Vec = (0..10).collect(); // 5 fits within 0..10 bounds so the search *doesnt* actually index into the out of bounds bisect_right_slice(&a, &5, ..15); } #[test] #[should_panic] fn left_slice_index_out_of_bounds_when_not_searched() { let a: Vec = (0..10).collect(); // 5 fits within 0..10 bounds so the search *doesnt* actually index into the out of bounds bisect_left_slice(&a, &5, ..15); } fn run_bisect_tests(direction: TestDirection, test_cases: TestCollection) { let bisect_func = match direction { TestDirection::Left => bisect_left, TestDirection::Right => bisect_right, }; for test_case in test_cases { let data = test_case.a.to_vec(); assert_eq!(test_case.expected_index, bisect_func(&data, &test_case.x)); } } fn run_bisect_slice_tests( direction: TestDirection, test_cases: TestCollection, ) { let bisect_func = match direction { TestDirection::Left => bisect_left_slice, TestDirection::Right => bisect_right_slice, }; for test_case in test_cases { let data = test_case.a.to_vec(); for lo in 0..4 { for hi in 3..8 { let hi = std::cmp::min(data.len(), hi); let ip = bisect_func(&data, &test_case.x, lo..hi); match direction { TestDirection::Left => { if ip < hi { assert!(test_case.x <= data[ip]); } if ip > lo { assert!(data[ip - 1] < test_case.x) } } TestDirection::Right => { if ip < hi { assert!(test_case.x < data[ip]); } if ip > lo { assert!(data[ip - 1] <= test_case.x) } } } assert_eq!( ip, std::cmp::max(lo, std::cmp::min(hi, test_case.expected_index)) ); } } } } #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] struct Person { name: String, age: u32, } fn arb_person() -> impl Strategy { ("[a-z]*", 1..100_u32).prop_map(|(name, age)| Person { name, age }) } fn check_index_right_invariant(a: &[T], target: &T, index: usize, mut f: F) where F: FnMut(&T, &T) -> Ordering, { // See `bisect_right_by` docs assert!(a[..index].iter().all(|x| match f(x, &target) { Ordering::Less | Ordering::Equal => true, _ => false, })); assert!(a[index..] .iter() .all(|x| f(x, &target) == Ordering::Greater)); } fn check_index_left_invariant(a: &[T], target: &T, index: usize, mut f: F) where F: FnMut(&T, &T) -> Ordering, { // See `bisect_left_by` docs assert!(a[..index].iter().all(|x| f(x, &target) == Ordering::Less)); assert!(a[index..].iter().all(|x| match f(x, &target) { Ordering::Greater | Ordering::Equal => true, _ => false, })); } proptest! { #[test] fn test_bisect_left_index_invariant( mut nums in prop::collection::vec(any::(), 0..500), num in any::() ) { nums.sort(); let i = bisect_left(&nums, &num); check_index_left_invariant(&nums, &num, i, u32::cmp); } #[test] fn test_bisect_left_by_index_invariant( mut people in prop::collection::vec(arb_person(), 0..500), new_person in arb_person() ) { // Sort by age only let f = |a: &Person, b: &Person| a.age.cmp(&b.age); people.sort_by(f); let i = bisect_left_by(&people, |p| f(p, &new_person)); check_index_left_invariant(&people, &new_person, i, f); // Sort by name only let f = |a: &Person, b: &Person| a.name.cmp(&b.name); people.sort_by(f); let i = bisect_left_by(&people, |p| f(p, &new_person)); check_index_left_invariant(&people, &new_person, i, f); } #[test] fn test_bisect_right_index_invariant( mut nums in prop::collection::vec(any::(), 0..500), num in any::() ) { nums.sort(); let i = bisect_right(&nums, &num); check_index_right_invariant(&nums, &num, i, u32::cmp) } #[test] fn test_bisect_right_by_index_invariant( mut people in prop::collection::vec(arb_person(), 0..500), new_person in arb_person() ) { // Sort by age only let f = |a: &Person, b: &Person| a.age.cmp(&b.age); people.sort_by(f); let i = bisect_right_by(&people, |p| f(&new_person, p)); check_index_right_invariant(&people, &new_person, i, f); // Sort by name only let f = |a: &Person, b: &Person| a.name.cmp(&b.name); people.sort_by(f); let i = bisect_right_by(&people, |p| f(&new_person, p)); check_index_right_invariant(&people, &new_person, i, f); } #[test] fn test_insort_vs_vec_sort( digits in prop::collection::vec(0..10, 0..500) ) { let left_digits = HashSet::::from_iter(vec![0, 2, 4, 6, 8]); let mut insorted = vec![]; for digit in digits { let f = if left_digits.contains(&digit) { insort_left } else { insort_right }; f(&mut insorted, digit); } let vec_sorted = { let mut v = insorted.clone(); v.sort(); v }; assert_eq!(vec_sorted, insorted); } } }