Skip to main content

arg_cmp/
lib.rs

1use ::std::cmp::Ordering;
2
3/// 2次元座標の偏角順
4///
5/// # Examples
6///
7/// ```
8/// use arg_cmp::ArgCmp;
9///
10/// let north = (0, 1);
11/// let south = (0, -1);
12/// let east = (1, 0);
13/// let west = (-1, 0);
14/// let mut points = vec![north, south, east, west];
15///
16/// points.sort_by_key(|&(x, y)| ArgCmp::new(x, y));
17///
18/// // 0°, 90°, 180°, 270°
19/// assert_eq!(points, vec![(1, 0), (0, 1), (-1, 0), (0, -1)]);
20/// ```
21///
22/// ## 偏角が等しい例
23///
24/// ```
25/// use arg_cmp::ArgCmp;
26///
27/// assert!(ArgCmp::new(1, 0).cmp(&ArgCmp::new(1, 0)).is_eq());
28/// assert!(ArgCmp::new(1, 0).cmp(&ArgCmp::new(2, 0)).is_eq());
29/// ```
30#[derive(Debug, PartialEq, Eq, Clone, Copy)]
31pub struct ArgCmp((i64, i64));
32
33/// 象限
34#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
35pub enum Quadrant {
36    /// 1. [0°, 90°)
37    First,
38    /// 2. [90°, 180°)
39    Second,
40    /// 3. [180°, 270°)
41    Third,
42    /// 4. [270°, 360°)
43    Fourth,
44}
45
46impl ArgCmp {
47    /// # Panics
48    ///
49    /// if (x, y) is origin
50    pub fn new(x: i64, y: i64) -> Self {
51        assert_ne!((x, y), (0, 0));
52        Self((x, y))
53    }
54
55    pub fn x(&self) -> i64 {
56        self.0.0
57    }
58
59    pub fn y(&self) -> i64 {
60        self.0.1
61    }
62
63    fn is_lower_half(&self) -> bool {
64        self.y() < 0 || (self.y() == 0 && self.x() < 0)
65    }
66
67    /// 象限を返す
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// use arg_cmp::{ArgCmp, Quadrant};
73    ///
74    /// let north_east = ArgCmp::new(1, 1);
75    /// let north_west = ArgCmp::new(-1, 1);
76    /// let south_west = ArgCmp::new(-1, -1);
77    /// let south_east = ArgCmp::new(1, -1);
78    ///
79    /// assert_eq!(north_east.quadrant(), Quadrant::First);
80    /// assert_eq!(north_west.quadrant(), Quadrant::Second);
81    /// assert_eq!(south_west.quadrant(), Quadrant::Third);
82    /// assert_eq!(south_east.quadrant(), Quadrant::Fourth);
83    /// ```
84    pub fn quadrant(&self) -> Quadrant {
85        match (self.is_lower_half(), self.x().cmp(&0)) {
86            (false, Ordering::Greater) => Quadrant::First,
87            (false, _) => Quadrant::Second,
88            (true, Ordering::Less) => Quadrant::Third,
89            (true, _) => Quadrant::Fourth,
90        }
91    }
92}
93
94impl Ord for ArgCmp {
95    // https://atcoder.jp/contests/abc442/editorial/15136
96    fn cmp(&self, other: &Self) -> Ordering {
97        self.is_lower_half()
98            .cmp(&other.is_lower_half())
99            .then_with(||
100            // cross_product = self.x() * other.y() - self.y() * other.x() > 0
101            (self.y() * other.x()).cmp(&(self.x() * other.y())))
102    }
103}
104
105impl PartialOrd for ArgCmp {
106    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
107        Some(self.cmp(other))
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use crate::{ArgCmp, Quadrant};
114
115    #[test]
116    fn test_arg_cmp() {
117        // 0°, 45°, 90°, ...
118        let points = vec![
119            (1, 0),
120            (1, 1),
121            (0, 1),
122            (-1, 1),
123            (-1, 0),
124            (-1, -1),
125            (0, -1),
126            (1, -1),
127        ];
128
129        for i in 0..8 {
130            for j in 0..8 {
131                let (xi, yi) = points[i];
132                let (xj, yj) = points[j];
133                assert_eq!(ArgCmp::new(xi, yi).cmp(&ArgCmp::new(xj, yj)), i.cmp(&j));
134            }
135        }
136    }
137
138    #[test]
139    fn test_quadrant() {
140        // 0°, 45°, 90°, 135°, 180°, 225°, 270°, 315°
141        let points = vec![
142            (1, 0),
143            (1, 1),
144            (0, 1),
145            (-1, 1),
146            (-1, 0),
147            (-1, -1),
148            (0, -1),
149            (1, -1),
150        ];
151        let expected = vec![
152            Quadrant::First,
153            Quadrant::First,
154            Quadrant::Second,
155            Quadrant::Second,
156            Quadrant::Third,
157            Quadrant::Third,
158            Quadrant::Fourth,
159            Quadrant::Fourth,
160        ];
161        for (&(x, y), &q) in points.iter().zip(&expected) {
162            assert_eq!(ArgCmp::new(x, y).quadrant(), q);
163        }
164    }
165}