1
\$\begingroup\$

Yes I'll keep doing this until I cannot be bothered to do it in Bash ;-) (Well today is probably the last one ;-))

Part 1

To paraphrase the puzzle, given some input ("engine schematic") like this:

467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..

The task is to compute the sum of integers that are adjacent to a non-digit horizontally, vertically, or diagonally. In the above example, only 114 and 58 are not adjacent to any symbol.

#!/usr/bin/env bash
#
# Solver for https://adventofcode.com/2023/day/3 part 1
# Redirect the input file to this script, for example day3part1.sh < path/to/input.txt
#
set -euo pipefail
solve_2023_day3_part1() {
 local lines first_line sum i j line num segment_current segment_next segment_prev
 # Read lines, padded with '.' characters at top, bottom, left, right.
 lines=()
 read -r first_line
 lines+=(".${first_line//?/.}.")
 lines+=(".$first_line.")
 while read -r line; do
 lines+=(".$line.")
 done
 lines+=(".${first_line//?/.}.")
 sum=0
 # For each row index except the padding row before and after.
 for ((i = 1; i < ${#lines[@]} - 1; ++i)); do
 line=${lines[i]}
 # For each column index except the padding column at the start and end.
 for ((j = 1; j < ${#line} - 1; )); do
 # Try to match numeric prefix.
 if [[ ${line:j} =~ ^([0-9]+) ]]; then
 num=${BASH_REMATCH[1]}
 # Check if the line segment on the current line, or above or below
 # has any non-numeric non-dot value.
 ((segment_length = ${#num} + 2))
 segment_current=${line:j-1:segment_length}
 segment_prev=${lines[i-1]:j-1:segment_length}
 segment_next=${lines[i+1]:j-1:segment_length}
 if [[ "$segment_current$segment_prev$segment_next" =~ [^.0-9] ]]; then
 ((sum += num))
 fi
 ((j += ${#num} + 1))
 else
 ((++j))
 fi
 done
 done
 echo "$sum"
}
solve_2023_day3_part1

Part 2

To paraphrase the change in part 2, we're looking for * symbols ("gears") that are adjacent to precisely two integers, and compute the sum of the product of adjacent integers. In the above example this would be 467 * 35 + 755 * 598 = 16345 +たす 451490 = 467835.

#!/usr/bin/env bash
#
# Solver for https://adventofcode.com/2023/day/3 part 2
# Redirect the input file to this script, for example day3part2.sh < path/to/input.txt
#
set -euo pipefail
solve_2023_day3_part2() {
 local lines first_line gears_to_parts line
 local num offset row col i j separator
 local sum value g1 g2 rest
 # Read lines, padded with '.' characters at top, bottom, left, right.
 lines=()
 read -r first_line
 lines+=(".${first_line//?/.}.")
 lines+=(".$first_line.")
 while read -r line; do
 lines+=(".$line.")
 done
 lines+=(".${first_line//?/.}.")
 # Mapping of gear positions and list of adjacent numbers.
 declare -A gears_to_parts
 separator=' '
 # For each row index except the padding row before and after.
 for ((i = 1; i < ${#lines[@]} - 1; ++i)); do
 line=${lines[i]}
 # For each column index except the padding column at the start and end.
 for ((j = 1; j < ${#line} - 1; )); do
 # Try to match numeric prefix.
 if [[ ${line:j} =~ ^([0-9]+) ]]; then
 num=${BASH_REMATCH[1]}
 # Find all "gears" around the number.
 for offset in -1 0 1; do
 ((row = i + offset)) || true
 for ((col = j-1; col < j + ${#num} + 1; ++col)); do
 if [ "${lines[row]:col:1}" = '*' ]; then
 # Gear found, add current number to list of numbers
 # next to this gear position.
 gears_to_parts[$row:$col]+="$num$separator"
 fi
 done
 done
 ((j += ${#num} + 1))
 else
 ((++j))
 fi
 done
 done
 sum=0
 for value in "${gears_to_parts[@]}"; do
 IFS="$separator" read g1 g2 rest <<< "$value"
 [ "$g2" ] && ! [ "$rest" ] && ((sum += g1 * g2))
 done
 echo "$sum"
}
solve_2023_day3_part2

Review request

I do realize this is a bit whimsical, and Bash is a poor choice to solve algorithmic puzzles. Also, this code is intended as a one-off, and not for reuse. I'm solving in Bash because, and as long as, it gives me joy. I won't make promises about how far I'm willing to stick to pure Bash, it will depend on the day and my mood. The main principles I do intend to stick to are:

  • The program must produce the correct solution to the full input within seconds.
  • The program must use idiomatic Bash.

Do you see any patterns here that you would replace with better patterns?

Do you see a simpler way to solve any part of the puzzle with Bash and common shell tools?

What would you do differently?

asked Dec 3, 2023 at 21:14
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.