Challenge 50
The Word Ladder
You’re tasked with building a word game feature where players transform one word into another by changing one letter at a time, with each step being a valid word.
Given a start word, an end word, and a dictionary of valid words, write a function to find the shortest “ladder” (sequence of words) from start to end.
For simplicity, assume all words are the same length.
Write your solution below
// Rust Bytes Challenge Issue #60 The Word Ladder fn find_word_ladder(start: String, end: String, dictionary: Vec<String>) -> Option<Vec<String>> { // your implementation goes here } #[cfg(test)] mod tests { use super::*; // Test Case 1: Basic ladder #[test] fn test_short_ladder() { let start = String::from("hit"); let end = String::from("cog"); let dictionary = vec![ String::from("hit"), String::from("hot"), String::from("dot"), String::from("dog"), String::from("cog"), ]; let result = find_word_ladder(start, end, dictionary); assert!(result.is_some()); let ladder = result.unwrap(); assert_eq!( ladder, vec![ String::from("hit"), String::from("hot"), String::from("dot"), String::from("dog"), String::from("cog"), ] ); } // Test Case 2: No ladder possible #[test] fn test_no_ladder() { let start = String::from("cat"); let end = String::from("zip"); let dictionary = vec![ String::from("cat"), String::from("cot"), String::from("cog"), ]; let result = find_word_ladder(start, end, dictionary); assert!(result.is_none()); } // Test Case 3: Start and end are the same #[test] fn test_same_word() { let start = String::from("cat"); let end = String::from("cat"); let dictionary = vec![ String::from("cat"), String::from("cot"), String::from("cog"), ]; let result = find_word_ladder(start, end, dictionary); assert!(result.is_some()); let ladder = result.unwrap(); assert_eq!(ladder, vec![String::from("cat")]); } // Test Case 4: Longer ladder with multiple paths #[test] fn test_longer_ladder() { let start = String::from("lead"); let end = String::from("gold"); let dictionary = vec![ String::from("lead"), String::from("load"), String::from("goad"), String::from("gold"), String::from("lewd"), // Extra word, not in shortest path ]; let result = find_word_ladder(start, end, dictionary); assert!(result.is_some()); let ladder = result.unwrap(); assert_eq!( ladder, vec![ String::from("lead"), String::from("load"), String::from("goad"), String::from("gold"), ] ); } }
Solution
Click to Show/Hide Solution
#![allow(unused)] fn main() { // Rust Bytes Challenge Issue #60 The Word Ladder fn find_word_ladder(start: String, end: String, dict: &Vec<String>) -> Option<&[String]> { let mut max_seq = 0; let mut seq_idx = 0; let mut seq_len = 0; let mut end_checked = false; for (i, word) in dict .iter() .enumerate() .skip_while(|&(_, word)| word != &start) .skip(1) { // the difference between current and previous words let diff_count = word .as_bytes() .iter() .zip(dict[i - 1].as_bytes().iter()) // zip with the prev word .fold(0, |acc, (a, b)| if a != b { acc + 1 } else { acc }); if diff_count <= 1 { seq_len += 1; // the sequence still proceeds if seq_len > max_seq { max_seq = seq_len; seq_idx = i; } } else { seq_len = 0; // reset sequence } if word == &end { end_checked = true; break; // termination } } if max_seq == 0 || !end_checked { None } else { Some(&dict[(seq_idx - max_seq)..=seq_idx]) } } #[cfg(test)] mod tests { use super::*; // Test Case 1: Basic ladder #[test] fn test_short_ladder() { let start = String::from("hit"); let end = String::from("cog"); let dictionary = vec![ String::from("hit"), String::from("hot"), String::from("dot"), String::from("dog"), String::from("cog"), ]; let result = find_word_ladder(start, end, dictionary); assert!(result.is_some()); let ladder = result.unwrap(); assert_eq!( ladder, vec![ String::from("hit"), String::from("hot"), String::from("dot"), String::from("dog"), String::from("cog"), ] ); } // Test Case 2: No ladder possible #[test] fn test_no_ladder() { let start = String::from("cat"); let end = String::from("zip"); let dictionary = vec![ String::from("cat"), String::from("cot"), String::from("cog"), ]; let result = find_word_ladder(start, end, dictionary); assert!(result.is_none()); } // Test Case 3: Start and end are the same #[test] fn test_same_word() { let start = String::from("cat"); let end = String::from("cat"); let dictionary = vec![ String::from("cat"), String::from("cot"), String::from("cog"), ]; let result = find_word_ladder(start, end, dictionary); assert!(result.is_some()); let ladder = result.unwrap(); assert_eq!(ladder, vec![String::from("cat")]); } // Test Case 4: Longer ladder with multiple paths #[test] fn test_longer_ladder() { let start = String::from("lead"); let end = String::from("gold"); let dictionary = vec![ String::from("lead"), String::from("load"), String::from("goad"), String::from("gold"), String::from("lewd"), // Extra word, not in shortest path ]; let result = find_word_ladder(start, end, dictionary); assert!(result.is_some()); let ladder = result.unwrap(); assert_eq!( ladder, vec![ String::from("lead"), String::from("load"), String::from("goad"), String::from("gold"), ] ); } } }