@@ 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 })
+}