~vam-jam/wpath

9c0bac875d1cd8d5deab32283a9f1bf3498a2465 — Vamist 1 year, 8 months ago
[dev] init
4 files changed, 223 insertions(+), 0 deletions(-)

A .gitignore
A Cargo.lock
A Cargo.toml
A src/main.rs
A  => .gitignore +1 -0
@@ 1,1 @@
/target

A  => Cargo.lock +7 -0
@@ 1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "wpaths"
version = "0.1.0"

A  => Cargo.toml +8 -0
@@ 1,8 @@
[package]
name = "wpaths"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

A  => src/main.rs +207 -0
@@ 1,207 @@
use core::fmt;
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Error, ErrorKind};
use std::{env, path::PathBuf, process::exit};

#[derive(Debug, PartialEq, Eq)]
struct WordPath {
    file: PathBuf,
    start: String,
    end: String,
}

#[derive(Debug, PartialEq, Eq)]
struct Solution {
    chain: Step,
}

impl fmt::Display for Solution {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for step in self.chain.previous.iter() {
            write!(f, "{step} -> ")?;
        }

        write!(f, "{}", self.chain.current)?;
        Ok(())
    }
}

impl Solution {
    fn from(step: &Step, end: String) -> Self {
        let mut step = step.clone();

        step.previous.push(step.current.clone());
        step.current = end;

        Self {
            chain: step.clone(),
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
struct Step {
    previous: Vec<String>,
    current: String,
}

impl Step {
    fn from(init: String) -> Self {
        Step {
            previous: Vec::new(),
            current: init,
        }
    }

    fn branch(&self, new: String) -> Self {
        let mut previous = self.previous.clone();
        previous.push(self.current.clone());

        Step {
            previous,
            current: new,
        }
    }
}

impl WordPath {
    fn read(&mut self) -> Result<Vec<String>, Error> {
        if !self.file.exists() {
            return Err(Error::new(
                ErrorKind::NotFound,
                format!("File {} could not be found", self.file.display()),
            ));
        }

        let file = File::open(&mut self.file)?;
        let content = BufReader::new(file).lines();

        let content: Result<Vec<String>, Error> = content.collect();
        let content = content?;

        Ok(content)
    }

    fn run(&mut self) -> Result<Solution, Error> {
        let words = self.read()?;

        let solution = self.solve(&words);

        if let Err(why) = solution {
            println!("{}", why);
            exit(1);
        }

        let solution = solution.unwrap();

        println!("{}", solution);

        todo!();
    }

    fn solve(&self, words: &[String]) -> Result<Solution, String> {
        let upper = std::cmp::max(self.start.len(), self.end.len());
        let lower = std::cmp::min(self.start.len(), self.end.len());

        // Remove any word that could never appear
        let mut words = words
            .iter()
            .filter(|x| x.len() >= lower && x.len() <= upper)
            .map(|x| (x, false))
            .collect::<HashMap<&String, bool>>();

        let mut to_search = vec![Step::from(self.start.clone())];

        // Some random arbitrary limit
        for _ in 0..10000 {
            let mut next_search = Vec::new();

            for step in to_search.iter_mut() {
                let lead = &step.current;
                for (index, _) in lead.chars().enumerate() {
                    for temp in 0..26 {
                        let mut raw = 'a' as u32;
                        raw += temp;

                        // Can panic if initial point is near the limit of utf8
                        let letter = char::from_u32(raw).unwrap();

                        if !letter.is_alphabetic() {
                            continue;
                        }

                        let mut word = lead.clone();
                        word.replace_range(index..index + 1, letter.to_string().as_str());

                        if word == self.end {
                            return Ok(Solution::from(step, word));
                        }

                        if let Some(searched) = words.get_mut(&word) {
                            if *searched {
                                continue;
                            }

                            println!("New word {word}");

                            *searched = true;
                            next_search.push(step.branch(word));
                        }

                        // words.get_mut(&word).unwrap().push(lead.clone());

                        // next_search.push(word.clone());
                    }
                }
            }

            if next_search.is_empty() {
                return Err("No future paths could be formed".to_string());
            }

            to_search = next_search;
        }

        Err("No word path could be formed".to_string())
    }
}

fn main() {
    let wordpath = fetch_args();

    if let Err(why) = wordpath {
        println!("{why}");
        exit(1);
    }

    let mut wordpath = wordpath.unwrap();

    wordpath.run();
}

fn fetch_args() -> Result<WordPath, String> {
    let mut args = env::args();

    if args.len() < 4 {
        return Err(
            "Not enough args supplied, example: wpaths /usr/share/dict/words cat dog".to_string(),
        );
    }

    // TODO: Scan for -h -help
    // Skip first, as thats us
    args.next().unwrap();

    let file = args.next().unwrap();
    let start = args.next().unwrap().to_lowercase();
    let end = args.next().unwrap().to_lowercase();

    let file = PathBuf::from(file);

    if start.len() != end.len() {
        return Err("Word lengths are not equal, this is not supported".to_string());
    }

    Ok(WordPath { file, start, end })
}