this post was submitted on 02 Dec 2024
14 points (100.0% liked)

NotAwfulTech

386 readers
4 users here now

a community for posting cool tech news you don’t want to sneer at

non-awfulness of tech is not required or else we wouldn’t have any posts

founded 1 year ago
MODERATORS
 

copy pasting the rules from last year's thread:

Rules: no spoilers.

The other rules are made up aswe go along.

Share code by link to a forge, home page, pastebin (Eric Wastl has one here) or code section in a comment.

top 50 comments
sorted by: hot top controversial new old
[–] [email protected] 5 points 3 weeks ago (1 children)

I can't sleep, so here's 1-1 and 1-2, unfortunately I couldn't think of any silly solutions this time, so it's straightforward instead:

spoiler

#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include <set>
#include <iterator>

int main() {
  std::multiset<int> l, r;
  int a, b;
  while (std::cin >> a >> b) {
    l.insert(a); r.insert(b);
  }
  std::vector<int> delta;
  std::transform(l.begin(), l.end(), r.begin(), std::back_inserter(delta),
    [](int x, int y) { return std::abs(x-y); }
  );
  std::cout << std::accumulate(delta.begin(), delta.end(), 0) << std::endl;
}

spoiler

#include <iostream>
#include <numeric>
#include <set>

int main() {
  std::multiset<int> l, r;
  int a, b;
  while (std::cin >> a >> b) {
    l.insert(a); r.insert(b);
  }
  std::cout << std::accumulate(l.begin(), l.end(), 0, [&r](int acc, int x) {
    return acc + x * r.count(x);
  }) << std::endl;
}

[–] [email protected] 6 points 3 weeks ago* (last edited 3 weeks ago) (1 children)

2-1: I have quickly run out of hecks to give. This is the sort of problem that gives prolog programmers feelings of smug superiority.

spoiler

#include <string>
#include <iostream>
#include <sstream>

int main() {
  int safe = 0;
  std::string s;
  while (std::getline(std::cin, s)) {
    std::istringstream iss(s);
    int a, b, c;
    if (!(iss >> a >> b)) {
      safe++; continue;
    }
    if (a == b || std::abs(a-b) > 3) continue;
    bool increasing = b > a;
    while (iss >> c) {
      if (b == c || std::abs(b-c) > 3) goto structuredprogrammingisfornerds;
      switch (increasing) {
        case false:
          if (c < b) { b = c; continue; }
          goto structuredprogrammingisfornerds;
        case true:
          if(c > b) { b = c; continue; }
          goto structuredprogrammingisfornerds;
      }
    }
    safe++;
    structuredprogrammingisfornerds:;
  }
  std::cout << safe << std::endl;
}

As usual the second part has punished me for my cowboy code, so I'll have to take a different more annoying tack (maybe tomorrow). Or you know I could just double down on the haphazard approach...

[–] [email protected] 6 points 3 weeks ago* (last edited 3 weeks ago) (1 children)

I decided to double down on 2-2, since bad code is one of life's little pleasures. Where we're going we won't need big-oh notation

spoiler

#include <string>
#include <iostream>
#include <sstream>
#include <vector>
#include <iterator>

template <typename It>
bool seemslegit(It begin, It end) {
    if (std::distance(begin, end) == 1) {
      return true;
    }
    int a = *begin++;
    int b = *begin++;
    if (a == b || std::abs(a-b) > 3) return false;;
    bool increasing = b > a;
    while (begin != end) {
      int c = *begin++;
      if (b == c || std::abs(b-c) > 3) return false;;
      switch (increasing) {
        case false:
          if (c < b) { b = c; continue; }
          return false;
        case true:
          if(c > b) { b = c; continue; }
          return false;
      }
    }
    return true;
}

template <typename It>
void debug(It begin, It end) {
  bool legit = seemslegit(begin, end);
  while (begin != end) {
    std::cout << *begin++ << " ";
  }
  //std::cout << ": " << std::boolalpha << legit << std::endl;
}

