meiosis 0.1.0

An evolutionary algorithm library with as many compile time checks as possible.
Documentation
use core::num::NonZeroUsize;

use rand::Rng;

use crate::neighbour::Neighbour;
use crate::random::Random;

use super::Gene;

impl Gene for char {}

impl Random for char {
    fn random<RNG>(rng: &mut RNG) -> Self
    where
        RNG: Rng,
    {
        rng.gen()
    }
}

impl Neighbour for char {
    fn random_forward_neighbour<RNG>(&self, rng: &mut RNG, maximal_distance: NonZeroUsize) -> Option<char>
    where
        RNG: Rng,
    {
        if maximal_distance.get() == 1 {
            return self.successor(rng);
        }

        // instead of collecting and then deciding on a concrete distance, we decide beforehand
        // and then try to reach that point
        let distance = rng.gen_range(1..=maximal_distance.get());

        let mut current_char = *self;

        // this implementation is not perfect and should be revised, both for speed and actual functionality
        // preferably, we should have a range of chars and then randomly choose.
        for _ in 0..distance {
            if let Some(new_char) = non_rng_successor(current_char) {
                current_char = new_char;
            } else {
                return None;
            }
        }

        Some(current_char)
    }

    fn random_backward_neighbour<RNG>(&self, rng: &mut RNG, maximal_distance: NonZeroUsize) -> Option<char>
    where
        RNG: Rng,
    {
        if maximal_distance.get() == 1 {
            return self.predecessor(rng);
        }

        // instead of collecting and then deciding on a concrete distance, we decide beforehand
        // and then try to reach that point
        let distance = rng.gen_range(1..=maximal_distance.get());

        let mut current_char = *self;

        // this implementation is not perfect and should be revised, both for speed and actual functionality
        // preferably, we should have a range of chars and then randomly choose.
        for _ in 0..distance {
            if let Some(new_char) = non_rng_predecessor(current_char) {
                current_char = new_char;
            } else {
                return None;
            }
        }

        Some(current_char)
    }

    fn successor<RNG>(&self, _rng: &mut RNG) -> Option<char>
    where
        RNG: Rng,
    {
        non_rng_successor(*self)
    }

    fn predecessor<RNG>(&self, _rng: &mut RNG) -> Option<char>
    where
        RNG: Rng,
    {
        non_rng_predecessor(*self)
    }
}

/// MISSING DOCS
fn non_rng_successor(char: char) -> Option<char> {
    if char == char::MAX {
        // we're at the biggest possible value, so we can't go higher
        None
    } else {
        // we're not at the maximum, but we may hit 0xd800
        if char == '\u{d7ff}' {
            Some('\u{e000}')
        } else {
            // This is okay as we're checking the `char::MAX` case further above, thus `char` has to be
            // some valid value which won't cause an overflow.
            #[allow(clippy::arithmetic_side_effects, clippy::integer_arithmetic)]
            char::from_u32(u32::from(char) + 1)
        }
    }
}

/// MISSING DOCS
fn non_rng_predecessor(char: char) -> Option<char> {
    if char == '\u{0}' {
        // we're at the smallest possible value, so we can't go lower
        None
    } else {
        // we're not at the minimum, but we may hit 0xdfff
        if char == '\u{e000}' {
            Some('\u{d7ff}')
        } else {
            // This is okay as we're checking the zero case further above, thus `char` has to be
            // some valid value which won't cause an underflow.
            #[allow(clippy::arithmetic_side_effects, clippy::integer_arithmetic)]
            char::from_u32(u32::from(char) - 1)
        }
    }
}

// invalid range: '\u{d800}'..='\u{dfff}'