nonogrid 0.7.3

Efficient nonogram solver
Documentation
use std::{
    fmt::Debug,
    hash::Hash,
    marker::Sized,
    ops::{Add, Range, Sub},
};

use hashbrown::HashMap;

use crate::utils::dedup;

use self::color::ColorId;

pub trait Color
where
    Self: Debug
        + Eq
        + Hash
        + Default
        + Copy
        + Send
        + Sync
        + Add<Output = Self>
        + Sub<Output = Result<Self, String>>,
{
    fn blank() -> Self;
    fn is_solved(self) -> bool;
    fn memoize_rate() -> bool {
        false
    }
    fn solution_rate(self, all_colors: &[ColorId]) -> f64;
    fn variants(self) -> Vec<Self>
    where
        Self: Sized;

    fn as_color_id(self) -> Option<ColorId>;
    fn from_color_ids(ids: &[ColorId]) -> Self;
}

pub trait Block
where
    Self: Debug + Eq + Hash + Default + Copy + Send + Sync,
{
    type Color: Color;

    fn from_str_and_color(s: &str, color: Option<ColorId>) -> Self {
        let size = s.parse().expect("Non-integer block size given");
        Self::from_size_and_color(size, color)
    }

    fn from_size_and_color(size: usize, color: Option<ColorId>) -> Self;
    fn partial_sums(desc: &[Self]) -> Vec<usize>
    where
        Self: Sized;

    fn size(self) -> usize;
    fn color(self) -> Self::Color;
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Description<T: Block> {
    pub vec: Vec<T>,
}

impl<T> Description<T>
where
    T: Block,
{
    pub fn new(mut vec: Vec<T>) -> Self {
        let zero = T::default();
        vec.retain(|x| *x != zero);
        Self { vec }
    }

    /// Generate clues for the given line of color codes
    fn from_line(line: &[ColorId], blank_code: ColorId) -> Self {
        let size = line.len();

        let mut description = vec![];

        let mut index = 0;
        while index < size {
            let block_begin = index;
            let color_number = line[index];

            index += line[index..]
                .iter()
                .take_while(|&&x| x == color_number)
                .count();

            let block_size = index - block_begin;
            if (block_size > 0) && (color_number != blank_code) {
                let block = T::from_size_and_color(block_size, Some(color_number));
                description.push(block);
            }
        }
        Self::new(description)
    }

    pub fn colors(&self) -> impl Iterator<Item = ColorId> + '_ {
        self.vec
            .iter()
            .filter_map(|block| block.color().as_color_id())
    }
}

/// Generate nonogram description (columns and rows) from a solution matrix.
pub fn clues_from_solution<B>(
    solution_matrix: &[Vec<ColorId>],
    blank_code: ColorId,
) -> (Vec<Description<B>>, Vec<Description<B>>)
where
    B: Block,
{
    let height = solution_matrix.len();
    if height == 0 {
        return (vec![], vec![]);
    }

    let width = solution_matrix[0].len();
    if width == 0 {
        return (vec![], vec![]);
    }

    let columns = (0..width)
        .map(|col_index| {
            let column: Vec<_> = (0..height)
                .map(|row_index| solution_matrix[row_index][col_index])
                .collect();
            Description::from_line(&column, blank_code)
        })
        .collect();
    let rows = solution_matrix
        .iter()
        .map(|row| Description::from_line(row, blank_code))
        .collect();
    (columns, rows)
}

