Skip to content

Commit 60eb4ca

Browse files
committed
feat: Add fruit basket challenge
1 parent 9263107 commit 60eb4ca

4 files changed

Lines changed: 165 additions & 2 deletions

File tree

src/challenge/challenge_02.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
//! Challenge 02: Fruit Basket Reorganization
2+
use anyhow::Result;
3+
use colored::Colorize;
4+
use std::collections::{BinaryHeap, HashMap};
5+
6+
pub fn run(input: &str, output: Option<&str>) -> Result<String> {
7+
let fruit_basket = input.trim();
8+
let result = reorganize_fruit_basket(fruit_basket);
9+
10+
if let Some(expected_output) = output {
11+
if result == expected_output {
12+
return Ok(format!("Result '{}' matches the specified output.", result));
13+
}
14+
15+
if is_valid_reorganization(&result) && expected_output != "impossible" {
16+
return Ok(format!(
17+
"Generated a valid reorganization '{result}', which is different from the provided sample '{expected_output}'."
18+
));
19+
}
20+
21+
anyhow::bail!("Output '{result}' of '{input}' doesn't match expected '{expected_output}'.");
22+
}
23+
24+
Ok(format!(
25+
"Reorganized fruit basket to '{result}', but {} output!",
26+
"missing".red().bold()
27+
))
28+
}
29+
30+
fn is_valid_reorganization(s: &str) -> bool {
31+
let mut chars = s.chars();
32+
let mut prev = chars.next();
33+
34+
for current in chars {
35+
if let Some(p) = prev {
36+
if p == current {
37+
return false;
38+
}
39+
}
40+
41+
prev = Some(current);
42+
}
43+
44+
true
45+
}
46+
47+
pub fn reorganize_fruit_basket(s: &str) -> String {
48+
let mut counts = HashMap::new();
49+
for c in s.chars() {
50+
*counts.entry(c).or_insert(0) += 1;
51+
}
52+
53+
if let Some((_, &max_freq)) = counts.iter().max_by_key(|&(_, count)| count) {
54+
if max_freq > s.len().div_ceil(2) {
55+
return "impossible".to_string();
56+
}
57+
}
58+
59+
let mut heap: BinaryHeap<(usize, char)> = BinaryHeap::new();
60+
for (c, count) in counts {
61+
heap.push((count, c));
62+
}
63+
64+
let mut result = Vec::with_capacity(s.len());
65+
let mut prev: Option<(usize, char)> = None;
66+
67+
while !heap.is_empty() {
68+
let (count, char) = heap.pop().unwrap();
69+
result.push(char);
70+
71+
if let Some(p) = prev {
72+
if p.0 > 0 {
73+
heap.push(p);
74+
}
75+
}
76+
77+
prev = Some((count - 1, char));
78+
}
79+
80+
let final_string: String = result.iter().collect();
81+
82+
if !is_valid_reorganization(&final_string) {
83+
return "impossible".to_string();
84+
}
85+
86+
if final_string.len() != s.len() {
87+
// This can happen if the last character pushed to prev has a count > 0
88+
// but the loop terminates. This indicates impossibility that wasn't caught
89+
// by the initial check, which can happen in some complex cases.
90+
// However, the initial check should be sufficient for this problem.
91+
// A more robust check would be to check if prev.0 > 0 after the loop.
92+
if let Some(p) = prev {
93+
if p.0 > 0 {
94+
return "impossible".to_string();
95+
}
96+
}
97+
}
98+
99+
final_string
100+
}
101+
102+
#[cfg(test)]
103+
mod tests {
104+
use super::reorganize_fruit_basket;
105+
106+
fn is_valid_reorganization(s: &str) -> bool {
107+
if s == "impossible" {
108+
return true;
109+
}
110+
111+
super::is_valid_reorganization(s)
112+
}
113+
114+
#[test]
115+
fn test_sample_input() {
116+
let input = "aapplle";
117+
let result = reorganize_fruit_basket(input);
118+
assert!(is_valid_reorganization(&result));
119+
assert_ne!(result, "impossible");
120+
}
121+
122+
#[test]
123+
fn test_impossible_case() {
124+
let input = "aaab";
125+
assert_eq!(reorganize_fruit_basket(input), "impossible");
126+
}
127+
128+
#[test]
129+
fn test_all_same_fruit() {
130+
let input = "aaaaa";
131+
assert_eq!(reorganize_fruit_basket(input), "impossible");
132+
}
133+
134+
#[test]
135+
fn test_possible_case() {
136+
let input = "aaabbc";
137+
let result = reorganize_fruit_basket(input);
138+
assert!(is_valid_reorganization(&result));
139+
assert_ne!(result, "impossible");
140+
}
141+
142+
#[test]
143+
fn test_empty_string() {
144+
let input = "";
145+
assert_eq!(reorganize_fruit_basket(input), "");
146+
}
147+
148+
#[test]
149+
fn test_single_char() {
150+
let input = "a";
151+
assert_eq!(reorganize_fruit_basket(input), "a");
152+
}
153+
154+
#[test]
155+
fn test_long_string() {
156+
let input = "vvvlo";
157+
assert_eq!(reorganize_fruit_basket(input), "impossible");
158+
}
159+
}

src/challenge/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
mod challenge_01;
2+
mod challenge_02;
23

34
pub fn name(challenge: &str) -> Option<&'static str> {
45
match challenge {
5-
"01" => Some("Balanced Meal Pair"),
6-
"02" => Some("Research Lab Recorder"),
6+
"01" => Some("Research Lab Recorder"),
7+
"02" => Some("Fruit Basket Reorganization"),
78
_ => None,
89
}
910
}
1011

1112
pub fn run(challenge: &str, input: &str, output: Option<&str>) -> anyhow::Result<String> {
1213
match challenge {
1314
"01" => challenge_01::run(input, output),
15+
"02" => challenge_02::run(input, output),
1416
_ => anyhow::bail!("Error: Unknown challenge '{challenge}'"),
1517
}
1618
}

testcases/02/01_input.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vovlv

testcases/02/01_output.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
impossible

0 commit comments

Comments
 (0)