Advent of Code

Advent of Code 2024 – Day 03 – Mull it Over

This was so much easier than yesterday, since I wasn’t splitting my focus between trying to be clever and trying to write shitty code.

Day 03’s problem involved digging through a block of “corrupted code” to pull out important and usable data.  I ended up forcing myself to use the logical method here, and built a Regex string.  Despite that I write a lot of simple scripts to process and convert data for automation, I don’t really use Regex ever.  It’s been something I keep telling myself to do and use and learn better.  A lot of these challenges are actually greatly helped if you use Regex searches.

So that’s what I did.

I found this online Regex builder/tester, stuck the sample code into it, and went to work until I got what I intended out of it.  I learned two things. 

  • Adding a * to “find a digit of any size” only sometimes works, and in this case, using “+” was better.   
  • You can search on multiple strings using a pipe ( | )

Once I had the Regex working and I extracted all of the valid “mul(###,###)” instances, I used a second Regex search, mostly for practice, to extract the digits from each.

Then part two added an on off trigger.  Which was fairly simple to add in, except that I left my first Regex statement in the code, for posterity, but forgot to actually use the new one, so I was getting the same answer twice.

Anyway, here is the code.

import re
# https://www.w3schools.com/python/python_regex.asp
# https://regex101.com/

with open("Day03Input.txt") as file:
    data = file.read()

rxstring = "mul\(\d+,\d+\)"
rxstring2 = "mul\(\d+,\d+\)|do\(\)|don't\(\)"
total = 0
total2 = 0
enabler = True

def multiplier(a,b):
   return int(a)*int(b)

valid = re.findall(rxstring2,data)

for each in valid:
#   print(each)
   if "don't()" in each:
     enabler = False
   elif "do()" in each:
     enabler = True
   else:
     digits = re.findall('\d+',each)
     value = multiplier(digits[0],digits[1])
     total += value
     if enabler:
       total2 += value

print(total)
print(total2)

#Part1 - 179571322
#Part2 - 103811193

Advent of Code 2024 – Day 02 – Red-Nosed Reports

This entire Post is one big code block, because I did a stupid thing that felt like a good idea at the time.

#Lets comment the heck out of this one and flow with the process.  So it may look a little ugly.

# I am essentially just... blogging in real time, in the comments now....

#Until the end, I am using the sample data set
# 7 6 4 2 1
# 1 2 7 8 9
# 9 7 6 2 1
# 1 3 2 4 5
# 8 6 4 4 1
# 1 3 6 7 9

# I started by copying Day01, then modifying the top lines to read my Day02Input.txt file, because, lazy.
# Then just deleting everything else below the set processing, because none of it is real useful.
with open("Day02Input.txt") as file:
    data = file.read()

sets = [each.split(" ") for each in data.split("\n")]

# Then I want to test it so I add a print of the result, just to make sure it looks right against the sample data set. 
##### NOTE Removed code will just be commented out, to follow the comments.  I'll mark these as ##
# Tehre will be a lot of prints removed.  These are used gratuitously to verify things are working.

##print(sets)

# This gives
# [['7', '6', '4', '2', '1'], ['1', '2', '7', '8', '9'], ['9', '7', '6', '2', '1'], ['1', '3', '2', '4', '5'], ['8', '6', '4', '4', '1'], ['1', '3', '6', '7', '9'], ['']]
# For some reason I keep getting black sets on these, so I'm just gonna do like day one and clip it.
sets.pop()

##print(sets)

# Part of the test on this one is if the digits are ascending or descending, I have an idea to test this out quickly
# Lets see if it works.

##for each in sets:
##   print(each)
##   if each == each.sort():
##      print("Ascending")
##   print(each.sort())
# Ok, I ran into a prolem here, because it's reading everything in as strings and not integers.

# There is probably a more elegant way to do this but this will work.
int_sets = []

for each in sets:
   int_sets.append(list(map(int, each)))

## print(int_sets)

# [[7, 6, 4, 2, 1], [1, 2, 7, 8, 9], [9, 7, 6, 2, 1], [1, 3, 2, 4, 5], [8, 6, 4, 4, 1], [1, 3, 6, 7, 9]]
# Perfect, let's try again.
# Also it looks like I needed to use a different sort, for reasons.
# https://stackoverflow.com/questions/403421/how-do-i-sort-a-list-of-objects-based-on-an-attribute-of-the-objects

##for each in int_sets:
##   if each == sorted(each):
##      print("Ascending")
##   else:
##      print("Bad")

# Bad
# Ascending
# Bad
# Bad
# Bad
# Ascending

# Ok, but I also need Descending Lists

##for each in int_sets:
##   if each == sorted(each):
##      print("Ascending")
##   elif each == sorted(each, reverse=True):
##      print("Descending")
##   else:
##      print("Bad")

# Descending
# Ascending
# Descending
# Bad
# Descending
# Ascending

# Ok, things are working well, I probably need a counter for the number of good lists though.
# Despite these tess and comments, I am still "at the top" of the pure code so.

good_lists = 0

# I also need to check for the other condition in the test
# Any two adjacent levels differ by at least one and at most three.

# Since I am still "at the top", I will go ahead and just make a function to check this here.
# Note to self, post a clean version of the code below.

def check_range(a,b):
##  print(f"{a},{b}")
##  print(abs(b-a))
##  if abs(b-a) >= 3 and abs(b-a) <= 1:
  if abs(b-a) <= 3 and abs(b-a) >= 1:
##    print("OK")
    return True
  return False

# I am also going to clean up my ascending/Descending loop a bit.

##for each in int_sets:
##   if each == sorted(each) or each == sorted(each, reverse=True):
##      for i in range(len(each)-1):
##         print(i)
##         print(f"{each[i]},{each[i+1]}")
##         check_range(each[i], each[i+1])
##      print("Good")
##   else:
##      print("Bad")

# Something is not working right, so I'm modifying the defined function above a bit and adding some prints to test.

# I found it, my Greater than less than signs were reversed, oops.
# Also, all this commenting is seriously slowing this whole process

# I'm copying the above loop though, for posterity, before finding a good way to track good loops.

for each in int_sets:
   if each == sorted(each) or each == sorted(each, reverse=True):
# Lets assume a set is true
      good_set = True
      for i in range(len(each)-1):
# If the set is still good, keep checking, otherwise, don't.
         if good_set:
           good_set = check_range(each[i], each[i+1])
# I hate having these conditionals like this, but this is starting to get exhausting typing this, and it's easy.
# If the set is still good after checking the digits, add one to the total number of good sets.
      if good_set:
         good_lists +=1
# I removed the else, because I don't care if it's bad.

print(good_lists)
# This gives 2, as expected, for the sample dataset.
# Time to adjust for my personal data set as input.

# This gives 326 (data sets and answers very by person).
# This is the correct answer, on to Part 2.

# For Part 2, the system can tolorate a single bad level.  Those are what I am checking for in "check_range'.
# Normally I would modify my code to do both tests at once, but for TODAY, i am just going to make a second version.
# Basically, if a bad pair happens, it needs to try matching with the next higher number.  But it only needs to try it once.

good_lists_p2 = 0

##for each in int_sets:
##   if each == sorted(each) or each == sorted(each, reverse=True):
##      print(each)
##      good_set = True
##      dampened = False
##      for i in range(len(each)-1):
##         if good_set:
##           good_set = check_range(each[i], each[i+1])
##           if not good_set and not dampened:
##              print(f"Trying {each[i]} and {each[i+2]}")
##              print(good_set)
##              good_set = check_range(each[i], each[i+2])
##              print(good_set)
##              dampened = True
##      print(good_set)
##      if good_set:
##         good_lists_p2 +=1
##      print(good_lists_p2)

# So, this is not working out.  It's mostly working, but it's failing my Ascending/Descending check.
# Specifically, this one in the sample data fails. "1 3 2 4 5"

# What I need, is a new way to check Ascending and Descending lists.  An annoying way, that is ugly.
# I could build this in to the existing checks, but at this point, I don't want to, I just need a yes/no check
# And all these notes and comments are getting annoying.

def check_order(list):
   if list[-1] > list[0]:
     direction = "Asc"
   elif list[-1] < list[0]:
     direction = "Desc"
   else:
     direction = "Equal"
#   print(direction)

   for n in range(len(list)-1):
      if list[n+1] > list[n] and direction == "Desc":
        if n+2 < len(list):
          if list[n+2] > list[n]:
             return False
      if list[n+1] < list[n] and direction == "Asc":
        if n+2 < len(list):
          if list[n+2] < list[n]:
             return False
   return True

for each in int_sets:
#   print(each)
   if check_order(each):
      good_set = True
      dampened = False
      for i in range(len(each)-1):
         if good_set:
           good_set = check_range(each[i], each[i+1])
           if not good_set and not dampened and i+2 < len(each):
#             print(f"Trying {each[i]} and {each[i+2]}")
#             print(good_set)
              good_set = check_range(each[i], each[i+2])
#              print(good_set)
              dampened = True
#      print(good_set)
      if good_set:
         good_lists_p2 +=1
#      print(good_lists_p2)






print(good_lists_p2)


# 415 too high
# 414 too high
# 400 too high
# Ok, I started Guessing
# I got it
# 381 for my data set.






Advent of Code 2024 – Day 01 – Historian Hysteria

Apparently I only attempt do Advent of Code every other year.  I had this vague thought about doing this thing in JavaScript this year, but considering I am way better at Python than JS, and I have yet to actually complete one of these 100%, i should stick with what am good-ish at.  

The early day are always stupid easy.  It’s usually around halfway they have some super goofy and hard problem that I get stuck on.  The main change this year is doing it through the Bash terminal.  Though I think I did it this way in 2020.  I may swap later, who knows.  The only real annoyance is opening and closing files to test run.

I also have not done a lot of coding in a while, so I had to search up on how to do a few things.  It was a good starter scenario though, it used a lot of things I have forgotten.

I don’t do anything super fancy with my code on these, I have seen some annoyingly unreadable one liners a few times.  I always prefer clean code.  The thing runs in essentially zero time anyway, I don’t need to be “milliseconds more efficient”.

Anyway, here is my solution for part 1 and 2 of Day 1.

with open("Day01Input.txt") as file:
    data = file.read()

sets = [each.split(" ") for each in data.split("\n")]
sets.pop()

list1 = []
list2 = []

for each in sets:
   list1.append(int(each[0]))
   list2.append(int(each[1]))

list1.sort()
list2.sort()

total = 0
total2 = 0
for i in range(len(list1)):
    total += abs(list1[i]-list2[i])
    total2 += list1[i]*list2.count(list1[i])

print(total)
print(total2)

Advent of Code 2022, I’m Done

Well, I made it farther than my last “in real time attempt” in 2020 by 3 starts. I may check in one the puzzles each day, but my experience is, they only get more complex as time goes on, so I doubt I’ll be completing any more of them. Each day is starting to take a lot more time to solve out, the solutions are getting a lot more finicky to produce. We’ve also reached the point where the puzzle inputs also feel ridiculously obtuse. Like the Day 15 puzzle, where every number was in the millions, basically, for the only purpose of making everything slow without some sort of magic reduction math. Though skimming through other’s solutions, there didn’t seem to really BE any “magic reduction” option there. \

Which is fine. It’s not supposed to be easy. I don’t expect it to be easy.

But I have long ago accepted that things I’m doing for relaxation or enjoyment, should at least be relaxing and enjoyable. And These puzzles have reached a point where the amount of enjoyment and relaxation I get from them is no longer worthwhile.

So I’m choosing to end this year’s journey here.

Maybe I’ll go back and finish them some day, but more at my own leisure. I mean, I had started doing the old 2015 puzzles in the week leading up to this year’s event. I was never doing this in any attempt to get on the leader boards or anything anyway, hell I didn’t even start most day’s puzzles until the day was half over or later.

For what it’s worth, i did make a strong attempt on Day 15 but I just could not get it to output the correct answer, and I’m not real sure why. I couldn’t even get the sample input to work out, I was always one off. It’s possible, and likely, I was counting the space where the beacon existed, but my actual input data was off by a little over 1 million, and there are not 1 million beacons on the board. Plus it was 1 million under, where my sample input solution was 1 over.

I’m not even attempting today’s, for Day 16. I can see the logic needed, but the nuance to accomplish it will just take me too long to code out and like I said above, enjoyment and relaxation is the point. I don’t need to add hours of stress to my day.

Advent of Code 2020 – Day 13

Today’s lesson in extremely, incredibly, stupidly, inefficient methodology, is brought to you by, the letter “i” and the number “640856202464541”. Day 13 consisted of calculating bus departure times. Part 1 was stupidly simple. You have a list of buses, each bus makes a regular round trip to wherever, and the round trip always takes the same amount of minutes. At some point in the past, the buses all left at once.

Say the buses were 4, 5, and 6, each number also being the minutes for a round trip. Every 4 minutes, bus 4 returns, every 5 minutes bus 5 returns, every 6 minutes, bus 6 returns. If you are available to leave at 9 minutes, which bus leaves first. In this example, the first bus to return would be 5 (5*2=10)

Part 1 was easy, start at your departure time, check to see if any of the numbers divide into it evenly, if not, increment by one and repeat.

with open('day13data.txt') as f:
    lines = [line.rstrip() for line in f]

time = int(lines[0])
raw = lines[1].split(',')
busses= [] 

for i in raw:
  if i != 'x':
    busses.append(int(i))

print time
print busses

for x in busses:
  bus_time=x*(round((float(time)/float(x))+.5))
  wait=bus_time-time
  print "For bus "+str(x)+": "+str(bus_time)+" and you will wait: "+str(wait)+" Ans: "+str(x*wait)

The trickiest part is that the data contained non existent buses, labeled with an “x”. These are not a factor for part 1, so they just get stripped out.

Part 2 is where the pain in the ass was. For Part 2, you have to find a starting time where each bus, will leave, one minute apart, according to their offset. This includes the “missing” buses. So for the quick example I had above of 4,5,6, this time would occur at well, 4 minutes, because I made them sequential, but the next one would occur at 64,65,66 (16*4, 13*5, 11*6). This gets even more complicated when you add in the blanks, for example, 4,x,5,x,6 occurs at 68,70,72. As you spread the numbers apart and rearrange them to be non sequential, it complicates everything more.

I actually figured out the methodology pretty quickly, and wrote a working, good bit of code pretty quickly, my problem, was picking a starting point. I am sure there is some numerology methods (I am pretty sure you can do something using remainders), to calculate even an approximate starting point. In my standard of ugly code, I was just brute forcing it.

with open('day13data.txt') as f:
    lines = [line.rstrip() for line in f]

lines_array = lines[1].split(',')
times = []
counter=0
#First for sample Data Set day13datab.txt, second for real data
#multiplier=152600
multiplier=15630639084400
offsets=[]
busses=[]

for i in lines_array:
  if i != 'x':
    busses.append(i)
    offsets.append(lines_array.index(i))

#print busses
#print offsets

while (1):
  for i in offsets:
    times.append(((int(busses[0]))*multiplier)+i)

  #print times
  counter=1
  for j in busses[1:]:
    #print j
    if (times[counter]/float(j)).is_integer():
      counter+=1
    else:
      break
        
  #print times
  #print counter
  if counter == len(busses):
    break
  counter = 0
  times =[]
  multiplier+=1
  #print multiplier

print times

The problem is, this takes a very, very, very, very, very, long time. I left this code running for hours on my server and it didn’t finish, I honestly feel like it could very likely run for days, weeks, years, and never finish. The iteration on my multiplier that gives the correct set, for my data (every person has a different data set) was 15,630,639,084,501. 15 TRILLION.

So I fudged it a bit, because I was pretty sure my code was good, but I didn’t have an eternity to wait. So I found someone else’s code, found the correct answer, then worked out my starting offset from there. Sure enough, when I start at 15,630,6390,084,400. It quickly finds the correct answer.

If I needed this code for something important, I would certainly try to clean it up. I even started working on an iteration that would sort out my bus list to start with the largest number, instead of iterating the list every (low value) it would iterate every (high value), which in my case I believe was something like 15 versus 900. This was trickier than a straight sort though because I had a second list of offsets that I needed to also re order. I got this code working for the sample data set, but it failed in my proper data set because, while the sample data set started with the lowest bus number, the actual data set did not, and I had not calculated for that.

PS, yes, I misspelled “busses” in my code