impl<B> Description<B>
where
    B: Block,
{
    pub fn block_starts(&self) -> Vec<usize> {
        self.vec
            .iter()
            .zip(Block::partial_sums(&self.vec))
            .map(|(block, end)| end - block.size())
            .collect()
    }

    /// How long should be the minimal line to contain given description?
    fn min_space(&self) -> usize {
        if self.vec.is_empty() {
            return 0;
        }
        *Block::partial_sums(&self.vec)
            .last()
            .expect("Partial sums should be non-empty")
    }

    /// The number of potential block positions for given line size.
    ///
    /// # Panics
    /// If the specified line length is less than the minimum space to put the blocks into.
    pub fn positions_number(&self, line_length: usize) -> usize {
        let min_space = self.min_space();
        assert!(line_length >= min_space);
        line_length - min_space + 1
    }

    /// For every color in the given description produce a valid position range
    pub fn color_ranges(&self, line_length: usize) -> HashMap<ColorId, Range<usize>> {
        let start_indexes = self.block_starts();
        let sums = B::partial_sums(&self.vec);
        let slack_space = self.positions_number(line_length) - 1;

        let line_colors: Vec<_> = dedup(self.colors());
        line_colors
            .into_iter()
            .map(|color| {
                let mut first_index = None;
                let mut last_index = None;
                for (block_index, block) in self.vec.iter().enumerate() {
                    if block.color().as_color_id() == Some(color) {
                        if first_index == None {
                            first_index = Some(block_index);
                        }
                        last_index = Some(block_index);
                    }
                }

                let first_index = first_index.expect("First position not found");
                let last_index = last_index.expect("First position not found");

                // start position of the first block of particular color
                let first_pos = start_indexes[first_index];
                // end position of the last block plus what's left
                let last_pos = sums[last_index] + slack_space;

                (color, first_pos..last_pos)
            })
            .collect()
    }
}

pub mod color {
    use super::HashMap;

    #[derive(Debug, PartialEq, Clone)]
    #[allow(variant_size_differences)]
    pub enum ColorValue {
        // "red", "blue", "pink"
        CommonName(String),
        // (0, 255, 0) for green
        RgbTriplet(u8, u8, u8),
        // 0xFF0 for yellow
        HexValue3(u16),
        // 0xFF00FF for magenta
        HexValue6(u32),
    }

    impl ColorValue {
        pub fn parse(value: &str) -> Self {
            #[allow(unused_imports)]
            use crate::utils::Stripper; // for Rust<1.45

            let value = value.strip_prefix('#').unwrap_or(value);

            if value.len() == 3 {
                let hex3 = u16::from_str_radix(value, 16);
                if let Ok(hex3) = hex3 {
                    return Self::HexValue3(hex3);
                }
            }

            if value.len() == 6 {
                let hex6 = u32::from_str_radix(value, 16);
                if let Ok(hex6) = hex6 {
                    return Self::HexValue6(hex6);
                }
            }

            let rgb: Vec<_> = value.split(',').collect();
            if rgb.len() == 3 {
                let rgb: Vec<u8> = rgb
                    .iter()
                    .filter_map(|component| component.trim().parse().ok())
                    .collect();

                if rgb.len() == 3 {
                    return Self::RgbTriplet(rgb[0], rgb[1], rgb[2]);
                }
            }

            Self::CommonName(value.to_string())
        }

        #[allow(clippy::cast_possible_truncation)]
        pub fn to_rgb(&self) -> (u8, u8, u8) {
            const MULTIPLIER: u8 = 0x11;
            match self {
                Self::RgbTriplet(r, g, b) => (*r, *g, *b),
                Self::HexValue3(hex3) => {
                    let (r, gb) = (hex3 >> 8, *hex3 as u8);
                    let (g, b) = (gb >> 4, gb % (1 << 4));

                    (r as u8 * MULTIPLIER, g * MULTIPLIER, b * MULTIPLIER)
                }
                Self::HexValue6(hex6) => {
                    let (r, gb) = (hex6 >> 16, *hex6 as u16);
                    let (g, b) = (gb >> 8, gb as u8);

                    (r as u8, g as u8, b)
                }
                // https://www.rapidtables.com/web/color/RGB_Color.html#color-table
                Self::CommonName(name) => match name.to_lowercase().as_str() {
                    "black" => (0, 0, 0),
                    "white" => (255, 255, 255),
                    "red" => (255, 0, 0),
                    "lime" => (0, 255, 0),
                    "blue" => (0, 0, 255),
                    "yellow" => (255, 255, 0),
                    "cyan" | "aqua" => (0, 255, 255),
                    "magenta" | "fuchsia" => (255, 0, 255),
                    "silver" => (192, 192, 192),
                    "gray" => (128, 128, 128),
                    "maroon" => (128, 0, 0),
                    "olive" => (128, 128, 0),
                    "green" => (0, 128, 0),
                    "purple" => (128, 0, 128),
                    "teal" => (0, 128, 128),
                    "navy" => (0, 0, 128),
                    _unknown_color => (0, 0, 0),
                },
            }
        }
    }

