lowdash/
max_by.rs

1/// Find the maximum element in a collection based on a custom comparison function.
2/// If the collection is empty, returns `None`.
3///
4/// # Arguments
5///
6/// * `collection` - A slice of items.
7/// * `comparison` - A function that takes two items and returns `true` if the first item is considered greater than the second.
8///
9/// # Returns
10///
11/// * `Option<T>` - The maximum item in the collection based on the comparison function, or `None` if the collection is empty.
12///
13/// # Examples
14/// ```rust
15/// use lowdash::max_by;
16///
17/// #[derive(Debug, PartialEq, Eq, Clone)]
18/// struct Person {
19///     age: u32,
20///     name: String,
21/// }
22///
23/// let people = vec![
24///     Person { age: 25, name: "Alice".to_string() },
25///     Person { age: 30, name: "Bob".to_string() },
26///     Person { age: 20, name: "Carol".to_string() },
27/// ];
28///
29/// let result = max_by(&people, |a, b| a.age > b.age);
30/// assert_eq!(
31///     result,
32///     Some(Person { age: 30, name: "Bob".to_string() })
33/// );
34/// ```
35pub fn max_by<T, F>(collection: &[T], comparison: F) -> Option<T>
36where
37    T: Clone,
38    F: Fn(&T, &T) -> bool,
39{
40    if collection.is_empty() {
41        return None;
42    }
43
44    if collection.len() == 1 {
45        return Some(collection[0].clone());
46    }
47
48    let mut max = collection[0].clone();
49
50    for item in &collection[1..] {
51        if comparison(item, &max) {
52            max = item.clone();
53        }
54    }
55
56    Some(max)
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn test_max_by_age() {
65        #[derive(Debug, PartialEq, Eq, Clone)]
66        struct Person {
67            age: u32,
68            name: String,
69        }
70
71        let people = vec![
72            Person {
73                age: 25,
74                name: "Alice".to_string(),
75            },
76            Person {
77                age: 30,
78                name: "Bob".to_string(),
79            },
80            Person {
81                age: 20,
82                name: "Carol".to_string(),
83            },
84        ];
85
86        let result = max_by(&people, |a, b| a.age > b.age);
87        assert_eq!(
88            result,
89            Some(Person {
90                age: 30,
91                name: "Bob".to_string()
92            })
93        );
94    }
95
96    #[test]
97    fn test_max_by_name() {
98        #[derive(Debug, PartialEq, Eq, Clone)]
99        struct Person {
100            age: u32,
101            name: String,
102        }
103
104        let people = vec![
105            Person {
106                age: 25,
107                name: "Alice".to_string(),
108            },
109            Person {
110                age: 30,
111                name: "Bob".to_string(),
112            },
113            Person {
114                age: 20,
115                name: "Carol".to_string(),
116            },
117        ];
118
119        let result = max_by(&people, |a, b| a.name > b.name);
120        assert_eq!(
121            result,
122            Some(Person {
123                age: 20,
124                name: "Carol".to_string()
125            })
126        );
127    }
128
129    #[test]
130    fn test_max_by_empty_collection() {
131        #[derive(Debug, PartialEq, Eq, Clone)]
132        struct Person {
133            age: u32,
134            name: String,
135        }
136
137        let people: Vec<Person> = vec![];
138        let result = max_by(&people, |a, b| a.age > b.age);
139        assert_eq!(result, None);
140    }
141
142    #[test]
143    fn test_max_by_single_element() {
144        #[derive(Debug, PartialEq, Eq, Clone)]
145        struct Person {
146            age: u32,
147            name: String,
148        }
149
150        let person = Person {
151            age: 25,
152            name: "Alice".to_string(),
153        };
154        let people = vec![person.clone()];
155        let result = max_by(&people, |a, b| a.age > b.age);
156        assert_eq!(result, Some(person));
157    }
158
159    #[test]
160    fn test_max_by_with_struct_various_criteria() {
161        #[derive(Debug, PartialEq, Eq, Clone)]
162        struct Person {
163            age: u32,
164            name: String,
165        }
166
167        let people = vec![
168            Person {
169                age: 25,
170                name: "Alice".to_string(),
171            },
172            Person {
173                age: 30,
174                name: "Bob".to_string(),
175            },
176            Person {
177                age: 35,
178                name: "Carol".to_string(),
179            },
180        ];
181
182        // Max by age
183        let result_age = max_by(&people, |a, b| a.age > b.age);
184        assert_eq!(
185            result_age,
186            Some(Person {
187                age: 35,
188                name: "Carol".to_string()
189            })
190        );
191
192        // Max by name lexicographically
193        let result_name = max_by(&people, |a, b| a.name > b.name);
194        assert_eq!(
195            result_name,
196            Some(Person {
197                age: 35,
198                name: "Carol".to_string()
199            })
200        );
201    }
202
203    #[test]
204    fn test_max_by_floats() {
205        let float_collection = vec![1.1, 2.2, 3.3, 4.4];
206        let result = max_by(&float_collection, |a, b| a > b);
207        assert_eq!(result, Some(4.4));
208
209        let more_floats = vec![5.5, 3.3, 5.5, 2.2];
210        let result_duplicate = max_by(&more_floats, |a, b| a > b);
211        assert_eq!(result_duplicate, Some(5.5));
212
213        let negative_floats = vec![-1.1, -2.2, -0.5, -3.3];
214        let result_negatives = max_by(&negative_floats, |a, b| a > b);
215        assert_eq!(result_negatives, Some(-0.5));
216
217        let all_nan = vec![std::f64::NAN, std::f64::NAN];
218        let result_all_nan = max_by(&all_nan, |a, b| a > b);
219        // The first NaN will remain as max since NaN > NaN is false
220        // Hence, the result should be the first NaN
221        assert!(result_all_nan.unwrap().is_nan());
222    }
223}