const_identify/
sort.rs

1use std::cmp::Ordering;
2
3use crate::ConstId;
4
5/// An array of [`ConstId`] structs that is guaranteed to be in sorted order.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub struct OrderedIdArray<const SIZE: usize> {
8    ids: [ConstId; SIZE],
9}
10
11impl<const SIZE: usize> OrderedIdArray<SIZE> {
12    /// Returns a new ordered array containing the sorted version of `ids`.
13    pub const fn new(ids: [ConstId; SIZE]) -> Self {
14        Self {
15            ids: const_sort(ids),
16        }
17    }
18
19    /// Returns the underlying id slice.
20    pub const fn as_raw_slice(&self) -> &[ConstId] {
21        &self.ids
22    }
23
24    /// Returns an [`OrderedIdSlice`] with this arrays content.
25    pub const fn as_slice(&self) -> OrderedIdSlice {
26        OrderedIdSlice::from_arr(self)
27    }
28
29    /// Consumes `self` and returns the underlying id array
30    pub const fn into_raw(self) -> [ConstId; SIZE] {
31        self.ids
32    }
33
34    /// Consumes `self` and returns a [`UniqueIdArray`].
35    ///
36    /// Returns `Err(Self)` if the items in this array contains duplicates
37    pub const fn into_unique(self) -> Result<UniqueIdArray<SIZE>, Self> {
38        match UniqueIdArray::new(self.ids) {
39            Some(unique) => Ok(unique),
40            None => Err(self),
41        }
42    }
43
44    crate::impl_cmp!();
45}
46
47/// An unsized slice of [`ConstId`] structs that is guaranteed to be in sorted order.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
49pub struct OrderedIdSlice<'a> {
50    ids: &'a [ConstId],
51}
52
53impl<'a> OrderedIdSlice<'a> {
54    /// Creates a new slice out of the data in `arr`.
55    pub const fn from_arr<const SIZE: usize>(arr: &'a OrderedIdArray<SIZE>) -> Self {
56        Self { ids: &arr.ids }
57    }
58
59    /// Returns the underlying slice.
60    pub const fn as_raw_slice(&self) -> &[ConstId] {
61        &self.ids
62    }
63
64    crate::impl_cmp!();
65}
66
67/// An array of [`ConstId`] structs that are guaranteed to be unique and in sorted order.
68#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
69pub struct UniqueIdArray<const SIZE: usize> {
70    ids: [ConstId; SIZE],
71}
72
73impl<const SIZE: usize> UniqueIdArray<SIZE> {
74    /// Returns a new unique and sorted array containing the sorted version of `ids`.
75    ///
76    /// Returns `None` if the `ids` contains duplicates.
77    pub const fn new(ids: [ConstId; SIZE]) -> Option<Self> {
78        let sorted = const_sort(ids);
79
80        let mut i = 1;
81        while i < SIZE {
82            let left = sorted[i - 1].raw_value();
83            let right = sorted[i].raw_value();
84            if left == right {
85                return None;
86            }
87
88            i += 1;
89        }
90
91        Some(Self { ids: sorted })
92    }
93
94    /// Returns the underlying id slice.
95    pub const fn as_raw_slice(&self) -> &[ConstId] {
96        &self.ids
97    }
98
99    /// Returns a [`UniqueIdSlice`] with this arrays content.
100    pub const fn as_slice(&self) -> UniqueIdSlice {
101        UniqueIdSlice::from_arr(self)
102    }
103
104    /// Consumes `self` and returns the underlying id array
105    pub const fn into_raw(&self) -> [ConstId; SIZE] {
106        self.ids
107    }
108
109    /// Consumes `self` and returns an [`OrderedIdArray`].
110    pub const fn into_ordered(&self) -> OrderedIdArray<SIZE> {
111        OrderedIdArray { ids: self.ids }
112    }
113
114    crate::impl_cmp!();
115}
116
117/// An unsized slice of [`ConstId`] structs that are guaranteed to be unique and in sorted order.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
119pub struct UniqueIdSlice<'a> {
120    ids: &'a [ConstId],
121}
122
123impl<'a> UniqueIdSlice<'a> {
124    /// Creates a new slice out of the data in `arr`.
125    pub const fn from_arr<const SIZE: usize>(arr: &'a UniqueIdArray<SIZE>) -> Self {
126        Self { ids: &arr.ids }
127    }
128
129    /// Returns the underlying slice.
130    pub const fn as_raw_slice(&self) -> &[ConstId] {
131        &self.ids
132    }
133
134    crate::impl_cmp!();
135}
136
137#[macro_export]
138macro_rules! ordered_ids {
139    [$( $item:ty ),* $(,)?] => {
140        $crate::OrderedIdArray::new([$(
141            <$item as $crate::ConstIdentify>::CONST_ID,
142        )*])
143    };
144}
145
146#[macro_export]
147macro_rules! unique_ids {
148    [$( $item:ty ),* $(,)?] => {
149        $crate::UniqueIdArray::new([$(
150            <$item as $crate::ConstIdentify>::CONST_ID,
151        )*])
152    };
153}
154
155/// Consumes an array of ids and returns the array as sorted
156const fn const_sort<const SIZE: usize>(mut arr: [ConstId; SIZE]) -> [ConstId; SIZE] {
157    // Bubble sort implementation pulled from this reddit comment. Thanks!
158    // https://www.reddit.com/r/rust/comments/qw18oa/comment/hl05kuj
159    loop {
160        let mut swapped = false;
161        let mut i = 1;
162        while i < SIZE {
163            let left = arr[i - 1].raw_value();
164            let right = arr[i].raw_value();
165            if left > right {
166                arr[i - 1] = ConstId::from_raw(right);
167                arr[i] = ConstId::from_raw(left);
168                swapped = true;
169            }
170
171            i += 1;
172        }
173        if !swapped {
174            break;
175        }
176    }
177
178    arr
179}
180
181const fn const_cmp(slice1: &[ConstId], slice2: &[ConstId]) -> Ordering {
182    // loop over every item in slice1
183    let mut i = 0;
184    while i < slice1.len() {
185        // if there is nothing left in slice2,
186        // slice1 has to be greater than slice2
187        if i >= slice2.len() {
188            return Ordering::Greater;
189        }
190
191        // compare left value to right value
192        let left = slice1[i].raw_value();
193        let right = slice2[i].raw_value();
194        if left > right {
195            // if left is greater, then slice1 is greater
196            return Ordering::Greater;
197        } else if left < right {
198            // if left is lesser, then slice1 is lesser
199            return Ordering::Less;
200        }
201
202        // increment i for the next iteration
203        i += 1;
204    }
205
206    // if slice2 still has items left,
207    // slice1 has to be lesser than slice2
208    if i < slice2.len() {
209        return Ordering::Less;
210    }
211
212    // if we make it here, then every item in each slice matched
213    Ordering::Equal
214}
215
216#[macro_use]
217mod sealed {
218    #[macro_export]
219    macro_rules! impl_cmp {
220        () => {
221            /// Returns the ordering between `self` and `other`
222            pub const fn const_cmp_ordered<const SIZE2: usize>(
223                &self,
224                other: &OrderedIdArray<SIZE2>,
225            ) -> Ordering {
226                const_cmp(self.as_raw_slice(), other.as_raw_slice())
227            }
228
229            /// Returns the ordering between `self` and `other`
230            pub const fn const_cmp_ordered_slice(&self, other: &OrderedIdSlice) -> Ordering {
231                const_cmp(self.as_raw_slice(), other.as_raw_slice())
232            }
233
234            /// Returns the ordering between `self` and `other`
235            pub const fn const_cmp_unique<const SIZE2: usize>(
236                &self,
237                other: &UniqueIdArray<SIZE2>,
238            ) -> Ordering {
239                const_cmp(self.as_raw_slice(), other.as_raw_slice())
240            }
241
242            /// Returns the ordering between `self` and `other`
243            pub const fn const_cmp_unique_slice(&self, other: &UniqueIdSlice) -> Ordering {
244                const_cmp(self.as_raw_slice(), other.as_raw_slice())
245            }
246        };
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253
254    const ID1: ConstId = ConstId::from_raw(0);
255    const ID2: ConstId = ConstId::from_raw(1);
256    const ID3: ConstId = ConstId::from_raw(2);
257
258    #[test]
259    fn ordered() {
260        let ordered = OrderedIdArray::new([ID2, ID1, ID3]);
261        let slice = ordered.as_raw_slice();
262        assert!(slice[0] < slice[1]);
263        assert!(slice[1] < slice[2]);
264    }
265
266    #[test]
267    fn unique() {
268        assert!(UniqueIdArray::new([ID2, ID1, ID3, ID2]).is_none());
269        let unique = UniqueIdArray::new([ID3, ID1, ID2]).unwrap();
270        let slice = unique.as_raw_slice();
271        assert!(slice[0] < slice[1]);
272        assert!(slice[1] < slice[2]);
273    }
274
275    #[test]
276    fn compare() {
277        let ordered = OrderedIdArray::new([ID2, ID1]);
278        let unique = UniqueIdArray::new([ID3, ID1]).unwrap();
279        let ordered_eq = OrderedIdArray::new([ID1, ID2]);
280        assert!(ordered.const_cmp_unique(&unique).is_lt());
281        assert!(unique.const_cmp_ordered(&ordered).is_gt());
282        assert!(ordered.const_cmp_ordered(&ordered_eq).is_eq());
283    }
284
285    #[test]
286    fn convert() {
287        let ordered = OrderedIdArray::new([ID3, ID1, ID2]);
288        assert!(ordered.into_unique().is_ok());
289
290        let ordered_duplicate = OrderedIdArray::new([ID1, ID2, ID1]);
291        assert!(ordered_duplicate.into_unique().is_err());
292    }
293}