    pub type ColorId = u32;

    #[derive(Debug, Clone)]
    pub struct ColorDesc {
        id: ColorId,
        name: String,
        value: ColorValue,
        symbol: char,
    }

    impl ColorDesc {
        /// used in `ShellRenderer`
        pub fn symbol(&self) -> String {
            self.symbol.to_string()
        }

        pub fn name(&self) -> &str {
            if let ColorValue::CommonName(value) = &self.value {
                value
            } else {
                &self.name
            }
        }

        pub fn rgb_value(&self) -> (u8, u8, u8) {
            self.value.to_rgb()
        }
    }

    #[derive(Debug, Clone)]
    pub struct ColorPalette {
        vec: HashMap<String, ColorDesc>,
        symbols: Vec<char>,
        default_color: Option<String>,
    }

    impl ColorPalette {
        pub const WHITE_ID: ColorId = 1;

        pub fn with_white_and_black(white_name: &str, black_name: &str) -> Self {
            let mut this = Self::with_white(white_name);
            this.color_with_name_value_and_symbol(black_name, ColorValue::HexValue3(0x000), 'X');
            this.set_default(black_name).unwrap();
            this
        }

        pub fn with_white(white_name: &str) -> Self {
            let mut this = Self::new();
            this.color_with_name_value_symbol_and_id(
                white_name,
                ColorValue::HexValue3(0xFFF),
                '.',
                Self::WHITE_ID,
            );

            this
        }

        fn new() -> Self {
            Self::with_colors(HashMap::new())
        }

        fn with_colors(colors: HashMap<String, ColorDesc>) -> Self {
            let symbols = (0_u8..0xFF)
                .filter_map(|ch| {
                    if ch.is_ascii_punctuation() {
                        Some(ch.into())
                    } else {
                        None
                    }
                })
                .collect();

            Self {
                vec: colors,
                symbols,
                default_color: None,
            }
        }

        pub fn set_default(&mut self, color_name: &str) -> Result<(), String> {
            if self.vec.contains_key(color_name) {
                self.default_color = Some(color_name.to_string());
                return Ok(());
            }

            Err(format!(
                "Cannot set default color {}: not in Palette",
                color_name
            ))
        }

        #[allow(dead_code)]
        #[allow(clippy::option_as_ref_deref)]
        pub fn get_default(&self) -> Option<&str> {
            self.default_color.as_ref().map(String::as_str)
        }

        pub fn id_by_name(&self, name: &str) -> Option<ColorId> {
            self.vec.get(name).map(|desc| desc.id)
        }

        pub fn desc_by_id(&self, id: ColorId) -> Option<ColorDesc> {
            self.vec
                .values()
                .find(|color_desc| color_desc.id == id)
                .cloned()
        }

        fn color_with_name_value_symbol_and_id(
            &mut self,
            name: &str,
            value: ColorValue,
            symbol: char,
            id: ColorId,
        ) {
            let new = ColorDesc {
                id,
                name: name.to_string(),
                value,
                symbol,
            };

            let _color = self.vec.entry(name.to_string()).or_insert(new);
        }

        pub fn color_with_name_value_and_symbol(
            &mut self,
            name: &str,
            value: ColorValue,
            symbol: char,
        ) {
            let current_max = self.vec.values().map(|color| color.id).max();
            let id = current_max.map_or(1, |val| val * 2);
            self.color_with_name_value_symbol_and_id(name, value, symbol, id);
        }

