use std::{cmp::Ordering, collections::BinaryHeap};
use crate::Story;
pub struct StoryCollector<T> {
stories: BinaryHeap<StoryWrapper<T>>,
capacity: usize,
}
struct StoryWrapper<T>(f32, T);
impl<T> Ord for StoryWrapper<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.total_cmp(&other.0).reverse()
}
}
impl<T> PartialOrd for StoryWrapper<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.0.total_cmp(&other.0).reverse())
}
}
impl<T> PartialEq for StoryWrapper<T> {
fn eq(&self, other: &Self) -> bool {
self.0.total_cmp(&other.0) == Ordering::Equal
}
}
impl<T> Eq for StoryWrapper<T> {}
impl<T> StoryCollector<T> {
pub fn new(capacity: usize) -> Self {
Self {
stories: BinaryHeap::with_capacity(capacity + 1),
capacity,
}
}
pub fn len(&self) -> usize {
self.stories.len()
}
pub fn min_score(&self) -> f32 {
self.stories.peek().map(|x| x.0).unwrap_or(f32::MIN)
}
#[inline(always)]
pub fn would_accept(&self, score: f32) -> bool {
self.stories.len() < self.capacity || score > self.min_score()
}
pub fn accept(&mut self, score: f32, story: T) -> bool {
if !self.would_accept(score) {
return false;
}
self.stories.push(StoryWrapper(score, story));
while self.stories.len() > self.capacity {
self.stories.pop();
}
true
}
pub fn to_sorted(mut self) -> Vec<T> {
let mut v = Vec::with_capacity(self.stories.len());
while let Some(story) = self.stories.pop() {
v.push(story.1);
}
v.reverse();
v
}
#[cfg(test)]
pub fn scores(&self) -> Vec<f32> {
use itertools::Itertools;
let mut v = self.stories.iter().map(|x| x.0).collect_vec();
v.sort_by(|a, b| a.total_cmp(b));
v
}
}
impl<S> StoryCollector<Story<S>> {
pub fn accept_story(&mut self, story: Story<S>) -> bool {
self.accept(story.score, story)
}
}
#[cfg(test)]
mod test {
use progscrape_scrapers::{ScrapeId, StoryDate, StoryUrl};
use super::*;
fn make_story_with_score(score: f32) -> Story<()> {
Story::new_from_parts(
"title".into(),
StoryUrl::parse("http://example.com").expect("url"),
StoryDate::year_month_day(2000, 1, 1).expect("date"),
score,
vec![],
Vec::<(ScrapeId, ())>::new(),
)
}
#[test]
fn test_collect_lower() {
let mut collector = StoryCollector::new(10);
collector.accept_story(make_story_with_score(10.0));
collector.accept_story(make_story_with_score(9.0));
assert_eq!(collector.len(), 2);
}
#[test]
fn test_collector() {
let mut collector = StoryCollector::new(10);
assert!(collector.would_accept(1.0));
assert!(collector.would_accept(-1000000.0));
for i in 0..10 {
assert!(collector.accept_story(make_story_with_score(i as f32 * 10.0)));
}
assert_eq!(collector.min_score() as i32, 0);
assert!(!collector.would_accept(-10.0));
assert!(!collector.accept_story(make_story_with_score(-10.0)));
assert!(!collector.would_accept(0.0));
assert!(!collector.accept_story(make_story_with_score(0.0)));
assert!(collector.accept_story(make_story_with_score(1.0)));
assert!(!collector.accept_story(make_story_with_score(1.0)));
}
}