Skip to main content

patterns/
lists.rs

1use patterns_macros::because;
2
3pub fn paged<T: Clone>(fetched: &[T], per_page: usize) -> (Vec<T>, bool) {
4    (fetched.iter().take(per_page).cloned().collect(), fetched.len() > per_page)
5}
6because!(paged, "one page out of one more item than the page holds, so the page knows another follows without a second count");
7
8pub fn without_empty<K, T>(series: Vec<(K, Vec<T>)>) -> Vec<(K, Vec<T>)> {
9    series.into_iter().filter(|(_, items)| !items.is_empty()).collect()
10}
11because!(without_empty, "the named lists that hold at least one item, since a chart with an empty line draws a legend entry for nothing");
12
13pub fn holds_in<T: PartialEq>(items: &[T], value: T) -> bool {
14    items.contains(&value)
15}
16because!(holds_in, "whether a list holds a value equal to the one given, for a list of plain values that carry no key of their own, where holds asks a keyed element for its key");
17
18pub fn extended<T>(items: Vec<T>, more: Vec<T>) -> Vec<T> {
19    let mut out = items;
20    out.extend(more);
21    out
22}
23because!(extended, "one list followed by another, taken by value like pushed, for the rows one event adds to the rows a batch has gathered");
24
25pub fn joined_lists<T>(lists: Vec<Vec<T>>) -> Vec<T> {
26    lists.into_iter().flatten().collect()
27}
28because!(joined_lists, "several lists as one in order, so a reducer that builds a movement per condition gathers them without a loop");
29
30pub fn any_true(flags: &[bool]) -> bool {
31    flags.iter().any(|f| *f)
32}
33because!(any_true, "whether any of several conditions holds, the or a binding may not write as an operator, paired with all_true which asks the same of every one");
34
35pub fn all_true(flags: &[bool]) -> bool {
36    flags.iter().all(|f| *f)
37}
38because!(all_true, "whether every one of several conditions holds, the and a binding may not write as an operator, paired with any_true which asks the same of any one");
39
40pub fn is_some<T>(value: &Option<T>) -> bool {
41    value.is_some()
42}
43because!(is_some, "whether a maybe holds a value, asked as a call because a method is not a pattern");
44
45pub fn or_else<T: Copy>(value: Option<T>, fallback: T) -> T {
46    value.unwrap_or(fallback)
47}
48because!(or_else, "a maybe's value or a fallback, so a reducer reads a lookup that may miss without a match of its own");
49
50pub fn only_if<T>(when: bool, value: T) -> Option<T> {
51    match when {
52        true => Some(value),
53        false => None,
54    }
55}
56because!(only_if, "a value present only when a condition holds, the way a reducer states an optional touch or an optional row without a branch");
57
58pub fn dedup_sorted<T: Ord + Clone>(items: &[T]) -> Vec<T> {
59    let mut out = items.to_vec();
60    out.sort();
61    out.dedup();
62    out
63}
64because!(dedup_sorted, "a list with each value once, in order, for the set of accounts a batch touches which a store is asked for in one request");
65
66pub fn mapped_maybe<T, U>(value: Option<T>, each: fn(&T) -> U) -> Option<U> {
67    value.as_ref().map(each)
68}
69because!(mapped_maybe, "a maybe's value passed through a function when present, so a binding that reads an optional fact and needs it in another form writes no match");
70
71pub fn converted<A, B: From<A>>(items: Vec<A>) -> Vec<B> {
72    items.into_iter().map(B::from).collect()
73}
74because!(converted, "a list of one shape as a list of another through the conversion a spec declares, so a binding turns what a pattern computed into what a page answers without a loop of its own");
75
76pub fn made<A, B: From<A>>(item: A) -> B {
77    B::from(item)
78}
79because!(made, "one value of one shape as another through the conversion a spec declares, the single form of converted");
80
81pub fn zipped<A, B>(first: Vec<A>, second: Vec<B>) -> Vec<(A, B)> {
82    first.into_iter().zip(second).collect()
83}
84because!(zipped, "two lists of equal length as one list of pairs, so a binding joins what two patterns computed for the same items");
85
86pub fn zipped3<A, B, C>(first: Vec<A>, second: Vec<B>, third: Vec<C>) -> Vec<(A, B, C)> {
87    first
88        .into_iter()
89        .zip(second)
90        .zip(third)
91        .map(|((a, b), c)| (a, b, c))
92        .collect()
93}
94because!(zipped3, "three lists of equal length as one list of triples, zipped for the items that carry three computed parts");
95
96pub fn zipped4<A, B, C, D>(first: Vec<A>, second: Vec<B>, third: Vec<C>, fourth: Vec<D>) -> Vec<(A, B, C, D)> {
97    first
98        .into_iter()
99        .zip(second)
100        .zip(third)
101        .zip(fourth)
102        .map(|(((a, b), c), d)| (a, b, c, d))
103        .collect()
104}
105because!(zipped4, "four lists of equal length as one list of quadruples, for the items that carry four computed parts");
106
107pub fn each_of<A, B>(items: &[A], read: fn(&A) -> B) -> Vec<B> {
108    items.iter().map(read).collect()
109}
110because!(each_of, "one projection of every item, the same as map, named apart because a binding that reads a spec table for every item of a list reads better as each_of than as map");
111
112pub fn each_named<A, B: Copy>(items: &[A], read: fn(&A) -> B) -> Vec<B> {
113    items.iter().map(read).collect()
114}
115because!(each_named, "one copyable projection of every item, the copyable twin of each_of, kept apart so a table of values and a table of texts do not share one entry");
116
117pub fn keep_where<A: Clone, B: Copy + PartialEq>(items: &[A], read: fn(&A) -> B, wanted: B) -> Vec<A> {
118    items.iter().filter(|item| read(item) == wanted).cloned().collect()
119}
120because!(keep_where, "the items whose projection equals a value, for a binding that keeps the rows of one kind without a closure");
121
122pub fn drop_where<A: Clone, B: Copy + PartialEq>(items: &[A], read: fn(&A) -> B, unwanted: B) -> Vec<A> {
123    items.iter().filter(|item| read(item) != unwanted).cloned().collect()
124}
125because!(drop_where, "the items whose projection differs from a value, the other half of keep_where, for a menu that hides the entries of one kind");
126
127pub fn paired<A: Clone, B: Clone>(items: &[A], with: &[B]) -> Vec<(A, B)> {
128    items.iter().cloned().zip(with.iter().cloned()).collect()
129}
130because!(paired, "two borrowed lists as one list of cloned pairs, the borrowing twin of zipped for lists a binding does not own");
131
132pub fn maybe_from<A, B: From<A>>(item: Option<A>) -> Option<B> {
133    item.map(B::from)
134}
135because!(maybe_from, "an optional value converted when present, the optional twin of made");
136
137pub fn is_blank(text: &str) -> bool {
138    text.is_empty()
139}
140because!(is_blank, "whether a text has nothing in it, asked as a call, the test that turns an empty sublabel into no sublabel");
141
142pub fn or_default<T: Default>(value: Option<T>) -> T {
143    value.unwrap_or_default()
144}
145because!(or_default, "a maybe's value or the empty value of its type, for the owned texts and lists a binding may not copy through or_else");
146
147pub fn each_with<A, C, B>(items: &[A], with: &C, read: fn(&A, &C) -> B) -> Vec<B> {
148    items.iter().map(|item| read(item, with)).collect()
149}
150because!(each_with, "one projection of every item read beside a shared value, for a table whose every row needs the deployment's token names, since a binding may not close over them");
151
152pub fn indexed_with<A, C, B>(items: &[A], with: &C, read: fn(usize, &A, &C) -> B) -> Vec<B> {
153    items.iter().enumerate().map(|(at, item)| read(at, item, with)).collect()
154}
155because!(indexed_with, "each_with told the item's place as well, for a chart whose lines take their ink from their order");
156
157pub fn after_prefix(text: &str, prefix: &str) -> Option<String> {
158    text.strip_prefix(prefix).map(String::from)
159}
160because!(after_prefix, "what follows a prefix in a text, or nothing when the text does not begin with it, which is how a path yields the address it carries");
161
162pub fn starts_with_text(text: &str, prefix: &str) -> bool {
163    text.starts_with(prefix)
164}
165because!(starts_with_text, "whether a text begins with another, asked as a call, the test a route match makes on a path");
166
167pub fn step_pick<T: Copy>(value: f64, steps: &[(f64, T)], low: T) -> T {
168    steps.iter().find(|(at, _)| value >= *at).map(|(_, pick)| *pick).unwrap_or(low)
169}
170because!(step_pick, "the first entry whose threshold the value reaches, or the lowest entry, the glyph a percentage earns");
171
172pub fn accumulated<T: Clone, K: PartialEq>(pages: &[Vec<T>], key: fn(&T) -> K) -> Vec<T> {
173    let mut out: Vec<T> = Vec::new();
174    for page in pages {
175        for item in page {
176            let seen = out.iter().any(|kept| key(kept) == key(item));
177            if !seen {
178                out.push(item.clone());
179            }
180        }
181    }
182    out
183}
184because!(accumulated, "every page's rows in order with a row seen on an earlier page left out, the table a reader has loaded more of");
185
186pub fn flattened<T: Clone>(pages: &[Vec<T>]) -> Vec<T> {
187    pages.iter().flat_map(|page| page.iter().cloned()).collect()
188}
189because!(flattened, "every page's rows in order, the plain twin of accumulated for rows that carry no key");
190
191pub fn last_of<T: Clone>(items: &[T]) -> Option<T> {
192    items.last().cloned()
193}
194because!(last_of, "the last item of a list, or nothing, the newest page a reader has loaded");
195
196pub fn count_of_list<T>(items: &[T]) -> u64 {
197    u64::try_from(items.len()).unwrap_or(u64::MAX)
198}
199because!(count_of_list, "how many items a list holds, as the number a page shows, the sizing a binding may not write as a cast");
200
201pub fn either_text(when: bool, yes: &str, no: &str) -> String {
202    match when {
203        true => String::from(yes),
204        false => String::from(no),
205    }
206}
207because!(either_text, "one of two texts by a flag, owned, so a binding chooses a word without a match");
208
209pub fn joined_texts(parts: &[String]) -> String {
210    parts.concat()
211}
212because!(joined_texts, "several texts run together with nothing between, the way a label with a figure in the middle is assembled");
213
214pub fn text_if(when: bool, text: &str) -> String {
215    match when {
216        true => String::from(text),
217        false => String::new(),
218    }
219}
220because!(text_if, "a text when a flag holds and nothing otherwise, the optional tail of a label");
221
222pub fn found_in<K: PartialEq, V: Copy>(table: &[(K, V)], key: &K, fallback: V) -> V {
223    table.iter().find(|(k, _)| k == key).map(|(_, v)| *v).unwrap_or(fallback)
224}
225because!(found_in, "the value paired with a key in a small table, or a fallback, the ink a series name earns");
226
227pub fn swapped_in<K: PartialEq + Clone, V: Clone>(table: &[(K, V)], key: &K, value: V) -> Vec<(K, V)> {
228    table
229        .iter()
230        .map(|(k, v)| match k == key {
231            true => (k.clone(), value.clone()),
232            false => (k.clone(), v.clone()),
233        })
234        .collect()
235}
236because!(swapped_in, "a small table with one key's value swapped and every other pair left alone, which is how one option of a control carries the whole reading with only its own value moved");
237
238pub fn interleaved<T: Clone>(items: &[T], between: &T) -> Vec<T> {
239    let mut out = Vec::new();
240    for (at, item) in items.iter().enumerate() {
241        if at > 0 {
242            out.push(between.clone());
243        }
244        out.push(item.clone());
245    }
246    out
247}
248because!(interleaved, "a list with a separator put between every two items, the runs of a chart title with the slashes between the token names");
249
250pub fn zipped_with<A: Clone, B: Clone, C, D>(firsts: &[A], seconds: &[B], with: &C, read: fn(&A, &B, &C) -> D) -> Vec<D> {
251    firsts.iter().zip(seconds.iter()).map(|(a, b)| read(a, b, with)).collect()
252}
253because!(zipped_with, "one reading of every pair of two lists beside a shared value, a chart definition beside the payload that fills it");
254
255pub fn any_some<T>(items: &[Option<T>]) -> bool {
256    items.iter().any(|item| item.is_some())
257}
258because!(any_some, "whether any item of a list is present, the test that says a page's payload has arrived");
259
260pub fn taken_from<T: Clone>(items: &[T], count: usize) -> Vec<T> {
261    items.iter().take(count).cloned().collect()
262}
263because!(taken_from, "the first count items of a list, the tokens a title names when the payload holds fewer lines than the title could");
264
265pub fn mapped_with<T, C, U>(value: &Option<T>, with: &C, read: fn(&T, &C) -> U) -> Option<U> {
266    value.as_ref().map(|inner| read(inner, with))
267}
268because!(mapped_with, "a maybe's value passed through a function beside a shared value, so a binding reads an optional payload with the deployment's names in hand and closes over nothing");
269
270pub fn first_where<T: Clone>(items: &[T], keep: fn(&T) -> bool) -> Option<T> {
271    items.iter().find(|item| keep(item)).cloned()
272}
273because!(first_where, "the first item a test keeps, or nothing, the chosen entry of a menu");
274
275pub fn flattened_maybe<T>(value: Option<Option<T>>) -> Option<T> {
276    value.flatten()
277}
278because!(flattened_maybe, "a maybe inside a maybe as one maybe, the shape an optional payload's optional field reads as");
279
280pub fn highest_value(values: &[f64]) -> f64 {
281    values.iter().copied().fold(crate::money::none(), f64::max)
282}
283because!(highest_value, "the largest of some figures, or zero for none, the tallest bar an axis is sized by");
284
285pub fn places_of(value: u64) -> usize {
286    usize::try_from(value).ok().unwrap_or_default()
287}
288because!(places_of, "a place count read from a payload as the width a formatter takes");
289
290pub fn is_non_empty<T>(items: &[T]) -> bool {
291    !items.is_empty()
292}
293because!(is_non_empty, "whether a list holds anything, the test that says a table has loaded a page");
294
295pub fn found_maybe<K: PartialEq, V: Copy>(table: &[(K, V)], key: &K) -> Option<V> {
296    table.iter().find(|(k, _)| k == key).map(|(_, v)| *v)
297}
298because!(found_maybe, "the value paired with a key in a small table, or nothing, the cell a spreadsheet leaves empty");
299
300pub fn picked<K: PartialEq, V: Clone>(table: &[(K, V)], key: &K, fallback: V) -> V {
301    for (k, v) in table {
302        if k == key {
303            return v.clone();
304        }
305    }
306    fallback
307}
308because!(picked, "the value paired with a key in a small table, or a fallback, for the owned values found_in cannot copy, a heading or a row's runs chosen by the sort");
309
310pub fn or_owned<T>(value: Option<T>, fallback: T) -> T {
311    match value {
312        Some(inner) => inner,
313        None => fallback,
314    }
315}
316because!(or_owned, "a maybe's value or a fallback, for the owned values or_else cannot copy, a menu entry or a glyph");
317
318pub fn cloned<T: Clone>(value: &T) -> T {
319    T::clone(value)
320}
321because!(cloned, "an owned copy of a borrowed value, the copy a binding may not make with a method");
322
323pub fn ranked_by<T>(items: Vec<T>, key: fn(&T) -> f64, descending: bool) -> Vec<T> {
324    let mut out = items;
325    out.sort_by(|a, b| {
326        let (x, y) = (key(a), key(b));
327        let cmp = x.partial_cmp(&y).unwrap_or(std::cmp::Ordering::Equal);
328        match descending {
329            true => cmp.reverse(),
330            false => cmp,
331        }
332    });
333    out
334}
335because!(ranked_by, "the items in the order of one figure, either way, with a stable sort so ties keep the order the items arrived in");
336
337pub fn kept<T>(items: Vec<T>, keep: fn(&T) -> bool) -> Vec<T> {
338    items.into_iter().filter(keep).collect()
339}
340because!(kept, "the items a rule keeps, by a plain function so a binding names the rule from a spec table");
341
342pub fn page_with_count<T: Clone>(items: &[T], page: usize, per_page: usize) -> (Vec<T>, bool, usize) {
343    let start = page.saturating_mul(per_page).min(items.len());
344    let end = start.saturating_add(per_page).min(items.len());
345    (items[start..end].to_vec(), end < items.len(), items.len())
346}
347because!(page_with_count, "one page of an ordered list with whether another follows and how many items there are in all, sliced from the whole because the list is small enough to hold");
348
349pub fn bars<T>(items: &[T], keep: fn(&T) -> bool, label: fn(&T, usize) -> String, value: fn(&T) -> f64, count: usize, label_chars: usize, places: u32) -> Vec<(String, f64)> {
350    items
351        .iter()
352        .filter(|item| keep(item))
353        .take(count)
354        .map(|item| (label(item, label_chars), crate::money::rounded(value(item), places)))
355        .collect()
356}
357because!(bars, "the first few kept items of an order as labelled bars, the chart above a table");
358
359pub fn bars_maybe<T>(items: &[T], keep: fn(&T) -> bool, label: fn(&T, usize) -> String, value: Option<fn(&T) -> f64>, count: usize, label_chars: usize, places: u32) -> Option<Vec<(String, f64)>> {
360    value.map(|read| bars(items, keep, label, read, count, label_chars, places))
361}
362because!(bars_maybe, "the bar chart of an order when the order has one, and nothing when it has none, so a table answers either without a branch");