        pub fn color_with_name_and_value(&mut self, name: &str, value: ColorValue) {
            let occupied_symbols: Vec<_> = self.vec.values().map(|color| color.symbol).collect();

            let &next_symbol = self
                .symbols
                .iter()
                .find(|available_symbol| !occupied_symbols.contains(available_symbol))
                .expect("Cannot create color: No more symbols available.");

            self.color_with_name_value_and_symbol(name, value, next_symbol);
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::block::{binary::BinaryBlock, multicolor::ColoredBlock};

    use super::*;

    #[test]
    fn block_starts_empty_binary() {
        let d = Description::new(Vec::<BinaryBlock>::new());
        assert!(d.block_starts().is_empty())
    }

    #[test]
    fn block_starts_empty_colored() {
        let d = Description::new(Vec::<ColoredBlock>::new());
        assert!(d.block_starts().is_empty())
    }

    #[test]
    fn block_starts_single_binary() {
        let d = Description::new(vec![BinaryBlock(5)]);
        assert_eq!(d.block_starts(), vec![0])
    }

    #[test]
    fn block_starts_single_colored() {
        let d = Description::new(vec![ColoredBlock::from_size_and_color(5, 1)]);
        assert_eq!(d.block_starts(), vec![0])
    }

    #[test]
    fn block_starts_binary() {
        let d = Description::new(vec![BinaryBlock(5), BinaryBlock(2), BinaryBlock(3)]);
        assert_eq!(d.block_starts(), vec![0, 6, 9])
    }

    #[test]
    fn block_starts_colored() {
        let d = Description::new(vec![
            ColoredBlock::from_size_and_color(5, 1),
            ColoredBlock::from_size_and_color(1, 1),
            ColoredBlock::from_size_and_color(3, 2),
        ]);
        assert_eq!(d.block_starts(), vec![0, 6, 7])
    }

    #[test]
    fn color_ranges_binary() {
        let d = Description::new(vec![BinaryBlock(5), BinaryBlock(2), BinaryBlock(3)]);
        let ranges = d.color_ranges(12);
        assert!(ranges.is_empty())
    }

    #[test]
    fn color_ranges_colored() {
        let d = Description::new(vec![
            ColoredBlock::from_size_and_color(5, 1),
            ColoredBlock::from_size_and_color(1, 4),
            ColoredBlock::from_size_and_color(3, 2),
            ColoredBlock::from_size_and_color(2, 1),
        ]);

        let mut ranges: Vec<_> = d.color_ranges(13).into_iter().collect();
        ranges.sort_by_key(|(k, _v)| *k);

        //         0 1 2 3 4 5 6 7 8 9 101112
        // <<<     1 1 1 1 1 4 2 2 2 1 1 - -
        // >>>     - - 1 1 1 1 1 4 2 2 2 1 1
        //
        //         1 . . . . . . . . . . . 1
        //                     2 . . . 2
        //                   4 . 4
        assert_eq!(ranges, vec![(1, 0..13), (2, 6..11), (4, 5..8)])
    }
}

#[cfg(test)]
mod color_value_tests {
    use super::color::ColorValue;

    #[test]
    fn construct() {
        assert_eq!(ColorValue::parse("0F0"), ColorValue::HexValue3(240));
        assert_eq!(ColorValue::parse("#0F0"), ColorValue::HexValue3(240));
        assert_eq!(ColorValue::parse("0000FF"), ColorValue::HexValue6(255));
        assert_eq!(ColorValue::parse("#0000FF"), ColorValue::HexValue6(255));
        assert_eq!(
            ColorValue::parse("white"),
            ColorValue::CommonName("white".to_string())
        );
        assert_eq!(
            ColorValue::parse("200, 16,0  "),
            ColorValue::RgbTriplet(200, 16, 0)
        );
        // invalid triplet: G component is not an u8
        assert_eq!(
            ColorValue::parse("200, X, 16"),
            ColorValue::CommonName("200, X, 16".to_string())
        );
    }

    #[test]
    fn rgb() {
        assert_eq!(ColorValue::parse("0F0").to_rgb(), (0, 255, 0));
        assert_eq!(ColorValue::parse("0000FF").to_rgb(), (0, 0, 255));
        assert_eq!(ColorValue::parse("red").to_rgb(), (255, 0, 0));
        assert_eq!(ColorValue::parse("YELLOW").to_rgb(), (255, 255, 0));
        assert_eq!(ColorValue::parse("teal").to_rgb(), (0, 128, 128));
        assert_eq!(ColorValue::parse("unknown").to_rgb(), (0, 0, 0));
        assert_eq!(ColorValue::parse("200, 16,0  ").to_rgb(), (200, 16, 0));
        // short form
        assert_eq!(
            ColorValue::parse("55bb88").to_rgb(),
            ColorValue::parse("5b8").to_rgb()
        );
    }
}