#AoC 2023 | Day 5 | Solutions & Spoilers
1285 messages · Page 2 of 2 (latest)
I am now print-debugging my solution becaue my brain can't let it go
unit test debug it instead
they were given in the example
First you think: I can just brute force this.
Then you think: wait no, no I need to work based on ranges and offsets and that gets you pt 1
Then in Pt 2 you think: I can just brute force this.
Then you think: wait no I need to work on ranges with ranges.
And then you get it!
If a brute force is a thing, it will always work for P1
79 14 55 13 = range(79,79+14) range(55, 55+13)
that I have full confidence in
May day 6 brute force worked for pt 2, only took about 2 mins to compute
you don't have to use ranges
waiting
thank
there's actually an alternative solution someone showed me where you kind of work backwards
did you not include the first pair or am i blind
and find the lowest valid possible output
well yeah, but it will always work for p1 regardless of day, is what I meant
Im not massive fan of the mathys optimisation stuff. Im still resentful of that bus one about 2 years ago
you aren't using enough force if bruteforce doesn't work
(I think its becasuse im bad at maths)
I posted the 2 seeds separately.
#1181459209422917683 message
#1181459209422917683 message
which day was that?
Oh i see mb
I cant remember but Pt2 relied on an obscure math formula
I believe so
I wait with anticipation
was cleaning it up a little and pycharm gave me this warning, lol
day6 ? didn't brute force work right away ?
We might be waiting a while
It took 5m43s just testing the first range
Ok i understand the first line the only thing i dont get is how you get the second range atm
rewrite it in a compiled lang
This year? ||Yeah pretty much. You can just compute every option and count them up||
i just know that last years file system is something i have no idea how to do. 2021 it is then
that was the code block. #1181459209422917683 message
so you start with a list of seed ranges. for each one you apply a rule.splitting them up any that are matched get converted and saved until after this group is done. Any that are not matched are new ranges to be checked by the next rule in this group.
after all the rules are checked, you have a collection of remaining new ranges, plus all the ones you saved this group.
Anyone able to review Part 2 of this for me. For some reason, I am unable to see why what I have for Part 2 is failing and producing the incorrect value. Much appreciated. https://github.com/MilaDog/Advent-Of-Code/blob/main/2023/05/05.py
how quick will this run
I will restrict my debugging offers only to solutions that run in less than 10 secs
Both parts (with part 2 not giving correct output) is 0.0013396998401731253
def less than 10 sec
ok will maybe look
im trying to understand but i dont think my brain is working
i dont get the conversion process at all
on a quick glance:
# Updating seed_range
start: int = dest + (curr_seed.start - section_range.start)
end: int = dest + (curr_seed.end - section_range.end)
curr_seed = SingleRange(start, end)
break
in this case when the match is inside your seed range, do you end up with 3 new ranges? The matched range which is saved for the next group, and the before the match and the after the match ranges, which are now looked at for the next rule in this group.
According to
We will be waiting for
you take the first seed range. you check your first group of ranges if any part of your range is inside them. remove the range from seed group. if it is then put the part inside in a new list and chuck eventual other parts of the range back onto the seed range pile.
say you have a range(10,14) and a rule(d=20,o=8,a=20) this amounts to a rule of range(8,28) right? which we can see covers our seed range.
the conversion turns range(10,14) into range(22,26) because d-o is a 12 difference.
Oh that cleared up alot
I cant thank you enough for the help man but i really really have to go now or il be a zombie tommorow
good night
you to
Im gonna try to finish this tommorow(no sunlight for me since i will do day 7 aswell) i will let you know how it goes
yeah should sleep. aoc is adicting. i got things to so that aren't code XD
TRAITOR
chem lab protocol
I have my history book next to me lol
"Your input is to high""
!e print("Traitor"> "traitor")
@hallow wren :white_check_mark: Your 3.12 eval job has completed with return code 0.
False
If I understood this correct, when going through a seed range, I check that range. If the range is 1) to the left, I cut it off and add it back to be processed in the next section, 2) same process. Then in the end, and that is the part you shared, the curr_seed range will be inside of the section range, which is then considered done.
Maybe I am understanding the problem incorrectly
you do have to keep the ones outside as well. just separate
ok, good luck!
YOU GO KNOCK ON SOME WOOD RIGHT NOW!!111!1!
“Knock, knock.”
“Who’s there?”
very long pause….
“Doggo's solution.”
Works better IRL but you get the idea
oh wrong doggo lol not you XD
ouch
So there are going be times where your rule falls in the middle of a seed range.
say seed range(1,10) and rule(4,7) you'll end up converting the seeds in range(4,7) and still have range(1,4) and range(7,10) to check for the next rule.
ahh
with open("05.txt", "r") as f:
sections = f.read()[:-1].split("\n\n")
# Part 1
class RangeMapping:
def __init__(self):
self.func = {}
def __getitem__(self, n: int) -> int:
for k, v in self.func.items():
if n in k:
return v[n - k[-1] - 1]
return n
def get(self, n: range) -> list[range]:
res = []
for k, v in self.func.items():
if n[-1] < k[0] or n[0] > k[-1]:
continue
elif k[0] <= n[0] <= k[-1] and n[-1] > k[-1]:
res.append(range(v[n[0] - k[-1] - 1], v[-1] + 1))
n = range(k[-1] + 1, n[-1] + 1)
elif k[0] <= n[-1] <= k[-1] and n[0] < k[0]:
res.append(range(v[0], v[n[-1] - k[-1] - 1] + 1))
n = range(n[0], k[0])
elif k[0] <= n[0] <= k[-1] and k[0] <= n[-1] <= k[-1]:
res.append(range(v[n[0] - k[0]], v[n[-1] - k[-1] - 1] + 1))
break
elif n[0] < k[0] < k[1] < n[-1]:
res.append(range(v[0], v[-1] + 1))
res.extend(self.get(range(n[0], k[0])))
res.extend(self.get(range(k[-1] + 1, n[-1])))
break
else:
if not any(r[0] <= n[0] and r[-1] >= n[-1] for r in res):
res.append(n)
return res
maps = [RangeMapping() for _ in range(len(sections) - 1)]
seeds = list(map(int, sections[0].split(": ")[1].split()))
for i, section in enumerate(sections[1:]):
for line in section.splitlines()[1:]:
end, start, step = map(int, line.split())
maps[i].func.update({range(start, start + step): range(end, end + step)})
min_val = float("inf")
for seed in seeds:
orig_seed = seed
for m in maps:
seed = m[seed]
if seed < min_val:
min_val = seed
print(min_val)
# Part 2
all_seeds = [
[range(seeds[i], seeds[i] + seeds[i + 1])] for i in range(0, len(seeds), 2)
]
all_intervals = []
for interval in all_seeds:
for m in maps:
new_interval = []
for i, part in enumerate(interval):
new_interval.extend(m.get(part))
interval = new_interval
all_intervals.extend(interval)
print(min(all_intervals, key=lambda x: x[0])[0])
I do love a good old for-else
Have to leave work now, but ~2h, starting at ~5, puts me at about ~7pm, which with a ~30 minute drive home, puts me right on schedule
Also this car really gets great mileage
_about to go open my CodeSpace on my phone to make sure that it doesn’t time out _
I have like literally one line left on my Rust rewrite
It already got the right answer for part 1
So I’ll have something that’s hopefully much faster if I do get the wrong answer
This is essentially what I ended up doing, took about 10 minutes to run
day 5, the first day where i look at my solution at grimace slightly 😩
https://github.com/Objectivitix/Advent-of-Code/blob/main/2023/day_05/part_2.py
nice variable, salt
speedy!
Hey
I'm still waiting
Count your wins
I'm 99% sure my Rust version is ready, but I'm scared of running it and running out of RAM
you know i did that so that ruff wouldn't three-line the mapped_gaps |= line
lmao well played
thought about adding __add__ to Gaps that will shift the intervals, but so far Gaps only requires the types to support __lt__
Is that good or bad
IDK yet
I just want to have some answer before....
IT FINISHED
It finished?!
@shenanigansd ➜ .../advent_of_code/2023/05/python (main) $ time python aoc_2023_05.py
196167384
125742456
real 184m37.737s
user 175m54.507s
sys 0m13.278s
But is it correct
you could've written an interval set library in that time
It works and that’s all that matters
Now to see how long Rust BTW takes
i'm legally blind, so i'm going to assume that sign says, "yes, i agree salt"
Rust is done
@shenanigansd ➜ /workspaces/scratchpad (main) $ time cargo run --bin aoc-2023-05
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/aoc-2023-05`
196167384
125742456
real 68m10.324s
user 62m42.414s
sys 0m4.424s
speedy!
It was pointed out to me that I forgot --release
Sub 10 minutes now
@shenanigansd ➜ /workspaces/scratchpad (main) $ time cargo run --release --bin aoc-2023-05
Compiling aoc-2023-05 v0.1.0 (/workspaces/scratchpad/events/advent_of_code/2023/05/rust)
Finished release [optimized] target(s) in 1.00s
Running `target/release/aoc-2023-05`
196167384
125742456
real 7m8.438s
user 7m1.666s
sys 0m0.543s
Now that's quite a difference!
Final runtime was: 205 minutes for a full-brute force of part 2.
Eish 🙈😆
your patience is commendable
Could someone help me understand day 5 part 2? I've seen the youtube video but still don't really understand it
Anything in particular?
I can understand how you can see overlaps mostly, but what gets me is what happens if there are overlaps - the un-overlapped stuff gets mapped to itself
file = open("Day5text", "r")
text = [i.strip("\n") for i in file.readlines()]
seeds = text[0][7:]
seed2soil = text[3:8]
soil2fert = text[11:53]
fert2water = text[56:94]
water2light = text[97:143]
light2temp = text[146:172]
temp2humid = text[175:182]
humid2loc = text[185:200]
loclist = []
print(seeds)
def dataConversion(input, listofrows):
input = int(input)
desranges = []
souranges = []
diff = -1
for row in listofrows:
row = row.strip().split()
desstart = int(row[0])
soustart = int(row[1])
rangl = int(row[2])
desranges.append((desstart, desstart + rangl - 1))
souranges.append((soustart, soustart + rangl - 1))
for i in range(len(souranges)):
if input >= souranges[i][0] and input <= souranges[i][1]:
diff = souranges[i][1] - input
if diff != -1:
return desranges[i][1] - diff
return input
for seed in seeds.split():
soil = dataConversion(seed, seed2soil)
print(soil)
fert = dataConversion(soil, soil2fert)
water = dataConversion(fert, fert2water)
light = dataConversion(water, water2light)
temp = dataConversion(light, light2temp)
humid = dataConversion(temp, temp2humid)
loc = dataConversion(humid, humid2loc)
loclist.append(loc)
print(min(loclist))
why doesnt this work?
oh yeah, i was gonna ask, is mind_the_gaps your own utils library for anything to do with ranges/intervals?
it's a interval set library, like sets of mathematical intervals -- but it works for any type that supports __lt__
oh it's third party
yeah okay xD
i thought it was from someone else
when you said "it's a interval set library"
but that's pretty cool
>>> from mind_the_gaps import *
>>> a = Gaps([0 <= x, x < 1, 5 <= x, x < 6])
>>> b = Gaps([.5 < x, x <=7])
>>> print(a, b, a | b, a - b)
{[0, 1), [5, 6)} {(0.5, 7]} {[0, 7]} {[0, 0.5]}
does this basically, but the numbers can be, like, datetimes are whatever
looks slick
i was about to say why not 0 <= x < 1 before i realized x is your dark magic xD
yeah, previous version of the library did have 0 <= x < 1, but it required some hacks with operator chaining, and it was easy to quietly user error
a closed interval can be made easily though
>>> print(Gaps([1, 2, 3, 4, 5, 6]))
{[1, 2], [3, 4], [5, 6]}
awesome
i'm guessing you didn't even have to chunk the seeds input
because your constructor does it automatically?
as in, it already recognizes groups of 2
yeah, it takes a list of endpoints instead of a list of intervals
can also construct from string:
>>> print(Gaps.from_string("{(-inf, 0], [1, inf)}"))
{(-inf, 0], [1, inf)}
but that only works for ints and floats
pretty cool
the code for var.py is so clean
i'll check out the rest too sometime
the line sweep algorithm is cool, i use a version of it in rects too
oh i've heard that term thrown around here and there
never dug into it though
some geometry algorithm?
yeah, it's a geometric algorithm
I got Day 5 Part 1! Hoo-ray!
now for me to try to understand the part 2
looks like I"ll just brute force the second part 😦
It can take a while to run - do consider doing it “properly”
i'm thinking of doing intersection approach
Right - that’s how I did it too
It can be a bit fiddle-y to code up though
I needed tests as a leading light to get me to the right answer
no kidding. i was thinking of every possible scenarios, and it seems like it involves multiple intersection, so... that's quite something of a puzzle
you can brute force today but depending on how you implement it you could wait a while
ok, i think i got a idea now. but, i think i'll try that tomorrow
i've spent the past hour trying to re-wrap my head around how to do this "properly", but i feel like i may just be making an even further-complicated solution than necessary 😩
even the simple goal of "flatten two consecutive maps into a list of parallel maps" seems harder than it should be
i may give that reverse-map approach a go at some point instead, which is still rather bruteforcey but allegedly a bit less so
I’m not sure I understand what you mean by this
I resorted to a while loop to check for intersection and to use 2 dynamic arrays. 1 when all intersections has been checked for, and the other for checking intersections
I drawn schematics to do this too
so my thought was that, since each map is essentially remapping the output values from the previous map the the input values for the next map, they could be "flattened" down into just a seed-to-location map
though that is now making me realise that doesn't solve the real issue of having to check every value in the given ranges of seeds...
Seems like rubber duck debugging strikes again :) And yes, you are right, I don’t think that will help you. Would you like some pointers?
i think the actual optimisation idea clicked for me (allowing the map rules to be applied to entire ranges of numbers, rather than just individual ones) :P
For one person on here their solution was hours
closer now, but now I'm in a infinite loop issue with the optimized solution for part 2 😦
:(
Debugging/unit testing time
well, I think I fixed it!
i'm not sure if this is correct, but it appears there are 128 intersections+non-intersections
ok, it's not correct. something, somewhere, something went wrong.
looks like i'm going to solve day 5 part 2 soon. what went wrong with me reading my drawings of intersection wrong
Fixed it and now it works!
And this is my solution (Not a generic language / G'MIC): https://pastebin.com/uP9aB6d4
@royal ocean here's the solution: that's going to be really hard to explain, but in my code that I give. I pop the last range on the available list of ranges and treat it as the auxiliary ranges. Then it involves a loop to check whether the auxiliary range overlap with the current map range. The rest involves checking how they intersect, and split the ranges into 2 or 3 sections, or even move on to the other map without split, and when there is no more map to check for that range, I move it into the list of range that has been checked. I posted my code though it'll be a while to understand, but like @elder oar it involves interval arithmetic.
Okay cool
Ah okay ur also checking ranges right?
I was thinking of a DSU sol at first for part 2 but ranges came out to be better sol 🤔
yeah. you should draw lines. call the top line auxiliary range. call the bottom line the source range. Have small lines as ending of line. And then you can see how to split them or even if splitting them is necessary
My range split logic was somewhat wrong ig
cuz my optimal sol never worked for that one
did u also took 4 cases?
Here's mine lol
??? isn't there a bot for this
def intersect_ranges(ranges: builtins.list[builtins.range], map: Table):
out_ranges: builtins.list[builtins.range] = []
for range in ranges:
splits = [range]
for src, dest, len in map.ranges:
next_splits = []
for split in splits:
if split.start < src and split.stop < src:
next_splits.append(split)
elif split.start < src and split.stop < src + len:
next_splits.append(builtins.range(split.start, src))
out_ranges.append(builtins.range(dest, split.stop - src + dest))
elif split.start < src and split.stop >= src + len:
next_splits.append(builtins.range(split.start, src))
out_ranges.append(builtins.range(dest, dest + len))
next_splits.append(builtins.range(src + len, split.stop))
elif split.start < src + len and split.stop < src + len:
out_ranges.append(
builtins.range(
split.start - src + dest, split.stop - src + dest
)
)
elif split.start < src + len and split.stop >= src + len:
out_ranges.append(
builtins.range(split.start - src + dest, dest + len)
)
next_splits.append(builtins.range(src + len, split.stop))
else:
next_splits.append(split)
splits = next_splits
out_ranges.extend(splits)
return simplify_ranges(out_ranges)
(complicated)
here's my new one, with the interval arithmetic I added to my aoc library
def intersect_ranges(ranges: multirange, map: Table):
out = multirange()
for range, offset in map.ranges:
intersection = range & ranges
if intersection:
out |= intersection + offset
ranges -= intersection
return out
Made library function wow 🛐
I mean I was already overloading range (to add the fluent methods from my iter overload to it), might as well also add interval arithmetic
¯_(ツ)_/¯
can you tell the 7 cases plss
i'm drawing them cleanly as possible. i do have a cat on my desk at the moment
|-------------------------|
|--------------------|
|-----------------------|
|--------------------|
|-----------------------|
|-----------------------------|
|---------------------------------|
|-----------------------|
What other cases am I missing?
|-----| |-----|
if any of these cases dont match
(it's still a case)
I push the same range to next map
yupp
almost there
so total 5 right?
just gotta find my phone to take a picture
My solution has
1
|---|
|---|
2
|-----|
|-----|
3
|---------|
|-|
4
|-|
|---------|
5
|-----|
|-----|
6
|---|
|---|
Not sure what the 7th is
Aren't cases 3 and 4 equivalent?
You split them in different way, so no, they're not the same
I mean, I didn't
I will rewrite day 5 part 2 today after today's advent
Oh wait I think it's just that you wrote case 4 upside-down lol
GLHF guys for today's advent : )
GLHF
@royal ocean I guess this helps?
Yes I am surely missing 1 case
so Imma implement the sol again today
once I get done with that can I ping you?
sure
Thanks : )
!pypi aoc
Lol
Did you ever solved it?
!pip aoc-lube
.bookmark
.bookmark
Im kinda stuck on solving part 2, if i have found my intersection in my source and destination row what do i do next?
What I did was a while loop to continue modifying ranges until there is no more to check. They get sent into fully processed array after all levels have been processed. You can make a class for this. This is the iterative way.
what ranges do you keep modifying, the remainder of the intersections being removed or the intersection itself?
I was looking at this and my code has identical results until there start to appear mutliple ranges, i dont understand how those mutliple ranges form
You split them. And modify all of the resulting ranges.
So you split them into 3 segments:
Left of internation
Intersection
Right of intersection right
Atleast in that example
And then just add the shift?
Or what does modifying mean exactly in this case
You split them according to the drawing I did. There can be no split, two segments or 3. Modifying ranges means that if it intersect with source ranges, only the intersection gets remapped into destination range.
Alright thanks
for pair in seed_pairs:
for seed in range(pair[0], pair[0] + pair[1]):
for current_map in maps_data:
for i in range(len(current_map)):
destination_range = current_map[i][0]
source_range = current_map[i][1]
range_length = current_map[i][2]
if seed in range(source_range, source_range + range_length + 1):
seed = (seed - (source_range - destination_range))
break
final_seeds.append(seed)
i know im brute forceing it
but what would be an effcient method
using a set?
Is this for the day where you have to convert between seed soil etc?
yes
Interval arithmetic
You essentially want to be doing all your operations on ranges of values, rather than individual ones
So possible seeds = (0..=4000), map (1000..2000) => (0..1000) => possible soils = (0..1000) or (2000..=4000)
after like 3 days i finally understand whats going on here
now i can finally finish this when im back from vacation
and then solve the other like 10 days i missed
.bookmark
LETS GOOOOOOO
nice
Very nice! Day 5 isn't easy - completing it is pretty good :)

'ed it
