Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refine api #3

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,20 @@ license = "Apache-2.0"
keywords = ["Interval Tree", "Augmented Tree", "Red-Black Tree"]

[dependencies]
serde = { version = "1.0", default-features = false, features = [
"derive",
"std",
], optional = true }

[dev-dependencies]
criterion = "0.5.1"
rand = "0.8.5"
serde_json = "1.0"

[features]
default = []
interval_tree_find_overlap_ordered = []
graphviz = []
serde = ["dep:serde"]

[[bench]]
name = "bench"
Expand Down
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The implementation of the interval tree in interval_map references "Introduction
To safely and efficiently handle insertion and deletion operations in Rust, `interval_map` innovatively **uses arrays to simulate pointers** for managing the parent-child references in the red-black tree. This approach also ensures that interval_map has the `Send` and `Unpin` traits, allowing it to be safely transferred between threads and to maintain a fixed memory location during asynchronous operations.

`interval_map` implements an `IntervalMap` struct:
- It accepts `Interval<T>` as the key, where `T` can be any type that implements `Ord+Clone` trait. Therefore, intervals such as $[1, 2)$ and $["aaa", "bbb")$ are allowed
- It accepts `Interval<T>` as the key, where `T` can be any type that implements `Ord` trait. Therefore, intervals such as $[1, 2)$ and $["aaa", "bbb")$ are allowed
- The value can be of any type

`interval_map` supports `insert`, `delete`, and `iter` fns. Traversal is performed in the order of `Interval<T>` . For instance, with intervals of type `Interval<u32>`:
Expand All @@ -22,15 +22,16 @@ Currently, `interval_map` only supports half-open intervals, i.e., $[...,...)$.

The benchmark was conducted on a platform with `AMD R7 7840H + DDR5 5600MHz`. The result are as follows:
1. Only insert
| insert | 100 | 1000 | 10, 000 | 100, 000 |
| --------------- | --------- | --------- | --------- | --------- |
| Time per insert | 5.4168 µs | 80.518 µs | 2.2823 ms | 36.528 ms |
| insert | 100 | 1000 | 10, 000 | 100, 000 |
| ---------- | --------- | --------- | --------- | --------- |
| Total time | 5.4168 µs | 80.518 µs | 2.2823 ms | 36.528 ms |
2. Insert N and remove N
| insert_and_remove | 100 | 1000 | 10, 000 | 100, 000 |
| ------------------ | --------- | --------- | --------- | --------- |
| Time per operation | 10.333 µs | 223.43 µs | 4.9358 ms | 81.634 ms |
| insert_and_remove | 100 | 1000 | 10, 000 | 100, 000 |
| ----------------- | --------- | --------- | --------- | --------- |
| Total time | 10.333 µs | 223.43 µs | 4.9358 ms | 81.634 ms |

## TODO
- [] Support for $(...,...)$, $[...,...]$ and $(...,...]$ interval types.
- [] Add more tests like [etcd](https://github.com/etcd-io/etcd/blob/main/pkg/adt/interval_tree_test.go)
- [] Add Point type for Interval
- [ ] ~~Support for $(...,...)$, $[...,...]$ and $(...,...]$ interval types.~~ There's no way to support these interval type without performance loss now.
- [ ] ~~Add Point type for Interval~~ To support Point type, it should also support $[...,...]$, so it couldn't be supported now, either. But you could write code like [examples/new_point](examples/new_point.rs).
- [x] Add more tests like [etcd](https://github.com/etcd-io/etcd/blob/main/pkg/adt/interval_tree_test.go).
- [x] Refine iter mod.
101 changes: 68 additions & 33 deletions benches/bench.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,25 @@
use criterion::{criterion_group, criterion_main, Bencher, Criterion};
use interval_map::{Interval, IntervalMap};
use rand::{rngs::StdRng, Rng, SeedableRng};
use std::hint::black_box;

struct Rng {
state: u32,
}
impl Rng {
fn new() -> Self {
Self { state: 0x87654321 }
}

fn gen_u32(&mut self) -> u32 {
self.state ^= self.state << 13;
self.state ^= self.state >> 17;
self.state ^= self.state << 5;
self.state
}

fn gen_range_i32(&mut self, low: i32, high: i32) -> i32 {
let d = (high - low) as u32;
low + (self.gen_u32() % d) as i32
}
}

struct IntervalGenerator {
rng: Rng,
limit: i32,
rng: StdRng,
}
impl IntervalGenerator {
fn new() -> Self {
const LIMIT: i32 = 100000;
let seed: [u8; 32] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,
];
GG2002 marked this conversation as resolved.
Show resolved Hide resolved
Self {
rng: Rng::new(),
limit: LIMIT,
rng: StdRng::from_seed(seed),
}
}

fn next(&mut self) -> Interval<i32> {
let low = self.rng.gen_range_i32(0, self.limit - 1);
let high = self.rng.gen_range_i32(low + 1, self.limit);
fn next(&mut self) -> Interval<u32> {
let low = self.rng.gen();
let high = self.rng.gen_range(low + 1..=u32::MAX);
Interval::new(low, high)
}
}
Expand All @@ -65,7 +46,7 @@ fn interval_map_insert_remove(count: usize, bench: &mut Bencher) {
black_box(map.insert(i, ()));
}
for i in &intervals {
black_box(map.remove(&i));
black_box(map.remove(i));
}
});
}
Expand Down Expand Up @@ -100,14 +81,68 @@ fn bench_interval_map_insert_remove(c: &mut Criterion) {
});
}

