├── .gitignore ├── Cargo.toml ├── LICENSE ├── benches ├── standard.rs └── eytzinger.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | *~ 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "eytzinger" 3 | description = 'This crate implements the "eytzinger" (aka BFS) array layout.' 4 | license = "MIT" 5 | documentation = "https://docs.rs/eytzinger" 6 | repository = "https://github.com/main--/rust-eytzinger" 7 | version = "1.1.2" 8 | authors = ["main() "] 9 | 10 | [profile.release] 11 | debug = true 12 | 13 | [dependencies.nonmax] 14 | version = "0.5.0" 15 | optional = true 16 | [dependencies.nohash-hasher] 17 | version = "0.2.0" 18 | optional = true 19 | 20 | [dev-dependencies] 21 | quickcheck = "0.4" 22 | 23 | [features] 24 | default = ["heap-permutator", "heap-permutator-sparse"] 25 | heap-permutator = ["nonmax"] 26 | heap-permutator-sparse = ["nohash-hasher"] 27 | branchless = [] 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 main() 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /benches/standard.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | #![feature(test)] 11 | 12 | extern crate test; 13 | 14 | use test::black_box; 15 | use test::Bencher; 16 | 17 | enum Cache { 18 | L1, 19 | L2, 20 | L3, 21 | } 22 | 23 | fn binary_search(b: &mut Bencher, cache: Cache, mapper: F) 24 | where 25 | F: Fn(usize) -> usize, 26 | { 27 | let size = match cache { 28 | Cache::L1 => 1000, // 8kb 29 | Cache::L2 => 10_000, // 80kb 30 | Cache::L3 => 1_000_000, // 8Mb 31 | }; 32 | let v = (0..size).map(&mapper).collect::>(); 33 | 34 | let mut r = 0usize; 35 | b.iter(move || { 36 | // LCG constants from https://en.wikipedia.org/wiki/Numerical_Recipes. 37 | r = r.wrapping_mul(1664525).wrapping_add(1013904223); 38 | // Lookup the whole range to get 50% hits and 50% misses. 39 | let i = mapper(r % size); 40 | black_box(v.binary_search(&i).is_ok()); 41 | }) 42 | } 43 | 44 | #[bench] 45 | fn binary_search_l1(b: &mut Bencher) { 46 | binary_search(b, Cache::L1, |i| i * 2); 47 | } 48 | 49 | #[bench] 50 | fn binary_search_l2(b: &mut Bencher) { 51 | binary_search(b, Cache::L2, |i| i * 2); 52 | } 53 | 54 | #[bench] 55 | fn binary_search_l3(b: &mut Bencher) { 56 | binary_search(b, Cache::L3, |i| i * 2); 57 | } 58 | 59 | #[bench] 60 | fn binary_search_l1_with_dups(b: &mut Bencher) { 61 | binary_search(b, Cache::L1, |i| i / 16 * 16); 62 | } 63 | 64 | #[bench] 65 | fn binary_search_l2_with_dups(b: &mut Bencher) { 66 | binary_search(b, Cache::L2, |i| i / 16 * 16); 67 | } 68 | 69 | #[bench] 70 | fn binary_search_l3_with_dups(b: &mut Bencher) { 71 | binary_search(b, Cache::L3, |i| i / 16 * 16); 72 | } 73 | -------------------------------------------------------------------------------- /benches/eytzinger.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | #![feature(test)] 11 | 12 | extern crate eytzinger; 13 | extern crate test; 14 | 15 | use eytzinger::SliceExt; 16 | use test::black_box; 17 | use test::Bencher; 18 | 19 | enum Cache { 20 | L1, 21 | L2, 22 | L3, 23 | } 24 | 25 | fn eytzinger_search(b: &mut Bencher, cache: Cache, mapper: F) 26 | where 27 | F: Fn(usize) -> usize, 28 | { 29 | let size = match cache { 30 | Cache::L1 => 1000, // 8kb 31 | Cache::L2 => 10_000, // 80kb 32 | Cache::L3 => 1_000_000, // 8Mb 33 | }; 34 | let mut v = (0..size).map(&mapper).collect::>(); 35 | v.eytzingerize(&mut eytzinger::permutation::InplacePermutator); 36 | let mut r = 0usize; 37 | b.iter(move || { 38 | // LCG constants from https://en.wikipedia.org/wiki/Numerical_Recipes. 39 | r = r.wrapping_mul(1664525).wrapping_add(1013904223); 40 | // Lookup the whole range to get 50% hits and 50% misses. 41 | let i = mapper(r % size); 42 | black_box(v.eytzinger_search(&i).is_some()); 43 | }) 44 | } 45 | 46 | #[bench] 47 | fn eytzinger_search_l1(b: &mut Bencher) { 48 | eytzinger_search(b, Cache::L1, |i| i * 2); 49 | } 50 | 51 | #[bench] 52 | fn eytzinger_search_l2(b: &mut Bencher) { 53 | eytzinger_search(b, Cache::L2, |i| i * 2); 54 | } 55 | 56 | #[bench] 57 | fn eytzinger_search_l3(b: &mut Bencher) { 58 | eytzinger_search(b, Cache::L3, |i| i * 2); 59 | } 60 | 61 | #[bench] 62 | fn eytzinger_search_l1_with_dups(b: &mut Bencher) { 63 | eytzinger_search(b, Cache::L1, |i| i / 16 * 16); 64 | } 65 | 66 | #[bench] 67 | fn eytzinger_search_l2_with_dups(b: &mut Bencher) { 68 | eytzinger_search(b, Cache::L2, |i| i / 16 * 16); 69 | } 70 | 71 | #[bench] 72 | fn eytzinger_search_l3_with_dups(b: &mut Bencher) { 73 | eytzinger_search(b, Cache::L3, |i| i / 16 * 16); 74 | } 75 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate implements the "eytzinger" (aka BFS) array layout where 2 | //! a binary search tree is stored by layer (instead of as a sorted array). 3 | //! This can have significant performance benefits 4 | //! (see [Khuong, Paul-Virak, and Pat Morin. "Array layouts for comparison-based searching."][1]). 5 | //! 6 | //! # Usage 7 | //! 8 | //! ``` 9 | //! use eytzinger::SliceExt; 10 | //! let mut data = [0, 1, 2, 3, 4, 5, 6]; 11 | //! data.eytzingerize(&mut eytzinger::permutation::InplacePermutator); 12 | //! assert_eq!(data, [3, 1, 5, 0, 2, 4, 6]); 13 | //! assert_eq!(data.eytzinger_search(&5), Some(2)); 14 | //! assert_eq!(data.eytzinger_search_by(|x| x.cmp(&6)), Some(6)); 15 | //! ``` 16 | //! 17 | //! [1]: https://arxiv.org/pdf/1509.05053.pdf 18 | 19 | #![warn(missing_docs, missing_debug_implementations)] 20 | 21 | use permutation::*; 22 | use std::borrow::Borrow; 23 | use std::cmp::{Ord, Ordering}; 24 | 25 | /// The basic building blocks this crate is made of. 26 | pub mod foundation { 27 | /// Given an array size (`n`), tree layer index (`ipk`) and element index (`li`), 28 | /// this function computes the index of this value in a sorted array. 29 | /// 30 | /// This is basically the core of this crate, everything else is trivial. 31 | /// Also, you usually want to use `PermutationGenerator` instead for convenience. 32 | /// 33 | /// # How it works 34 | /// 35 | /// This computes the magic function: 36 | /// 37 | /// ```text 38 | /// f(n, k) = 2^floor(log2(n+1)) * 2k - max(0, (1+k) * 2^floor(log2(n+1)) - (n + 1)) - 1 39 | /// (where n ∈ ℕ, k ∈ [0, 1]) 40 | /// ``` 41 | /// 42 | /// Because this is integer math: `k = zk * 2^-ipk` 43 | /// And because we only care about certain values of zk: `zk = li * 2 + 1` 44 | /// 45 | /// Even though I discovered this on my own and am quite certain that it's correct, 46 | /// I only have a very vague feeling about **why** it works. If you want to understand this 47 | /// (you really don't!), have a look at this sequence: 48 | /// 49 | /// ```text 50 | /// a_n = (2n - 2^floor(log2(2n)) + 1) / 2^floor(log2(2n)) 51 | /// (1/2, 1/4, 3/4, 1/8, 3/8, 5/8, 7/8, 1/16, 3/16, 5/16, 7/16, 9/16, 11/16, 13/16, 15/16, ...) 52 | /// ``` 53 | /// 54 | /// The basic idea is that this sequence basically establishes a mapping between a sorted array 55 | /// and an eytzinger array: If you look at the plot sideways (literally), you can see the tree 56 | /// with all its layers. The only secret sauce here is `2^floor(log2(x))` which is elementary to 57 | /// get exponentially growing windows (to build tree layers). 58 | #[inline] 59 | pub fn get_permutation_element_by_node(n: usize, ipk: usize, li: usize) -> usize { 60 | let zk = li * 2 + 1; 61 | // k = zk * 2^-ipk 62 | 63 | let last_power_of_two = (n + 2).next_power_of_two() / 2; 64 | 65 | let y = (last_power_of_two >> (ipk - 1)) * zk; 66 | let kp = y >> 1; 67 | let x = kp + last_power_of_two; // (1+k) * last_power_of_two 68 | let x = x.saturating_sub(n + 1); 69 | 70 | //println!("n={} x={} y={} z={} kp={} lpot={}", n, x,y,z, kp, last_power_of_two); 71 | y - x - 1 72 | } 73 | 74 | /// Converts an index in an eytzinger array to the corresponding tree coordinates `(ipk, li)`. 75 | #[inline] 76 | pub fn index_to_node(i: usize) -> (usize, usize) { 77 | let ipk = (i + 2).next_power_of_two().trailing_zeros() as usize; 78 | let li = i + 1 - (1 << (ipk - 1)); 79 | (ipk, li) 80 | } 81 | 82 | /// Given an array size (`n`) and an index into the eytzinger array (`ì`), 83 | /// this function computes the index of this value in a sorted array. 84 | /// 85 | /// This is simply `index_to_node` + `get_permutation_element_by_node`. 86 | #[inline] 87 | pub fn get_permutation_element(n: usize, i: usize) -> usize { 88 | let (ipk, li) = index_to_node(i); 89 | get_permutation_element_by_node(n, ipk, li) 90 | } 91 | } 92 | 93 | /// Abstractions around applying generic permutations using generic implementations. 94 | /// 95 | /// You should pick one that matches your use case. 96 | pub mod permutation { 97 | use std::iter::{Cloned, Enumerate}; 98 | use std::slice::Iter; 99 | 100 | /// A generic permutation. 101 | pub trait Permutation { 102 | /// An iterator through the permutation. 103 | /// This may be more efficient than indexing a counter. 104 | type Iter: Iterator; 105 | 106 | /// Get an iterator. 107 | fn iterable(&self) -> Self::Iter; 108 | /// Index into this permutation. 109 | fn index(&self, i: usize) -> usize; 110 | } 111 | 112 | impl<'a> Permutation for &'a [usize] { 113 | type Iter = Cloned>; 114 | 115 | #[inline] 116 | fn iterable(&self) -> Self::Iter { 117 | self.iter().cloned() 118 | } 119 | 120 | #[inline] 121 | fn index(&self, i: usize) -> usize { 122 | self[i] 123 | } 124 | } 125 | 126 | /// A generic permutator. 127 | pub trait Permutator { 128 | /// Applies the given permutation to the given array. 129 | fn permute(&mut self, data: &mut [T], permutation: &P); 130 | } 131 | 132 | /// Simple permutator that does not allocate. 133 | /// 134 | /// Worst-case runtime is in `O(n^2)`, so you should only use this for small permutations. 135 | #[derive(Clone, Copy, Debug, Default)] 136 | pub struct InplacePermutator; 137 | 138 | impl Permutator for InplacePermutator { 139 | #[inline] 140 | fn permute(&mut self, data: &mut [T], permutation: &P) { 141 | for (i, mut p) in permutation.iterable().enumerate() { 142 | while p < i { 143 | p = permutation.index(p); 144 | } 145 | 146 | if p > i { 147 | data.swap(i, p); 148 | } 149 | } 150 | } 151 | } 152 | 153 | /// Simple permutator that stack-allocates a copy of the data (using recursion). 154 | /// 155 | /// Worst-case runtime is `O(n)`, but this takes `O(n)` stack space so it WILL NOT work for large permutations. 156 | #[derive(Clone, Copy, Debug, Default)] 157 | pub struct StackCopyPermutator; 158 | 159 | fn recursive_permute( 160 | data: &mut [T], 161 | permutation: &mut Enumerate, 162 | ) { 163 | if let Some((i, p)) = permutation.next() { 164 | let item = data[p].clone(); 165 | recursive_permute::(data, permutation); 166 | data[i] = item; 167 | } 168 | } 169 | 170 | impl Permutator for StackCopyPermutator { 171 | #[inline] 172 | fn permute(&mut self, data: &mut [T], permutation: &P) { 173 | let mut iter = permutation.iterable().enumerate(); 174 | recursive_permute::(data, &mut iter); 175 | } 176 | } 177 | 178 | /// Simple permutator that heap-allocates a copy of the data. 179 | /// 180 | /// Worst-case runtime is `O(n)`, taking `O(n)` heap space in a reusable buffer. 181 | /// This is an acceptable permutator for large permutations, provided that the data 182 | /// is (efficiently) cloneable. 183 | #[derive(Debug, Default)] 184 | pub struct HeapCopyPermutator { 185 | buffer: Vec, 186 | } 187 | 188 | impl Permutator for HeapCopyPermutator { 189 | #[inline] 190 | fn permute(&mut self, data: &mut [T], permutation: &P) { 191 | self.buffer.clear(); 192 | self.buffer 193 | .extend(permutation.iterable().map(|i| data[i].clone())); 194 | for (i, t) in self.buffer.drain(..).enumerate() { 195 | data[i] = t; 196 | } 197 | } 198 | } 199 | 200 | /// Permutator that uses an auxiliary heap buffer to ensure linear runtime. 201 | /// 202 | /// Worst-case runtime is `O(n)`, taking `O(n)` heap space in a reusable buffer. 203 | /// The buffer we allocate uses exactly `sizeof(usize) * data.len()` bytes. 204 | /// This is a decent permutator for large permutations. 205 | #[derive(Debug, Default)] 206 | #[cfg(feature = "heap-permutator")] 207 | pub struct HeapPermutator { 208 | buffer: Vec>, 209 | } 210 | 211 | #[cfg(feature = "heap-permutator")] 212 | impl Permutator for HeapPermutator { 213 | #[inline] 214 | fn permute(&mut self, data: &mut [T], permutation: &P) { 215 | use std::convert::TryInto; 216 | self.buffer.clear(); 217 | self.buffer.resize(data.len(), None); 218 | for mut i in 0..data.len() { 219 | let mut j = permutation.index(i); 220 | if j < i { 221 | j = self.buffer[j].take().unwrap().get(); 222 | } 223 | data.swap(i, j); 224 | if let Some(x) = self.buffer[i].take() { 225 | i = x.get(); 226 | } 227 | 228 | if j != i { 229 | self.buffer[j] = Some(i.try_into().unwrap()); 230 | self.buffer[i] = Some(j.try_into().unwrap()); 231 | } 232 | } 233 | } 234 | } 235 | 236 | /// Permutator that uses an auxiliary heap buffer to ensure linear runtime. 237 | /// 238 | /// Worst-case runtime is `O(n)`, worst-case memory usage is also `O(n)`. 239 | /// The difference to `HeapPermutator` is that this implementation uses a `HashMap` 240 | /// instead of a `Vec` for storage. Hence, the exact the memory usage can no longer be 241 | /// predicted - but in return, it should be lower in most cases. 242 | /// This is a decent permutator for large permutations. 243 | #[derive(Debug, Default)] 244 | #[cfg(feature = "heap-permutator-sparse")] 245 | pub struct SparseHeapPermutator { 246 | buffer: std::collections::HashMap>, 247 | } 248 | 249 | #[cfg(feature = "heap-permutator-sparse")] 250 | impl Permutator for SparseHeapPermutator { 251 | #[inline] 252 | fn permute(&mut self, data: &mut [T], permutation: &P) { 253 | self.buffer.clear(); 254 | for mut i in 0..data.len() { 255 | let mut j = permutation.index(i); 256 | if j < i { 257 | j = self.buffer.remove(&j).unwrap(); 258 | } 259 | data.swap(i, j); 260 | if let Some(x) = self.buffer.remove(&i) { 261 | i = x; 262 | } 263 | 264 | if j != i { 265 | self.buffer.insert(j, i); 266 | self.buffer.insert(i, j); 267 | } 268 | } 269 | } 270 | } 271 | } 272 | 273 | /// Generates a permutation that transforms a sorted array into an eytzinger array. 274 | /// 275 | /// This is an iterator which yields a permutation (indexes into the sorted array) 276 | /// in the order of an eytzinger array. 277 | #[derive(Clone, Debug)] 278 | pub struct PermutationGenerator { 279 | size: usize, 280 | ipk: usize, 281 | li: usize, 282 | } 283 | 284 | impl PermutationGenerator { 285 | /// Generate a new permutation for a sorted array of a given size. 286 | #[inline] 287 | pub fn new(size: usize) -> PermutationGenerator { 288 | PermutationGenerator { 289 | size, 290 | ipk: 1, 291 | li: 0, 292 | } 293 | } 294 | } 295 | 296 | impl Iterator for PermutationGenerator { 297 | type Item = usize; 298 | 299 | #[inline] 300 | fn next(&mut self) -> Option { 301 | let k2 = 1 << (self.ipk - 1); 302 | 303 | if k2 + self.li - 1 >= self.size { 304 | return None; 305 | } 306 | 307 | if self.li >= k2 { 308 | self.li = 0; 309 | self.ipk += 1; 310 | } 311 | 312 | let li = self.li; 313 | self.li += 1; 314 | Some(foundation::get_permutation_element_by_node( 315 | self.size, self.ipk, li, 316 | )) 317 | } 318 | 319 | #[inline] 320 | fn size_hint(&self) -> (usize, Option) { 321 | let k2 = 1 << (self.ipk - 1); 322 | let size = self.size - (k2 + self.li - 1); 323 | (size, Some(size)) 324 | } 325 | } 326 | impl ExactSizeIterator for PermutationGenerator {} 327 | 328 | impl Permutation for PermutationGenerator { 329 | type Iter = PermutationGenerator; 330 | 331 | #[inline] 332 | fn iterable(&self) -> PermutationGenerator { 333 | self.clone() 334 | } 335 | #[inline] 336 | fn index(&self, i: usize) -> usize { 337 | foundation::get_permutation_element(self.size, i) 338 | } 339 | } 340 | 341 | /// Converts a sorted array to its eytzinger representation. 342 | /// 343 | /// # Example 344 | /// 345 | /// ```rust 346 | /// let mut data = [0, 1, 2, 3, 4, 5, 6]; 347 | /// eytzinger::eytzingerize(&mut data, &mut eytzinger::permutation::InplacePermutator); 348 | /// assert_eq!(data, [3, 1, 5, 0, 2, 4, 6]); 349 | /// ``` 350 | #[inline] 351 | pub fn eytzingerize>(data: &mut [T], permutator: &mut P) { 352 | let len = data.len(); 353 | permutator.permute(data, &PermutationGenerator::new(len)) 354 | } 355 | 356 | /// Eytzinger extension methods for slices. 357 | pub trait SliceExt { 358 | /// Converts an already sorted array to its eytzinger representation. 359 | /// 360 | /// # Example 361 | /// 362 | /// ```rust 363 | /// use eytzinger::SliceExt; 364 | /// let mut data = [0, 1, 2, 3, 4, 5, 6]; 365 | /// data.eytzingerize(&mut eytzinger::permutation::InplacePermutator); 366 | /// assert_eq!(data, [3, 1, 5, 0, 2, 4, 6]); 367 | /// ``` 368 | fn eytzingerize>(&mut self, permutator: &mut P); 369 | 370 | /// Binary searches this eytzinger slice for a given element. 371 | /// 372 | /// If the value is found then `Some` is returned, containing the index of the matching element; 373 | /// if the value is not found then `None` is returned. 374 | /// 375 | /// # Example 376 | /// 377 | /// ```rust 378 | /// use eytzinger::SliceExt; 379 | /// let s = [3, 1, 5, 0, 2, 4, 6]; 380 | /// assert_eq!(s.eytzinger_search(&5), Some(2)); 381 | /// assert_eq!(s.eytzinger_search(&6), Some(6)); 382 | /// assert_eq!(s.eytzinger_search(&7), None); 383 | /// ``` 384 | fn eytzinger_search(&self, x: &Q) -> Option 385 | where 386 | Q: Ord, 387 | T: Borrow; 388 | 389 | /// Binary searches this eytzinger slice with a comparator function. 390 | /// 391 | /// The comparator function should implement an order consistent with the sort order 392 | /// of the underlying eytzinger slice, returning an order code that indicates whether 393 | /// its argument is `Less`, `Equal` or `Greater` than the desired target. 394 | /// 395 | /// If a matching value is found then `Some` is returned, containing the index of the 396 | /// matching element; if no match is found then `None` is returned. 397 | /// 398 | /// # Examples 399 | /// 400 | /// ```rust 401 | /// use eytzinger::SliceExt; 402 | /// let s = [3, 1, 5, 0, 2, 4, 6]; 403 | /// assert_eq!(s.eytzinger_search_by(|x| x.cmp(&5)), Some(2)); 404 | /// assert_eq!(s.eytzinger_search_by(|x| x.cmp(&6)), Some(6)); 405 | /// assert_eq!(s.eytzinger_search_by(|x| x.cmp(&7)), None); 406 | /// ``` 407 | fn eytzinger_search_by<'a, F>(&'a self, f: F) -> Option 408 | where 409 | F: FnMut(&'a T) -> Ordering, 410 | T: 'a; 411 | 412 | /// Binary searches this sorted slice with a key extraction function. 413 | /// 414 | /// Assumes that the slice is eytzinger-sorted by the key, for instance with 415 | /// `slice::sort_by_key` combined with `eytzinger::eytzingerize` using the 416 | /// same key extraction function. 417 | /// 418 | /// If a matching value is found then `Some` is returned, containing the index of the 419 | /// matching element; if no match is found then `None` is returned. 420 | /// 421 | /// # Examples 422 | /// 423 | /// ```rust 424 | /// use eytzinger::SliceExt; 425 | /// let s = [(3, 'd'), (1, 'b'), (5, 'f'), (0, 'a'), (2, 'c'), (4, 'e'), (6, 'g')]; 426 | /// assert_eq!(s.eytzinger_search_by_key(&'f', |&(_, b)| b), Some(2)); 427 | /// assert_eq!(s.eytzinger_search_by_key(&'g', |&(_, b)| b), Some(6)); 428 | /// assert_eq!(s.eytzinger_search_by_key(&'x', |&(_, b)| b), None); 429 | /// ``` 430 | fn eytzinger_search_by_key<'a, B, F, Q: ?Sized>(&'a self, b: &Q, f: F) -> Option 431 | where 432 | B: Borrow, 433 | F: FnMut(&'a T) -> B, 434 | Q: Ord, 435 | T: 'a; 436 | 437 | /// Binary searches this eytzinger slice with a comparator function for interpolation. 438 | /// 439 | /// Called "interpolative" search because it's useful for when you need to interpolate to a value between two values in the array. 440 | /// 441 | /// The comparator function should implement an order consistent with the sort order 442 | /// of the underlying eytzinger slice, returning an order code that indicates whether 443 | /// its argument is `Less`, `Equal` or `Greater` than the desired target. 444 | /// 445 | /// The first return value is the index of the highest value that's less than or equal to the target value. 446 | /// The second return value is the index of the lowest value that's greater than to the target value. 447 | /// 448 | /// # Examples 449 | /// 450 | /// ```rust 451 | /// use eytzinger::SliceExt; 452 | /// let s = [3, 1, 5, 0, 2, 4, 6]; 453 | /// assert_eq!(s.eytzinger_interpolative_search_by(|x| x.cmp(&3)), (Some(0_usize), Some(5_usize))); 454 | /// assert_eq!(s.eytzinger_interpolative_search_by(|x| x.cmp(&5)), (Some(2_usize), Some(6_usize))); 455 | /// assert_eq!(s.eytzinger_interpolative_search_by(|x| x.cmp(&6)), (Some(6_usize), None)); 456 | /// assert_eq!(s.eytzinger_interpolative_search_by(|x| x.cmp(&7)), (Some(6_usize), None)); 457 | /// assert_eq!(s.eytzinger_interpolative_search_by(|x| x.cmp(&0)), (Some(3_usize), Some(1_usize))); 458 | /// assert_eq!(s.eytzinger_interpolative_search_by(|x| x.cmp(&-1)), (None, Some(3_usize))); 459 | /// assert_eq!(s.eytzinger_interpolative_search_by(|x| (*x as f32).partial_cmp(&3.5).unwrap()), (Some(0_usize), Some(5_usize))); 460 | /// ``` 461 | fn eytzinger_interpolative_search_by<'a, F>(&'a self, f: F) -> (Option, Option) 462 | where 463 | F: FnMut(&'a T) -> Ordering, 464 | T: 'a; 465 | 466 | /// Binary searches this eytzinger slice for interpolation. 467 | /// 468 | /// The first return value is the index of the highest value that's less than or equal to the target value. 469 | /// The second return value is the index of the lowest value that's greater than to the target value. 470 | /// 471 | /// # Examples 472 | /// 473 | /// ```rust 474 | /// use eytzinger::SliceExt; 475 | /// let s = [3, 1, 5, 0, 2, 4, 6]; 476 | /// assert_eq!(s.eytzinger_interpolative_search(&3), (Some(0_usize), Some(5_usize))); 477 | /// assert_eq!(s.eytzinger_interpolative_search(&5), (Some(2_usize), Some(6_usize))); 478 | /// assert_eq!(s.eytzinger_interpolative_search(&6), (Some(6_usize), None)); 479 | /// assert_eq!(s.eytzinger_interpolative_search(&7), (Some(6_usize), None)); 480 | /// assert_eq!(s.eytzinger_interpolative_search(&0), (Some(3_usize), Some(1_usize))); 481 | /// assert_eq!(s.eytzinger_interpolative_search(&-1), (None, Some(3_usize))); 482 | /// ``` 483 | fn eytzinger_interpolative_search(&self, x: &Q) -> (Option, Option) 484 | where 485 | Q: Ord, 486 | T: Borrow; 487 | 488 | /// Binary searches this sorted slice with a key extraction function for interpolation. 489 | /// 490 | /// Assumes that the slice is eytzinger-sorted by the key, for instance with 491 | /// `slice::sort_by_key` combined with `eytzinger::eytzingerize` using the 492 | /// same key extraction function. 493 | /// 494 | /// The first return value is the index of the highest value that's less than or equal to the target value. 495 | /// The second return value is the index of the lowest value that's greater than to the target value. 496 | /// 497 | /// # Examples 498 | /// 499 | /// ```rust 500 | /// use eytzinger::SliceExt; 501 | /// let s = [(3, 'd'), (1, 'b'), (5, 'f'), (0, 'a'), (2, 'c'), (4, 'e'), (6, 'g')]; 502 | /// assert_eq!(s.eytzinger_interpolative_search_by_key(&'d', |&(_, b)| b), (Some(0), Some(5))); 503 | /// assert_eq!(s.eytzinger_interpolative_search_by_key(&'f', |&(_, b)| b), (Some(2), Some(6))); 504 | /// assert_eq!(s.eytzinger_interpolative_search_by_key(&'g', |&(_, b)| b), (Some(6), None)); 505 | /// assert_eq!(s.eytzinger_interpolative_search_by_key(&'x', |&(_, b)| b), (Some(6), None)); 506 | /// ``` 507 | fn eytzinger_interpolative_search_by_key<'a, B, F, Q: ?Sized>(&'a self, b: &Q, f: F) -> (Option, Option) 508 | where 509 | B: Borrow, 510 | F: FnMut(&'a T) -> B, 511 | Q: Ord, 512 | T: 'a; 513 | 514 | } 515 | 516 | /// Binary searches this eytzinger slice with a comparator function. 517 | /// 518 | /// Called "interpolative" search because it's useful because it's useful for when you need to interpolate to a value between two values in the array. 519 | /// 520 | /// The comparator function should implement an order consistent with the sort order 521 | /// of the underlying eytzinger slice, returning an order code that indicates whether 522 | /// its argument is `Less`, `Equal` or `Greater` than the desired target. 523 | /// 524 | /// The first return value is the index of the highest value that's less than or equal to the target value. 525 | /// The second return value is the index of the lowest value that's greater than to the target value. 526 | /// 527 | /// # Examples 528 | /// 529 | /// ```rust 530 | /// use eytzinger::eytzinger_interpolative_search_by; 531 | /// let s = [3, 1, 5, 0, 2, 4, 6]; 532 | /// assert_eq!(eytzinger_interpolative_search_by(&s, |x| x.cmp(&3)), (Some(0_usize), Some(5_usize))); 533 | /// assert_eq!(eytzinger_interpolative_search_by(&s, |x| x.cmp(&5)), (Some(2_usize), Some(6_usize))); 534 | /// assert_eq!(eytzinger_interpolative_search_by(&s, |x| x.cmp(&6)), (Some(6_usize), None)); 535 | /// assert_eq!(eytzinger_interpolative_search_by(&s, |x| x.cmp(&7)), (Some(6_usize), None)); 536 | /// assert_eq!(eytzinger_interpolative_search_by(&s, |x| x.cmp(&0)), (Some(3_usize), Some(1_usize))); 537 | /// assert_eq!(eytzinger_interpolative_search_by(&s, |x| x.cmp(&-1)), (None, Some(3_usize))); 538 | /// assert_eq!(eytzinger_interpolative_search_by(&s, |x| (*x as f32).partial_cmp(&3.5).unwrap()), (Some(0_usize), Some(5_usize))); 539 | /// ``` 540 | #[inline] 541 | pub fn eytzinger_interpolative_search_by<'a, T: 'a, F>( 542 | data: &'a [T], 543 | mut f: F, 544 | ) -> (Option, Option) 545 | where 546 | F: FnMut(&'a T) -> Ordering, 547 | { 548 | let mut lte: Option = None; // Index of highest value <= target 549 | let mut gt: Option = None; // Index of lowest value > target 550 | let mut i = 0; 551 | 552 | while i < data.len() { 553 | let v = &data[i]; 554 | match f(v) { 555 | Ordering::Less | Ordering::Equal => { 556 | // Current value <= target 557 | lte = Some(i); 558 | // Go right to find larger values (potentially better lte or gt) 559 | i = 2 * i + 2; 560 | } 561 | Ordering::Greater => { 562 | // Current value > target 563 | // This is a candidate for gt (lowest value > target) 564 | gt = Some(i); 565 | // Go left to find smaller values (potentially lte or better gt) 566 | i = 2 * i + 1; 567 | } 568 | } 569 | } 570 | 571 | (lte, gt) 572 | } 573 | 574 | /// Binary searches this eytzinger slice with a comparator function. 575 | /// 576 | /// The comparator function should implement an order consistent with the sort order 577 | /// of the underlying eytzinger slice, returning an order code that indicates whether 578 | /// its argument is `Less`, `Equal` or `Greater` than the desired target. 579 | /// 580 | /// If a matching value is found then `Some` is returned, containing the index of the 581 | /// matching element; if no match is found then `None` is returned. 582 | /// 583 | /// # Examples 584 | /// 585 | /// ```rust 586 | /// use eytzinger::eytzinger_search_by; 587 | /// let s = [3, 1, 5, 0, 2, 4, 6]; 588 | /// assert_eq!(eytzinger_search_by(&s, |x| x.cmp(&3)), Some(0)); 589 | /// assert_eq!(eytzinger_search_by(&s, |x| x.cmp(&5)), Some(2)); 590 | /// assert_eq!(eytzinger_search_by(&s, |x| x.cmp(&6)), Some(6)); 591 | /// assert_eq!(eytzinger_search_by(&s, |x| x.cmp(&7)), None); 592 | /// ``` 593 | #[inline] 594 | pub fn eytzinger_search_by<'a, T: 'a, F>(data: &'a [T], f: F) -> Option 595 | where 596 | F: FnMut(&'a T) -> Ordering, 597 | { 598 | eytzinger_search_by_impl(data, f) 599 | } 600 | 601 | #[inline] 602 | #[cfg(not(feature = "branchless"))] 603 | fn eytzinger_search_by_impl<'a, T: 'a, F>(data: &'a [T], mut f: F) -> Option 604 | where 605 | F: FnMut(&'a T) -> Ordering, 606 | { 607 | let mut i = 0; 608 | loop { 609 | match data.get(i) { 610 | Some(ref v) => { 611 | match f(v) { 612 | Ordering::Equal => return Some(i), 613 | o => { 614 | // I was hoping the optimizer could handle this but it can't 615 | // So here goes the evil hack: Ordering is -1/0/1 616 | // So we use this dirty trick to map this to +2/X/+1 617 | let o = o as usize; 618 | let o = (o >> 1) & 1; 619 | i = 2 * i + 1 + o; 620 | } 621 | }; 622 | } 623 | None => return None, 624 | } 625 | } 626 | } 627 | 628 | #[inline] 629 | #[cfg(feature = "branchless")] 630 | fn eytzinger_search_by_impl<'a, T: 'a, F>(data: &'a [T], mut f: F) -> Option 631 | where 632 | F: FnMut(&'a T) -> Ordering, 633 | { 634 | let mut i = 0; 635 | while i < data.len() { 636 | let v = &data[i]; // this range check is optimized out :D 637 | i = match f(v) { 638 | Ordering::Greater | Ordering::Equal => 2 * i + 1, 639 | Ordering::Less => 2 * i + 2, 640 | }; 641 | } 642 | 643 | // magic from the paper to fix up the (incomplete) final tree layer 644 | // (only difference is that we recheck f() because this is exact search) 645 | let p = i + 1; 646 | let j = p >> (1 + (!p).trailing_zeros()); 647 | if j != 0 && (f(&data[j - 1]) == Ordering::Equal) { 648 | Some(j - 1) 649 | } else { 650 | None 651 | } 652 | } 653 | 654 | impl SliceExt for [T] { 655 | #[inline] 656 | fn eytzingerize>(&mut self, permutator: &mut P) { 657 | eytzingerize(self, permutator) 658 | } 659 | 660 | #[inline] 661 | fn eytzinger_search(&self, x: &Q) -> Option 662 | where 663 | Q: Ord, 664 | T: Borrow, 665 | { 666 | self.eytzinger_search_by(|e| e.borrow().cmp(x)) 667 | } 668 | 669 | #[inline] 670 | fn eytzinger_search_by<'a, F>(&'a self, f: F) -> Option 671 | where 672 | F: FnMut(&'a T) -> Ordering, 673 | T: 'a, 674 | { 675 | eytzinger_search_by(self, f) 676 | } 677 | 678 | #[inline] 679 | fn eytzinger_search_by_key<'a, B, F, Q: ?Sized>(&'a self, b: &Q, mut f: F) -> Option 680 | where 681 | B: Borrow, 682 | F: FnMut(&'a T) -> B, 683 | Q: Ord, 684 | T: 'a, 685 | { 686 | self.eytzinger_search_by(|k| f(k).borrow().cmp(b)) 687 | } 688 | 689 | #[inline] 690 | fn eytzinger_interpolative_search_by<'a, F>(&'a self, f: F) -> (Option, Option) 691 | where 692 | F: FnMut(&'a T) -> Ordering, 693 | T: 'a, 694 | { 695 | eytzinger_interpolative_search_by(self, f) 696 | } 697 | 698 | #[inline] 699 | fn eytzinger_interpolative_search(&self, x: &Q) -> (Option, Option) 700 | where 701 | Q: Ord, 702 | T: Borrow, 703 | { 704 | self.eytzinger_interpolative_search_by(|e| e.borrow().cmp(x)) 705 | } 706 | 707 | #[inline] 708 | fn eytzinger_interpolative_search_by_key<'a, B, F, Q: ?Sized>(&'a self, b: &Q, mut f: F) -> (Option, Option) 709 | where 710 | B: Borrow, 711 | F: FnMut(&'a T) -> B, 712 | Q: Ord, 713 | T: 'a, 714 | { 715 | self.eytzinger_interpolative_search_by(|k| f(k).borrow().cmp(b)) 716 | } 717 | } 718 | 719 | #[cfg(test)] 720 | #[macro_use] 721 | extern crate quickcheck; 722 | #[cfg(test)] 723 | mod tests { 724 | use super::foundation::*; 725 | use super::*; 726 | 727 | #[test] 728 | fn magic() { 729 | for (i, &v) in [0, 1, 1, 2, 3, 3, 3, 4, 5, 6, 7, 7, 7, 7, 7, 8] 730 | .iter() 731 | .enumerate() 732 | { 733 | assert_eq!(get_permutation_element_by_node(i + 1, 1, 0), v); 734 | } 735 | for (i, &v) in [0, 0, 1, 1, 1, 1, 2, 3, 3].iter().enumerate() { 736 | assert_eq!(get_permutation_element_by_node(i + 2, 2, 0), v); 737 | } 738 | for (i, &v) in [2, 3, 4, 5, 5, 6, 7, 8].iter().enumerate() { 739 | assert_eq!(get_permutation_element_by_node(i + 3, 2, 1), v); 740 | } 741 | for (i, &v) in [0, 0, 0, 0, 1, 1, 1].iter().enumerate() { 742 | assert_eq!(get_permutation_element_by_node(i + 4, 3, 0), v); 743 | } 744 | } 745 | 746 | const REF_PERMUTATIONS: &[&'static [usize]] = &[ 747 | &[], 748 | &[0], 749 | &[1, 0], 750 | &[1, 0, 2], 751 | &[2, 1, 3, 0], 752 | &[3, 1, 4, 0, 2], 753 | &[3, 1, 5, 0, 2, 4], 754 | &[3, 1, 5, 0, 2, 4, 6], 755 | &[4, 2, 6, 1, 3, 5, 7, 0], 756 | &[5, 3, 7, 1, 4, 6, 8, 0, 2], 757 | &[6, 3, 8, 1, 5, 7, 9, 0, 2, 4], 758 | &[7, 3, 0xb, 1, 5, 9, 0xd, 0, 2, 4, 6, 8, 0xa, 0xc, 0xe], 759 | ]; 760 | 761 | #[test] 762 | fn reference_permutations() { 763 | for &array in REF_PERMUTATIONS { 764 | let permut: Vec<_> = PermutationGenerator::new(array.len()).collect(); 765 | assert_eq!(array, permut.as_slice()); 766 | } 767 | } 768 | 769 | #[test] 770 | fn eytzingerize_simple() { 771 | let mut permutator = InplacePermutator; 772 | for &array in REF_PERMUTATIONS { 773 | let mut payload: Vec<_> = (0..array.len()).collect(); 774 | eytzingerize(payload.as_mut_slice(), &mut permutator); 775 | assert_eq!(payload, array); 776 | } 777 | } 778 | 779 | const NODE_INDEXES: &[(usize, usize)] = &[ 780 | (1, 0), 781 | (2, 0), 782 | (2, 1), 783 | (3, 0), 784 | (3, 1), 785 | (3, 2), 786 | (3, 3), 787 | (4, 0), 788 | (4, 1), 789 | (4, 2), 790 | (4, 3), 791 | (4, 4), 792 | (4, 5), 793 | (4, 6), 794 | (4, 7), 795 | ]; 796 | 797 | #[test] 798 | fn calc_index() { 799 | for (i, &x) in NODE_INDEXES.iter().enumerate() { 800 | assert_eq!(x, index_to_node(i)); 801 | } 802 | } 803 | 804 | #[test] 805 | fn simple_inplace_permutation() { 806 | let permutation: &[usize] = &[4, 2, 3, 0, 1]; 807 | let mut data = [1, 2, 3, 4, 5]; 808 | InplacePermutator.permute(&mut data, &permutation); 809 | assert_eq!(data, [5, 3, 4, 1, 2]); 810 | } 811 | 812 | #[test] 813 | #[cfg(feature = "heap-permutator")] 814 | fn simple_heap_permutation() { 815 | let permutation: &[usize] = &[4, 2, 3, 0, 1]; 816 | let mut data = [1, 2, 3, 4, 5]; 817 | HeapPermutator::default().permute(&mut data, &permutation); 818 | assert_eq!(data, [5, 3, 4, 1, 2]); 819 | } 820 | 821 | #[test] 822 | #[cfg(feature = "heap-permutator-sparse")] 823 | fn simple_heap_permutation_sparse() { 824 | let permutation: &[usize] = &[4, 2, 3, 0, 1]; 825 | let mut data = [1, 2, 3, 4, 5]; 826 | SparseHeapPermutator::default().permute(&mut data, &permutation); 827 | assert_eq!(data, [5, 3, 4, 1, 2]); 828 | } 829 | 830 | #[test] 831 | fn simple_heap_copy_permutation() { 832 | let permutation: &[usize] = &[4, 2, 3, 0, 1]; 833 | let mut data = [1, 2, 3, 4, 5]; 834 | HeapCopyPermutator::default().permute(&mut data, &permutation); 835 | assert_eq!(data, [5, 3, 4, 1, 2]); 836 | } 837 | 838 | #[test] 839 | fn search_negative() { 840 | let data: &[i32] = &[6, 2, 10, 0, 4, 8, 12]; 841 | for i in -10..20 { 842 | let expected = data.iter().position(|&x| x == i); 843 | assert_eq!(expected, data.eytzinger_search(&i)); 844 | } 845 | } 846 | 847 | fn test_permutation(junk: Vec) -> bool 848 | where 849 | for<'a> P: Permutator, 850 | { 851 | // first create a permutation from the random array 852 | let mut perm: Vec<_> = (0..junk.len()).collect(); 853 | perm.sort_by_key(|&i| junk[i]); 854 | 855 | // now test 856 | let mut data: Vec<_> = (0..perm.len()).collect(); 857 | P::default().permute(data.as_mut_slice(), &perm.as_slice()); 858 | data == perm 859 | } 860 | 861 | quickcheck! { 862 | fn inplace_permutation(junk: Vec) -> bool { 863 | test_permutation::(junk) 864 | } 865 | 866 | fn stack_permutation(junk: Vec) -> bool { 867 | test_permutation::(junk) 868 | } 869 | 870 | #[cfg(feature = "heap-permutator")] 871 | fn heap_permutation(junk: Vec) -> bool { 872 | test_permutation::(junk) 873 | } 874 | 875 | #[cfg(feature = "heap-permutator-sparse")] 876 | fn heap_permutation_sparse(junk: Vec) -> bool { 877 | test_permutation::(junk) 878 | } 879 | 880 | fn heap_copy_permutation(junk: Vec) -> bool { 881 | test_permutation::>(junk) 882 | } 883 | 884 | fn eytzinger_tree_invariants(length: usize) -> bool { 885 | let perm: Vec<_> = PermutationGenerator::new(length).collect(); 886 | 887 | let mut todo = Vec::new(); 888 | todo.push((0, 0..length)); 889 | let mut checked = 0; 890 | while let Some((i, range)) = todo.pop() { 891 | match perm.get(i) { 892 | Some(&v) => { 893 | if !(range.start <= v && v < range.end) { return false; } 894 | todo.push((2 * (i + 1) - 1, range.start..v)); 895 | todo.push((2 * (i + 1), v..range.end)); 896 | checked += 1; 897 | } 898 | None => continue, 899 | } 900 | } 901 | 902 | checked == length 903 | } 904 | 905 | fn search_works(data: Vec) -> bool { 906 | let mut data = data; 907 | data.sort(); 908 | data.dedup(); 909 | data.eytzingerize(&mut InplacePermutator); 910 | 911 | data.iter().enumerate().all(|(i, v)| data.eytzinger_search(v) == Some(i)) 912 | } 913 | } 914 | } 915 | --------------------------------------------------------------------------------