index_permute/
lib.rs

1//! - A tool to reorder an array by an index array inplace when the elements are not [`Clone`] or [`Copy`].
2//! - lock-free parallel implementation.
3//! ## Example
4//! ```
5//! use index_permute::PermuteIndex;
6//! let index = PermuteIndex::try_new(&[2, 0, 1
7//! ]).unwrap();
8//! let mut data = vec![10, 20, 30];
9//! index_permute::order_by_index_inplace(&mut data, index);
10//! assert_eq!(data, vec![30, 10, 20]);
11//! ```
12#![deny(missing_docs)]
13#![deny(warnings)]
14
15use std::{mem::forget, ptr};
16use thiserror::Error;
17
18/// A struct to hold a permutation index.
19/// The index must be unique and in the range of `0..len`, where `len` is the length of the data to be permuted.
20/// This struct is used to ensure that the index is valid before performing any operations on the data.
21/// It can be created using [`PermuteIndex::try_new`], which checks the validity of the index.
22/// If the index is invalid, it returns a [`PermuteError::InvalidIndex`] error.
23/// The index can be used to permute data using the [`try_order_by_index_inplace`] function.
24/// The index length must match the data length, otherwise it returns a [`PermuteError::LengthMismatch`] error.
25/// The [`order_by_index_inplace`] function is a convenience function that panics if the index is invalid or the lengths do not match.
26/// # Example
27/// ```
28/// use index_permute::PermuteIndex;
29/// let index = PermuteIndex::try_new(&[2, 0, 1
30/// ]).unwrap();
31/// let mut data = vec![10, 20, 30];
32/// index_permute::order_by_index_inplace(&mut data, index);
33/// assert_eq!(data, vec![30, 10, 20]);
34/// ```
35pub struct PermuteIndex<T> {
36    data: T,
37}
38
39/// An error type for [`PermuteIndex`] and [`try_order_by_index_inplace`].
40#[derive(Debug, Error)]
41pub enum PermuteError {
42    /// An error indicating that the index is invalid.
43    #[error("Invalid index: indices must be unique and in 0..len")]
44    InvalidIndex,
45    /// An error indicating that the index length does not match the data length.
46    #[error("Index length must match data length")]
47    LengthMismatch,
48}
49impl<T> PermuteIndex<T>
50where
51    T: AsRef<[usize]>,
52{
53    fn check_index(index: &T) -> bool {
54        // make sure all indices are unique and from 0 to len-1
55        let indices = index.as_ref();
56        let len = indices.len();
57        let mut seen = vec![false; len];
58        for &i in indices {
59            if i >= len || seen[i] {
60                return false; // index out of bounds or duplicate
61            }
62            seen[i] = true;
63        }
64        true
65    }
66    /// Creates a new [`PermuteIndex`] if the index is valid.
67    /// Returns [`PermuteError::InvalidIndex`] if the index is not valid.
68    /// The index must be unique and in the range of `0..len`, where `len` is the length of the data to be permuted.
69    /// The index can be used to permute data using the [`try_order_by_index_inplace`] function.
70    /// The index length must match the data length, otherwise it returns [`PermuteError::LengthMismatch`] error.
71    /// The [`order_by_index_inplace`] function is a convenience function that panics if the index is invalid or the lengths do not match.
72    /// # Example
73    /// ```
74    /// use index_permute::PermuteIndex;
75    /// let index = PermuteIndex::try_new(&[2, 0, 1]).unwrap();
76    /// let mut data = vec![10, 20, 30];
77    /// index_permute::order_by_index_inplace(&mut data, index);
78    /// assert_eq!(data, vec![30, 10, 20]);
79    /// ```
80    pub fn try_new(index: T) -> Result<Self, PermuteError> {
81        if Self::check_index(&index) {
82            Ok(PermuteIndex { data: index })
83        } else {
84            Err(PermuteError::InvalidIndex)
85        }
86    }
87}
88
89/// Reorders the data in place according to the given index.
90/// First create a [`PermuteIndex`], then, it reorders the data in place
91/// # Example
92/// ```
93/// use index_permute::PermuteIndex;
94/// let index = PermuteIndex::try_new(&[2, 0, 1]).unwrap();
95/// let mut data = vec![10, 20, 30];
96/// index_permute::try_order_by_index_inplace(&mut data, index).unwrap();
97/// assert_eq!(data, vec![30, 10, 20]);
98/// ```
99pub fn try_order_by_index_inplace<T, I>(
100    data: &mut [T],
101    index: PermuteIndex<I>,
102) -> Result<(), PermuteError>
103where
104    I: AsRef<[usize]>,
105{
106    let indices = index.data.as_ref();
107    let len = data.len();
108    if indices.len() != len {
109        return Err(PermuteError::LengthMismatch);
110    }
111
112    // SAFETY: indices are unique and a valid permutation of 0..len,
113    // so we can move elements without overlap.
114
115    // Create a Vec<T> with uninitialized memory
116    let mut temp: Vec<T> = Vec::with_capacity(len);
117    unsafe {
118        temp.set_len(len);
119
120        for (i, &idx) in indices.iter().enumerate() {
121            // Move from data[idx] to temp[i]
122            ptr::write(
123                temp.get_unchecked_mut(i),
124                ptr::read(data.get_unchecked(idx)),
125            );
126        }
127
128        // Move back from temp to data
129        for i in 0..len {
130            ptr::write(data.get_unchecked_mut(i), ptr::read(temp.get_unchecked(i)));
131        }
132        forget(temp); // Prevent deallocation of temp
133    }
134    Ok(())
135}
136
137/// A convenience function that panics if the index is invalid or the lengths do not match.
138/// It is recommended to use [`try_order_by_index_inplace`] for error handling.
139/// # Example
140/// ```
141/// use index_permute::PermuteIndex;
142/// let index = PermuteIndex::try_new(&[2, 0, 1]).unwrap();
143/// let mut data = vec![10, 20, 30];
144/// index_permute::order_by_index_inplace(&mut data, index);
145/// assert_eq!(data, vec![30, 10, 20]);
146/// ```
147pub fn order_by_index_inplace<T, I>(data: &mut [T], index: PermuteIndex<I>)
148where
149    I: AsRef<[usize]>,
150{
151    if let Err(e) = try_order_by_index_inplace(data, index) {
152        panic!("Failed to order by index: {}", e);
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_permute_index() {
162        let _ = PermuteIndex::try_new(&[0usize, 2, 1]);
163        let _ = PermuteIndex::try_new(vec![0, 1, 2]);
164        let _ = PermuteIndex::try_new(&vec![0, 1, 2]);
165        let _ = PermuteIndex::try_new(&[0, 1, 2][..]);
166    }
167
168    #[test]
169    fn test_permute_order() {
170        let mut data = vec![10, 20, 30];
171        let index = PermuteIndex::try_new(&[2, 0, 1]).unwrap();
172        assert!(try_order_by_index_inplace(&mut data, index).is_ok());
173        assert_eq!(data, vec![30, 10, 20]);
174    }
175
176    #[test]
177    fn test_drop() {
178        struct DropTest {
179            value: usize,
180        }
181        impl Drop for DropTest {
182            fn drop(&mut self) {
183                println!("Dropping {}", self.value);
184            }
185        }
186        let mut data = vec![
187            DropTest { value: 1 },
188            DropTest { value: 2 },
189            DropTest { value: 3 },
190        ];
191        let index = PermuteIndex::try_new(&[2, 0, 1]).unwrap();
192
193        // now, there should be no drop
194        assert!(try_order_by_index_inplace(&mut data, index).is_ok());
195        println!("no drop should happen here");
196
197        assert_eq!(data[0].value, 3);
198        assert_eq!(data[1].value, 1);
199        assert_eq!(data[2].value, 2);
200    }
201}