// FilterIter helper fn
fn interval_map_filter_iter(count: usize, bench: &mut Bencher) {
let mut gen = IntervalGenerator::new();
let intervals: Vec<_> = std::iter::repeat_with(|| gen.next()).take(count).collect();
let mut map = IntervalMap::new();
for i in intervals.clone() {
map.insert(i, ());
}
bench.iter(|| {
for i in intervals.clone() {
black_box(map.filter_iter(&i).collect::<Vec<_>>());
}
});
}

// iter().filter() helper fn
fn interval_map_iter_filter(count: usize, bench: &mut Bencher) {
let mut gen = IntervalGenerator::new();
let intervals: Vec<_> = std::iter::repeat_with(|| gen.next()).take(count).collect();
let mut map = IntervalMap::new();
for i in intervals.clone() {
map.insert(i, ());
}
bench.iter(|| {
for i in intervals.clone() {
black_box(map.iter().filter(|v| v.0.overlap(&i)).collect::<Vec<_>>());
}
});
}

fn bench_interval_map_filter_iter(c: &mut Criterion) {
c.bench_function("bench_interval_map_filter_iter_100", |b| {
interval_map_filter_iter(100, b)
});
c.bench_function("bench_interval_map_filter_iter_1000", |b| {
interval_map_filter_iter(1000, b)
});
}

fn bench_interval_map_iter_filter(c: &mut Criterion) {
c.bench_function("bench_interval_map_iter_filter_100", |b| {
interval_map_iter_filter(100, b)
});
c.bench_function("bench_interval_map_iter_filter_1000", |b| {
interval_map_iter_filter(1000, b)
});
}

fn criterion_config() -> Criterion {
Criterion::default().configure_from_args().without_plots()
}

criterion_group! {
name = benches;
name = benches_basic_op;
config = criterion_config();
targets = bench_interval_map_insert, bench_interval_map_insert_remove,
}

criterion_group! {
name = benches_iter;
config = criterion_config();
targets = bench_interval_map_insert, bench_interval_map_insert_remove
targets = bench_interval_map_filter_iter, bench_interval_map_iter_filter
}

criterion_main!(benches);
criterion_main!(benches_basic_op, benches_iter);
27 changes: 27 additions & 0 deletions examples/new_point.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use interval_map::{Interval, IntervalMap};

trait Point<T> {
fn new_point(x: T) -> Interval<T>;
}

impl Point<u32> for Interval<u32> {
fn new_point(x: u32) -> Self {
Interval::new(x, x + 1)
}
}

fn main() {
let mut interval_map = IntervalMap::<u32, i32>::new();
interval_map.insert(Interval::new(3, 7), 20);
interval_map.insert(Interval::new(2, 6), 15);

let tmp_point = Interval::new_point(5);
assert_eq!(tmp_point, Interval::new(5, 6));

interval_map.insert(tmp_point.clone(), 10);
assert_eq!(interval_map.get(&tmp_point).unwrap(), &10);
assert_eq!(
interval_map.find_all_overlap(&Interval::new_point(5)).len(),
3
);
}
68 changes: 68 additions & 0 deletions examples/string_affine.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use std::cmp;

