Kth Smallest Heap

use crate::data_structures::Heap;
use std::cmp::{Ord, Ordering};

/// Returns k-th smallest element of an array.
/// Time complexity is stably O(nlog(k)) in all cases
/// Extra space is required to maintain the heap, and it doesn't
/// mutate the input list.
///
/// It is preferrable to the partition-based algorithm in cases when
/// we want to maintain the kth smallest element dynamically against
/// a stream of elements. In that case, once the heap is built, further
/// operation's complexity is O(log(k)).
pub fn kth_smallest_heap<T>(input: &[T], k: usize) -> Option<T>
where
    T: Ord + Copy,
{
    if input.len() < k {
        return None;
    }

    // heap will maintain the kth smallest elements
    // seen so far, when new elements, E_new arrives,
    // it is compared with the largest element of the
    // current Heap E_large, which is the current kth
    // smallest elements.
    // if E_new > E_large, then E_new cannot be the kth
    // smallest because there are already k elements smaller
    // than it
    // otherwise, E_large cannot be the kth smallest, and should
    // be removed from the heap and E_new should be added
    let mut heap = Heap::new_max();

    // first k elements goes to the heap as the baseline
    for &val in input.iter().take(k) {
        heap.add(val);
    }

    for &val in input.iter().skip(k) {
        // compare new value to the current kth smallest value
        let cur_big = heap.pop().unwrap(); // heap.pop() can't be None
        match val.cmp(&cur_big) {
            Ordering::Greater => {
                heap.add(cur_big);
            }
            _ => {
                heap.add(val);
            }
        }
    }

    heap.pop()
}

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

    #[test]
    fn empty() {
        let zero: [u8; 0] = [];
        let first = kth_smallest_heap(&zero, 1);

        assert_eq!(None, first);
    }

    #[test]
    fn one_element() {
        let one = [1];
        let first = kth_smallest_heap(&one, 1);

        assert_eq!(1, first.unwrap());
    }

    #[test]
    fn many_elements() {
        // 0 1 3 4 5 7 8 9 9 10 12 13 16 17
        let many = [9, 17, 3, 16, 13, 10, 1, 5, 7, 12, 4, 8, 9, 0];

        let first = kth_smallest_heap(&many, 1);
        let third = kth_smallest_heap(&many, 3);
        let sixth = kth_smallest_heap(&many, 6);
        let fourteenth = kth_smallest_heap(&many, 14);

        assert_eq!(0, first.unwrap());
        assert_eq!(3, third.unwrap());
        assert_eq!(7, sixth.unwrap());
        assert_eq!(17, fourteenth.unwrap());
    }
}