2022-19
2022-19 attempt 1 Signed-off-by: AKP <tom@tdpain.net> 2022-19 attempt 2 Signed-off-by: AKP <tom@tdpain.net> I think this is semi-functional Signed-off-by: AKP <tom@tdpain.net> 2022-19 Signed-off-by: AKP <tom@tdpain.net>
This commit is contained in:
parent
7846d9675f
commit
8572888192
8 changed files with 235 additions and 3 deletions
3
challenges/2022/19-notEnoughMinerals/README.md
Normal file
3
challenges/2022/19-notEnoughMinerals/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# [Day 19: Not Enough Minerals](https://adventofcode.com/2022/day/19)
|
||||
|
||||
My part 2 took 48 minutes to run. ***48!!*** Even though part 1 only took 17! aaaaaaaaaaa
|
15
challenges/2022/19-notEnoughMinerals/benchmark.json
Normal file
15
challenges/2022/19-notEnoughMinerals/benchmark.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"day": 19,
|
||||
"dir": "challenges/2022/19-notEnoughMinerals",
|
||||
"implementations": {
|
||||
"Python": {
|
||||
"part.1.avg": 17.7188,
|
||||
"part.1.max": 17.7188,
|
||||
"part.1.min": 17.7188,
|
||||
"part.2.avg": 2903.2192,
|
||||
"part.2.max": 2903.2192,
|
||||
"part.2.min": 2903.2192
|
||||
}
|
||||
},
|
||||
"numRuns": 1
|
||||
}
|
17
challenges/2022/19-notEnoughMinerals/info.json
Normal file
17
challenges/2022/19-notEnoughMinerals/info.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"inputFile": "input.txt",
|
||||
"testCases": {
|
||||
"one": [
|
||||
{
|
||||
"input": "Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.\nBlueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian.\n",
|
||||
"expected": "33"
|
||||
}
|
||||
],
|
||||
"two": [
|
||||
{
|
||||
"input": "Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.\nBlueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian.\n",
|
||||
"expected": "3472"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
143
challenges/2022/19-notEnoughMinerals/py/__init__.py
Normal file
143
challenges/2022/19-notEnoughMinerals/py/__init__.py
Normal file
|
@ -0,0 +1,143 @@
|
|||
from typing import *
|
||||
from aocpy import BaseChallenge, foldl
|
||||
from dataclasses import dataclass
|
||||
import re
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Material(Enum):
|
||||
ORE = 0
|
||||
CLAY = 1
|
||||
OBSIDIAN = 2
|
||||
GEODE = 3
|
||||
|
||||
|
||||
MaterialTracker = Tuple[int, int, int, int]
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class Blueprint:
|
||||
number: int
|
||||
|
||||
robots: Dict[Material, MaterialTracker]
|
||||
|
||||
def __init__(self, number: int, ore_robot_cost: int, clay_robot_cost: int, obsidian_robot_cost_ore: int, obsidian_robot_cost_clay: int, geode_robot_cost_ore: int, geode_robot_cost_obsidian: int):
|
||||
self.number = number
|
||||
|
||||
self.robots = {
|
||||
Material.ORE: (ore_robot_cost, 0, 0, 0),
|
||||
Material.CLAY: (clay_robot_cost, 0, 0, 0),
|
||||
Material.OBSIDIAN: (obsidian_robot_cost_ore, obsidian_robot_cost_clay, 0, 0),
|
||||
Material.GEODE: (geode_robot_cost_ore, 0, geode_robot_cost_obsidian, 0),
|
||||
}
|
||||
|
||||
def iter_robots(self) -> Generator[Tuple[Material, MaterialTracker], None, None]:
|
||||
for key in self.robots:
|
||||
yield (key, self.robots[key])
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.number)
|
||||
|
||||
|
||||
parse_re = re.compile(r"Blueprint (\d+): Each ore robot costs (\d+) ore\. Each clay robot costs (\d+) ore\. Each obsidian robot costs (\d+) ore and (\d+) clay\. Each geode robot costs (\d+) ore and (\d+) obsidian\.")
|
||||
|
||||
|
||||
def parse(instr: str) -> List[Blueprint]:
|
||||
res: List[Blueprint] = []
|
||||
for line in instr.strip().splitlines():
|
||||
res.append(
|
||||
Blueprint(*map(int, parse_re.match(line).groups())),
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
def calc_max_geodes(blueprint: Blueprint, max_time: int, materials: MaterialTracker, robots: MaterialTracker, robot_quota: MaterialTracker, minute: int, cannot_build: int) -> int:
|
||||
if minute == max_time + 1:
|
||||
return materials[Material.GEODE.value]
|
||||
|
||||
try_build: int = 0
|
||||
for (robot_type, robot_materials) in blueprint.iter_robots():
|
||||
rtv = robot_type.value
|
||||
if cannot_build & (1 << rtv) != 0:
|
||||
continue
|
||||
|
||||
if robot_type != Material.GEODE and robots[rtv] == robot_quota[rtv]:
|
||||
continue
|
||||
|
||||
has_enough_materials = True
|
||||
for (required, available) in zip(robot_materials, materials):
|
||||
if required > available:
|
||||
has_enough_materials = False
|
||||
break
|
||||
|
||||
if has_enough_materials:
|
||||
try_build = try_build | (1 << robot_type.value)
|
||||
|
||||
materials = (
|
||||
materials[0] + robots[0],
|
||||
materials[1] + robots[1],
|
||||
materials[2] + robots[2],
|
||||
materials[3] + robots[3],
|
||||
)
|
||||
|
||||
max_score = 0
|
||||
for i in range(5):
|
||||
if i == 4 and cannot_build | try_build != 0b1111:
|
||||
# always try not building anything
|
||||
sc = calc_max_geodes(blueprint, max_time, materials, robots, robot_quota, minute + 1, cannot_build | try_build)
|
||||
else:
|
||||
if try_build & (1 << i) == 0:
|
||||
continue
|
||||
|
||||
robot_type = Material(i)
|
||||
robot_materials = blueprint.robots[robot_type]
|
||||
|
||||
# subtract materials required to build this robot
|
||||
mc = (
|
||||
materials[0] - robot_materials[0],
|
||||
materials[1] - robot_materials[1],
|
||||
materials[2] - robot_materials[2],
|
||||
materials[3],
|
||||
)
|
||||
|
||||
# update robot counts
|
||||
rc = tuple((robots[j] + (1 if i == j else 0) for j in range(4)))
|
||||
|
||||
# recurse
|
||||
sc = calc_max_geodes(blueprint, max_time, mc, rc, robot_quota, minute + 1, 0)
|
||||
|
||||
if sc is not None and sc > max_score:
|
||||
max_score = sc
|
||||
|
||||
return max_score
|
||||
|
||||
|
||||
class Challenge(BaseChallenge):
|
||||
|
||||
@staticmethod
|
||||
def one(instr: str) -> int:
|
||||
inp = parse(instr)
|
||||
|
||||
quals: List[int] = []
|
||||
for bp in inp:
|
||||
robot_quota: Tuple[int] = tuple(max(x) for x in zip(*bp.robots.values()))
|
||||
quals.append(
|
||||
calc_max_geodes(bp, 24, (0,0,0,0), (1,0,0,0), robot_quota, 1, 0) * bp.number,
|
||||
)
|
||||
|
||||
return sum(quals)
|
||||
|
||||
@staticmethod
|
||||
def two(instr: str) -> int:
|
||||
inp = parse(instr)
|
||||
|
||||
nums: List[int] = []
|
||||
for bp in inp[:min(3, len(inp))]:
|
||||
print(f"{bp.number=}", flush=True)
|
||||
robot_quota: Tuple[int] = tuple(max(x) for x in zip(*bp.robots.values()))
|
||||
nums.append(
|
||||
calc_max_geodes(bp, 32, (0,0,0,0), (1,0,0,0), robot_quota, 1, 0)
|
||||
)
|
||||
print(nums[-1], flush=True)
|
||||
|
||||
return foldl(lambda x, y: x * y, nums, 1)
|
50
challenges/2022/19-notEnoughMinerals/runtime-wrapper.py
Normal file
50
challenges/2022/19-notEnoughMinerals/runtime-wrapper.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
from py import Challenge
|
||||
|
||||
import time
|
||||
import json
|
||||
|
||||
# TASKS_STR = input()
|
||||
# TASKS = json.loads(TASKS_STR)
|
||||
|
||||
def send_result(task_id, ok, output, duration):
|
||||
print(json.dumps({
|
||||
"task_id": task_id,
|
||||
"ok": ok,
|
||||
"output": str(output) if output is not None else "",
|
||||
"duration": float(duration),
|
||||
}), flush=True)
|
||||
|
||||
while True:
|
||||
task = json.loads(input())
|
||||
taskPart = task["part"]
|
||||
task_id = task["task_id"]
|
||||
|
||||
run = None
|
||||
|
||||
if taskPart == 1:
|
||||
run = lambda: Challenge.one(task["input"])
|
||||
elif taskPart == 2:
|
||||
run = lambda: Challenge.two(task["input"])
|
||||
elif taskPart == 3:
|
||||
run = lambda: Challenge.vis(task["input"], task["output_dir"])
|
||||
else:
|
||||
send_result(task_id, False, "unknown task part", 0)
|
||||
continue
|
||||
|
||||
start_time = time.time()
|
||||
res = None
|
||||
err = None
|
||||
try:
|
||||
res = run()
|
||||
except Exception as e:
|
||||
err = f"{type(e)}: {e}"
|
||||
import traceback
|
||||
err = f"{type(e)}: {e}\n{''.join(traceback.format_tb(e.__traceback__))}"
|
||||
end_time = time.time()
|
||||
|
||||
running_time = end_time-start_time
|
||||
|
||||
if err is not None:
|
||||
send_result(task_id, False, err, running_time)
|
||||
else:
|
||||
send_result(task_id, True, res, running_time)
|
|
@ -33,4 +33,5 @@ The red dotted line denotes 15 seconds.
|
|||
| 15 - Beacon Exclusion Zone | ★ ★ | [Python](15-beaconExclusionZone/py/__init__.py) | Searching through a 4000000^2 size grid for a literal single empty spot |
|
||||
| 16 - Proboscidea Volcanium | ★ ★ | [Python](16-proboscideaVolcanium/py/__init__.py) | Nasty combinatorics |
|
||||
| 17 - Pyroclastic Flow | ★ ★ | [Python](17-pyroclasticFlow/py/__init__.py) | Detecting cycles in a large amount of knock-off Tetris. |
|
||||
| 18 - Boiling Boulders | ★ ★ | [Python](18-boilingBoulders/py/__init__.py) | Finding the surface area of a shape specified by a list of unit cubes. |
|
||||
| 18 - Boiling Boulders | ★ ★ | [Python](18-boilingBoulders/py/__init__.py) | Finding the surface area of a shape specified by a list of unit cubes. |
|
||||
| 19 - Not Enough Minerals | ★ ★ | [Python](19-notEnoughMinerals/py/__init__.py) | Finding the most effective sequence of operations to complete a specific task. |
|
Binary file not shown.
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 61 KiB |
|
@ -26,6 +26,7 @@ var args struct {
|
|||
Benchmark bool `arg:"-b,--benchmark" help:"benchmark a day's implementations'"`
|
||||
BenchmarkN int `arg:"-n,--benchmark-n" help:"Number of iterations to run for benchmarking" default:"1000"`
|
||||
TestOnly bool `arg:"-t,--test-only" help:"Only run test inputs"`
|
||||
NoTest bool `arg:"-x,--no-test" help:"Do not run test inputs"`
|
||||
Visualise bool `arg:"-g,--visualise" help:"Run visualisation generation"`
|
||||
}
|
||||
|
||||
|
@ -123,8 +124,10 @@ func run() error {
|
|||
|
||||
fmt.Print("Running...\n\n")
|
||||
|
||||
if err := runTests(runner, challengeInfo); err != nil {
|
||||
return err
|
||||
if !args.NoTest {
|
||||
if err := runTests(runner, challengeInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !args.TestOnly {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue