solve Day19 in python
This commit is contained in:
parent
3c3a481824
commit
905246bbbe
64
Day19/README.md
Normal file
64
Day19/README.md
Normal file
@ -0,0 +1,64 @@
|
||||
# Day 19: Aplenty
|
||||
## Part One
|
||||
The Elves of Gear Island are thankful for your help and send you on your way. They even have a hang glider that someone stole from Desert Island; since you're already going that direction, it would help them a lot if you would use it to get down there and return it to them.
|
||||
|
||||
As you reach the bottom of the relentless avalanche of machine parts, you discover that they're already forming a formidable heap. Don't worry, though - a group of Elves is already here organizing the parts, and they have a system.
|
||||
|
||||
To start, each part is rated in each of four categories:
|
||||
- `x`: Extremely cool looking
|
||||
- `m`: Musical (it makes a noise when you hit it)
|
||||
- `a`: Aerodynamic
|
||||
- `s`: Shiny
|
||||
|
||||
Then, each part is sent through a series of workflows that will ultimately accept or reject the part. Each workflow has a name and contains a list of rules; each rule specifies a condition and where to send the part if the condition is true. The first rule that matches the part being considered is applied immediately, and the part moves on to the destination described by the rule. (The last rule in each workflow has no condition and always applies if reached.)
|
||||
|
||||
Consider the workflow `ex{x>10:one,m<20:two,a>30:R,A}`. This workflow is named ex and contains four rules. If workflow ex were considering a specific part, it would perform the following steps in order:
|
||||
- Rule `x>10:one`: If the part's x is more than 10, send the part to the workflow named `one`.
|
||||
- Rule `m<20:two`: Otherwise, if the part's m is less than 20, send the part to the workflow named `two`.
|
||||
- Rule `a>30:R`: Otherwise, if the part's a is more than 30, the part is immediately rejected `(R)`.
|
||||
- Rule `A`: Otherwise, because no other rules matched the part, the part is immediately accepted `(A)`.
|
||||
|
||||
If a part is sent to another workflow, it immediately switches to the start of that workflow instead and never returns. If a part is accepted (sent to `A`) or rejected (sent to `R`), the part immediately stops any further processing.
|
||||
|
||||
The system works, but it's not keeping up with the torrent of weird metal shapes. The Elves ask if you can help sort a few parts and give you the list of workflows and some part ratings (your puzzle input). For example:
|
||||
```
|
||||
px{a<2006:qkq,m>2090:A,rfg}
|
||||
pv{a>1716:R,A}
|
||||
lnx{m>1548:A,A}
|
||||
rfg{s<537:gd,x>2440:R,A}
|
||||
qs{s>3448:A,lnx}
|
||||
qkq{x<1416:A,crn}
|
||||
crn{x>2662:A,R}
|
||||
in{s<1351:px,qqz}
|
||||
qqz{s>2770:qs,m<1801:hdj,R}
|
||||
gd{a>3333:R,R}
|
||||
hdj{m>838:A,pv}
|
||||
|
||||
{x=787,m=2655,a=1222,s=2876}
|
||||
{x=1679,m=44,a=2067,s=496}
|
||||
{x=2036,m=264,a=79,s=2244}
|
||||
{x=2461,m=1339,a=466,s=291}
|
||||
{x=2127,m=1623,a=2188,s=1013}
|
||||
```
|
||||
|
||||
The workflows are listed first, followed by a blank line, then the ratings of the parts the Elves would like you to sort. All parts begin in the workflow named in. In this example, the five listed parts go through the following workflows:
|
||||
- `{x=787,m=2655,a=1222,s=2876}`: in -> qqz -> qs -> lnx -> A
|
||||
- `{x=1679,m=44,a=2067,s=496}`: in -> px -> rfg -> gd -> R
|
||||
- `{x=2036,m=264,a=79,s=2244}`: in -> qqz -> hdj -> pv -> A
|
||||
- `{x=2461,m=1339,a=466,s=291}`: in -> px -> qkq -> crn -> R
|
||||
- `{x=2127,m=1623,a=2188,s=1013}`: in -> px -> rfg -> A
|
||||
|
||||
Ultimately, three parts are accepted. Adding up the x, m, a, and s rating for each of the accepted parts gives 7540 for the part with `x=787`, 4623 for the part with `x=2036`, and 6951 for the part with `x=2127`. Adding all of the ratings for all of the accepted parts gives the sum total of 19114.
|
||||
|
||||
Sort through all of the parts you've been given; what do you get if you add together all of the rating numbers for all of the parts that ultimately get accepted?
|
||||
|
||||
## Part Two
|
||||
Even with your help, the sorting process still isn't fast enough.
|
||||
|
||||
One of the Elves comes up with a new plan: rather than sort parts individually through all of these workflows, maybe you can figure out in advance which combinations of ratings will be accepted or rejected.
|
||||
|
||||
Each of the four ratings `(x, m, a, s)` can have an integer value ranging from a minimum of 1 to a maximum of 4000. Of all possible distinct combinations of ratings, your job is to figure out which ones will be accepted.
|
||||
|
||||
In the above example, there are 167409079868000 distinct combinations of ratings that will be accepted.
|
||||
|
||||
Consider only your list of workflows; the list of part ratings that the Elves wanted you to sort is no longer relevant. How many distinct combinations of ratings will be accepted by the Elves' workflows?
|
96
Day19/python/solution1.py
Normal file
96
Day19/python/solution1.py
Normal file
@ -0,0 +1,96 @@
|
||||
import sys
|
||||
|
||||
|
||||
def parse_input(file_path):
|
||||
"""Parse the input file into workflows and parts."""
|
||||
with open(file_path) as file:
|
||||
rules, parts = file.read().strip().split("\n\n")
|
||||
|
||||
workflows = {}
|
||||
for rule in rules.split("\n"):
|
||||
name, rest = rule.split("{")
|
||||
workflows[name] = rest[:-1]
|
||||
|
||||
return workflows, parts.split("\n")
|
||||
|
||||
|
||||
def parse_conditions(rule):
|
||||
"""Parse the conditions and results from a rule string."""
|
||||
conditions = []
|
||||
for cmd in rule.split(","):
|
||||
if ":" in cmd:
|
||||
condition, result = cmd.split(":")
|
||||
conditions.append((condition, result))
|
||||
else:
|
||||
conditions.append((None, cmd))
|
||||
return conditions
|
||||
|
||||
|
||||
def accepted(workflows, part):
|
||||
"""Determine if a part is accepted by the workflows."""
|
||||
state = "in"
|
||||
while True:
|
||||
conditions = parse_conditions(workflows[state])
|
||||
for condition, result in conditions:
|
||||
if not condition or evaluate_condition(part, condition):
|
||||
if result == "A":
|
||||
return True
|
||||
elif result == "R":
|
||||
return False
|
||||
state = result
|
||||
break
|
||||
|
||||
|
||||
def evaluate_condition(part, condition):
|
||||
"""Evaluate a condition against a part's attributes."""
|
||||
var, op, n = condition[0], condition[1], int(condition[2:])
|
||||
if op == ">":
|
||||
return part[var] > n
|
||||
else:
|
||||
return part[var] < n
|
||||
|
||||
|
||||
def calculate_sum_of_accepted_parts(workflows, parts):
|
||||
"""Calculate the sum of all accepted parts."""
|
||||
total_sum = 0
|
||||
for part_str in parts:
|
||||
part = parse_part(part_str)
|
||||
if accepted(workflows, part):
|
||||
total_sum += sum(part.values())
|
||||
return total_sum
|
||||
|
||||
|
||||
def parse_part(part_str):
|
||||
"""Parse a part string into a dictionary of attributes."""
|
||||
part_str = part_str[1:-1]
|
||||
return {x.split("=")[0]: int(x.split("=")[1]) for x in part_str.split(",")}
|
||||
|
||||
|
||||
def test_algorithm():
|
||||
"""Test the algorithm with the test input."""
|
||||
workflows, parts = parse_input("../test.txt")
|
||||
expected_result = 19114 # Expected result from the test input
|
||||
result = calculate_sum_of_accepted_parts(workflows, parts)
|
||||
assert (
|
||||
result == expected_result
|
||||
), f"Test failed: Expected {expected_result}, got {result}"
|
||||
print("Test passed successfully.")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to run the algorithm."""
|
||||
try:
|
||||
test_algorithm() # Run the test first
|
||||
workflows, parts = parse_input("../input.txt")
|
||||
result = calculate_sum_of_accepted_parts(workflows, parts)
|
||||
print(f"Result for the puzzle input: {result}")
|
||||
except AssertionError as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Unexpected error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
91
Day19/python/solution2.py
Normal file
91
Day19/python/solution2.py
Normal file
@ -0,0 +1,91 @@
|
||||
import math
|
||||
import sys
|
||||
from itertools import groupby
|
||||
|
||||
|
||||
def parse_input(file_path):
|
||||
"""Parse input file to extract workflows."""
|
||||
with open(file_path) as f:
|
||||
lines = f.read().splitlines()
|
||||
|
||||
workflows_raw, _ = [
|
||||
tuple(group) for not_empty, group in groupby(lines, bool) if not_empty
|
||||
]
|
||||
|
||||
workflows = {}
|
||||
for workflow in workflows_raw:
|
||||
name, rules_str = workflow.split("{")
|
||||
rules = rules_str[:-1].split(",")
|
||||
workflows[name] = ([], rules.pop())
|
||||
for rule in rules:
|
||||
condition, target = rule.split(":")
|
||||
key, comparison, *value = tuple(condition)
|
||||
workflows[name][0].append((key, comparison, int("".join(value)), target))
|
||||
|
||||
return workflows
|
||||
|
||||
|
||||
def count_ranges(workflows, ranges, name="in"):
|
||||
"""Count the number of valid ranges recursively."""
|
||||
if name == "R":
|
||||
return 0
|
||||
if name == "A":
|
||||
return math.prod(stop - start + 1 for start, stop in ranges.values())
|
||||
|
||||
rules, fallback = workflows[name]
|
||||
total = 0
|
||||
for key, comparison, value, target in rules:
|
||||
start, stop = ranges[key]
|
||||
|
||||
if comparison == "<":
|
||||
t_start, t_stop = start, value - 1
|
||||
f_start, f_stop = value, stop
|
||||
else:
|
||||
t_start, t_stop = value + 1, stop
|
||||
f_start, f_stop = start, value
|
||||
|
||||
if t_start <= t_stop:
|
||||
total += count_ranges(workflows, {**ranges, key: (t_start, t_stop)}, target)
|
||||
if f_start <= f_stop:
|
||||
ranges[key] = (f_start, f_stop)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
total += count_ranges(workflows, ranges, fallback)
|
||||
|
||||
return total
|
||||
|
||||
|
||||
def test_algorithm():
|
||||
"""Run the algorithm on a test file and compare the result."""
|
||||
test_file = "../test.txt"
|
||||
expected_result = 167409079868000
|
||||
workflows = parse_input(test_file)
|
||||
result = count_ranges(workflows, {category: (1, 4000) for category in "xmas"})
|
||||
assert (
|
||||
result == expected_result
|
||||
), f"Test failed: Expected {expected_result}, got {result}"
|
||||
print("Test passed successfully.")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to execute the algorithm."""
|
||||
try: # Expected result for the test input
|
||||
print("Running test...")
|
||||
test_algorithm()
|
||||
|
||||
print("Test passed. Running on actual input...")
|
||||
input_file = "../input.txt"
|
||||
workflows = parse_input(input_file)
|
||||
result = count_ranges(workflows, {category: (1, 4000) for category in "xmas"})
|
||||
print(f"Result for the puzzle input: {result}")
|
||||
except AssertionError as e:
|
||||
print(f"Assertion Error: {e}")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Unexpected error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
17
Day19/test.txt
Normal file
17
Day19/test.txt
Normal file
@ -0,0 +1,17 @@
|
||||
px{a<2006:qkq,m>2090:A,rfg}
|
||||
pv{a>1716:R,A}
|
||||
lnx{m>1548:A,A}
|
||||
rfg{s<537:gd,x>2440:R,A}
|
||||
qs{s>3448:A,lnx}
|
||||
qkq{x<1416:A,crn}
|
||||
crn{x>2662:A,R}
|
||||
in{s<1351:px,qqz}
|
||||
qqz{s>2770:qs,m<1801:hdj,R}
|
||||
gd{a>3333:R,R}
|
||||
hdj{m>838:A,pv}
|
||||
|
||||
{x=787,m=2655,a=1222,s=2876}
|
||||
{x=1679,m=44,a=2067,s=496}
|
||||
{x=2036,m=264,a=79,s=2244}
|
||||
{x=2461,m=1339,a=466,s=291}
|
||||
{x=2127,m=1623,a=2188,s=1013}
|
@ -3,8 +3,8 @@
|
||||
<div><img src="title_image.png" style="margin: 0 auto;" height="300" width="300" ></div>
|
||||
|
||||
![Build Status](https://github.com/wieerwill/advent_of_code_2023/actions/workflows/lint.yml/badge.svg)
|
||||
![Completed](https://img.shields.io/badge/days%20completed-17-red)
|
||||
![Stars](https://img.shields.io/badge/stars%20⭐-34-yellow)
|
||||
![Completed](https://img.shields.io/badge/days%20completed-18-red)
|
||||
![Stars](https://img.shields.io/badge/stars%20⭐-37-yellow)
|
||||
![License](https://img.shields.io/github/license/wieerwill/advent_of_code_2023.svg)
|
||||
|
||||
Welcome to my repository where I share my solutions for the [Advent of Code 2023](https://adventofcode.com/2023). Advent of Code is an annual online event where participants solve a series of programming puzzles released daily from December 1st until December 25th. Each puzzle is a fun and unique challenge designed to test problem-solving skills.
|
||||
@ -50,9 +50,9 @@ I aim to complete each day's puzzle on the same day, but as with any challenge,
|
||||
| 14 | ✅ | ❓ | [Day14 README](/Day14/README.md) |
|
||||
| 15 | ✅ | ✅ | [Day15 README](/Day15/README.md) |
|
||||
| 16 | ✅ | ✅ | [Day16 README](/Day16/README.md) |
|
||||
| 17 | ✅ | ❓ | [Day17 README](/Day17/README.md) |
|
||||
| 17 | ✅ | ✅ | [Day17 README](/Day17/README.md) |
|
||||
| 18 | ✅ | ✅ | [Day18 README](/Day18/README.md) |
|
||||
| 19 | ❓ | ❓ | [Day19 README](/Day19/README.md) |
|
||||
| 19 | ✅ | ✅ | [Day19 README](/Day19/README.md) |
|
||||
| 20 | ❓ | ❓ | [Day20 README](/Day20/README.md) |
|
||||
| 21 | ❓ | ❓ | [Day21 README](/Day21/README.md) |
|
||||
| 22 | ❓ | ❓ | [Day22 README](/Day22/README.md) |
|
||||
|
Loading…
Reference in New Issue
Block a user