Last active
December 29, 2023 23:43
-
-
Save x86-39/8b137a2d8df42cd856c8d266f865235f to your computer and use it in GitHub Desktop.
Ansible Advent of Code 2023, Day 3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
# WARNING: THIS TAKES A LONG TIME TO RUN ON REAL INPUT (6 hours!) | |
# This entire shitfuckery is exponential as all hell, Ansible was not made to do 190k tasks! | |
# Could be optimised I think by doing more logic within the Jinja2 loops, which would be calculated in the main Ansible process | |
# Set the input | |
- name: Set example input | |
ansible.builtin.set_fact: | |
day3_input: |- | |
467..114.. | |
...*...... | |
..35..633. | |
......#... | |
617*...... | |
.....+.58. | |
..592..... | |
......755. | |
...$.*.... | |
.664.598.. | |
when: day3_input is not defined | |
# Get a list we can loop over | |
- name: Split input into separate lines | |
ansible.builtin.set_fact: | |
lines: "{{ day3_input | split('\n') }}" | |
# Create a list of characters for each list (2D array, e.g. [0][0]) | |
- name: Split rows into columns | |
ansible.builtin.set_fact: | |
rows: "{{ lines | map('list') }}" | |
# We'll need this to check if we're not going over the limit | |
- name: Set the number of rows and row length | |
ansible.builtin.set_fact: | |
rows_length: "{{ rows | length }}" | |
row_length: "{{ rows[0] | length }}" | |
- name: Loop through every coordinate | |
ansible.builtin.set_fact: | |
# PART 1: | |
# Add the coordinates to a list, so we don't check them again (e.g. so 123 become 123, 23, 3) | |
coords_checked_already: "{{ coords_checked_already | default([]) + numbers_checked }}" | |
# If the number is adjacent to a symbol, add it to the list of numbers | |
numbers: "{% if is_adjacent_to_symbol | bool %}{{ numbers | default([]) + [number] }}{% else %}{{ numbers | default([]) }}{% endif %}" | |
# PART 2: | |
# Set the gears dict | |
# it should end up looking like { x: y: [gears_related_numbers]} | |
gears_dict: "{{ _gears_dict | trim | default({}) | from_yaml }}" | |
vars: | |
# Current character | |
current_item: "{{ rows[item.0][item.1] }}" | |
# Is this character a number? | |
current_is_number: "{% if current_item | regex_search('[0-9]') != None %}true{% else %}false{% endif %}" | |
# Every character to the right of the current character | |
look_right: "{{ rows[item.0][item.1 + 1:rows[0] | length] | map('regex_search', '([0-9]+)') }}" | |
# Some absolutely fucking abhorrent Jinja2 statement that: | |
# If we have not already checked this coord | |
# Gets the current position, loops over to the right until it finds a non-number character | |
# Every number it adds to the string (which we later cast to int) | |
# Adds to the coordinates we've checked, so we don't get duplicates | |
_look_right_nums: " | |
{# COMMENT: Check if we are at a number #} | |
{% if current_is_number | bool %} | |
{# COMMENT: Skip if we already have this in a different number #} | |
{% if [item.0, item.1] not in coords_checked_already | default([]) %} | |
{# COMMENT: Set vars so we track symbols, whether to break the loop and the numbers #} | |
{%- set ns = namespace(has_symbol = false, continues = true, numstring = '', coords_checked = []) -%} | |
{%- set ns.numstring = ns.numstring + current_item -%} | |
{% if ns.numstring != '' %} | |
{# COMMENT: Add current coordinate to coords checked #} | |
{%- set ns.coords_checked = ns.coords_checked + [(item.0, item.1)] -%} | |
{# COMMENT: Loop over every character to the right of the current character #} | |
{% for i in range(0, look_right | length) %} | |
{# COMMENT: If we can continue, and the current character is a number #} | |
{% if ns.continues %} | |
{% if look_right[i] | regex_search('[0-9]') != None %} | |
{%- set ns.coords_checked = ns.coords_checked + [(item.0, item.1 + 1 + i)] -%} | |
{# COMMENT: Concatenate the number to the string #} | |
{%- set ns.numstring = ns.numstring + look_right[i] -%} | |
{% else %} | |
{# COMMENT: If this isn't a number, break the loop #} | |
{%- set ns.continues = false -%} | |
{% endif %} | |
{% endif %} | |
{% endfor %} | |
{% endif %} | |
{% endif %} | |
{% endif %} | |
{{ [ns.numstring | default(''),ns.coords_checked | default([])] }}" | |
look_right_nums: "{{ _look_right_nums | trim }}" # Every indent is taken literally, but I do want to keep them for """readability""", so we trim them | |
numbers_checked: "{{ look_right_nums | from_yaml | json_query('[1]') | list }}" | |
number: "{{ look_right_nums | from_yaml | json_query('[0]') }}" | |
# Surrounding chars, including diagonals, NOT going in negatives | |
# Apparently it can get worse than the previous statement | |
# Check every surrounding character to the numbers we've checked | |
# Check if it's within the bounds | |
# If it's not also a number we've checked | |
# Add it to a list we return | |
# what the fuck is this shit! | |
_surrounding_chars: " | |
{# COMMENT: Set empty list #} | |
{% set ns = namespace(chars = []) %} | |
{# COMMENT: If we are a number #} | |
{% if number != '' %} | |
{% for coord in numbers_checked %} | |
{% if coord.0 - 1 >= 0 %} | |
{% if coord.1 - 1 >= 0 %} | |
{# COMMENT: Check if this is a number we checked #} | |
{% if (coord.0 - 1, coord.1 - 1) not in numbers_checked %} | |
{%- set ns.chars = ns.chars + [[coord.0 - 1, coord.1 - 1, rows[coord.0 - 1][coord.1 - 1]]] -%} | |
{% endif %} | |
{% endif %} | |
{% if (coord.0 - 1, coord.1) not in numbers_checked %} | |
{%- set ns.chars = ns.chars + [[coord.0 - 1, coord.1, rows[coord.0 - 1][coord.1]]] -%} | |
{% endif %} | |
{% if coord.1 + 1 < row_length | int %} | |
{% if (coord.0 - 1, coord.1 + 1) not in numbers_checked %} | |
{%- set ns.chars = ns.chars + [[coord.0 - 1, coord.1 + 1, rows[coord.0 - 1][coord.1 + 1]]] -%} | |
{% endif %} | |
{% endif %} | |
{% endif %} | |
{% if coord.1 - 1 >= 0 %} | |
{% if (coord.0, coord.1 - 1) not in numbers_checked %} | |
{%- set ns.chars = ns.chars + [[coord.0, coord.1 - 1, rows[coord.0][coord.1 - 1]]] -%} | |
{% endif %} | |
{% endif %} | |
{% if coord.1 + 1 < row_length | int %} | |
{% if (coord.0, coord.1 + 1) not in numbers_checked %} | |
{%- set ns.chars = ns.chars + [[coord.0, coord.1 + 1, rows[coord.0][coord.1 + 1]]] -%} | |
{% endif %} | |
{% endif %} | |
{% if coord.0 + 1 < rows_length | int %} | |
{% if coord.1 - 1 >= 0 %} | |
{% if (coord.0 + 1, coord.1 - 1) not in numbers_checked %} | |
{%- set ns.chars = ns.chars + [[coord.0 + 1, coord.1 - 1, rows[coord.0 + 1][coord.1 - 1]]] -%} | |
{% endif %} | |
{% endif %} | |
{% if (coord.0 + 1, coord.1) not in numbers_checked %} | |
{%- set ns.chars = ns.chars + [[coord.0 + 1, coord.1, rows[coord.0 + 1][coord.1]]] -%} | |
{% endif %} | |
{% if coord.1 + 1 < row_length | int %} | |
{% if (coord.0 + 1, coord.1 + 1) not in numbers_checked %} | |
{%- set ns.chars = ns.chars + [[coord.0 + 1, coord.1 + 1, rows[coord.0 + 1][coord.1 + 1]]] -%} | |
{% endif %} | |
{% endif %} | |
{% endif %} | |
{% endfor %} | |
{% endif %} | |
{{ ns.chars | list }}" | |
# Surrounding chars, trimmed | |
surrounding_chars: "{{ _surrounding_chars | trim | from_yaml | map('last') }}" | |
# Surrounding chars, without dots | |
surrounding_chars_without_dots: "{{ surrounding_chars | reject('equalto', '.') }}" | |
# Is this number adjacent to a symbol (not a dot)? | |
is_adjacent_to_symbol: "{{ surrounding_chars_without_dots | length > 0 }}" | |
# PART 2: | |
# Get the surrounding characters, but keep the coordinates | |
surrounding_chars_with_coords: "{{ _surrounding_chars | trim | from_yaml }}" | |
# Surrounding gears (*) | |
surrounding_gears: "{{ surrounding_chars_with_coords | selectattr('2', 'equalto', '*') }}" | |
# Get all the gears that are adjacent to the current number | |
# Then loop over them, add the current number to the gears' list in the gears dict | |
# it should end up looking like { x: y: [gears_related_numbers]} | |
# this took so long to get right. fuck this shit | |
_gears_dict: " | |
{%- set ns = namespace(new_dict = gears_dict | default({} )) -%} | |
{%- for gear in surrounding_gears -%} | |
{%- set gearns = namespace(gear_current_row = gears_dict[gear.0] | default({}), gears_related_numbers = gears_dict[gear.0][gear.1] | default([])) -%} | |
{%- set gearns.gears_related_numbers = gearns.gears_related_numbers + [number] -%} | |
{%- set gearns.gear_current_row = gearns.gear_current_row | combine({gear.1: gearns.gears_related_numbers}) -%} | |
{%- set ns.new_dict = ns.new_dict | combine({gear.0: gearns.gear_current_row}) -%} | |
{%- endfor -%} | |
{{ ns.new_dict }}" | |
additional_gears_dict: "{{ _additional_gears_dict | trim | from_yaml }}" | |
loop: "{{ range(0, rows | length) | list | product(range(0, rows[0] | length)) | list }}" | |
- name: ANSWER TO PART 1 | Sum the integers that are next to a symbol | |
ansible.builtin.debug: | |
msg: "{{ numbers | map('int') | sum }}" | |
- name: Get the gears that have two neighbouring numbers | |
ansible.builtin.set_fact: | |
gear_ratios: "{{ _gear_ratios | trim | from_yaml }}" | |
vars: | |
# Get the numbers without the coordinates (we just needed the coordinates earlier so we can know which numbers touch through a gear) | |
# Create a new empty list | |
# For every X | |
# For every Y | |
# Add the Y value (the list of numbers) to the new list | |
_flattened_numbers: " | |
{%- set ns = namespace(new_list = []) -%} | |
{% for x in gears_dict | dict2items %} | |
{% for y in x.value | dict2items %} | |
{%- set ns.new_list = ns.new_list + [y.value] -%} | |
{% endfor %} | |
{% endfor %} | |
{{ ns.new_list }}" | |
# Remove whitespace & make object | |
flattened_numbers: "{{ _flattened_numbers | trim | from_yaml }}" | |
# Get the numbers that have two neighbours | |
# Use a JMESPath query to get the numbers that have two neighbours | |
# This was hellish too i fucking hate this | |
equal_to_two: "{{ flattened_numbers | json_query('[? length(@) == `2`]') }}" | |
# Now for every pair we multiply them together..... almost there.. my sanity is dwindling | |
_gear_ratios: " | |
{%- set ns = namespace(new_list = []) -%} | |
{% for pair in equal_to_two %} | |
{%- set ns.new_list = ns.new_list + [pair.0 | int * pair.1 | int] -%} | |
{% endfor %} | |
{{ ns.new_list }}" | |
- name: ANSWER TO PART 2 | Sum the gear ratios | |
ansible.builtin.debug: | |
msg: "{{ gear_ratios | map('int') | sum }}" | |
... |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment