Skip to content

Commit

Permalink
FIX: lazy pairs Iterator sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
tsionyx committed Sep 25, 2024
1 parent 5c584f1 commit 5ba5925
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 33 deletions.
18 changes: 18 additions & 0 deletions examples/hsom-exercises/ch6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,14 @@ pub fn funk_groove() -> Music {
.with_tempo(3)
}

#[test]
fn test_funk_groove() {
use musik::Performable as _;

let p = funk_groove().perform();
let _smf = p.into_midi(None).unwrap();
}

/// Exercise 6.7
/// Write a program that generates all of the General MIDI
/// percussion sounds, playing through each of them one at a time.
Expand Down Expand Up @@ -382,6 +390,16 @@ pub fn drum_pattern() -> Music {
.with_tempo(Ratio::new(4, 3))
}

#[test]
fn test_drum_pattern() {
use musik::Performable as _;

let p = drum_pattern().perform();
// let x: Vec<_> = p.iter().collect();
// dbg!(x);
let _smf = p.into_midi(None).unwrap();
}

pub fn test_volume(vol: Volume) -> Music<(Pitch, Volume)> {
let oc4 = Octave::OneLined;
Music::line(vec![
Expand Down
171 changes: 138 additions & 33 deletions src/utils/iter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{cmp::Ordering, fmt, ops::Deref};
use std::{cmp::Ordering, collections::BinaryHeap, fmt, iter::Peekable, ops::Deref};

use dyn_clone::{clone_trait_object, DynClone};

Expand Down Expand Up @@ -128,10 +128,27 @@ where
}
}

/// Produce sorted iterator by zipping the iterator of pairs `(a, b)`.
///
/// The precondition is that the pairs' 'minimum' elements
/// taken alone should be sorted: `for all i: (min(a[i], b[i]) <= min(a[i+1], b[i+1]))`.
///
/// So, the main challenge is to ensure the stream of second elements (`max(a, b)`)
/// gets inserted in the (already sorted) stream of (`min(a, b)`).
///
/// Elements in pairs could also be internally sorted `for all i: (a[i] <= b[i])`,
/// however this is not required, because it is a cheap operation
/// and it is made if necessary.
///
/// # Attention
/// If the precondition of sorted min elements does not hold,
/// it is impossible to forecast
/// whether less-y value occurred before fully consuming the iterator,
/// so the iterator's order will be incorect.
pub fn merge_pairs_by<I, T, F>(iter: I, is_first: F) -> MergePairsBy<I, T, F>
where
I: Iterator<Item = (T, T)>,
F: FnMut(&T, &T) -> bool,
F: Fn(&T, &T) -> bool,
{
MergePairsBy::new(iter, is_first)
}
Expand All @@ -140,58 +157,93 @@ pub struct MergePairsBy<I, T, F>
where
I: Iterator<Item = (T, T)>,
{
iter: I,
next1: Option<T>,
next2: Option<T>,
cmp_fn: F,
iter: Peekable<I>,
pending: BinaryHeap<OrdFromKeyWrapper<T>>,
is_first_fn: F,
}

struct OrdFromKeyWrapper<T> {
item: T,
less_fn: Box<dyn Fn(&T, &T) -> bool>,

Check failure on line 167 in src/utils/iter.rs

View workflow job for this annotation

GitHub Actions / Static analysis with `clippy` on rust '1.74.0'

very complex type used. Consider factoring parts into `type` definitions

Check failure on line 167 in src/utils/iter.rs

View workflow job for this annotation

GitHub Actions / Static analysis with `clippy` on rust 'stable'

very complex type used. Consider factoring parts into `type` definitions
}

impl<T> PartialEq for OrdFromKeyWrapper<T> {
fn eq(&self, other: &Self) -> bool {
!(self.less_fn)(&self.item, &other.item)
&& !(self.less_fn)(&other.item, &self.item)
&& !(other.less_fn)(&self.item, &other.item)
&& !(other.less_fn)(&other.item, &self.item)
}
}

impl<T> Eq for OrdFromKeyWrapper<T> {}

impl<T> PartialOrd for OrdFromKeyWrapper<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl<T> Ord for OrdFromKeyWrapper<T> {
fn cmp(&self, other: &Self) -> Ordering {
if (self.less_fn)(&self.item, &other.item) {
// the first value for our condition
// should be the greatest to be on top of the queue
Ordering::Greater
} else if (self.less_fn)(&other.item, &self.item) {
Ordering::Less
} else {
Ordering::Equal
}
}
}

impl<I, T, F> MergePairsBy<I, T, F>
where
I: Iterator<Item = (T, T)>,
{
fn new(mut iter: I, cmp: F) -> Self {
let (next1, next2) = iter
.next()
.map_or((None, None), |(a, b)| (Some(a), Some(b)));
fn new(iter: I, is_first_fn: F) -> Self {
Self {
iter,
next1,
next2,
cmp_fn: cmp,
iter: iter.peekable(),
pending: BinaryHeap::new(),
is_first_fn,
}
}
}

impl<I, T, F> Iterator for MergePairsBy<I, T, F>
where
I: Iterator<Item = (T, T)>,
F: FnMut(&T, &T) -> bool,
F: Fn(&T, &T) -> bool + Clone + 'static,
{
type Item = T;

fn next(&mut self) -> Option<Self::Item> {
match (self.next1.take(), self.next2.take()) {
(Some(a), Some(b)) => {
if (self.cmp_fn)(&a, &b) {
self.next2 = Some(b);
Some(a)
} else {
self.next1 = Some(a);
Some(b)
if let Some(next_item) = self.iter.peek() {
let (a, b) = (&next_item.0, &next_item.1);
let min = if (self.is_first_fn)(b, a) { b } else { a };

let min_pending = self.pending.peek();
if let Some(pending) = min_pending {
// return from pending while it is less than current pair's min
if !(self.is_first_fn)(min, &pending.item) {
return self.pending.pop().map(|x| x.item);
}
}
(Some(next), None) | (None, Some(next)) => {
if let Some((next1, next2)) = self.iter.next() {
self.next1 = Some(next1);
self.next2 = Some(next2);
}
Some(next)
}

if let Some((mut a, mut b)) = self.iter.next() {
if (self.is_first_fn)(&b, &a) {
std::mem::swap(&mut a, &mut b);
}
(None, None) => self.iter.next().map(|(a, b)| {
self.next2 = Some(b);
a
}),

self.pending.push(OrdFromKeyWrapper {
item: b,
less_fn: Box::new(self.is_first_fn.clone()),
});
Some(a)
} else {
self.pending.pop().map(|x| x.item)
}
}

Expand Down Expand Up @@ -221,3 +273,56 @@ where
let right = iter.take_while(take).filter(move |x| !filter(x));
(left, right)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn merge_pairs() {
#[derive(Debug, Clone)]
struct X {
order: u32,
id: char,
}

impl X {
fn new(order: u32, id: char) -> Self {
Self { order, id }
}
}

let pairs = vec![
(X::new(0, 'a'), X::new(0, 'b')),
(X::new(0, 'c'), X::new(4, 'd')),
(X::new(0, 'e'), X::new(2, 'f')),
(X::new(0, 'g'), X::new(0, 'h')),
(X::new(3, 'i'), X::new(5, 'j')),
(X::new(3, 'k'), X::new(4, 'l')),
(X::new(8, 'm'), X::new(5, 'n')),
(X::new(8, 'o'), X::new(8, 'p')),
(X::new(8, 'q'), X::new(9, 'r')),
];

let (orders, ids): (Vec<_>, String) =
merge_pairs_by(pairs.iter().cloned(), |x1, x2| x1.order < x2.order)
.map(|X { order, id }| (order, id))
.unzip();

assert_eq!(
orders,
[0, 0, 0, 0, 0, 0, 2, 3, 3, 4, 4, 5, 5, 8, 8, 8, 8, 9]
);
// the ids are: "000000233445588889"
assert_eq!(ids, "abceghfikdljnmopqr");
// or (in case of non-stable insert into the internal priority queue)
// assert_eq!(ids, "abceghfikldjnmopqr");

let mut flat: Vec<_> = pairs.into_iter().flat_map(|(x1, x2)| [x1, x2]).collect();

Check failure on line 321 in src/utils/iter.rs

View workflow job for this annotation

GitHub Actions / Static analysis with `clippy` on rust '1.74.0'

it looks like you're trying to convert a tuple to an array
flat.sort_by_key(|x| x.order);
let (orders2, ids2): (Vec<_>, String) =
flat.into_iter().map(|X { order, id }| (order, id)).unzip();
assert_eq!(orders, orders2);
assert_eq!(ids, ids2);
}
}

0 comments on commit 5ba5925

Please sign in to comment.