1#![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#[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 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
61impl<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
71impl<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}