perky 0.1.2

An application to permute and score keyboard layouts.
Documentation
pub fn calculate_frac(n: u64, t: u64) -> Option<f64> {
    if t == 0 {
        None
    } else {
        Some(n as f64 / t as f64)
    }
}

pub fn calculate_perc(n: u64, t: u64) -> Option<f64> {
    calculate_frac(n, t).map(|f| f * 100.0)
}

pub fn crop_matrix<const C: usize, const R: usize, T>(
    matrix: &[[T; C]; R],
    predicate: impl Fn(&T) -> bool,
) -> (usize, usize, usize, usize) {
    let mut top = 0;
    let mut right = 0;
    let mut bottom = 0;
    let mut left = 0;
    for r in matrix {
        if r.iter().any(|b| predicate(b)) {
            break;
        }
        top += 1;
    }
    for c in (0..C).rev() {
        if matrix.iter().any(|row| predicate(&row[c])) {
            break;
        }
        right += 1;
    }
    for r in matrix.iter().rev() {
        if r.iter().any(|b| predicate(b)) {
            break;
        }
        bottom += 1;
    }
    for c in 0..C {
        if matrix.iter().any(|row| predicate(&row[c])) {
            break;
        }
        left += 1;
    }
    (top, right, bottom, left)
}

pub fn factorial(n: u64) -> u64 {
    (1..=n).product()
}

pub fn index_to_permutation<T: Copy>(mut index: u64, input: &[T]) -> Vec<T> {
    let input_length = input.len();
    debug_assert!(
        index < factorial(input_length as u64),
        "index {} out of bounds for {}-length permutation",
        index,
        input_length
    );
    let mut available = input.to_vec();
    let mut output = Vec::with_capacity(available.len());
    for i in (1..=available.len()).rev() {
        let f = factorial(i as u64 - 1);
        let pos = (index / f) as usize;
        index %= f;
        output.push(available.remove(pos));
    }
    output
}

pub fn index_to_permutation_in_place<const N: usize, T: Copy + Default>(
    mut index: u64,
    input: &[T],
    output: &mut [T],
) {
    let input_length = input.len();
    if input_length == 0 {
        return;
    }
    debug_assert!(
        input_length <= N,
        "input length {} must be <= maximum permutation length {}",
        input_length,
        N
    );
    let output_length = output.len();
    debug_assert!(
        input_length <= output_length,
        "input length {} must be <= output length {}",
        input_length,
        output_length
    );
    let mut f = factorial(input_length as u64);
    debug_assert!(
        index < f,
        "index {} out of bounds for {}-length permutation",
        index,
        input_length
    );
    let mut available = [T::default(); N];
    available[..input_length].copy_from_slice(input);
    f /= input_length as u64;
    let mut remaining = input_length;
    for i in 0..input_length {
        let pos = (index / f) as usize;
        index %= f;
        output[i] = available[pos];
        for j in pos..remaining - 1 {
            available[j] = available[j + 1];
        }
        remaining -= 1;
        if remaining > 1 {
            f /= remaining as u64;
        }
    }
}

pub fn generate_permutations<const N: usize, T>(
    array: [T; N],
    callback: impl FnMut(&[T; N]) -> bool,
) {
    generate_permutations_to_limit(array, N, callback);
}

pub fn generate_permutations_to_limit<const N: usize, T>(
    mut array: [T; N],
    limit: usize,
    mut callback: impl FnMut(&[T; N]) -> bool,
) {
    fn permute<const N: usize, T>(
        slice: &mut [T; N],
        start: usize,
        limit: usize,
        callback: &mut impl FnMut(&[T; N]) -> bool,
    ) -> bool {
        if start == limit {
            return callback(slice);
        }
        for i in start..limit {
            slice.swap(start, i);
            if !permute(slice, start + 1, limit, callback) {
                slice.swap(start, i);
                return false;
            }
            slice.swap(start, i);
        }
        true
    }

    let limit = limit.min(N);
    permute(&mut array, 0, limit, &mut callback);
}