ui/CompactListing: use Segment Trees to calculate max page column width
Given a range of entries that occupy a page (eg [0, 50] for a page of 50 rows high) get the max entry width for this column by using maximum range queries with segment trees.master
parent
7432be5aaa
commit
328b17a995
|
@ -21,6 +21,7 @@
|
|||
|
||||
use std::iter::{Extend, FromIterator};
|
||||
use std::ops::Index;
|
||||
use std::ops::IndexMut;
|
||||
|
||||
const STACK_VEC_CAPACITY: usize = 32;
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
|
@ -154,6 +155,16 @@ impl<T: Default + Copy + std::fmt::Debug> Index<usize> for StackVec<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Default + Copy + std::fmt::Debug> IndexMut<usize> for StackVec<T> {
|
||||
fn index_mut(&mut self, idx: usize) -> &mut T {
|
||||
if self.len > self.array.len() {
|
||||
&mut self.heap_vec[idx]
|
||||
} else {
|
||||
&mut self.array[idx]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + Copy + std::fmt::Debug> Extend<T> for StackVec<T> {
|
||||
fn extend<I>(&mut self, iter: I)
|
||||
where
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
*/
|
||||
|
||||
use super::*;
|
||||
use crate::types::segment_tree::SegmentTree;
|
||||
|
||||
mod conversations;
|
||||
pub use self::conversations::*;
|
||||
|
@ -37,6 +38,7 @@ pub use self::plain::*;
|
|||
pub struct DataColumns {
|
||||
pub columns: [CellBuffer; 12],
|
||||
pub widths: [usize; 12], // widths of columns calculated in first draw and after size changes
|
||||
pub segment_tree: [SegmentTree; 12],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -23,6 +23,7 @@ use super::EntryStrings;
|
|||
use super::*;
|
||||
use crate::components::utilities::PageMovement;
|
||||
use std::cmp;
|
||||
use std::convert::TryInto;
|
||||
use std::iter::FromIterator;
|
||||
|
||||
const MAX_COLS: usize = 500;
|
||||
|
@ -294,6 +295,29 @@ impl ListingTrait for CompactListing {
|
|||
self.data_columns.widths[2] = min_col_width;
|
||||
}
|
||||
}
|
||||
for &i in &[2, 4] {
|
||||
/* Set From and Subject column widths to their maximum value width in the range
|
||||
* [top_idx, top_idx + rows]. By using a segment tree the query is O(logn), which is
|
||||
* great!
|
||||
*/
|
||||
self.data_columns.widths[i] =
|
||||
self.data_columns.segment_tree[i].get_max(top_idx, top_idx + rows) as usize;
|
||||
}
|
||||
if self.data_columns.widths.iter().fold(0, |acc, &w| acc + w) > width {
|
||||
let diff = self.data_columns.widths.iter().fold(0, |acc, &w| acc + w) - width;
|
||||
if self.data_columns.widths[2] > 2 * diff {
|
||||
self.data_columns.widths[2] -= diff;
|
||||
} else {
|
||||
self.data_columns.widths[2] = std::cmp::max(
|
||||
15,
|
||||
self.data_columns.widths[2].saturating_sub((2 * diff) / 3),
|
||||
);
|
||||
self.data_columns.widths[4] = std::cmp::max(
|
||||
15,
|
||||
self.data_columns.widths[4].saturating_sub(diff / 3 + diff % 3),
|
||||
);
|
||||
}
|
||||
}
|
||||
clear_area(grid, area);
|
||||
/* Page_no has changed, so draw new page */
|
||||
let mut x = get_x(upper_left);
|
||||
|
@ -656,6 +680,19 @@ impl CompactListing {
|
|||
self.length = 0;
|
||||
let mut rows = Vec::with_capacity(1024);
|
||||
let mut min_width = (0, 0, 0, 0, 0);
|
||||
let mut row_widths: (
|
||||
StackVec<u8>,
|
||||
StackVec<u8>,
|
||||
StackVec<u8>,
|
||||
StackVec<u8>,
|
||||
StackVec<u8>,
|
||||
) = (
|
||||
StackVec::new(),
|
||||
StackVec::new(),
|
||||
StackVec::new(),
|
||||
StackVec::new(),
|
||||
StackVec::new(),
|
||||
);
|
||||
|
||||
threads.sort_by(self.sort, self.subsort, &account.collection.envelopes);
|
||||
|
||||
|
@ -700,6 +737,32 @@ impl CompactListing {
|
|||
thread_node,
|
||||
threads.is_snoozed(root_idx),
|
||||
);
|
||||
row_widths.1.push(
|
||||
entry_strings
|
||||
.date
|
||||
.grapheme_width()
|
||||
.try_into()
|
||||
.unwrap_or(255),
|
||||
); /* date */
|
||||
row_widths.2.push(
|
||||
entry_strings
|
||||
.from
|
||||
.grapheme_width()
|
||||
.try_into()
|
||||
.unwrap_or(255),
|
||||
); /* from */
|
||||
row_widths.3.push(
|
||||
entry_strings
|
||||
.flag
|
||||
.grapheme_width()
|
||||
.try_into()
|
||||
.unwrap_or(255),
|
||||
); /* flags */
|
||||
row_widths.4.push(
|
||||
(entry_strings.subject.grapheme_width() + 1 + entry_strings.tags.grapheme_width())
|
||||
.try_into()
|
||||
.unwrap_or(255),
|
||||
);
|
||||
min_width.1 = cmp::max(min_width.1, entry_strings.date.grapheme_width()); /* date */
|
||||
min_width.2 = cmp::max(min_width.2, entry_strings.from.grapheme_width()); /* from */
|
||||
min_width.3 = cmp::max(min_width.3, entry_strings.flag.grapheme_width()); /* flags */
|
||||
|
@ -721,18 +784,21 @@ impl CompactListing {
|
|||
/* index column */
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(min_width.0, rows.len(), Cell::with_char(' '), context);
|
||||
|
||||
/* date column */
|
||||
self.data_columns.columns[1] =
|
||||
CellBuffer::new_with_context(min_width.1, rows.len(), Cell::with_char(' '), context);
|
||||
/* from column */
|
||||
self.data_columns.columns[2] =
|
||||
CellBuffer::new_with_context(min_width.2, rows.len(), Cell::with_char(' '), context);
|
||||
self.data_columns.segment_tree[2] = row_widths.2.into();
|
||||
/* flags column */
|
||||
self.data_columns.columns[3] =
|
||||
CellBuffer::new_with_context(min_width.3, rows.len(), Cell::with_char(' '), context);
|
||||
/* subject column */
|
||||
self.data_columns.columns[4] =
|
||||
CellBuffer::new_with_context(min_width.4, rows.len(), Cell::with_char(' '), context);
|
||||
self.data_columns.segment_tree[4] = row_widths.4.into();
|
||||
let threads_iter = if self.filter_term.is_empty() {
|
||||
Box::new(threads.root_iter()) as Box<dyn Iterator<Item = ThreadHash>>
|
||||
} else {
|
||||
|
|
|
@ -147,3 +147,97 @@ pub struct Notification {
|
|||
|
||||
_timestamp: std::time::Instant,
|
||||
}
|
||||
|
||||
pub mod segment_tree {
|
||||
/*! Simple segment tree implementation for maximum in range queries. This is useful if given an
|
||||
* array of numbers you want to get the maximum value inside an interval quickly.
|
||||
*/
|
||||
use melib::StackVec;
|
||||
use std::convert::TryFrom;
|
||||
use std::iter::FromIterator;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct SegmentTree {
|
||||
array: StackVec<u8>,
|
||||
tree: StackVec<u8>,
|
||||
}
|
||||
|
||||
impl From<StackVec<u8>> for SegmentTree {
|
||||
fn from(val: StackVec<u8>) -> SegmentTree {
|
||||
SegmentTree::new(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl SegmentTree {
|
||||
pub fn new(val: StackVec<u8>) -> SegmentTree {
|
||||
if val.is_empty() {
|
||||
return SegmentTree {
|
||||
array: val.clone(),
|
||||
tree: val,
|
||||
};
|
||||
}
|
||||
|
||||
let height = (f64::from(u32::try_from(val.len()).unwrap_or(0)))
|
||||
.log2()
|
||||
.ceil() as u32;
|
||||
let max_size = 2 * (2_usize.pow(height)) - 1;
|
||||
|
||||
let mut segment_tree: StackVec<u8> =
|
||||
StackVec::from_iter(core::iter::repeat(0).take(max_size));
|
||||
for i in 0..val.len() {
|
||||
segment_tree[val.len() + i] = val[i];
|
||||
}
|
||||
|
||||
for i in (1..val.len()).rev() {
|
||||
segment_tree[i] = std::cmp::max(segment_tree[2 * i], segment_tree[2 * i + 1]);
|
||||
}
|
||||
|
||||
SegmentTree {
|
||||
array: val,
|
||||
tree: segment_tree,
|
||||
}
|
||||
}
|
||||
|
||||
/// (left, right) is inclusive
|
||||
pub fn get_max(&self, mut left: usize, mut right: usize) -> u8 {
|
||||
let len = self.array.len();
|
||||
debug_assert!(left <= right);
|
||||
if right >= len {
|
||||
right = len.saturating_sub(1);
|
||||
}
|
||||
|
||||
left += len;
|
||||
right += len + 1;
|
||||
|
||||
let mut max = 0;
|
||||
|
||||
while left < right {
|
||||
if (left & 1) > 0 {
|
||||
max = std::cmp::max(max, self.tree[left]);
|
||||
left += 1;
|
||||
}
|
||||
|
||||
if (right & 1) > 0 {
|
||||
right -= 1;
|
||||
max = std::cmp::max(max, self.tree[right]);
|
||||
}
|
||||
|
||||
left /= 2;
|
||||
right /= 2;
|
||||
}
|
||||
max
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_segment_tree() {
|
||||
let array: StackVec<u8> = [9, 1, 17, 2, 3, 23, 4, 5, 6, 37]
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.collect::<StackVec<u8>>();
|
||||
let segment_tree = SegmentTree::from(array.clone());
|
||||
|
||||
assert_eq!(segment_tree.get_max(0, 5), 23);
|
||||
assert_eq!(segment_tree.get_max(6, 9), 37);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue