leftwm_core/utils/
helpers.rs

1//! Generic intersection, finding, reordering, and Vec extraction
2use std::cmp::Ordering;
3
4pub fn intersect<T>(v1: &[T], v2: &[T]) -> bool
5where
6    T: PartialEq,
7{
8    for a in v1 {
9        for b in v2 {
10            if a == b {
11                return true;
12            }
13        }
14    }
15    false
16}
17
18pub fn vec_extract<T, F>(list: &mut Vec<T>, test: F) -> Vec<T>
19where
20    F: Fn(&T) -> bool,
21    T: Clone,
22{
23    let len = list.len();
24    let mut removed = vec![];
25    let mut del = 0;
26    {
27        let v = &mut **list;
28
29        for i in 0..len {
30            if test(&v[i]) {
31                removed.push(v[i].clone());
32                del += 1;
33            } else if del > 0 {
34                v.swap(i - del, i);
35            }
36        }
37    }
38    list.truncate(len - del);
39    removed
40}
41
42pub fn cycle_vec<T>(list: &mut Vec<T>, shift: i32) -> Option<()>
43where
44    T: Clone,
45{
46    let v = &mut **list;
47    let change = shift.unsigned_abs() as usize;
48    if v.len() < change {
49        return None;
50    }
51    match shift.cmp(&0) {
52        Ordering::Less => v.rotate_left(change),
53        Ordering::Greater => v.rotate_right(change),
54        Ordering::Equal => {}
55    }
56    Some(())
57}
58
59//shifts a object left or right in an Vec by a given amount
60pub fn reorder_vec<T, F>(list: &mut Vec<T>, test: F, shift: i32) -> Option<()>
61where
62    F: Fn(&T) -> bool,
63    T: Clone,
64{
65    let len = list.len() as i32;
66    if len < 2 {
67        return None;
68    }
69    let index = list.iter().position(test)?;
70    let item = list.get(index)?.clone();
71
72    let mut new_index = index as i32 + shift;
73    list.remove(index);
74    let v = &mut **list;
75
76    if new_index < 0 {
77        new_index += len;
78        v.rotate_right(1);
79    } else if new_index >= len {
80        new_index -= len;
81        v.rotate_left(1);
82    }
83    list.insert(new_index as usize, item);
84    Some(())
85}
86
87/// Find element relative to reference element.
88///
89/// eg. to get the next element, use `shift` 1,
90/// to get the previous element, use `shift` -1.
91///
92/// ## Arguments
93/// * `list` - The list to get the element from
94/// * `reference_finder` - Predicate to find the reference element in the list
95/// * `shift` - The shift (distance) of the element you try to find relative to the reference element, can be negative to move left
96/// * `should_loop` - If the list should loop when the `shift` goes beyond the start/end of the list
97///
98/// ## Example
99/// ```
100/// let list = vec!["hello", "world", "foo", "bar"];
101/// let result = leftwm_core::utils::helpers::relative_find(&list, |&e| e == "world", 2, false);
102/// assert_eq!(result, Some(&"bar"));
103/// ```
104pub fn relative_find<T, F>(
105    list: &[T],
106    reference_finder: F,
107    shift: i32,
108    should_loop: bool,
109) -> Option<&T>
110where
111    F: Fn(&T) -> bool,
112{
113    let len = list.len() as i32;
114    let reference_index = list.iter().position(reference_finder)?;
115    let loops = if shift.is_negative() {
116        // check if shift is larger than there are elements on the left
117        shift.unsigned_abs() as usize > reference_index
118    } else {
119        // check if shift is larger than there are elements on the right
120        shift as usize > len as usize - (reference_index + 1)
121    };
122
123    let relative_index = if loops && !should_loop {
124        None
125    } else {
126        let shift = shift % len;
127        let shifted_index = reference_index as i32 + shift;
128        let max_index = len - 1;
129        if shifted_index < 0 {
130            Some((len + shifted_index) as usize)
131        } else if shifted_index > max_index {
132            Some((shifted_index - len) as usize)
133        } else {
134            Some(shifted_index as usize)
135        }
136    }?;
137
138    list.get(relative_index)
139}
140
141#[cfg(test)]
142pub(crate) mod test {
143    use crate::utils::helpers::relative_find;
144
145    pub async fn temp_path() -> std::io::Result<std::path::PathBuf> {
146        tokio::task::spawn_blocking(|| tempfile::Builder::new().tempfile_in("../target"))
147            .await
148            .expect("Blocking task joined")?
149            .into_temp_path()
150            .keep()
151            .map_err(Into::into)
152    }
153
154    #[test]
155    fn relative_find_should_work_both_ways() {
156        let list = vec!["hello", "world", "foo", "bar"];
157        let result = relative_find(&list, |&e| e == "hello", 2, false);
158        assert_eq!(result, Some(&"foo"));
159        let result = relative_find(&list, |&e| e == "bar", -2, false);
160        assert_eq!(result, Some(&"world"));
161    }
162
163    #[test]
164    fn relative_find_with_inexistent_reference_must_return_none() {
165        let list = vec!["hello", "world", "foo", "bar"];
166        let result = relative_find(&list, |&e| e == "inexistent", 2, false);
167        assert_eq!(result, None);
168    }
169
170    #[test]
171    fn relative_find_should_be_able_to_loop() {
172        let list = vec!["hello", "world", "foo", "bar"];
173        let result = relative_find(&list, |&e| e == "hello", 4, true);
174        assert_eq!(result, Some(&"hello"));
175        let result = relative_find(&list, |&e| e == "hello", 9, true);
176        assert_eq!(result, Some(&"world"));
177        let result = relative_find(&list, |&e| e == "hello", -9, true);
178        assert_eq!(result, Some(&"bar"));
179    }
180
181    #[test]
182    fn relative_find_loop_can_be_disabled() {
183        let list = vec!["hello", "world", "foo", "bar"];
184        let result = relative_find(&list, |&e| e == "hello", 9, false);
185        assert_eq!(result, None);
186    }
187}