From a4d15af694cb09a2bf338ea7b6b573d274cdeddb Mon Sep 17 00:00:00 2001 From: Bertrand Chardon <51328958+bertrand-chardon@users.noreply.github.com> Date: Mon, 9 Dec 2024 23:39:06 +0100 Subject: [PATCH] perf: optimize entry ranges (#110) --- benches/results_list_benchmark.rs | 256 ++++++++++++++++++++---- crates/television-channels/src/entry.rs | 128 ++++-------- crates/television-screen/src/results.rs | 2 +- 3 files changed, 255 insertions(+), 131 deletions(-) diff --git a/benches/results_list_benchmark.rs b/benches/results_list_benchmark.rs index 02350b3..9eba87c 100644 --- a/benches/results_list_benchmark.rs +++ b/benches/results_list_benchmark.rs @@ -3,6 +3,7 @@ use devicons::FileIcon; use ratatui::layout::Alignment; use ratatui::prelude::{Line, Style}; use ratatui::widgets::{Block, BorderType, Borders, ListDirection, Padding}; +use television_channels::entry::merge_ranges; use television_channels::entry::{Entry, PreviewType}; use television_screen::colors::BORDER_COLOR; use television_screen::results::build_results_list; @@ -15,7 +16,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/LICENSE".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{f016}', @@ -27,7 +33,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/README.md".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{f48a}', @@ -39,7 +50,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/re.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -51,7 +67,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/io.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -63,7 +84,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/gc.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -75,7 +101,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/uu.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -87,7 +118,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/nt.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -99,7 +135,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/dis.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -111,7 +152,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/imp.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -123,7 +169,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/bdb.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -135,7 +186,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/abc.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -147,7 +203,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/cgi.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -159,7 +220,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/bz2.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -171,7 +237,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/grp.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -183,7 +254,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/ast.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -195,7 +271,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/csv.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -207,7 +288,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/pdb.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -219,7 +305,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/pwd.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -231,7 +322,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/ssl.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -243,7 +339,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/tty.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -255,7 +356,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/nis.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -267,7 +373,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/pty.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -279,7 +390,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/cmd.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -291,7 +407,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/tests/utils.py".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -303,7 +424,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/pyproject.toml".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e6b2}', @@ -315,7 +441,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/MAINTAINERS.md".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{f48a}', @@ -327,7 +458,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/enum.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -339,7 +475,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/hmac.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -351,7 +492,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/uuid.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -363,7 +509,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/glob.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -375,7 +526,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/_ast.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -387,7 +543,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/_csv.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -399,7 +560,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/code.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -411,7 +577,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/spwd.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -423,7 +594,12 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/_msi.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), value_match_ranges: None, icon: Some(FileIcon { icon: '\u{e606}', @@ -435,14 +611,20 @@ pub fn results_list_benchmark(c: &mut Criterion) { Entry { name: "typeshed/stdlib/time.pyi".to_string(), value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), - value_match_ranges: None, + icon: Some(FileIcon { icon: '\u{e606}', color: "#ffbc03", }), line_number: None, preview_type: PreviewType::Files, + name_match_ranges: Some(merge_ranges(&vec![ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + ])), + value_match_ranges: None, }, ]; diff --git a/crates/television-channels/src/entry.rs b/crates/television-channels/src/entry.rs index 66e876e..a4d7de7 100644 --- a/crates/television-channels/src/entry.rs +++ b/crates/television-channels/src/entry.rs @@ -26,6 +26,25 @@ pub struct Entry { pub preview_type: PreviewType, } +#[allow(clippy::needless_return)] +pub fn merge_ranges(ranges: &[(u32, u32)]) -> Vec<(u32, u32)> { + ranges.iter().fold( + Vec::new(), + |mut acc: Vec<(u32, u32)>, x: &(u32, u32)| { + if let Some(last) = acc.last_mut() { + if last.1 == x.0 { + last.1 = x.1; + } else { + acc.push(*x); + } + } else { + acc.push(*x); + } + return acc; + }, + ) +} + impl Entry { /// Create a new entry with the given name and preview type. /// @@ -65,36 +84,12 @@ impl Entry { self.value = Some(value); self } - #[allow(clippy::needless_return)] - pub fn minimal_name_match_ranges(&self) -> Option> { - // This method takes the existing `name_match_ranges` - // and merges contiguous ranges into the minimal equivalent - // set of ranges. If no ranges exist, it returns `None`. - if let Some(name_match_ranges) = &self.name_match_ranges { - let minimal_name_match_ranges: Vec<(u32, u32)> = - name_match_ranges.iter().fold(Vec::new(), |mut acc, x| { - if let Some(last) = acc.last_mut() { - if last.1 == x.0 { - last.1 = x.1; - } else { - acc.push(*x); - } - } else { - acc.push(*x); - } - return acc; - }); - Some(minimal_name_match_ranges) - } else { - None - } - } pub fn with_name_match_ranges( mut self, name_match_ranges: Vec<(u32, u32)>, ) -> Self { - self.name_match_ranges = Some(name_match_ranges); + self.name_match_ranges = Some(merge_ranges(&name_match_ranges)); self } @@ -102,7 +97,7 @@ impl Entry { mut self, value_match_ranges: Vec<(u32, u32)>, ) -> Self { - self.value_match_ranges = Some(value_match_ranges); + self.value_match_ranges = Some(merge_ranges(&value_match_ranges)); self } @@ -176,79 +171,26 @@ mod tests { use super::*; #[test] - fn test_minimal_name_match_ranges_none() { - let entry = Entry { - name: "test".to_string(), - value: None, - name_match_ranges: None, - value_match_ranges: None, - icon: None, - line_number: None, - preview_type: PreviewType::Files, - }; - assert_eq!(entry.minimal_name_match_ranges(), None); - } - #[test] - fn test_minimal_name_match_ranges_empty() { - let entry = Entry { - name: "test".to_string(), - value: None, - name_match_ranges: Some(vec![]), - value_match_ranges: None, - icon: None, - line_number: None, - preview_type: PreviewType::Files, - }; - - assert_eq!(entry.minimal_name_match_ranges(), Some(vec![])); - } - #[test] - fn test_minimal_name_match_ranges_non_contiguous() { - let entry = Entry { - name: "test".to_string(), - value: None, - name_match_ranges: Some(vec![(0, 1), (2, 4), (6, 9)]), - value_match_ranges: None, - icon: None, - line_number: None, - preview_type: PreviewType::Files, - }; - assert_eq!( - entry.minimal_name_match_ranges(), - Some(vec![(0, 1), (2, 4), (6, 9)]) - ); + fn test_empty_input() { + let ranges: Vec<(u32, u32)> = vec![]; + assert_eq!(merge_ranges(&ranges), Vec::<(u32, u32)>::new()); } #[test] - fn test_minimal_name_match_ranges_contiguous() { - let entry = Entry { - name: "test".to_string(), - value: None, - name_match_ranges: Some(vec![(0, 1), (1, 2), (2, 3), (3, 4)]), - value_match_ranges: None, - icon: None, - line_number: None, - preview_type: PreviewType::Files, - }; - - assert_eq!(entry.minimal_name_match_ranges(), Some(vec![(0, 4)])); + fn test_single_range() { + let ranges = vec![(1, 3)]; + assert_eq!(merge_ranges(&ranges), vec![(1, 3)]); } #[test] - fn test_minimal_name_match_ranges_both_contiguous_and_non_contiguous() { - let entry = Entry { - name: "test".to_string(), - value: None, - name_match_ranges: Some(vec![(0, 1), (2, 3), (3, 4)]), - value_match_ranges: None, - icon: None, - line_number: None, - preview_type: PreviewType::Files, - }; + fn test_contiguous_ranges() { + let ranges = vec![(1, 2), (2, 3), (3, 4), (4, 5)]; + assert_eq!(merge_ranges(&ranges), vec![(1, 5)]); + } - assert_eq!( - entry.minimal_name_match_ranges(), - Some(vec![(0, 1), (2, 4)]) - ); + #[test] + fn test_non_contiguous_ranges() { + let ranges = vec![(1, 2), (3, 4), (5, 6)]; + assert_eq!(merge_ranges(&ranges), vec![(1, 2), (3, 4), (5, 6)]); } } diff --git a/crates/television-screen/src/results.rs b/crates/television-screen/src/results.rs index 22d3250..5303924 100644 --- a/crates/television-screen/src/results.rs +++ b/crates/television-screen/src/results.rs @@ -56,7 +56,7 @@ where // entry name let (entry_name, name_match_ranges) = make_matched_string_printable( &entry.name, - entry.minimal_name_match_ranges().as_deref(), + entry.name_match_ranges.as_deref(), ); let mut last_match_end = 0; for (start, end) in name_match_ranges