#interval-tree #map #library

nodit

此软件包提供离散区间树数据结构,基于 BTreeMap 构建。

6 个版本

0.9.2 2024 年 8 月 3 日
0.9.1 2024 年 4 月 6 日
0.9.0 2024 年 2 月 11 日
0.8.0 2024 年 1 月 28 日
0.7.1 2024 年 1 月 5 日

#157数据结构

Download history 1921/week @ 2024-05-04 1850/week @ 2024-05-11 2450/week @ 2024-05-18 2509/week @ 2024-05-25 1538/week @ 2024-06-01 2145/week @ 2024-06-08 2729/week @ 2024-06-15 2545/week @ 2024-06-22 2480/week @ 2024-06-29 3196/week @ 2024-07-06 307/week @ 2024-07-13 108/week @ 2024-07-20 129/week @ 2024-07-27 239/week @ 2024-08-03 115/week @ 2024-08-10 120/week @ 2024-08-17

622 每月下载次数

MIT 许可证

155KB
2.5K SLoC

nodit

License Docs Maintained Crates.io

nodit_logo

此软件包提供基于 BTreeMap 的离散区间树数据结构。

no_std 支持,并应与默认功能一起工作。

已实现多个离散区间树数据结构,以下是每个数据结构的简要概述及其使用场景:

结构 缩写 使用场景
NoditMap 非重叠离散区间树映射 一种通用的方式,用于将数据与不重叠的区间关联
NoditSet 非重叠离散区间树集 当您想存储区间但不需要/不需要与每个区间关联数据时很有用
ZosditMap 零重叠顺序离散区间树映射 对于时间图遍历算法和其他可能的事物很有用
Gqdit 间隙查询离散区间树 当您有一组不同的非重叠区间并想在所有区间的集合上执行高效的间隙查询搜索时很有用

Copy 部分需要

由于与非 Copy 类型实现的复杂性,当前的数据结构要求区间类型和区间覆盖的点都是 Copy。然而,使用 NoditMap 时使用的值类型不需要是 Copy。实际上,用于值类型的唯一要求是有时是 Clone

Eq

use nodit::interval::ie;
use nodit::NoditMap;

let mut map = NoditMap::new();

map.insert_strict(ie(0, 5), true);
map.insert_strict(ie(5, 10), false);

assert_eq!(map.overlaps(ie(-2, 12)), true);
assert_eq!(map.contains_point(20), false);
assert_eq!(map.contains_point(5), true);

但仅限于某些方法,因此如果您不确定,请检查方法的特征限制。

use std::ops::{Bound, RangeBounds};

use nodit::interval::ie;
use nodit::{
	DiscreteFinite, InclusiveInterval, Interval, NoditMap,
};

#[derive(Debug, Copy, Clone)]
enum Reservation {
	// Start, End (Inclusive-Inclusive)
	Finite(i8, i8),
	// Start (Inclusive-Infinity)
	Infinite(i8),
}

// First, we need to implement InclusiveInterval
impl InclusiveInterval<i8> for Reservation {
	fn start(&self) -> i8 {
		match self {
			Reservation::Finite(start, _) => *start,
			Reservation::Infinite(start) => *start,
		}
	}
	fn end(&self) -> i8 {
		match self {
			Reservation::Finite(_, end) => *end,
			Reservation::Infinite(_) => i8::MAX,
		}
	}
}

// Second, we need to implement From<Interval<i8>>
impl From<Interval<i8>> for Reservation {
	fn from(value: Interval<i8>) -> Self {
		if value.end() == i8::MAX {
			Reservation::Infinite(value.start())
		} else {
			Reservation::Finite(
				value.start(),
				value.end().up().unwrap(),
			)
		}
	}
}

// Next we can create a custom typed NoditMap
let reservation_map = NoditMap::from_slice_strict([
	(Reservation::Finite(10, 20), "Ferris".to_string()),
	(Reservation::Infinite(21), "Corro".to_string()),
])
.unwrap();

for (reservation, name) in reservation_map.overlapping(ie(16, 17))
{
	println!(
		"{name} has reserved {reservation:?} inside the interval 16..17"
	);
}

for (reservation, name) in reservation_map.iter() {
	println!("{name} has reserved {reservation:?}");
}

assert_eq!(
	reservation_map.overlaps(Reservation::Infinite(0)),
	true
);

示例使用包含-排除区间

示例使用自定义区间类型

此软件包旨在与离散类型(如Discrete)一起使用,而不是与连续类型(如Continuous)一起使用。例如,u8是一种离散类型,但如果将其解析为十进制值,则String是一种连续类型。

之所以如此,是因为常见的区间数学运算取决于基础类型是离散类型还是连续类型。例如,5..=6触及7..=8,因为整数是离散的,但5.0..=6.0不会触及7.0..=8.0,因为存在值6.5

重要的是,这也使得包含/不包含端点的区间很容易处理,因为它们可以无损地在彼此之间转换。例如,3..6等同于3..=5

有限性

目前,此软件包仅设计用于与有限类型(如u8i128)一起使用,而不是与无限类型(如BigInt)一起使用,后者来自num_bigint软件包。这是因为如果类型是无限类型(如BigInt),则get_key_value_at_point()方法将无法从空映射中返回任何内容,因为它没有最大值。

当您不需要达到类型的顶部端点时,可以使用实际无限来模拟无限类型,这是一种有用的技巧。例如,如果您使用u8作为您的点类型,则可以创建一个包装类型,如下所示

use std::cmp::Ordering;

use nodit::DiscreteFinite;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum WithInfinity<T> {
	Finite(T),
	Infinity,
}

