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.async
parent
7432be5aaa
commit
328b17a995
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
use std::iter::{Extend, FromIterator};
|
use std::iter::{Extend, FromIterator};
|
||||||
use std::ops::Index;
|
use std::ops::Index;
|
||||||
|
use std::ops::IndexMut;
|
||||||
|
|
||||||
const STACK_VEC_CAPACITY: usize = 32;
|
const STACK_VEC_CAPACITY: usize = 32;
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[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> {
|
impl<T: Default + Copy + std::fmt::Debug> Extend<T> for StackVec<T> {
|
||||||
fn extend<I>(&mut self, iter: I)
|
fn extend<I>(&mut self, iter: I)
|
||||||
where
|
where
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::types::segment_tree::SegmentTree;
|
||||||
|
|
||||||
mod conversations;
|
mod conversations;
|
||||||
pub use self::conversations::*;
|
pub use self::conversations::*;
|
||||||
|
@ -37,6 +38,7 @@ pub use self::plain::*;
|
||||||
pub struct DataColumns {
|
pub struct DataColumns {
|
||||||
pub columns: [CellBuffer; 12],
|
pub columns: [CellBuffer; 12],
|
||||||
pub widths: [usize; 12], // widths of columns calculated in first draw and after size changes
|
pub widths: [usize; 12], // widths of columns calculated in first draw and after size changes
|
||||||
|
pub segment_tree: [SegmentTree; 12],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -23,6 +23,7 @@ use super::EntryStrings;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::components::utilities::PageMovement;
|
use crate::components::utilities::PageMovement;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
use std::convert::TryInto;
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
const MAX_COLS: usize = 500;
|
const MAX_COLS: usize = 500;
|
||||||
|
@ -294,6 +295,29 @@ impl ListingTrait for CompactListing {
|
||||||
self.data_columns.widths[2] = min_col_width;
|
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);
|
clear_area(grid, area);
|
||||||
/* Page_no has changed, so draw new page */
|
/* Page_no has changed, so draw new page */
|
||||||
let mut x = get_x(upper_left);
|
let mut x = get_x(upper_left);
|
||||||
|
@ -656,6 +680,19 @@ impl CompactListing {
|
||||||
self.length = 0;
|
self.length = 0;
|
||||||
let mut rows = Vec::with_capacity(1024);
|
let mut rows = Vec::with_capacity(1024);
|
||||||
let mut min_width = (0, 0, 0, 0, 0);
|
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);
|
threads.sort_by(self.sort, self.subsort, &account.collection.envelopes);
|
||||||
|
|
||||||
|
@ -700,6 +737,32 @@ impl CompactListing {
|
||||||
thread_node,
|
thread_node,
|
||||||
threads.is_snoozed(root_idx),
|
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.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.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 */
|
min_width.3 = cmp::max(min_width.3, entry_strings.flag.grapheme_width()); /* flags */
|
||||||
|
@ -721,18 +784,21 @@ impl CompactListing {
|
||||||
/* index column */
|
/* index column */
|
||||||
self.data_columns.columns[0] =
|
self.data_columns.columns[0] =
|
||||||
CellBuffer::new_with_context(min_width.0, rows.len(), Cell::with_char(' '), context);
|
CellBuffer::new_with_context(min_width.0, rows.len(), Cell::with_char(' '), context);
|
||||||
|
|
||||||
/* date column */
|
/* date column */
|
||||||
self.data_columns.columns[1] =
|
self.data_columns.columns[1] =
|
||||||
CellBuffer::new_with_context(min_width.1, rows.len(), Cell::with_char(' '), context);
|
CellBuffer::new_with_context(min_width.1, rows.len(), Cell::with_char(' '), context);
|
||||||
/* from column */
|
/* from column */
|
||||||
self.data_columns.columns[2] =
|
self.data_columns.columns[2] =
|
||||||
CellBuffer::new_with_context(min_width.2, rows.len(), Cell::with_char(' '), context);
|
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 */
|
/* flags column */
|
||||||
self.data_columns.columns[3] =
|
self.data_columns.columns[3] =
|
||||||
CellBuffer::new_with_context(min_width.3, rows.len(), Cell::with_char(' '), context);
|
CellBuffer::new_with_context(min_width.3, rows.len(), Cell::with_char(' '), context);
|
||||||
/* subject column */
|
/* subject column */
|
||||||
self.data_columns.columns[4] =
|
self.data_columns.columns[4] =
|
||||||
CellBuffer::new_with_context(min_width.4, rows.len(), Cell::with_char(' '), context);
|
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() {
|
let threads_iter = if self.filter_term.is_empty() {
|
||||||
Box::new(threads.root_iter()) as Box<dyn Iterator<Item = ThreadHash>>
|
Box::new(threads.root_iter()) as Box<dyn Iterator<Item = ThreadHash>>
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -147,3 +147,97 @@ pub struct Notification {
|
||||||
|
|
||||||
_timestamp: std::time::Instant,
|
_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