int main() {
  int safe = 0;
  std::string s;
  while (std::getline(std::cin, s)) {
    std::istringstream iss(s);
    std::vector<int> report((std::istream_iterator<int>(iss)),
                            std::istream_iterator<int>());
    debug(report.begin(), report.end());
    if (seemslegit(report.begin(), report.end())) {
      safe++;
      std::cout << "\n\n";
      continue;
    }
    for (int i = 0; i < report.size(); ++i) {
      auto report2 = report;
      auto it = report2.erase(report2.begin()+i);
      debug(report2.begin(), report2.end());
      if (seemslegit(report2.begin(), report2.end())) {
        safe++;
        break;
      }
    }
    std::cout << "\n\n";
 }
  std::cout << safe << std::endl;
}

CommentaryDoing this "efficiently" should be possible. since you only need ~2-ish look-back you should be able to score reports in O(n) time. One complication is you might get the direction wrong, need to consider that erasing one of the first two elements could change the direction. But that requires thinking, and shoving all the permutations into a function with ungodly amounts of copying does not.

[–] [email protected] 4 points 3 weeks ago (1 children)

re: 2-2yeah that’s what I ended up thinking. Just try the brute force and if it’s too slow, maybe I’ll try be smarter about it.

[–] [email protected] 5 points 3 weeks ago (1 children)

re: 2-2

I was convinced that some of the Perl gods in the subreddit would reveal some forgotten lore that solved this in one line but looks like my brute force method of removing one element at a time was the way to go.

[–] [email protected] 5 points 3 weeks ago* (last edited 3 weeks ago)

I am now less sleep deprived so can say how to do better somewhat sensibly, albeit I cannot completely escape from C++s verbosity:

2-2

  1. Don't worry about the sequences changing direction. Just call the check function both assuming it is increasing and assuming it is decreasing. This is cheap enough because the wrong branch will fail after 3 elements or so.
  2. When encountering an element that fails, you only need to consider removing the previous element, or the current element. If you can get to the next element removing one of those then you can continue on without any real backtracking.

Updated code:

2-2

#include <string>
#include <iostream>
#include <sstream>
#include <vector>
#include <iterator>

bool valid_pair(const std::vector<int> &arr, int i, int j, bool direction) {
  if (i < 0) return true;
  if (j == arr.size()) return true;
  return    !(arr[i] == arr[j])
         && (direction ? arr[i] < arr[j] : arr[j] < arr[i])
         && (std::abs(arr[j]-arr[i]) <= 3);
}

bool valid(const std::vector<int> &arr, bool direction) {
  int checks = 1;
  for (int i = 1; i < arr.size(); ++i) {
    if (valid_pair(arr, i-1, i, direction)) continue;
    if (checks == 0) return false;
    if (   valid_pair(arr, i-2,  i, direction)
        && valid_pair(arr, i,  i+1, direction)) {
      checks -= 1; i += 1;
    } else if (valid_pair(arr, i-1, i+1, direction)) {
      checks -= 1; i += 1;
    } else return false;
  }
  return true;
}

int main() {
  int safe = 0;
  std::string s;
  while (std::getline(std::cin, s)) {
    std::istringstream iss(s);
    std::vector<int> report((std::istream_iterator<int>(iss)),
                            std::istream_iterator<int>());
    safe += (valid(report, true) || valid(report, false));
  }
  std::cout << safe << std::endl;
}

[–] [email protected] 4 points 3 weeks ago (2 children)

Day 3

3-2

I expect much wailing and gnashing of teeth regarding the parsing, which of course is utterly trivial if you know a bit if regex.

I got bit by the input being more than one line. Embarrasing.

I wonder if any input starts with a "don't()" or if it's too early for Erik to pull such trickery.

[–] [email protected] 3 points 3 weeks ago

same

I got bit by the input being more than one line. Embarrasing.

[–] [email protected] 2 points 3 weeks ago

InputOne of the lines of my input had a don't() first, but I got bit by it being more lines as well.

[–] [email protected] 4 points 2 weeks ago

Day 8

Al lot of grid index shuffling these past few days! Not too difficult yet though, will this year be gentler or much harsher later?

Part 2 code in JQ#!/usr/bin/env jq -n -R -f

[ inputs / "" ] | [.,.[0]|length] as [$H,$W] |

#----- In bound selectors -----#
def x: select(. >= 0 and . < $W);
def y: select(. >= 0 and . < $H);