impl<T> Ord for WithInfinity<T>
where
	T: Ord,
{
	fn cmp(&self, other: &Self) -> Ordering {
		match (self, other) {
			(
				WithInfinity::Finite(x),
				WithInfinity::Finite(y),
			) => x.cmp(y),
			(WithInfinity::Finite(_), WithInfinity::Infinity) => {
				Ordering::Less
			}
			(WithInfinity::Infinity, WithInfinity::Finite(_)) => {
				Ordering::Greater
			}
			(WithInfinity::Infinity, WithInfinity::Infinity) => {
				Ordering::Equal
			}
		}
	}
}

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

impl<T> DiscreteFinite for WithInfinity<T>
where
	T: DiscreteFinite,
{
	const MIN: Self = WithInfinity::Finite(T::MIN);
	const MAX: Self = WithInfinity::Infinity;

	fn up(self) -> Option<Self>
	where
		Self: Sized,
	{
		match self {
			WithInfinity::Finite(x) => match x.up() {
				Some(y) => Some(WithInfinity::Finite(y)),
				None => Some(WithInfinity::Infinity),
			},
			WithInfinity::Infinity => None,
		}
	}
	fn down(self) -> Option<Self>
	where
		Self: Sized,
	{
		match self {
			WithInfinity::Finite(x) => {
				Some(WithInfinity::Finite(x.down()?))
			}
			WithInfinity::Infinity => {
				Some(WithInfinity::Finite(T::MAX))
			}
		}
	}
}

// And then you this means you can be explicit with when
// Infinity is encountered such as when it might be
// returned by `get_key_value_at_point()`, for example:

use nodit::interval::uu;
use nodit::{Interval, NoditMap};

let map: NoditMap<
	WithInfinity<u8>,
	Interval<WithInfinity<u8>>,
	bool,
> = NoditMap::new();

let mut gap = map.get_key_value_at_point(WithInfinity::Finite(4));

assert_eq!(gap, Err(uu()));

无效区间

在此软件包中,并非所有区间都被认为是有效的区间。此软件包中使用的区间有效性的定义是:一个区间仅当它包含基础域的至少一个值时才有效。

例如,4..6 被视为有效,因为它包含值 45,但是 4..4 被视为无效,因为它不包含任何值。另一个无效区间的例子是起始值大于结束值的区间。例如,5..2100..=40

以下是几个区间及其有效性的示例

区间 是否有效
0..=0
0..0
0..1
9..8
(Bound::Excluded(3), Bound::Excluded(4))
400..=400

重叠

如果有某个点同时包含在两个区间内,则称两个区间“重叠”。例如,2..42..6 重叠,但 2..44..8 不重叠。

接触

如果有两个区间没有重叠且它们之间没有其他值,则称这两个区间“接触”。例如,2..44..6 接触,但 2..46..8 不接触,同样 2..64..8 也不接触。

进一步阅读

请参阅维基百科上的数学区间文章:https://en.wikipedia.org/wiki/Interval_(mathematics)

特性

特性名称 描述
默认 默认启用的隐式默认特性目前不激活任何其他特性
serde 启用可选的 serde 依赖,并在本包中的所有类型上实现 serde::Serializeserde::Deserialize

致谢

我的许多灵感来自 rangemap 包。

底层的 BTreeMap 实现(《btree_monstrousity》)是从 copse 包中启发和分叉而来的。

名称变更

此包之前名为 range_bounds_map,大约于 2023-04-24 更名为 discrete_range_map,因为它不再是一个准确的名称。

此包于 2023-01-02 再次重命名为 discrete_range_map,原因是相同的,希望新名称的抽象性意味着它永远不会需要再次更改。

类似包

在搜索相关主题区域时,我找到了一些相关的crate,请注意阅读时的偏见。

  • https://docs.rs/rangemap 与这个crate非常相似,但只能使用std RangeRangeInclusive 作为其 mapset 结构体(分别)的键。
  • https://docs.rs/btree-range-map
  • https://docs.rs/ranges 一个非常酷的库,用于完全通用的范围(与std::ops范围不同),还有一个用于存储它们的 Ranges 数据结构(不幸的是基于Vec)。
  • https://docs.rs/intervaltree 允许重叠区间,但遗憾的是它是不可变的。
  • https://docs.rs/nonoverlapping_interval_treerangemap 非常相似,但没有 gaps() 函数,并且仅针对 Range 而不是 RangeInclusive。而且也没有复杂的合并函数。
  • https://docs.rs/unbounded-interval-tree 基于一篇2007年发表的论文实现的数据结构!它支持任何范围作为键,但遗憾的是,它使用一个非平衡的基于 Box<Node> 的树实现,但它也支持重叠范围,而我自己的库不支持。
  • https://docs.rs/rangetree 我不确定这个库是做什么的,或者它不是什么,但它看起来是一个专门用于Range Tree的自定义红黑树/BTree实现。很有趣,但也相当古老(5年),并且使用了unsafe。
  • https://docs.rs/rust-lapper 另一个类似不可变的(可以插入,但代价非常高)区间数据结构,针对大量相同大小的区间进行了优化,例如它们的主要用例基因组数据集。
  • https://docs.rs/store-interval-tree 与这个crate和 rangemap 非常相似的区间树,具有许多相同的方法(以及大量的文档示例!)除了使用自定义的内部自平衡树实现。从我的文档阅读来看,不清楚它们是否支持重叠区间。一方面,他们的示例显示了重叠区间,但另一方面,他们的 insert() 方法说“如果区间已经存在,则区间将被忽略”,所以也许它允许重叠但不允许重复的区间?在我看来,这是一个有点奇怪的选择。
  • https://docs.rs/biohttps://docs.rs/rudac 都基本上与 store-interval-tree 相同,因为看起来 store-interval-treerudac 的区间树的分支。《code>bio 专门针对生物信息学。

依赖关系

~1MB
~24K SLoC