mykl

joined 2 years ago
MODERATOR OF
[–] mykl 2 points 3 weeks ago

Thanks for sharing this. It’s always good to be reminded of the thinking process and dead ends behind the successful solutions.

I really enjoyed figuring out your C code this year, and would be very interested to see what you could do with Uiua :-)

[–] mykl 3 points 3 weeks ago

Uiua

A Christmas Day treat: a one-liner for you all to decipher!

"#####\n.####\n.####\n.####\n.#.#.\n.#...\n.....\n\n#####\n##.##\n.#.##\n...##\n...#.\n...#.\n.....\n\n.....\n#....\n#....\n#...#\n#.#.#\n#.###\n#####\n\n.....\n.....\n#.#..\n###..\n###.#\n###.#\n#####\n\n.....\n.....\n.....\n#....\n#.#..\n#.#.#\n#####"
/+♭⊞(/×<8+)∩°□°⊟ ⊕(□≡≡/+⌕@#)≠@#≡(⊢⊢). ⊜(⍉⊜∘⊸≠@\n)¬±⦷"\n\n". 
[–] mykl 4 points 3 weeks ago* (last edited 3 weeks ago)

Dart

Quick and dirty, and slightly tipsy, code.

Happy Christmas everyone!

Thanks to Eric and the team at Advent of Code, to @[email protected] and @[email protected] for giving us somewhere to share and discuss our solutions, and to everyone here for the friendly and supportive community.

See you all next year!

import 'package:collection/collection.dart';
import 'package:more/more.dart';

part1(List<String> lines) {
  var (w, h) = (lines.first.length, lines.indexOf(''));
  var (falsey: keys, truthy: locks) = (lines..insert(0, ''))
      .splitBefore((l) => l.isEmpty)
      .map((g) => [
            for (var x in 0.to(w)) [for (var y in 1.to(h + 1)) g[y][x]]
          ])
      .partition((g) => g[0][0] == '#');
  return keys
      .map((l) => locks.count((k) =>
          0.to(w).every((r) => (l[r] + k[r]).count((e) => e == '#') < 8)))
      .sum;
}
[–] mykl 1 points 3 weeks ago (1 children)

True, I've just been looking at the comments on the other place, and pretty much everyone there seems to have resorted to figuring it out manually too.

[–] mykl 1 points 3 weeks ago (3 children)

Haha, I spent about two hours fiddling about in the debugger and drawing bad diagrams to get an answer. I just checked and it turns out that I also got my personal best ranking on it too, though I'm not sure it should count :-)

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

Dart

Not very happy with this, as for part 2 the code just told me which four pairs of bits of the output needed investigation and I then tediously worked through how they differed from the correct adder implementation in the debugger.

Spoilered as it is overly long and not very interesting.

spoiler

import 'package:collection/collection.dart';
import 'package:more/more.dart';

var nodes = <String, Node>{};

class Node {
  String name = '';
  bool? state;
  var outputGates = <String>[];
}

class Wire implements Node {
  @override
  String name;
  @override
  bool? state;
  @override
  var outputGates = <String>[];
  Wire(this.name, this.state);
  set() {
    for (var g in outputGates) {
      (nodes[g]! as Gate).set();
    }
  }
}

class Gate implements Node {
  String input1, input2, type;
  @override
  String name;
  @override
  bool? state;
  @override
  var outputGates = <String>[];
  Gate(this.name, this.input1, this.input2, this.type);
  set() {
    var a = nodes[input1]!.state;
    var b = nodes[input2]!.state;
    if (a == null || b == null) return;
    state = switch (type) { 'AND' => a && b, 'OR' => a || b, _ => a ^ b };
    for (var g in outputGates) {
      (nodes[g]! as Gate).set();
    }
  }
}

void setup(List<String> lines) {
  var parts = lines.splitAfter((e) => e.isEmpty);
  Map<String, Node> wires = Map.fromEntries(parts.first.skipLast(1).map((e) {
    var p = e.split(': ');
    return MapEntry(p[0], Wire(p[0], p[1] == '1'));
  }));
  nodes = Map.fromEntries(parts.last.map((e) {
    var p = e.split(' ');
    return MapEntry(p.last, Gate(p.last, p[0], p[2], p[1]));
  }));
  nodes.addAll(wires);
  for (var g in nodes.values) {
    if (g is! Gate) continue;
    nodes[g.input1]!.outputGates.add(g.name);
    nodes[g.input2]!.outputGates.add(g.name);
  }
}

String line(String s) => nodes.keys
    .where((e) => e.startsWith(s))
    .sorted()
    .reversed
    .map((e) => nodes[e]!.state! ? '1' : '0')
    .join('');

part1(List<String> lines) {
  setup(lines);
  nodes.values.whereType<Wire>().forEach((e) => e.set());
  return int.parse(line('z'), radix: 2);
}

part2(List<String> lines) {
  setup(lines);
  var bits = nodes.keys.count((e) => e.startsWith('x'));
  for (var b in 0.to(bits)) {
    nodes.values.whereType<Gate>().forEach((e) => e.state = null);
    nodes.values.whereType<Wire>().forEach(((e) => e.state = false));
    nodes['y${b.toString().padLeft(2, "0")}']!.state = true;
    nodes.values.whereType<Wire>().forEach((e) => e.set());
    print('${line("x")}\t${line("y")}\t${line("z")}\t$b');
    var output = line('z');
    for (var i in 0.to(bits)) {
      if (output[bits - i] != ((i == b) ? "1" : "0")) print(i);
    }
  }
  tree('z05');
  tree('z06');
  // Add break point here and inspect and solve manually using `tree`.
  print('break here');
}

tree(String s, {indent = ''}) {
  var here = nodes[s]!;
  if (here is Wire) {
    print('$indent${here.name} (wire)');
    return;
  }
  here as Gate;
  print('$indent${here.name} ${here.type}');
  tree(here.input1, indent: indent + '| ');
  tree(here.input2, indent: indent + '| ');
}

 
[–] mykl 2 points 3 weeks ago

Dart

A little bit of googling, a little bit of Wikipedia, and so a little bit of

magicthe basic Bron-Kerbosch algorithm

gave me the following which takes about 300ms.

import 'package:collection/collection.dart';
import 'package:more/more.dart';

SetMultimap<String, String> getNodes(List<String> lines) {
  var nodes = SetMultimap<String, String>();
  for (var l in lines) {
    var p = l.split('-');
    nodes[p.first].add(p.last);
    nodes[p.last].add(p.first);
  }
  return nodes;
}

part1(List<String> lines) {
  var nodes = getNodes(lines);
  var ts = nodes.keys.where((e) => e.startsWith('t'));
  var ret = <String>[];
  for (var t in ts) {
    ret.addAll(nodes[t]
        .combinations(2)
        .where((e) => nodes[e.first].contains(e.last))
        .map((e) => (([t] + e)..sort()).toString()));
  }
  return ret.toSet().length;
}

part2(List<String> lines) {
  var nodes = getNodes(lines);
  Iterable<Set<String>> useBK1(
      Set<String> R, Set<String> P, Set<String> X) sync* {
    if (P.isEmpty && X.isEmpty) {
      yield R;
      return;
    }
    for (var v in P.toSet()) {
      yield* useBK1(R.toSet()..add(v), P.intersection(nodes[v]),
          X.intersection(nodes[v]));
      P.remove(v);
      X.add(v);
    }
  }
  var vs = nodes.keys.toSet()..addAll(nodes.values.deepFlatten());
  var ret = useBK1({}, vs, {}).toList();
  var max = ret.map((e) => e.length).max;
  return ret.firstWhere((e) => e.length == max).sorted().join(',');
}
[–] mykl 4 points 3 weeks ago

A messy drawer? There should be at least two: one for the kitchen and one for general.

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

Uiua

It's been a while since I posted one of these, but I thought this would be straightforward in Uiua. Turns out that bitwise operations are a bit (haha) of a pain, so the Rng operation is very slow at 4sec for live data.

I took this as an opportunity to play with the ⧈(stencil) operator which probably slowed things down too.

Data ← 1_2_3_2024
Xor  ← °⋯◿2⬚0+∩⋯ # Bitwise xor of two numbers.
Rng  ← ⊙◌◿,Xor×2048.◿,Xor⌊÷32.◿,Xor×64.⊙16777216
Runs ← ⍉(⇌[⍥(Rng.)])2000 Data # Should be constant?
Firsts ← (
  ⊟⊂0⧈₂/-.◿10 ↘¯1         # Build run, gen pair diffs
  ⊢⧈(⊟⊙⊣/(+×40+20)°⊟) 2_4 # Convert 4-diff into key, collect.
  ⊕⊢⊛⊙⍉⊙◌°⊟.⍉             # Only keep first of each key. # ⍜(map°⊟⍉⇌|∘) failed. 
)
&p /+≡⊣.Runs
&p /↥⊕(/+)+1⊛°⊟⍉/◇⊂wait≡spawn(□Firsts) # Group by key, sum prices, return highest.
[–] mykl 1 points 3 weeks ago

Great, thanks! I've always had Roassal at the back of my mind as a way of generating visualisations for AOC, but never got round to it. This might kick-start me, but maybe for next year at this point :-)

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

Dart

Well, that was certainly a bit easier than yesterday...

I know running a window over each full list of 2000 prices rather than looking for cycles etc means I'm doing a lot of unnecessary work, but it only takes a couple of seconds, so that'll do.

import 'package:collection/collection.dart';
import 'package:more/more.dart';

rng(int i) {
  i = ((i << 6) ^ i) % 16777216;
  i = ((i >> 5) ^ i) % 16777216;
  i = ((i << 11) ^ i) % 16777216;
  return i;
}

Iterable<int> getPrices(int val, int rounds) {
  var ret = [val];
  for (var _ in 1.to(rounds)) {
    ret.add(val = rng(val));
  }
  return ret.map((e) => e % 10);
}

int run(int val, int rounds) => 0.to(rounds).fold(val, (s, t) => s = rng(s));
part1(lines) => [for (var i in lines.map(int.parse)) run(i, 2000)].sum;

part2(List<String> lines) {
  var market = <int, int>{}.withDefault(0);
  for (var seed in lines.map(int.parse)) {
    var seen = <int>{};
    for (var w in getPrices(seed, 2000).window(5)) {
      var key = // Can't use lists as keys, so make cheap hash.
          w.window(2).map((e) => e[1] - e[0]).reduce((s, t) => (s << 4) + t);
      if (seen.contains(key)) continue;
      seen.add(key);
      market[key] += w.last;
    }
  }
  return market.values.max;
}
22
submitted 4 weeks ago* (last edited 4 weeks ago) by mykl to c/[email protected]
 

I wanted to get an idea of how the blocks were landing and here's some thoughts on what I came up with:

  • they were building a simple maze (duh I guess).
  • the final (blocking) block is highlighted as a tiny red dot for half a second or so (edit: now flashing!).
  • my generated path jumped about seemingly at random even when blocks landed elsewhere so I don't animate the dropping of the first 1000 blocks as it's more noise than data.
  • the ~500 blocks before the final one don't affect my path at all, so it's quite a boring end.
  • Lemmy doesn't like long animations, so I skip 10 blocks at a time.

If you want to toast your CPU for a few seconds, here's some terrible Uiua code.

Data  ← ≡◇(⊜⋕⊸≠@,)°/$"_\n_" &fras "AOC2024day18.txt"
End   ← 70_70
Count ← 1024

D₄      ← [1_0 ¯1_0 0_1 0_¯1]
Valid   ← ▽¬⊸∊:▽⊸(≡/××⊃(≤⊢End|≥0))+D₄¤
BestLen ← ⍣(-1⧻⊢path(Valid|≍End)0_0↙:Data|∞)
Chop!   ← ◌⍢(⨬(⊙◌+1|⊙⊙◌:):⟜^0⌊÷2+,,|>)

BadBlock ← -1Chop!(=∞BestLen)Count ⧻Data
Skip     ← 1000
Step     ← 10
Times    ← ⍜(-Skip|⁅⍜(÷Step|⇡⌊))+1BadBlock

# paths - will ruthlessly spawn new threads until your CPU burns.
∵(×1_1_0)wait≡spawn(°⊚°□⊢path(Valid|≍End)0_0↙:Data)Times

¤∵(×0_0_1)⬚0+↯+1End0°⊚↙Skip Data           # old blocks
+:∵(×0_1_1)≡(⬚0+↯+1End0°⊚↘Skip↙)Times¤Data # add new blocks
≡+                                         # superimpose
⊂:⍜(⊡|⋅1_0_0) ⊡BadBlock Data⊣.             # Add frame for final block.
⍥(⊂:↙¯2.)10                                # Freeze frame.
≡(▽⟜≡▽4)                                   # Scale up.
&fwa "AOC2024day18.gif"gif 60
 
24
submitted 1 month ago* (last edited 1 month ago) by mykl to c/[email protected]
 

I've had a few comments on my Uiua solutions asking how you're even supposed to understand them, so I thought I'd write a little explainer.

Uiua is a new language that uses the array programming paradigm (other languages in this family are APL, J, K, R and BQN). This approach recognises that a great deal of programming is about the manipulation and interrogation of arrays of data and so provides tools to handle arrays of data as fundamental units. So rather than building nested for-loops to access data items, you manipulate the array as a whole, e.g. to add 1 to every element of a multi-dimensional array A, you would simply write +1A. This approach not only makes some aspects of programming easier, it also means that the compiler can generate extremely efficient code, and in principle make use of massively parallel processes for further speedups (I don't know to what extent Uiua supports this). Array programming languages are very useful for people who want fast processing of large amounts of multidimensional data, e.g. audio, video, scientific data, or financial data.

There are three factors that can make Uiua code hard to understand.

First, as we have already seen, Uiua is an array programming language, which not only mean that it uses a very different approach to problem solving, but that it also inherits that family of language's love of using glyphs for operator names rather than ascii names. Using the Uiua online editor and learning the documentation are the only ways to deal with this massive barrier :-(

Second, Uiua is stack based, so values are normally held on the stack rather than in named variables. This introduces some of the same challenges as writing in Factor, Forth etc, where you have to build up a mental model of what's on the stack at any time. There are variables available, but idiomatic code tends to avoid them as far as possible.

Third, function application is generally in mathematic order i.e. right-to-left. This can be complicated by some operators having different numbers of arguments which affect the binding order, but you learn to see through that…

Okay, so given all of that, how does one interpret some Uiua code? Let's work though my solution to day seven.

Data   ← ⊜(□⊜⋕⊸(¬∈": "))⊸≠@\n "190: 10 19\n3267: 81 40 27\n83: 17 5\n156: 15 6\n7290: 6 8 6 15\n161011: 16 10 13\n192: 17 8 14\n21037: 9 7 18 13\n292: 11 6 16 20"
Calib! ← ≡◇⊢▽⊸≡◇(∈♭/[^0]:°⊂) # Calibration targets which can be constructed from their values.
&p/+Calib!⊃(+|×)Data
&p/+Calib!⊃(+|×|+×ⁿ:10+1⌊ₙ₁₀,)Data

What the heck does it all mean?

Let’s find out! We'll go through it step by step.

Line 1

Data ← ⊜(□⊜⋕⊸(¬∈": "))⊸≠@\n "190: 10 19\n3267: 81 …. a string ….”

⊜(…)⊸≠@\n Can be read as partition () the string by building an array of sub-strings where each char is not \n (≠@\n) then perform the first function (i.e. the code in parentheses) on each sub-string in turn, concatenating each of the results into an array.

□⊜⋕⊸(¬∈": ") For each of these sub-strings, we immediately re-partition it by only keeping those characters that are not in string “: “, and then for each of these resulting (sub-sub-)strings, parse it as a number (). As each of the lines has a different number of entries, the outer partition would not be able to build a regular array, so we box () each line before passing it out to hide the contents away, allowing the outer array to be built successfully.

So at this point, Data has been defined as a constant looking something like [[190 10 19] [3267 81 40 27] …etc… ]

Line 2

Calib! ← ≡◇⊢▽⊸≡◇(∈♭/[^0]:°⊂)

Calib! is a ‘macro’, that is a function that takes a function as its first argument and then inserts it into its own body whenever it sees a ^0

▽⊸≡◇(…) Means only keep those lines of the array that meet a certain condition (▽⊸≡◇ reads as “keep by rows content” where 'content" means 'look inside the box').

∈♭/[^0]:°⊂ This is where the power of an array programming language really starts to show.

For each row of the input this is passed the numbers in that row as an array. First we remove the first entry (our target) and push it down the stack for later :°⊂.

Then we reduce (/) the rest of the array by repeatedly applying a function on the result so far and the next element. The function here is partially suppled by the macro substitution, so for part 1 this would be in full [⊃(+|×)] This means ’take the two arguments and fork (), or perform two functions on them, wrapping the results in an array [...].

Some magic happens here: Uiua supports ‘pervasive’ application of functions, so executing + 5 [[1 2] [3 4]] gives [[6 7] [8 9]]. For each succeeding entry in the list of numbers, we’re adding, and multiplying (and concatenating for part 2) it to every existing result, and storing these new results in a new dimension of the accumulated array.

Finally, we flatten () this monstrous array into a single list of numbers and check whether that target value is in this list (member ) . That true/false is passed out to the surrounding ‘keep’ functionality.

≡◇⊢ Now we have kept only the lines where the target can be calculated from the numbers. So all we have to do is pass back the ≡◇⊢ “rows contents first” i.e. the first number in each line.

Lines 3 and 4

&p/+Calib!⊃(+|×)Data
&p/+Calib!⊃(+|×|+×ⁿ:10+1⌊ₙ₁₀,)Data

Call the macro on the data array, with the two different sets of operators. /+ is reduce (/) by addition (i.e. sum the results).

+×ⁿ:10+1⌊ₙ₁₀, Takes a copy of the second number, get the floor of the base-10 log, adds 1, raises 10 to that power and multiplies the first number by that before adding the second number. This is an arithmetic concatenation, which works as a pervasive operator as discussed above. [N.B. I just couldn't get ⋕$"__" or similar string approaches to work in this context. If you know how to, please let me know!]

That's it. Problem seven dealt with in 72 tokens :-)

Next steps

If you want to learn more run the code, read the Uiua language tour, explore that website and documentation, or ask away here. I'm by no mean an expert, but I'm happy to help with the basics.

 

I created this group last year but soon found that there was a lot more active group on programming.dev with many more participants and even a private leaderboard, so you should probably head on over there!

Happy coding everyone!

 
 

Am I in danger?

 

Thanks Homer.

8
Day 1 solutions (adventofcode.com)
submitted 1 year ago* (last edited 1 year ago) by mykl to c/adventofcode
 

How was day one for everyone? I was surprised at how tricky it was to get the right answer for part two. Not the usual easy start I've seen in the past. I'd be interested to see any solutions here.

 

Hi All, I posted here recently that I was spending November revisiting my AOC 2022 solutions (written in Dart) and posting them for reading, running and editing online.

With my last solution being posted yesterday, the series is now complete, and might be interesting if anyone's looking for an idea of the level of difficulty involved in a typical year.

To ensure that this post isn't just about posts on other communities, I've added a little bonus content - a simple visualisation I created for my solution for day 14 (flowing sand).

 

As a warmup for this year's Advent of Code, I'm re-reading and posting my solutions to last year's challenges. You can read, run and edit today's solution by following the post link.

Today was the final challenge for the year, and as usual was quite simple and had only one part. This involved parsing and process numbers written in what I learned was called "balanced base five" notation.

Thanks for following along, now we just need to wait a few days to see what this year holds!

 

As a warmup for this year's Advent of Code, I'm re-reading and posting my solutions to last year's challenges. You can read, run and edit today's solution by following the post link.

Today had us running round a maze with moving obstacles. Treating time as a dimension allowed me to build a graph and find the shortest path. Part 2 just required doing this three times. This took me closer than I like to a full second runtime, but not close enough to make me re-think my solution.

view more: next ›