reduce (
  [
    to_entries[] | .key as $y | .value |
    to_entries[] | .key as $x | .value |
    [ [$x,$y],. ]  | select(last!=".")
  ] | group_by(last)[] # Every antenna pair #
    | combinations(2)  | select(first < last)
) as [[[$ax,$ay]],[[$bx,$by]]] ({};
  # Assign linear anti-nodes #
  .[ range(-$H;$H) as $i | "\(
    [($ax+$i*($ax-$bx)|x), ($ay+$i*($ay-$by)|y)] | select(length==2)
  )"] = true
) | length

[–] [email protected] 4 points 3 weeks ago

It's that time of the year again. Last year was tough for me, i got laid off in the middle of dec and it kinda killed the vibe. I'll see how long I keep up this year. My historical backlog is growing but I've made peace with it.

[–] [email protected] 4 points 3 weeks ago* (last edited 3 weeks ago) (1 children)

Day 4 - Ceres Search

discussion

There's probably a smarter way to do this...

For part 1, I dumped everything into a matrix. Then I scanned it element by element. If I found an X, I searched in 8 directions from there and counted up if I found M A S in sequence.

For part 2 I searched for an A, checked each diagonal corner, and counted up if the opposites were M S or S M

https://github.com/gustafe/aoc2024/blob/main/d04-Ceres-Search.pl

[–] [email protected] 2 points 3 weeks ago* (last edited 3 weeks ago)

discussionSame, except in 4-1 I used a recursive function to traverse each direction according to the offset decided by the selected direction (like SW is row++,col--) , due to functional programming induced brain damage.

Would have been pretty useful too if 4-2 turned out to be about finding longer patterns, instead of smaller and in half the directions.

4-1Inlined some stuff to fit everything in one function:

let rec isXmas (row:int) (col:int) (dir:int) (depth:int) (arr: char array2d) : bool =
    let value = arr.[row,col]
    if depth = 3
    then value = 'S'
    else if  [|'X';'M';'A';'S'|].[depth] = value
    then
        let (nextRow, nextCol) =
            match dir with
            | 1 -> row + 1, col - 1
            | 2 -> row + 1, col
            | 3 -> row + 1, col + 1
            | 4 -> row, col - 1
            | 6 -> row, col + 1
            | 7 -> row - 1, col - 1
            | 8 -> row - 1, col
            | 9 -> row - 1, col + 1
            | _ -> failwith $"{dir} isn't a numpad direction." 

        let rowCount = arr |> Array2D.length1
        let colCount = arr |> Array2D.length2
       
        if nextRow >= 0 && nextRow < rowCount && nextCol >= 0 && nextCol < colCount
        then isXmas nextRow nextCol dir (depth+1) arr
        else false
    else false

Then you run this for every appropriately pruned 'X' times every direction and count the trues.

[–] [email protected] 4 points 3 weeks ago* (last edited 3 weeks ago)

Got stuck forever on 2-2 because of an edge case that only showed up in 7/1000 reports, ended up just brute forcing it, just ran the fitness function after removing one element at a time sequentially.

Then solved 3.x in like minutes because I could be worse at regex, posting code mostly because no-one else posted F# yet.

edited to fix spoiler header formatting

3-2 in F#

"./input.actual"
|> System.IO.File.ReadAllText
|> fun source -> 
    System.Text.RegularExpressions.Regex.Matches(source, @"don't\(\)|do\(\)|mul\((\d+),(\d+)\)")
    |> Seq.fold
        (fun (acc, enabled) m ->
            match m.Value with
            | "don't()" -> acc, false
            | "do()" -> acc, true
            | mul when enabled && mul.StartsWith("mul") ->
                let (x, y) = int m.Groups.[1].Value, int m.Groups.[2].Value
                acc + x * y, enabled
            | _ -> acc, enabled ) 
        (0, true)
    |> fst
|> printfn "The sum of all valid multiplications with respect to do() and don't() is %A"

commentsNot much to say, the regex grabs all relevant strings and the folding function propagates a flag that flips according to do/don't and an accumulator that is increased when a mul() is encountered and parsed.

[–] [email protected] 4 points 2 weeks ago* (last edited 2 weeks ago) (1 children)

Day 5 - Print Queue

day 5

urgh this took me much longer than it should have... part 1 was easy enough but then I got tied up in knots with part 2. Finally I just sorto-bogo-sorted it all into shape