use interval_map::{Interval, IntervalMap};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StringAffine {
/// String
String(String),
/// Unbounded
Unbounded,
}

impl StringAffine {
pub fn new_key(s: &str) -> Self {
Self::String(s.to_string())
}

pub fn new_unbounded() -> Self {
Self::Unbounded
}
}

impl PartialOrd for StringAffine {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for StringAffine {
fn cmp(&self, other: &Self) -> cmp::Ordering {
match (self, other) {
(StringAffine::String(x), StringAffine::String(y)) => x.cmp(y),
(StringAffine::String(_), StringAffine::Unbounded) => cmp::Ordering::Less,
(StringAffine::Unbounded, StringAffine::String(_)) => cmp::Ordering::Greater,
(StringAffine::Unbounded, StringAffine::Unbounded) => cmp::Ordering::Equal,
}
}
}

trait Point<T> {
fn new_point(x: T) -> Interval<T>;
}

impl Point<StringAffine> for Interval<StringAffine> {
fn new_point(x: StringAffine) -> Interval<StringAffine> {
match x {
StringAffine::String(mut x_string) => {
let low = x_string.clone();
x_string.push('\0');
Interval::new(
StringAffine::new_key(&low),
StringAffine::new_key(&x_string),
)
}
_ => panic!("new_point only receive StringAffine::String!"),
}
}
}

fn main() {
let mut interval_map = IntervalMap::<StringAffine, u32>::new();
interval_map.insert(
Interval::new(StringAffine::new_key("8"), StringAffine::Unbounded),
123,
);
assert!(interval_map.overlaps(&Interval::new_point(StringAffine::new_key("9"))));
assert!(!interval_map.overlaps(&Interval::new_point(StringAffine::new_key("7"))));
}
21 changes: 15 additions & 6 deletions src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use crate::node::Node;

/// A view into a single entry in a map, which may either be vacant or occupied.
#[derive(Debug)]
pub enum Entry<'a, T, V, Ix> {
pub enum Entry<'a, T, V, Ix>
where
T: Ord,
{
/// An occupied entry.
Occupied(OccupiedEntry<'a, T, V, Ix>),
/// A vacant entry.
Expand All @@ -15,17 +18,23 @@ pub enum Entry<'a, T, V, Ix> {
/// A view into an occupied entry in a `IntervalMap`.
/// It is part of the [`Entry`] enum.
#[derive(Debug)]
pub struct OccupiedEntry<'a, T, V, Ix> {
pub struct OccupiedEntry<'a, T, V, Ix>
where
T: Ord,
{
/// Reference to the map
pub map_ref: &'a mut IntervalMap<T, V, Ix>,
/// The entry node
pub node: NodeIndex<Ix>,
pub node_idx: NodeIndex<Ix>,
}

/// A view into a vacant entry in a `IntervalMap`.
/// It is part of the [`Entry`] enum.
#[derive(Debug)]
pub struct VacantEntry<'a, T, V, Ix> {
pub struct VacantEntry<'a, T, V, Ix>
where
T: Ord,
{
/// Mutable reference to the map
pub map_ref: &'a mut IntervalMap<T, V, Ix>,
/// The interval of this entry
Expand Down Expand Up @@ -53,7 +62,7 @@ where
#[inline]
pub fn or_insert(self, default: V) -> &'a mut V {
match self {
Entry::Occupied(entry) => entry.map_ref.node_mut(entry.node, Node::value_mut),
Entry::Occupied(entry) => entry.map_ref.node_mut(entry.node_idx, Node::value_mut),
Entry::Vacant(entry) => {
let entry_idx = NodeIndex::new(entry.map_ref.nodes.len());
let _ignore = entry.map_ref.insert(entry.interval, default);
Expand Down Expand Up @@ -88,7 +97,7 @@ where
{
match self {
Entry::Occupied(entry) => {
f(entry.map_ref.node_mut(entry.node, Node::value_mut));
f(entry.map_ref.node_mut(entry.node_idx, Node::value_mut));
Self::Occupied(entry)
}
Entry::Vacant(entry) => Self::Vacant(entry),
Expand Down
Loading
Loading