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
Manos Pitsidianakis 2019-12-12 11:11:32 +02:00
parent 7432be5aaa
commit 328b17a995
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
4 changed files with 173 additions and 0 deletions

View File

@ -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

View File

@ -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)]

View File

@ -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 {

View File

@ -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);
}
}