Perl: https://github.com/gustafe/aoc2024/blob/main/d05-Print-Queue.pl

Recheck array size: 98
All rechecks passed after 5938 passes
Duration: 00h00m00s (634.007 ms)

[–] [email protected] 2 points 2 weeks ago* (last edited 2 weeks ago)

tl;dr: Day 5 was most perfectly fine code thrown out for me, because I ran face first into eliminating imaginary edge cases instead of starting out simple.

5-1 commentaryI went straight into a rabbit hole of doing graph traversal to find all implicit rules (i.e. 1|2, 2|3, 3|4 imply 1|3, 1|4, 2|4) so I could validate updates by making sure all consequent pairs appear in the expanded ruleset. Basically I would depth first search a tree with page numbers for nodes and rules for edges, to get all branches and recombine them to get the full ruleset.

So ideally 1|2, 2|3, 3|4 -> 1|2|3|4 -> 1|2, 2|3, 3|4, 1|3, 1|4, 2|4

Except I forgot the last part and just returned the branch elements pairwise in sequence, which is just the original rules, which I noticed accidentally after the fact since I was getting correct results, because apparently it was completely unnecessary and I was just overthinking myself into several corners at the same time.

5-2 commentary and some codeThe obvious cornerstone was the comparison function to reorder the invalid updates, this is what I came up with:

let comparerFactory (ruleset: (int*int) list) :int -> int -> int = 
    let leftIndex = 
        ruleset 
        |> List.groupBy fst 
        |> List.map (fun (key,grp)-> key, grp |> List.map snd)
        |> Map.ofList

    fun page1 page2 -> 
        match (leftIndex  |> Map.tryFind page1) with
        | Some afterSet when afterSet |> List.contains page2 -> -1
        | _ -> 1

The memoization pattern is for caching an index of rules grouped by the before page, so I can sort according to where each part of the comparison appears. I started out with having a second index where the key was the 'after' page of the rule which I would check if the page didn't appear on the left side of any rule, but it turned out I could just return the opposite case, so again unnecessary.

[–] [email protected] 3 points 3 weeks ago

Day 3 well suited to JQ

Part 2#!/usr/bin/env jq -n -R -f

reduce (
  inputs |   scan("do\\(\\)|don't\\(\\)|mul\\(\\d+,\\d+\\)")
         | [[scan("(do(n't)?)")[0]], [ scan("\\d+") | tonumber]]
) as [[$do], [$a,$b]] (
  { do: true, s: 0 };
    if $do == "do" then .do = true
  elif $do         then .do = false
  elif .do         then .s = .s + $a * $b end
) | .s

[–] [email protected] 3 points 3 weeks ago* (last edited 3 weeks ago)

For 3: I made dart one-liners for both. Pasting the juicy parts.

3:1RegExp(r"mul\((\d*),(\d*)\)").allMatches(input).fold<int>( 0, (p, e) => p + e.groups([1, 2]).fold(1, (p, f) => p * int.parse(f!))));

3:2My original solution found do, don't and mul entries, then stepped through them to get the solve. I decided to try regex my way through it. What I realised was that you want to ignore strings starting with don't() and ending at the first do(). Some amount of trial and error later, I figured out the (ecma*) regex to do it, which I am proud of:

RegExp(r"(?:don\'t\(\)(?:.(?<!do\(\)))*do\(\))|(?:mul\((\d*),(\d*)\))") .allMatches(input) .fold<int>( 0, (p, e) => p + (e.group(0)![0] != 'd' ? e.groups([1, 2]).fold<int>(1, (p, f) => p * int.parse(f!)) : 0))

*ecma balls

[–] [email protected] 3 points 3 weeks ago (2 children)

Advent of Code is one of these things I wanna do every year and then I end up in fucking end-of-the-year crunch time every December and work for 10-12 hours and really don't wanna code after work anymore.

But hey, here's a quick solution for day 1. Let's see how far I make it.

Day 1

use strict;
use List::Util qw( min max );

open(FH, '<', $ARGV[0]) or die $!;

my @left;
my @right;

while (<FH>) {
	my @nums = split /\s+/, $_;
	push(@left, $nums[0]);
	push(@right, $nums[1]);
}

@left = sort { $b <=> $a } @left;
@right = sort { $b <=> $a } @right;

