103 个版本 (稳定)
1.9.6 | 2024 年 8 月 6 日 |
---|---|
1.9.5 | 2024 年 6 月 16 日 |
1.9.0 | 2024 年 4 月 19 日 |
1.8.9 | 2024 年 1 月 17 日 |
0.2.6 | 2021 年 7 月 21 日 |
#67 在 数据结构
5,840 每月下载量
用于 17 个 crate (7 直接)
96KB
1.5K SLoC
Indxvec
作者:Libor Spacek
用法
使用 100% 安全的 Rust 编写。
以下将导入所有内容
use indxvec::{ here, qsortf64(), MinMax, Search, Indices, Vecops, Mutops, Printing, printing::* };
描述
向量排序、搜索、索引、排序、合并、反转、交集、打印等
Indxvec
轻量级且无依赖。所有特质的函数可以链式调用,以紧凑的形式实现对 Ranges
、Vec
和它们的索引的多种操作。
提供的功能包括
- 通用二分搜索
- 排序、合并(归并排序和哈希排序)、索引、选择、划分
- 对泛型向量和它们的索引的许多有用操作
- 集合操作
- 将泛型切片和向量切片序列化为字符串:
to_plainstr()
- 打印和写入泛型切片和向量切片:
pvec()
、wvec(&mut f)
- 彩色美观打印(ANSI 终端输出,主要用于测试)
- 宏
here!()
用于更详细的错误报告
强烈建议阅读并运行tests/tests.rs
中的示例用法,以学习其中的用法。使用单个线程运行它们,以保持输出顺序正确。有必要单独运行时间基准测试sorts()
以获得有意义的结果。
cargo test --release -- --test-threads=1 --nocapture
cargo test sorts --release -- --nocapture
或者直接点击上面的test
徽章,即可查看自动化测试运行的日志。
术语表
-
排序索引 - 是一个数据子脚本的vec,其中第一个子脚本标识数据中的最小项,依此类推(按升序排列)。数据保持不变。适用于不易移动的庞大数据。它回答了问题:“哪个数据项占据给定的排序位置?”
-
子空间索引 - 列出了在投影到子空间时要保留的子脚本(维度)。
-
反转索引 - 排序索引可以通过泛型反转操作
revs()
或mutrevs()
来反转。这会改变排序顺序(升序/降序)之间,而无需重新排序甚至反转(可能是庞大的)实际数据。 -
秩索引 - 对应给定的数据顺序,列出了数据项的排序位置(秩),例如秩索引中的第三个条目给出第三个数据项的秩。一些统计指标需要数据秩。它回答了问题:“数据项的排序位置是什么?”
-
反转索引 - 排序索引和秩索引是相互逆的。因此,可以通过
invindex()
轻松切换。这通常是获得秩索引的最简单方法。对于已按升序排列的数据,它们都将等于0..n
。 -
索引的补数 - 注意,标准反转不会直接在升序和降序排名之间转换。这个目的由
complindex()
来实现。或者,可以通过将invindex()
应用于降序排序索引来重建降序排名。 -
选择 - 给定一个子空间索引和一些数据向量,收集索引中存在的维度的该向量组件,并忽略其余部分(即,它将数据投影到由索引定义的子空间)。
-
取消索引 - 给定一个显式的排序索引和一些数据,
unindex()
将按排序索引定义的新顺序选择数据。它可以用来高效地将大量数据向量转换成相同的(固定的)顺序。例如:假设我们有向量:keys
和data_1,..data_n
,它们没有明确地结合到某种公共数据结构中。例如通过let index = keys.hashsort_indexed();
获得的排序索引可以高效地应用于单独排序数据向量:index.unindex(data_n,true)
(false以不额外付费的方式获得降序顺序)。
特质搜索
该功能适用于 RangeInclusive<T>
,指定搜索的范围。其二分搜索方法不仅限于特定类型的显式数据。数据探测是通过比较闭包 cmpr
实现的,该闭包从某处捕获一些数据项和目标,并定义它们的比较。数据索引不仅限于 usize
。调用中指定的比较器可以很容易地进行逻辑反转,例如 |data_item,target| target.cmp(data_item)
。这些方法将对隐式降序排列的数据进行操作。
/// Binary search algoritms implemented on RangeInclusive<T>.
/// Using a closure `cmpr` to sample and compare data to captured target.
pub trait Search<T> {
/// Unchecked Ok(first hit) or Err(insert order of a missing item).
fn binary_by(self, cmpr: impl FnMut(T) -> Ordering) -> Result <T,T>;
/// Unchecked first hit or insert order, and the final search range.
fn binary_any(&self, cmpr: impl FnMut(T) -> Ordering) -> (T, Range<T>);
/// General Binary Search, returns the range of all matching items
fn binary_all(&self, cmpr: impl FnMut(T)-> Ordering) -> Range<T>;
}
binary_by
在包含范围内进行二分搜索。如果目标不存在,则返回其插入位置为 Err<T>
。
与 std::slice::binary_search_by()
相同,但更通用。
binary_any
找到并返回第一个命中项及其最后一个包含范围。返回的范围由 binary_all
使用以限制其搜索所有匹配项。此外,当任何匹配项都行时,也可以单独使用 binary_any
。例如,为了迭代解决非线性方程,使用 f64
类型的范围值(参见 tests/tests.rs
)。
binary_all
找到所有匹配项的二分搜索。这种实现是独一无二的通用。它也非常快,特别是在长范围内。
在给定的 RangeInclusive<T>
(自身)内进行搜索。它可以在功能链式 'builder' 风格 API 中使用,该 API 选择更接近目标的子范围。
范围值可以是任何泛型类型 T(满足列出的限制),例如 usize 用于内存中的索引,u128 用于搜索整个磁盘或互联网,f64 用于解决方程...
比较闭包 cmpr
是将数据与其环境中的目标进行比较。使用闭包可以自定义用户自己数据类型的比较。此外,此代码对目标(和数据)的类型一无所知!
当目标在 self.start 之前时,返回空 self.start..self.start
范围。
当目标在 self.end 之后时,返回 self.end..self.end
。
当目标未找到时,则返回 ip..ip
,其中 ip
是其插入位置。
否则返回与目标 PartiallyEqual
的所有连续值的范围。
遇到的第一个命中项将位于一些未知数量的匹配项中的任何位置。然后算法在主搜索中找到的第一个命中项两侧进行两次更多二分搜索。这些次要搜索仅在主搜索期间找到的最后一个(最窄)范围内应用。找到两个方向上的第一个非匹配项,给出完整的包含匹配范围。
特质 Indices
use indxvec::{Indices};
本特性的方法实现了对子索引切片的处理,即它们以类型 &[usize]
(self)作为输入,并生成新的索引 Vec<usize>
、新的数据向量 Vec<T>
或 Vec<f64>
,或其他适当的结果。请参阅术语表以了解索引及其操作的描述。
pub trait Indices {
/// Indices::newindex(n) creates a new index without rePartialOrdering
fn newindex(n: usize) -> Vec<usize> {
Vec::from_iter(0..n)
}
/// Invert an index - turns a sort order into rank order and vice-versa
fn invindex(self) -> Vec<usize>;
/// complement of an index - reverses the ranking order
fn complindex(self) -> Vec<usize>;
/// Using a subspace index, projects `v`, into it.
fn select<T:Clone>(self, v: &[T]) -> Vec<T>;
/// Given a complete (sort) index, extracts indicated values from `v`
fn unindex<T:Clone>(self, v: &[T], ascending: bool) -> Vec<T>;
/// Correlation coefficient of two &[usize] slices.
/// Pearsons on raw data, Spearman's when applied to ranks.
fn ucorrelation(self, v: &[usize]) -> f64;
/// Potentially useful clone-recast of &[usize] to Vec<f64>
fn indx_to_f64(self) -> Vec<f64>;
}
Trait Vecops
use indxvec::Vecops;
本特性的方法适用于所有泛型切片 &[T]
(数据)。因此,它们将在所有Rust原始数值端类型上工作,例如f64。只要为T
实现了通常所需的特性 Ord
和/或 Clone
,它们也可以在包含任何任意复杂端类型的切片上工作。这里无法列出所有方法,请参阅 lib.rs
中的声明和 vecops.rs
中的源代码。
Trait Mutops
use indxvec::Mutops;
本特性包含可变逆序和可变排序方法。它们都将输出覆盖到 self
上。当我们不需要保留原始顺序时,这通常是排序的最高效方式。在 Vecops
特性中实现了非破坏性版本。
mutisort
避免对要排序的端类型(如 Ord
或 Partial_Ord
)的特性和约束通常很有用。这些约束是“粘性”的,并必须随后应用于所有地方。我们新的 mutisort
(插入日志排序)通过使用自定义闭包比较器绕过这些问题。其复杂度是比较器排序可达到的最佳。它几乎与std提供的排序一样快,但它之所以最终更快,只是因为它可以利用不稳定的Rust mem moves。在浮点数上测试表明,mutisort
实际上比数据长度约为4500更快。此外,mutisort
允许仅对指定范围(子切片)内的数据进行排序。
比较器闭包参数可以很容易地反转以执行降序排序。
其非破坏性版本是 Vecops::isort_indexed
,它返回显式的排序索引和 Vcops::isort_refs()
,它返回按排序顺序的引用 Vec<&T>
,这要快一点。这两个版本都不会复制可能庞大的端类型(数据项)。
/// Mutable Operators on `&mut[T]`
pub trait Mutops<T> {
/// Associated method `part` partitions `s: &mut [&T]` within range `rng`, using comparator `c`.
/// Suitable pivot should be selected and placed in `s[rng.start]`.
/// Returns the boundaries of the rearranged partitions, (eqstart,gtstart), where
/// `rng.start..eqstart` (may be empty) contains references to items lesser than the pivot,
/// `gtstart-eqstart` is the number (>= 1) of items equal to the pivot (contains undefined references)
/// `gtstart..rng.end` (may be empty) contains references to items greater than the pivot.
fn part(
s: &mut [&T],
rng: &Range<usize>,
c: &mut impl FnMut(&T, &T) -> Ordering,
) -> (usize, usize) {
// get pivot from the first location
let pivot = s[rng.start];
let mut eqstart = rng.start;
let mut gtstart = eqstart + 1;
for t in rng.start + 1..rng.end {
match c(s[t], pivot) {
Less => {
s[eqstart] = s[t];
eqstart += 1;
s[t] = s[gtstart];
gtstart += 1;
}
Equal => {
s[t] = s[gtstart];
gtstart += 1;
}
Greater => (),
}
}
(eqstart, gtstart)
}
/// partitions by bitmask
fn part_binary(self, rng: &Range<usize>, bitmask: u64) -> usize
where
T: Copy, u64: From<T>;
/// mutable reversal of &mut[T]
fn mutrevs(self);
/// swaps two indexed items into ascending order
fn mutsorttwo(self, i0: usize, i1: usize) -> bool
where
T: PartialOrd;
/// mutably sorts three indexed items into ascending order
fn mutsortthree(self, i0: usize, i1: usize, i2: usize)
where
T: PartialOrd;
/// Possibly the fastest sort for long lists. Wrapper for `muthashsortslice`.
fn muthashsort(self, quantify: impl Copy + Fn(&T) -> f64)
where
T: PartialOrd + Clone;
/// Sorts n items from i in self. Used by muthashsort.
fn muthashsortslice(
self,
i: usize,
n: usize,
min: f64,
max: f64,
quantify: impl Copy + Fn(&T) -> f64,
) where
T: PartialOrd + Clone;
/// Mutable insert logsort. Pass in reversed comparator `c` for descending sort
fn mutisort<F>(self, rng: Range<usize>, c: F)
where
T: Copy,
F: Fn(&T, &T) -> Ordering;
}
Trait Printing
use indxvec::Printing; // the trait methods
use indxvec::printing::*; // the ANSI colour constants
请参阅 tests/tests.rs
了解使用示例。
适用于打印或写入文件,最多包含不同类型项的4元组、所有类型的Vec和切片以及不规则形状的2D矩阵。
序列化元组: &(T,U)
,&(T,U,V)
,&(T,U,V,W)
以及切片: &[T]
,&[&[T]]
,&[Vec<T>]
。
此外,wvec
将自身的内容作为普通空格分隔的值(.ssv
)写入文件,可能会抛出io::Error(s)
fn wvec(self,f:&mut File) -> Result<(), io::Error> where Self: Sized;
同样,pvec
输出到stdout
fn pvec(self) where Self: Sized;
上述所有类型都转换为字符串,并可选地进行装饰和着色。包括方法和常量,用于将结果字符串渲染为六种主要加粗ANSI终端颜色。
请注意,所有这些类型在标准Rust中都是不可打印的(它们没有实现Display
)。这对初学者来说是一个大障碍。此特质的这些方法将这些类型转换为可打印(可写入)的字符串。
着色方法将相关颜色添加到字符串输出中。这使得测试输出看起来更漂亮,并避免了在生产代码中依赖调试模式。为了对着色进行更精细的控制,从printing::*
导入颜色常量,并手动在格式化字符串中使用它们。例如,切换颜色
use indxvec::printing::*; // ANSI colours constants
println!("{GR}green text, {RD}red warning, {BL}feeling blue{UN}");
请注意,所有这些颜色插值都会设置自己的新颜色,而不管之前的设置如何。插值{UN}
会将终端重置为默认的前景色渲染。UN
会自动附加到颜色方法生成的字符串的末尾rd()..cy()
。务必始终以其中一个或显式的{UN}
结束。否则,所有后续输出将继续使用最后选择的颜色前景渲染!
示例来自tests/tests.rs
println!("Memsearch for {BL}{midval}{UN}, found at: {}",
vm.memsearch(midval)
.map_or_else(||"None".rd(),|x| x.gr())
);
memsearch
在midval
不在vm
中找到时返回Option(None)
。在这里,None
将以红色打印,而任何找到的项目将以绿色打印。由于x已经通过.gr()
转换为String
,因此两个闭包都返回相同的类型,这是map_or_else
所要求的。
结构和实用函数
use indxvec::{MinMax,here};
pub struct Minmax
结构体保存了一个Vec
的最小值和最大值及其索引。here!()
是一个宏,用于提供调用它的文件名、行号和函数名。它可以被插入到任何错误/跟踪消息和报告中。qsortf64()
使用total_cmp()
安全地将sort_unstable_by()
应用于 f64s 的可变切片。
发行说明(按时间顺序排列)
版本 1.9.5 将 best_k_indexed
和 subspace
添加到 Vecops
,用于构建 subspace index
。将 select
添加到 Indices,以便将 subspace index
应用于数据向量,并有效地将其投影到该子空间。
版本 1.9.1 通过实现 &T
而不是 T
来停止 Trait Printing 消费单个项。
版本 1.9.0 在 trait Search 中将 Fn closure 参数更改为 FnMut,以用户请求。添加了到 trait Mutops 的方法 partbinary
版本 1.8.9 到 trait Mutops 添加了关联函数 part
(调用方法:<&mut [T]>::part(s, &rng, c)
)。
到 trait Vecops 添加了方法 ref_vec
和关联函数 deref_vec
。
版本 1.8.8 升级到 ran 2.0
。
版本 1.8.7 改进了 isort_refs()
和 isort_indexed
。
版本 1.8.6 为大型的末尾类型添加了 isort_refs()
。添加了 best_k
,可能是提取和排序 k 个最大或最小项(通过自定义比较器)的最快方式。
版本 1.8.5 添加了新的算法“插入日志排序”:到 mutisort()
和 isort_indexed()
分别添加到 Mutops
和 Vecops
特性中。也添加到 tests.rs
。
版本 1.8.4 到 trait Search 添加了 binary_by()
。它类似于 std::slice::binary_search_by()
,但更通用,不期望任何特定类型的明确数据。索引也不限于 usize
。
版本 1.8.3 将宏 here(msg:&str)
的参数增加了 &str
以整合负载错误信息。将 ierror
改为 idx_error
。现在它返回 Result
(Err 变体),可以更方便地使用 ?
运算符进行上游处理。这目前还没有在代码中使用,因此这次改进应该与旧版本兼容。例如:return idx_error("size",here!("my specific further message"))?
将对所有必要的 Size
变体的 IdxError
报告进行操作,并输出带有文件、行位置和方法名称的自定义消息。
版本 1.8.2 对测试进行了少量整理和补充。提高了依赖项。
版本 1.8.1 添加了函数 qsortf64()
,它可以安全地对 f64 进行排序。
版本 1.8.0 将闭包参数的 trait 从 &mut FnMut(&T)
改为 Fn(T)
,这是足够的,也更简单。