I wasted hours on the parsing, because my regex ([0-9]*)
was giving me empty strings. Made me feel very dumb when I worked it out
I wasted hours on the parsing, because my regex ([0-9]*)
was giving me empty strings. Made me feel very dumb when I worked it out
I wonder if the phrasing was an attempt to throw off LLMs?
WINED LOVER RIDES - probably not wrong, but interesting name for their variable :D
Its networking is a bit hard to tweak, but I also dont find I need to most of the time. And when I do, its usually just setting the network to host and calling it done.
Are you using docker compose scripts? Backup should be easy, you have your compose scripts to configure the containers, then the scripts can easily be commited somewhere or backed up.
Data should be volume mounted into the container, and then the host disk can be backed up.
The only app that I’ve had to fight docker on is Seafile, and even that works quite well now.
Yet more proof that the dutch are mad :D
Is it your own esolang, or is it commonly used by dutch speakers?
Yeah, disk loading definitely shouldn’t count if I was timing properly, I’m just lazy and dont want to do proper timing. :D
Most of my slowdown is in the hashmap, looks like that answer deconstructs the hashmap and builds it from a fastmap and a vec. Not sure I want to go down that road, at this stage.
Thanks again for your code and help :)
Using release to beat python code is just a very hollow victory :D
It does somewhat depend on how we measure as well, you are benching the algorithm itself, and I’m doing the entire execution time
Yeah, I have been using these slow challenges to improve my profiling ability. It is a bit of a dark art though, especially with compiled languages.
My slowest part seems to be the hashmap, but there isnt much I can do about that I think.
Also, if I do a release run I can get 10ms, but that feels like cheating :D
Its really comforting to know i’m not alone, but this one really made me feel smooth brained
Stepping through this code is what made it click for me, thanks. I was really mentally locked in on “memoizing” of the transform function, instead of realizing that the transform function only needs to be applied once per stone value.
Yours is still a lot faster than my rust version, so i’ll have to work out what is happening there.
I’ll buy “better than default on screen keyboard”, but I doubt it’ll be better than a physical keyboard. Would be nice if there was a virtual keyboard that let you easily modify the layout. I keep hitting .
instead of space on my phone.
Definitely a nice and easy one, I accidentally solved part 2 first, because I skimmed the challenge and missed the unique part.
#[cfg(test)]
mod tests {
const DIR_ORDER: [(i8, i8); 4] = [(-1, 0), (0, 1), (1, 0), (0, -1)];
fn walk_trail(board: &Vec<Vec<i8>>, level: i8, i: i8, j: i8) -> Vec<(i8, i8)> {
let mut paths = vec![];
if i < 0 || j < 0 {
return paths;
}
let actual_level = match board.get(i as usize) {
None => return paths,
Some(line) => match line.get(j as usize) {
None => return paths,
Some(c) => c,
},
};
if *actual_level != level {
return paths;
}
if *actual_level == 9 {
return vec![(i, j)];
}
for dir in DIR_ORDER.iter() {
paths.extend(walk_trail(board, level + 1, i + dir.0, j + dir.1));
}
paths
}
fn count_unique(p0: &Vec<(i8, i8)>) -> u32 {
let mut dedup = vec![];
for p in p0.iter() {
if !dedup.contains(p) {
dedup.push(*p);
}
}
dedup.len() as u32
}
#[test]
fn day10_part1_test() {
let input = std::fs::read_to_string("src/input/day_10.txt").unwrap();
let board = input
.trim()
.split('\n')
.map(|line| {
line.chars()
.map(|c| {
if c == '.' {
-1
} else {
c.to_digit(10).unwrap() as i8
}
})
.collect::<Vec<i8>>()
})
.collect::<Vec<Vec<i8>>>();
let mut total = 0;
for (i, row) in board.iter().enumerate() {
for (j, pos) in row.iter().enumerate() {
if *pos == 0 {
let all_trails = walk_trail(&board, 0, i as i8, j as i8);
total += count_unique(&all_trails);
}
}
}
println!("{}", total);
}
#[test]
fn day10_part2_test() {
let input = std::fs::read_to_string("src/input/day_10.txt").unwrap();
let board = input
.trim()
.split('\n')
.map(|line| {
line.chars()
.map(|c| {
if c == '.' {
-1
} else {
c.to_digit(10).unwrap() as i8
}
})
.collect::<Vec<i8>>()
})
.collect::<Vec<Vec<i8>>>();
let mut total = 0;
for (i, row) in board.iter().enumerate() {
for (j, pos) in row.iter().enumerate() {
if *pos == 0 {
total += walk_trail(&board, 0, i as i8, j as i8).len();
}
}
}
println!("{}", total);
}
}
You coded this on a phone, with a touchscreen keyboard? Not sure who is more impressive, you or the unicode wizard :D
haha, kept going at it, got it down to 4ms, by tracking where the searches ended, and starting from there next time.
Definitely done now :D
Found a cheaty way to get sub 1s:
fn defrag2(p0: &mut [i64]) {
let mut i = *p0.last().unwrap();
while i > 3000 { // Stop defragging here, probably cant find free spots after this point
let (old_pos, size) = find_file(p0, i);
if let Some(new_pos) = find_slot(p0, size, old_pos) {
move_file(p0, i, size, old_pos, new_pos);
}
i -= 1;
}
}
Might be possible to correctly do this in the find_slot code, so that if it fails to find a slot, it never searches for that size again.
edit:
fn defrag2(p0: &mut [i64]) {
let mut i = *p0.last().unwrap();
let mut max_size = 10;
while i > 0 {
let (old_pos, size) = find_file(p0, i);
if size <= max_size {
if let Some(new_pos) = find_slot(p0, size, old_pos) {
move_file(p0, i, size, old_pos, new_pos);
} else {
max_size = size - 1;
}
}
if max_size == 0 {
return;
}
i -= 1;
}
}
500ms, I can go to sleep now.
Pretty poor performance on part 2, was initially 10s, got down to 2.5s, but still seems pretty poor.
#[cfg(test)]
mod tests {
fn checksum(p0: &[i64]) -> i64 {
let mut csum = 0;
for (i, val) in p0.iter().enumerate() {
if *val == -1 {
continue;
}
csum += *val * (i as i64);
}
csum
}
fn defrag(p0: &[i64]) -> Vec<i64> {
let mut start = 0;
let mut end = p0.len() - 1;
let mut defragged = vec![];
while start != end + 1 {
if p0[start] != -1 {
defragged.push(p0[start]);
start += 1;
continue;
}
if p0[start] == -1 {
defragged.push(p0[end]);
start += 1;
end -= 1;
while p0[end] == -1 {
end -= 1;
}
}
}
defragged
}
fn expand_disk(p0: &str) -> Vec<i64> {
let mut disk = vec![];
let mut file_index = 0;
let mut is_file = true;
for char in p0.chars() {
let val = char.to_digit(10).unwrap();
if is_file {
for _ in 0..val {
disk.push(file_index)
}
file_index += 1;
} else {
for _ in 0..val {
disk.push(-1)
}
}
is_file = !is_file;
}
disk
}
#[test]
fn day9_part1_test() {
let input: String = std::fs::read_to_string("src/input/day_9.txt")
.unwrap()
.trim()
.into();
let disk: Vec<i64> = expand_disk(&input);
let defraged = defrag(&disk);
let checksum = checksum(&defraged);
println!("{}", checksum);
}
fn find_file(p0: &[i64], file: i64) -> (usize, usize) {
let mut count = 0;
let mut i = p0.len() - 1;
while i > 0 && p0[i] != file {
i -= 1;
}
// At end of file
while i > 0 && p0[i] == file {
count += 1;
i -= 1;
}
(i + 1, count)
}
fn find_slot(p0: &[i64], size: usize, end: usize) -> Option<usize> {
let mut i = 0;
while i < end {
while p0[i] != -1 && i < end {
i += 1;
}
let mut count = 0;
while p0[i] == -1 && i < end {
i += 1;
count += 1;
if count == size {
return Some(i - count);
}
}
}
None
}
fn move_file(p0: &mut [i64], file: i64, size: usize, old_pos: usize, new_pos: usize) {
for i in 0..size {
p0[old_pos + i] = -1;
p0[new_pos + i] = file;
}
}
fn defrag2(p0: &mut [i64]) {
let mut i = *p0.last().unwrap();
while i > 0 {
let (old_pos, size) = find_file(p0, i);
match find_slot(p0, size, old_pos) {
None => {}
Some(new_pos) => {
if new_pos < old_pos {
move_file(p0, i, size, old_pos, new_pos);
}
}
}
i -= 1;
}
}
#[test]
fn day9_part2_test() {
let input: String = std::fs::read_to_string("src/input/day_9.txt")
.unwrap()
.trim()
.into();
let mut disk: Vec<i64> = expand_disk(&input);
defrag2(&mut disk);
let checksum = checksum(&disk);
println!("{}", checksum);
}
}
For the first time, I can post my solution, because I actually solved it on the day :D Probably not the cleanest or optimal solution, but it does solve the problem.
Very long, looking forward to someone solving it in 5 lines of unicode :D
#[cfg(test)]
mod tests {
fn get_frequences(input: &str) -> Vec<char> {
let mut freq = vec![];
for char in input.chars() {
if char == '.' {
continue;
}
if !freq.contains(&char) {
freq.push(char);
}
}
freq
}
fn find_antennas(board: &Vec<Vec<char>>, freq: char) -> Vec<(isize, isize)> {
let mut antennas = vec![];
for (i, line) in board.iter().enumerate() {
for (j, char) in line.iter().enumerate() {
if *char == freq {
antennas.push((i as isize, j as isize));
}
}
}
antennas
}
fn calc_antinodes(first: &(isize, isize), second: &(isize, isize)) -> Vec<(isize, isize)> {
let deltax = second.0 - first.0;
let deltay = second.1 - first.1;
if deltax == 0 && deltay == 0 {
return vec![];
}
vec![
(first.0 - deltax, first.1 - deltay),
(second.0 + deltax, second.1 + deltay),
]
}
#[test]
fn test_calc_antinodes() {
let expected = vec![(0, -1), (0, 2)];
let actual = calc_antinodes(&(0, 0), &(0, 1));
for i in &expected {
assert!(actual.contains(i));
}
let actual = calc_antinodes(&(0, 1), &(0, 0));
for i in &expected {
assert!(actual.contains(i));
}
}
fn calc_all_antinodes(board: &Vec<Vec<char>>, freq: char) -> Vec<(isize, isize)> {
let antennas = find_antennas(&board, freq);
let mut antinodes = vec![];
for (i, first) in antennas.iter().enumerate() {
for second in antennas[i..].iter() {
antinodes.extend(calc_antinodes(first, second));
}
}
antinodes
}
fn prune_nodes(
nodes: &Vec<(isize, isize)>,
height: isize,
width: isize,
) -> Vec<(isize, isize)> {
let mut pruned = vec![];
for node in nodes {
if pruned.contains(node) {
continue;
}
if node.0 < 0 || node.0 >= height {
continue;
}
if node.1 < 0 || node.1 >= width {
continue;
}
pruned.push(node.clone());
}
pruned
}
fn print_board(board: &Vec<Vec<char>>, pruned: &Vec<(isize, isize)>) {
for (i, line) in board.iter().enumerate() {
for (j, char) in line.iter().enumerate() {
if pruned.contains(&(i as isize, j as isize)) {
print!("#");
} else {
print!("{char}");
}
}
println!();
}
}
#[test]
fn day8_part1_test() {
let input: String = std::fs::read_to_string("src/input/day_8.txt").unwrap();
let frequencies = get_frequences(&input);
let board = input
.trim()
.split('\n')
.map(|line| line.chars().collect::<Vec<char>>())
.collect::<Vec<Vec<char>>>();
let mut all_nodes = vec![];
for freq in frequencies {
let nodes = calc_all_antinodes(&board, freq);
all_nodes.extend(nodes);
}
let height = board.len() as isize;
let width = board[0].len() as isize;
let pruned = prune_nodes(&all_nodes, height, width);
println!("{:?}", pruned);
print_board(&board, &pruned);
println!("{}", pruned.len());
// 14 in test
}
fn calc_antinodes2(first: &(isize, isize), second: &(isize, isize)) -> Vec<(isize, isize)> {
let deltax = second.0 - first.0;
let deltay = second.1 - first.1;
if deltax == 0 && deltay == 0 {
return vec![];
}
let mut nodes = vec![];
for n in 0..50 {
nodes.push((first.0 - deltax * n, first.1 - deltay * n));
nodes.push((second.0 + deltax * n, second.1 + deltay * n));
}
nodes
}
fn calc_all_antinodes2(board: &Vec<Vec<char>>, freq: char) -> Vec<(isize, isize)> {
let antennas = find_antennas(&board, freq);
let mut antinodes = vec![];
for (i, first) in antennas.iter().enumerate() {
for second in antennas[i..].iter() {
antinodes.extend(calc_antinodes2(first, second));
}
}
antinodes
}
#[test]
fn day8_part2_test() {
let input: String = std::fs::read_to_string("src/input/day_8.txt").unwrap();
let frequencies = get_frequences(&input);
let board = input
.trim()
.split('\n')
.map(|line| line.chars().collect::<Vec<char>>())
.collect::<Vec<Vec<char>>>();
let mut all_nodes = vec![];
for freq in frequencies {
let nodes = calc_all_antinodes2(&board, freq);
all_nodes.extend(nodes);
}
let height = board.len() as isize;
let width = board[0].len() as isize;
let pruned = prune_nodes(&all_nodes, height, width);
println!("{:?}", pruned);
print_board(&board, &pruned);
println!("{}", pruned.len());
}
}
That’s probably quite optimal, compared with checking every state in the path, or running off a fixed number of steps
Rust
Hardest part was parsing the input, i somehow forgot how regexes work and wasted hours.
Learning how to do matrix stuff in rust was a nice detour as well.
#[cfg(test)] mod tests { use nalgebra::{Matrix2, Vector2}; use regex::Regex; fn play_game(ax: i128, ay: i128, bx: i128, by: i128, gx: i128, gy: i128) -> i128 { for a_press in 0..100 { let rx = gx - ax * a_press; let ry = gy - ay * a_press; if rx % bx == 0 && ry % by == 0 && rx / bx == ry / by { return a_press * 3 + ry / by; } } 0 } fn play_game2(ax: i128, ay: i128, bx: i128, by: i128, gx: i128, gy: i128) -> i128 { // m * p = g // p = m' * g // |ax bx|.|a_press| = |gx| // |ay by| |b_press| |gy| let m = Matrix2::new(ax as f64, bx as f64, ay as f64, by as f64); match m.try_inverse() { None => return 0, Some(m_inv) => { let g = Vector2::new(gx as f64, gy as f64); let p = m_inv * g; let pa = p[0].round() as i128; let pb = p[1].round() as i128; if pa * ax + pb * bx == gx && pa * ay + pb * by == gy { return pa * 3 + pb; } } }; 0 } #[test] fn day13_part1_test() { let input = std::fs::read_to_string("src/input/day_13.txt").unwrap(); let re = Regex::new(r"[0-9]+").unwrap(); let games = input .trim() .split("\n\n") .map(|line| { re.captures_iter(line) .map(|x| { let first = x.get(0).unwrap().as_str(); first.parse::<i128>().unwrap() }) .collect::<Vec<i128>>() }) .collect::<Vec<Vec<i128>>>(); let mut total = 0; for game in games { let cost = play_game2(game[0], game[1], game[2], game[3], game[4], game[5]); total += cost; } // 36870 println!("{}", total); } #[test] fn day12_part2_test() { let input = std::fs::read_to_string("src/input/day_13.txt").unwrap(); let re = Regex::new(r"[0-9]+").unwrap(); let games = input .trim() .split("\n\n") .map(|line| { re.captures_iter(line) .map(|x| { let first = x.get(0).unwrap().as_str(); first.parse::<i128>().unwrap() }) .collect::<Vec<i128>>() }) .collect::<Vec<Vec<i128>>>(); let mut total = 0; for game in games { let cost = play_game2( game[0], game[1], game[2], game[3], game[4] + 10000000000000, game[5] + 10000000000000, ); total += cost; } println!("{}", total); } }