my $dist = 0;
my $sim = 0;
my $i = 0;

foreach my $lnum (@left) {
	$sim += $lnum * grep { $_ == $lnum } @right;

	my $rnum = $right[$i++];
	$dist += max($lnum, $rnum) - min($lnum, $rnum);
}

print 'Part 1: ', $dist, "\n";
print 'Part 2: ', $sim, "\n";

close(FH);

load more comments (2 replies)
[–] [email protected] 3 points 2 weeks ago (1 children)

d8: done, and nothing to say about it.

[–] [email protected] 4 points 2 weeks ago (1 children)

day 8 part 2

Eric added some 0s in the grid which fucked up my existence code, sneaky devil

[–] [email protected] 2 points 2 weeks ago* (last edited 2 weeks ago)

I almost got done in by floating point arithmetic, I think

8-2 commentaryUsed the coordinates of every two same type frequences to create the ilnear equation (y = ax + b) and then fed it all the matrix coordinates to see which belonged to the line. To get the correct number of antinodes I had to check for |y - ax - b| < 0.0001, otherwise I got around 20 too few.

[–] [email protected] 3 points 2 weeks ago

relevant to this sub's interest is this Reddit thread about "LLM cheaters" in AoC:

https://old.reddit.com/r/adventofcode/comments/1h9cub8/discussion_on_llm_cheaters/

[–] [email protected] 3 points 2 weeks ago (4 children)

Time for a new thread for a new week?

load more comments (4 replies)
[–] [email protected] 3 points 2 weeks ago (1 children)

Day 10. I lied about doing this later, I guess.

p1, 2 I accidentally solved 2. before 1.My initial code was: for every 9, mark that square with a score of 1. Then: for (I = 8, then 7 ... 0) => mark the square with the sum of the scores of the squares around it with a value of i + 1.

Except that gives you all the ways to reach 9s from a 0, which is part 2. For part 1, I changed the scores to be sets of reachable 9s, and the score of a square was the size of the set at that position.

[–] [email protected] 2 points 2 weeks ago (1 children)

10 commentaryYeah basically if you were doing DFS and forgot to check if you'd already visited the next node you were solving for pt2, since the rule about the next node always having a value of +1 compared to the current one was already preventing cyclic paths.

10 CodeHardly a groundbreaking implementation but I hadn't posted actual code in a while so

