4
๐ฆ Everybody.Codes 2025 Quest 11 Solutions ๐ฆ
(programming.dev)
Since, an optimized phase 2 is enough to solve p2 and p3, I did not optimize any further.
import math
# Brute-force phase one
# Simulate all phase one rounds until no more moves can be made or max rounds reached
def phase_one(ducks: list[int], max_rounds: int) -> int:
n = len(ducks)
round = 1
while round <= max_rounds:
moved = False
for i in range(n - 1):
if ducks[i] > ducks[i + 1]:
ducks[i] -= 1
ducks[i + 1] += 1
moved = True
if not moved:
break
round += 1
return round - 1
# Brute-force phase two
# Simulate all phase two rounds until no more moves can be made or max rounds reached
def phase_two(ducks: list[int], max_rounds: int) -> int:
n = len(ducks)
round = 1
while round <= max_rounds:
moved = False
for i in range(n - 1):
if ducks[i] < ducks[i + 1]:
ducks[i] += 1
ducks[i + 1] -= 1
moved = True
if not moved:
break
round += 1
return round - 1
# Optimized phase two for limitless rounds
# Every round will move one duck from every duck above average to below average
# So the resulting number of rounds is simply the total number of ducks above average
# Inversely, you can also total the number of ducks below average
def phase_two_limitless(ducks: list[int]) -> int:
avg = 0
for i, d in enumerate(ducks):
avg += (d - avg) / (i + 1)
avg = round(avg)
rounds = sum(x - avg for x in ducks if x > avg)
return rounds
# Balance ducks through both phases, respecting max rounds
def balance_ducks(ducks: list[int], max_rounds: int) -> int:
rounds1 = phase_one(ducks, max_rounds)
# if the number of rounds is unbounded, use the optimized phase two
if max_rounds < math.inf:
rounds2 = phase_two(ducks, max_rounds - rounds1)
else:
rounds2 = phase_two_limitless(ducks)
return rounds1 + rounds2
# Calculate checksum of the ducks
def calculate_checksum(ducks: list[int]) -> int:
n = len(ducks)
checksum = sum((i + 1) * ducks[i] for i in range(n))
return checksum
# Brute-force both phases
def part1(data: str):
ducks = [int(d) for d in data.splitlines()]
balance_ducks(ducks, 10)
return calculate_checksum(ducks)
# <asserts snipped>
# Brute-force phase one, use optimized phase two
def part2(data: str):
ducks = [int(d) for d in data.splitlines()]
rounds = balance_ducks(ducks, math.inf) # type: ignore
return rounds
# <asserts snipped>
# You can use the same function for p2 and p3
part3 = part2
I hate when you need to look at the data to solve a simpler subclass of the problem.
use num::Integer;
fn one_round(columns: &mut [i64], forward: bool) -> bool {
let mut modified = false;
for i in 1..columns.len() {
if forward {
if columns[i - 1] > columns[i] {
columns[i - 1] -= 1;
columns[i] += 1;
modified = true;
}
} else if columns[i - 1] < columns[i] {
columns[i - 1] += 1;
columns[i] -= 1;
modified = true;
}
}
modified
}
pub fn solve_part_1(input: &str) -> String {
let mut columns = input
.lines()
.map(|l| l.parse().unwrap())
.collect::<Vec<i64>>();
let mut rounds_remaining = 10;
while rounds_remaining > 0 {
if one_round(&mut columns, true) {
rounds_remaining -= 1;
} else {
break;
}
}
while rounds_remaining > 0 {
if one_round(&mut columns, false) {
rounds_remaining -= 1;
} else {
break;
}
}
columns
.iter()
.enumerate()
.map(|(column_idx, count)| (column_idx + 1) as i64 * *count)
.sum::<i64>()
.to_string()
}
pub fn solve_part_2(input: &str) -> String {
let mut columns = input
.lines()
.map(|l| l.parse().unwrap())
.collect::<Vec<i64>>();
let mut total_rounds = 0;
loop {
if one_round(&mut columns, true) {
total_rounds += 1;
} else {
break;
}
}
loop {
if one_round(&mut columns, false) {
total_rounds += 1;
} else {
break;
}
}
total_rounds.to_string()
}
pub fn solve_part_3(input: &str) -> String {
let mut columns = input
.lines()
.map(|l| l.parse().unwrap())
.collect::<Vec<i64>>();
let invariant_sum = columns.iter().sum::<i64>();
let mut total_rounds = 0i64;
loop {
let first_gap = (0..columns.len() - 1).find(|&i| columns[i].abs_diff(columns[i + 1]) > 1);
let last_gap = (0..columns.len() - 1)
.filter(|&i| columns[i].abs_diff(columns[i + 1]) > 1)
.next_back();
if let (Some(first_gap), Some(last_gap)) = (first_gap, last_gap)
&& (last_gap > first_gap)
{
let amount_to_fill: i64 = columns
.iter()
.take(first_gap + 1)
.map(|&x| columns[first_gap + 1] - x)
.sum();
let amount_available: i64 = columns
.iter()
.skip(last_gap + 1)
.map(|&x| x - columns[last_gap])
.sum();
let amount_to_transfer = amount_to_fill.min(amount_available);
let new_amount_left: i64 =
columns.iter().take(first_gap + 1).sum::<i64>() + amount_to_transfer;
let (left_base, remainder) = new_amount_left.div_rem(&((first_gap + 1) as i64));
for (i, new_value) in columns.iter_mut().enumerate().take(first_gap + 1) {
*new_value = left_base
+ if first_gap - i < remainder as usize {
1
} else {
0
};
}
let new_amount_right: i64 =
columns.iter().skip(last_gap + 1).sum::<i64>() - amount_to_transfer;
let (right_base, remainder) =
new_amount_right.div_rem(&((columns.len() - last_gap - 1) as i64));
for i in last_gap + 1..columns.len() {
columns[i] = right_base
+ if columns.len() - i <= remainder as usize {
1
} else {
0
};
}
assert_eq!(invariant_sum, columns.iter().sum::<i64>());
total_rounds += amount_to_transfer;
} else {
break;
}
}
loop {
if one_round(&mut columns, false) {
total_rounds += 1;
} else {
break;
}
}
total_rounds.to_string()
}
An unofficial home for the advent of code community on programming.dev! Other challenges are also welcome!
Advent of Code is an annual Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.
Everybody Codes is another collection of programming puzzles with seasonal events.
Solution Threads
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 |
Icon base by Lorc under CC BY 3.0 with modifications to add a gradient
console.log('Hello World')