unordered_pair/
lib.rs

1//! This crate provides a tuple struct for an unordered pair
2//! ## Crate Features
3//! - `serde`: Enables serde support for [`UnorderedPair`].
4
5#![deny(
6    rust_2018_idioms,
7    missing_debug_implementations,
8    missing_docs,
9    clippy::doc_markdown,
10    clippy::unimplemented
11)]
12
13use std::cmp::Ordering;
14use std::hash::{Hash, Hasher};
15
16/// A tuple struct representing an unordered pair
17#[derive(Debug, Copy, Clone, Eq, Default)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub struct UnorderedPair<T>(pub T, pub T);
20
21impl<T: Ord> UnorderedPair<T> {
22    /// Transforms the `UnorderedPair<T>` into a `(T,T)`.
23    /// The tuple's components are always in the same order, smallest to largest.
24    ///
25    /// # Examples
26    ///
27    /// ```
28    /// use unordered_pair::UnorderedPair;
29    ///
30    /// let pair = UnorderedPair(1,2);
31    /// let rev = UnorderedPair(2,1);
32    ///
33    /// let tuple_pair = pair.into_ordered_tuple();
34    /// let tuple_rev = rev.into_ordered_tuple();
35    ///
36    /// assert_eq!(tuple_pair, (1,2));
37    /// assert_eq!(tuple_rev, (1,2));
38    /// ```
39    pub fn into_ordered_tuple(self) -> (T, T) {
40        let UnorderedPair(first, second) = self;
41
42        match first.cmp(&second) {
43            Ordering::Greater => (second, first),
44            _ => (first, second),
45        }
46    }
47}
48
49impl<T> From<(T, T)> for UnorderedPair<T> {
50    fn from(tuple: (T, T)) -> UnorderedPair<T> {
51        UnorderedPair(tuple.0, tuple.1)
52    }
53}
54
55impl<T> From<UnorderedPair<T>> for (T, T) {
56    fn from(pair: UnorderedPair<T>) -> (T, T) {
57        (pair.0, pair.1)
58    }
59}
60
61/// Compares two pairs while disregarding the order of the contained items
62impl<T> PartialEq for UnorderedPair<T>
63where
64    T: PartialEq,
65{
66    fn eq(&self, other: &UnorderedPair<T>) -> bool {
67        (self.0 == other.0 && self.1 == other.1) || (self.0 == other.1 && self.1 == other.0)
68    }
69}
70
71/// Computes the same hash regardless of the order of the contained items
72impl<T> Hash for UnorderedPair<T>
73where
74    T: Ord + Hash,
75{
76    fn hash<H>(&self, state: &mut H)
77    where
78        H: Hasher,
79    {
80        let UnorderedPair(first, second) = self;
81
82        match first.cmp(second) {
83            Ordering::Greater => {
84                second.hash(state);
85                first.hash(state);
86            }
87            _ => {
88                first.hash(state);
89                second.hash(state);
90            }
91        }
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn partial_eq_different_internal_order() {
101        let pair = UnorderedPair(5, 7);
102        let rev = UnorderedPair(7, 5);
103        assert_eq!(pair, rev);
104    }
105
106    #[test]
107    fn partial_eq_same_internal_order() {
108        let pair1 = UnorderedPair(5, 7);
109        let pair2 = UnorderedPair(5, 7);
110        assert_eq!(pair1, pair2);
111    }
112
113    #[test]
114    fn partial_eq_nan() {
115        let pair1 = UnorderedPair(f32::NAN, 1.3);
116        let pair2 = UnorderedPair(1.3, f32::NAN);
117        assert_ne!(pair1, pair2);
118    }
119
120    #[test]
121    fn hash_different_internal_order() {
122        use std::collections::hash_map::DefaultHasher as Hasher;
123
124        let hash_pair = {
125            let pair = UnorderedPair(5, 7);
126            let mut hasher = Hasher::new();
127            pair.hash(&mut hasher);
128            hasher.finish()
129        };
130
131        let hash_rev = {
132            let pair = UnorderedPair(7, 5);
133            let mut hasher = Hasher::new();
134            pair.hash(&mut hasher);
135            hasher.finish()
136        };
137
138        assert_eq!(hash_rev, hash_pair);
139    }
140}