(* F# - file reading code and other boilerplate omited *)

let mapAllTrails (matrix : int array2d) =
    let rowCount = matrix |> Array2D.length1
    let colCount = matrix |> Array2D.length2

    let rec search (current:int*int) (visited: HashSet<int*int>) (path: (int*int) list) : (int*int) list list= 
        let (row,col) = current
        let currentValue = matrix.[row,col]

        // Remove to solve for 10-2
        visited.Add (row,col) |> ignore

        // If on a 9 return the complete path
        if currentValue = 9 then [List.append path [row,col] ]
        // Otherwise filter for eligible neihboring cells and continue search
        else                    
            [ row-1, col;row, col-1; row, col+1; row+1,col]
            |> List.filter (fun (r,c) -> 
                not (visited.Contains(r,c))
                && r >= 0 && c>=0 && r < rowCount && c < colCount
                && matrix.[r,c]-currentValue = 1 )
            |> List.collect (fun next ->
                [row,col] 
                |> List.append path  
                |> search next visited)

    // Find starting cells, i.e. contain 0
    matrix
    |> Global.matrixIndices
    |> Seq.filter (fun (row,col) -> matrix.[row,col] = 0)
    // Find all trails starting from those cells and flatten the result
    |> Seq.collect (fun trailhead -> search trailhead (HashSet<int*int>()) [])
    

"./input.example"
|> Common.parse 
|> mapAllTrails
|> Seq.length
|> Global.shouldBe 81

"./input.actual"
|> Common.parse 
|> mapAllTrails
|> Seq.length
|> printfn "The sum total of trail rankings is %d"

[–] [email protected] 4 points 2 weeks ago (1 children)

re: 10 commentary

apparently "everyone" did this. Me too

[–] [email protected] 1 points 2 weeks ago

re:10Mwahaha I'm just lazy and did are "unique" (single word dropped for part 2) of start/end pairs.

#!/usr/bin/env jq -n -R -f

([
     inputs/ "" | map(tonumber? // -1) | to_entries
 ] | to_entries | map( # '.' = -1 for handling examples #
     .key as $y | .value[]
   | .key as $x | .value   | { "\([$x,$y])":[[$x,$y],.] }
)|add) as $grid | #           Get indexed grid          #

[
  ($grid[]|select(last==0)) | [.] |    #   Start from every '0' head
  recurse(                             #
    .[-1][1] as $l |                   # Get altitude of current trail
    (                                  #
      .[-1][0]                         #
      | ( .[0] = (.[0] + (1,-1)) ),    #
        ( .[1] = (.[1] + (1,-1)) )     #
    ) as $np |                         #   Get all possible +1 steps
    if $grid["\($np)"][1] != $l + 1 then
      empty                            #     Drop path if invalid
    else                               #
    . += [ $grid["\($np)"] ]           #     Build path if valid
    end                                #
  ) | select(last[1]==9)               #   Only keep complete trails
    | . |= [first,last]                #      Only Keep start/end
]

# Get score = sum of unique start/end pairs.
| group_by(first) | map(unique|length) | add

[–] [email protected] 3 points 2 weeks ago* (last edited 2 weeks ago)

OK nerds, I was coerced into doing day five so I'm posting it here.

spoilerstable sort with the ordering critera as the sort condition and it just works, either that or I got lucky inputs

5-1 / 5-2

#include <iostream>
#include <vector>
#include <algorithm>
#include <sstream>
#include <string>
#include <unordered_map>

std::unordered_multimap<int, int> ordering;

bool sorted_before(int a, int b) {
  auto range = ordering.equal_range(a);
  for (auto it = range.first; it != range.second; ++it) {
    if (it->second == b) return true;
  }
  return false;
}

int main() {
  int sum = 0;
  std::string line;
  while (std::getline(std::cin, line) && !line.empty()) {
    int l, r;
    char c;
    std::istringstream iss(line);
    iss >> l >> c >> r;
    ordering.insert(std::make_pair(l,r));
  }
  while (std::getline(std::cin, line)) {
    std::istringstream iss(line);
    std::vector<int> pages;
    int page;
    while (iss >> page) {
      pages.push_back(page);
      iss.get();
    }   
    std::vector<int> sorted_pages = pages;
    std::stable_sort(sorted_pages.begin(), sorted_pages.end(), sorted_before);
    if (pages == sorted_pages) {  // Change to != for 5-2
      sum += sorted_pages[sorted_pages.size()/2];
    }
  }
  std::cout << "Sum: " << sum << std::endl;
}

[–] [email protected] 2 points 3 weeks ago

Ah thanks for reminding me that time progresses and that it is now December.

I have done 1.1 through 2.2 and have nothing interesting to say about them.

[–] [email protected] 2 points 3 weeks ago* (last edited 3 weeks ago)

I would do day 3 today except

spoilerI'd stubbornly insist on using SIMD instructions to scan for the keyword now that I've thought of that. Even though this doesn't make any sense. And I'm not ready to dive into all that today.

Maybe this weekend or if I can't sleep again or if I get especially bored at work.

[–] [email protected] 2 points 3 weeks ago (1 children)

4:

4-1I tried something clever and was punished for my hubris, but it turned out ok.

I treated the input as a list of strings and searched for "XMAS" and "SAMX", rotating and transposing the list to scan for vertical and diagonal words.

In my arrogance, I thought I could do the diagonal rotation easily, but I was very wrong. I got it out in the end, though.

4-2this was easier than 4-1. An O(n^2^) solution just scanning for X-MAS was good enough. Maybe there was a more clever solution that used, i dunno, string hashing or something but whatever.

[–] [email protected] 2 points 3 weeks ago (1 children)

re: 4-2

is it really n^2 tho? You have to do a constant check around each element, but that does not increase with the number of elements. And you can optimize a bit by marking already seen 'A's as illegal states and don't have to check them when the next row is processed.

[–] [email protected] 2 points 3 weeks ago

i am a simple manI see square input, i say n^2^

[–] [email protected] 2 points 2 weeks ago* (last edited 2 weeks ago) (1 children)

Day 7

1 and 2On reflection, it was a pretty fun little problem to solve. There wasn't much of a difference between the two parts. I ran into some bugs with my recursion termination conditions, but I got them in the end.

Part 1. A quick look at the data showed that the input length was short enough to perform an O(2^n^) search with some early exits. I coded it as a dfs.

Part 2. Adding concatenation just changes the base from 2 to 3, which, while strictly slower, wasn't much slower for this input.

code

void d7(bool sub) => print(getLines()
    .map((l) => l.split(RegExp(r':? ')).map(int.parse).toList())
    .fold<int>(
        0, (p, ops) => test(ops, ops[0], ops[1], 2, sub) ? ops[0] + p : p));

bool test(List<int> l, int cal, int cur, int i, bool sub) =>
    cur == cal && i == l.length ||
    (i < l.length && cur <= cal) &&
        (test(l, cal, cur + l[i], i + 1, sub) ||
            test(l, cal, cur * l[i], i + 1, sub) ||
            (sub && test(l, cal, cur.concat(l[i]), i + 1, sub)));

[–] [email protected] 4 points 2 weeks ago* (last edited 2 weeks ago) (2 children)

Re: day 7 parts 1 and 2

same here, I was dicking around with combinatorics to get all combos of plus and multiply but realized before I got to the end it was gonna take too long. Then I figured that a DFS was the way to go.

I tried to optimize a bit by exiting early if the cumulative result became too large, but for some reason that gave me incorrect (too low) answers. Part 2 runs in around 1 min anyway.

https://github.com/gustafe/aoc2024/blob/main/d07-Bridge-Repair.pl

[–] [email protected] 3 points 2 weeks ago* (last edited 2 weeks ago) (1 children)

My graph search solves 7-1 and passes the example cases for 7-2, but gives too low a result for the complete puzzle input, and there's no way I'm manually going through every case to find the false negative. On to day 8 I guess.

7-2 Check case by simple graph search that mostly works

// F#
let isLegit ((total: int64), (calibration : int64 array)) = 

    let rec search (index : int) (acc: int64) =
        let currentValue = calibration.[index]
        
        [Add; Times; Concat] // operators - remove 'Concat' to solve for 7-1
        |> List.exists (fun op -> // List.exists returns true the first time the lambda returns true, so search stops at first true
                match op with // update accumulator
                | Add -> acc + currentValue
                | Times -> acc * currentValue
                | Concat -> int64 (sprintf "%d%d" acc currentValue)
                |> function // stop search on current accumulator value (state) exceeding total, or being just right
                | state when state > total -> false
                | state when state = total && index < (calibration.Length-1) -> false // this was the problem
                | state when state = total && index = (calibration.Length-1) -> true
                | state -> // stop if index exceeds input length, or continue search
                    if index+1 = calibration.Length
                    then false
                    else search (index+1) state
        )
     
    // start search from second element using the first as current sum
    search 1 calibration.[0]

EDIT: total && index < (calibration.Length-1) -> false -- i.e. stop if you reach the total before using all numbers, well, also stops you from checking the next operator, So, removing it worked.

Rubber ducking innocent people on the internets works, who knew.

[–] [email protected] 4 points 2 weeks ago

this looks like the same issue I had!

[–] [email protected] 2 points 2 weeks ago (1 children)

re: branch cuttingIDK if this is what your issue was, but one thing I ran into was that if you do something like if (current_total >= target) prune(), this can be problematic because if the tail end of the data is 0s and 1s, you exit too early. Basically I would prune strictly when the current total > target.

[–] [email protected] 2 points 2 weeks ago (1 children)

re: branch cutting

thanks for the tip, I looked into it again and I found I was cutting in the wrong place. Fixed now, and halves the time for part 2

[–] [email protected] 2 points 2 weeks ago

We love to see it

[–] [email protected] 2 points 2 weeks ago* (last edited 2 weeks ago) (1 children)

Day 9 seemed pretty straightforward, don't really have anything to add.

[–] [email protected] 3 points 2 weeks ago* (last edited 2 weeks ago) (1 children)

I had a lot of trouble because my input was truncated, despite the site warning me about that. Did I listen? I did not.

edit

day 9 discussion

Principal Skinner moment

"Did I miscopy the input?"

"No, it is my algorithm that is wrong"

kinda satisfying to figure out I was correct all along.

Part 2 is not as fast as I'd like (14s), but faster than it was. People on reddit are wittering on about search trees, me I just sling Perl hashrefs around

https://github.com/gustafe/aoc2024/blob/main/d09-Disk-Fragmenter.pl

load more comments (1 replies)
[–] [email protected] 2 points 2 weeks ago

I will probably slow down and try to solve the problems on the weekends rather than daily. Anyway, 9:

part 1This was straightforward in that all you need to do is implement the algorithm as given. I did optimise a little using the arithmetic progression sum, but this is a speed-up of, at most like, a factor of 9.

I am pretty sure I did an OK job at avoiding edge cases, though I suspect this problem has many of them.

part 2Again, the algorithm is more or less given: Start from the back, look for a hole that'll work, and put it in if you can. Otherwise, don't. Then, calculate the contribution to the checksum.

The complex part was the "look for a hole" part. My input size was 19999, meaning an O(n^2^) solution was probably fast enough, but I decided to optimise and use a min heap for each space size prematurely. I foresaw that you need to split up a space if it is oversized for a particular piece of data, i.e. pop the slot from the heap, reduce the size of the slot, and put it in the heap corresponding to the new size.

[–] [email protected] 1 points 2 weeks ago (2 children)

days 5 and 6.

5:

p1, p2:Initially, I was thrown for a loop. It wasn't apparent to me what data structure to use or the problem's properties. My first (and correct) instinct was to interpret the data as a directed graph, but then what? Try to find some total ordering, if such a thing was possible?

As it turns out, that instinct was also correct. By drawing the sample data (or counting or printing it out), I noticed that every page number had a defined relation with every other page number. This meant that a total ordering (rather than a lattice) existed, meaning it was possible to construct a comparison function.

So, the algorithm for part 1 was to check if a list was sorted, and part 2 was to sort the list. There's probably a 1-3 line solution for both parts a and b, but that's for Mr. The Reader.

6:

p1, p2as discussed in a different part of the thread, I consider the input size for square inputs to be N, the "side length" of the square.

Context: I participated (and completed!) in AoC last year and pragmatically wrote my code as a set of utility modules for solving these pathological problems. So, I had about 80% of the boilerplate for this problem written, waiting for me to read and relearn.

Anyway, the analysis: P1. was pretty straightforward. Just walk along the map, turn right if you hit an obstacle, and stop when you leave the map. I guessed that there may be a case where one needs to turn in place more than once to escape an obstacle, but I never checked if that was true. Either way, I got the answer out. This is a worst-case O(n^2^) solution, which was fast for n = 130.

P2. I chose to brute force this, and it was fine. I iterated through the grid, placing a wall if possible, and checked if this produced a loop using an explored set. This is worst case O(n^4^), which, for n=130, takes a few seconds to spit out the answer. It's parallelisable, though, so there's that. If a faster solution existed, I'd love to know.

[–] [email protected] 2 points 2 weeks ago* (last edited 2 weeks ago)

day 6

part 2

I also brute-forced this. I figured there's a bit optimization to be done if you "draw a line" to the next obstacle instead of going step by step, and also maybe exclude some areas, but in the end I just set an exit value to break if the number of steps exceeded a certain value and say that was a loop. Took almost 20m but a star is a star.

update I only added obstructions in the original path, which cut the time down to 5 minutes or so.

[–] [email protected] 1 points 2 weeks ago* (last edited 2 weeks ago)

code for day 5, written in dart to be short:

spoiler

void d5(bool isPart2) {
  Map<int, Set<int>> ordering = {};
  int comp(int a, int b) => ordering[a]?.contains(b) ?? false ? -1 : 1;

  List<String> lines = getLines();
  int rulesEnd = lines.indexWhere((line) => line.isEmpty);

  lines
      .sublist(0, rulesEnd)
      .map((line) => line.split('|').map((e) => int.parse(e)).toList())
      .forEach((rule) => ordering.putIfAbsent(rule[0], Set.new).add(rule[1]));

  print(lines
      .sublist(rulesEnd + 1)
      .map((line) => line.split(",").map((s) => int.parse(s)).toList())
      .fold<int>(
          0,
          (p, e) =>
              p +
              (e.isSorted(comp) != isPart2
                  ? e.sorted(comp)[e.length >> 1]
                  : 0)));
}

load more comments
view more: next ›