mykl

joined 2 years ago
MODERATOR OF
[–] mykl 5 points 8 hours ago (1 children)

Rust on a DS? What a time to be alive.

[–] mykl 2 points 8 hours ago

I followed the link for Spectral Decomposition, and noped right out when I saw the banner "This article may be too technical for most readers to understand."

Great list though, thanks!

[–] mykl 1 points 9 hours ago* (last edited 8 hours ago)

1.8s now. 99% of that in Sides. I've just had an idea though... ~~maybe too late for today though!~~

edit: prepending β‰‘βš(-€⊸/↧) toFields spared me from manipulating hundreds of irrelevant 0's, so time is very good now at 55ms.

[–] mykl 3 points 9 hours ago

Oh, good catch. That's certainly the case if an initial value of 0 correctly generates the required value of the quine. As I'd already started running some code against the live data that's what I tested against, and so it's only when I just tested it against the example data that I saw the problem.

I have changed the for-loop to read for (var a in (top ? 1 : 0).to(8)) for maximum terseness :-)

That still works for the example and my live data, and I don't think there should be a valid solution that relies on the first triplet being 0. Thanks for your reply!

[–] mykl 2 points 13 hours ago* (last edited 13 hours ago) (2 children)

Ooh, interesting, I'll have to give that a try. Thanks!

(edit) Wow, that replaced my three lines of overly complex code without a hitch. classify is an operator I never really got the point of before. Beautiful.

Data ← βŠœβˆ˜βŠΈβ‰ @\n "AAAA\nBBCD\nBBCC\nEEEC"
Nβ‚„     ← [Β―1_0 1_0 0_Β―1 0_1]               # Four orthogonal neighbours.
Fences ← /+≑(/+=0⬚0⊑+Nβ‚„Β€)βŠ™Β€βŠš.°⊚            # Fences for a field, by looking for edges.
Cs     ← [0 1 1 0 1 0 2 1 1 2 0 1 0 1 1 0] # Number of corners keyed by bitarray of 2x2 grid.
Sides  ← /+/+⧈(⊑:CsΒ°β‹―β™­)2_2βŒβ†˜Β―1_Β―1βŒβ†˜1_1°⊚   # Add border, look for corners in 2x2 windows.

Fields ← βŠœβ–‘:⇑△.+1βœβ™­βŠ›Data

/+Γ—β‰‘β—‡βŠƒβ§»Fences Fields
/+Γ—β‰‘β—‡βŠƒβ§»Sides Fields
[–] mykl 2 points 17 hours ago* (last edited 8 hours ago) (2 children)

Dart

Part one was an exercise in building a simple OpCode machine. Part two was trickier. It was clear that the a register was repeatedly being divided by 8, and testing a few values showed that each 3-bits of the initial value defined one entry in the output, so I built a recursive routine that brute-forced each one in turn. Only <1ms to run though, so not that brutal.

(It's worth pointing out that at some stages multiple 3-bit values gave rise to the required value, causing a failure to resolve later on if not checked.)

(edit: for-loop in buildQuine now avoids using a 0 for the initial triplet, as this should not be a valid value, and if it turns out to generate the required digit of the quine, you will get an infinite recursion. Thanks to SteveDinn for bringing this to my attention.)

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

var prog = <int>[];
typedef State = (int, int, int);
State parse(List<String> lines) {
  var regs = lines.takeTo('').map((e) => int.parse(e.split(' ').last)).toList();
  var (a, b, c) = (regs[0], regs[1], regs[2]);
  prog = lines.last.split(' ').last.split(',').map(int.parse).toList();
  return (a, b, c);
}

List<int> runProg(State rec) {
  var (int a, int b, int c) = rec;
  combo(int v) => switch (v) { 4 => a, 5 => b, 6 => c, _ => v };
  var output = <int>[], pc = 0;
  while (pc < prog.length) {
    var code = prog[pc], arg = prog[pc + 1];
    var _ = switch (code) {
      0 => a ~/= pow(2, combo(arg)),
      1 => b ^= arg,
      2 => b = combo(arg) % 8,
      3 => (a != 0) ? (pc = arg - 2) : 0,
      4 => b ^= c, //ignores arg
      5 => output.add(combo(arg) % 8),
      6 => b = a ~/ pow(2, combo(arg)),
      7 => c = a ~/ pow(2, combo(arg)),
      _ => 0
    };
    pc += 2;
  }
  return output;
}

Function eq = const ListEquality().equals;
Iterable<int> buildQuine(State rec, List<int> quine, [top = false]) sync* {
  var (int a0, int b0, int c0) = rec;
  if (top) a0 = 0;
  if (quine.length == prog.length) {
    yield a0;
    return;
  }
  for (var a in (top ? 1 : 0).to(8)) {
    var newState = (a0 * 8 + a, b0, c0);
    var newQuine = runProg(newState);
    if (eq(prog.slice(prog.length - newQuine.length), newQuine)) {
      yield* buildQuine(newState, newQuine);
    }
  }
}

part1(List<String> lines) => runProg(parse(lines)).join(',');

part2(List<String> lines) => buildQuine(parse(lines), [], true).first;
[–] mykl 3 points 1 day ago

Very interesting approach. Pruning deadends by spawning additional walls is a very clever idea.

[–] mykl 5 points 1 day ago (1 children)

Tell everyone that I’ve just watched a Jordan Peterson video and I’m going down the rabbit hole and ask them if they know where I can get my hands on some ketamine. My irrational behaviour and urgent need for fast access to my funds would then seem pretty par for the course.

[–] mykl 2 points 1 day ago* (last edited 1 day ago)

Dart

I liked the flexibility of the path operator in the Uiua solution so much that I built a similar search function in Dart. Not quite as compact, but still an interesting piece of code that I will keep on hand for other path-finding puzzles.

About 80 lines of code, about half of which is the super-flexible search function.

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

List<Point<num>> d4 = [Point(1, 0), Point(-1, 0), Point(0, 1), Point(0, -1)];

/// Returns cost to destination, plus list of routes to destination.
/// Does Dijkstra/A* search depending on whether heuristic returns 1 or
/// something better.
(num, List<List<T>>) aStarSearch<T>(T start, Map<T, num> Function(T) fNext,
    int Function(T) fHeur, bool Function(T) fAtEnd) {
  var cameFrom = SetMultimap<T, T>.fromEntries([MapEntry(start, start)]);

  var ends = <T>{};
  var front = PriorityQueue<T>((a, b) => fHeur(a).compareTo(fHeur(b)))
    ..add(start);
  var cost = <T, num>{start: 0};
  while (front.isNotEmpty) {
    var here = front.removeFirst();
    if (fAtEnd(here)) {
      ends.add(here);
      continue;
    }
    var ns = fNext(here);
    for (var n in ns.keys) {
      var nCost = cost[here]! + ns[n]!;
      if (!cost.containsKey(n) || nCost < cost[n]!) {
        cost[n] = nCost;
        front.add(n);
        cameFrom.removeAll(n);
      }
      if (cost[n] == nCost) cameFrom[n].add(here);
    }
  }

  Iterable<List<T>> routes(T h) sync* {
    if (h == start) {
      yield [h];
      return;
    }
    for (var p in cameFrom[h]) {
      yield* routes(p).map((e) => e + [h]);
    }
  }

  var minCost = ends.map((e) => cost[e]!).min;
  ends = ends.where((e) => cost[e]! == minCost).toSet();
  return (minCost, ends.fold([], (s, t) => s..addAll(routes(t).toList())));
}

typedef PP = (Point, Point);

(num, List<List<PP>>) solve(List<String> lines) {
  var grid = {
    for (var r in lines.indexed())
      for (var c in r.value.split('').indexed().where((e) => e.value != '#'))
        Point<num>(c.index, r.index): c.value
  };
  var start = grid.entries.firstWhere((e) => e.value == 'S').key;
  var end = grid.entries.firstWhere((e) => e.value == 'E').key;
  var dir = Point<num>(1, 0);

  fHeur(PP pd) => 1; // faster than euclidean distance.
  fNextAndCost(PP pd) => <PP, int>{
        for (var n in d4
            .where((n) => n != pd.last * -1 && grid.containsKey(pd.first + n)))
          (pd.first + n, n): ((n == pd.last) ? 1 : 1001) // (Point, Dir) : Cost
      };
  fAtEnd(PP pd) => pd.first == end;

  return aStarSearch<PP>((start, dir), fNextAndCost, fHeur, fAtEnd);
}

part1(List<String> lines) => solve(lines).first;

part2(List<String> lines) => solve(lines)
    .last
    .map((l) => l.map((e) => e.first).toSet())
    .flattenedToSet
    .length;
[–] mykl 3 points 1 day ago* (last edited 1 day ago) (1 children)

Uiua

Uiua's new builtin path operator makes this a breeze. Given a function that returns valid neighbours for a point and their relative costs, and another function to test whether you have reached a valid goal, it gives the minimal cost, and all relevant paths. We just need to keep track of the current direction as we work through the maze.

(edit: forgot the Try It Live! link)

Data  ← ≑°░°/$"_\n_" "#################\n#...#...#...#..E#\n#.#.#.#.#.#.#.#^#\n#.#.#.#...#...#^#\n#.#.#.#.###.#.#^#\n#>>v#.#.#.....#^#\n#^#v#.#.#.#####^#\n#^#v..#.#.#>>>>^#\n#^#v#####.#^###.#\n#^#v#..>>>>^#...#\n#^#v###^#####.###\n#^#v#>>^#.....#.#\n#^#v#^#####.###.#\n#^#v#^........#.#\n#^#v#^#########.#\n#S#>>^..........#\n#################"
Dβ‚„    ← [1_0 Β―1_0 0_1 0_Β―1]
End   ← ⊒⊚=@EData
Costs ← :βˆ©β–½βŸœ:≑(β‰ @#⊑:Data⊒).β‰‘βŠŸβŠ™βŸœ(+1Γ—1000¬≑/Γ—=)+⟜:Dβ‚„βˆ©Β€Β°βŠŸ
path(Costs|≍EndβŠ™β—ŒΒ°βŠŸ)⊟:1_0⊒⊚=@SData
&p β§»β—΄β‰‘βŠ’/β—‡βŠ‚ &p :

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

Dart

canMove does a recursive search and returns all locations that need moving, or none if there's an obstacle anywhere downstream. For part2, that involves checking if there's half of a box in front of us, and if so ensuring that we also check the other half of that box. I don't bother tracking whether we're double-checking as it runs fast enough as is.

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

var d4 = <Point<num>>[Point(1, 0), Point(-1, 0), Point(0, 1), Point(0, -1)];
var m4 = '><v^';

solve(List<String> lines, {wide = false}) {
  if (wide) {
    lines = lines
        .map((e) => e
            .replaceAll('#', '##')
            .replaceAll('.', '..')
            .replaceAll('O', '[]')
            .replaceAll('@', '@.'))
        .toList();
  }
  var room = {
    for (var r in lines.takeWhile((e) => e.isNotEmpty).indexed())
      for (var c in r.value.split('').indexed().where((c) => (c.value != '.')))
        Point<num>(c.index, r.index): c.value
  };
  var bot = room.entries.firstWhere((e) => e.value == '@').key;
  var moves = lines.skipTo('').join('').split('');
  for (var d in moves.map((m) => d4[m4.indexOf(m)])) {
    if (didMove(d, bot, room)) bot += d;
  }
  return room.entries
      .where((e) => e.value == '[' || e.value == 'O')
      .map((e) => e.key.x + 100 * e.key.y)
      .sum;
}

bool didMove(Point m, Point here, Map<Point, String> room) {
  var moves = canMove(m, here, room).toSet();
  if (moves.isNotEmpty) {
    var vals = moves.map((e) => room.remove(e)!).toList();
    for (var ms in moves.indexed()) {
      room[ms.value + m] = vals[ms.index];
    }
    return true;
  }
  return false;
}

List<Point> canMove(Point m, Point here, Map<Point, String> room) {
  if (room[here + m] == '#') return [];
  if (!room.containsKey(here + m)) return [here];
  var cm1 = canMove(m, here + m, room);
  if (m.x != 0) return (cm1.isEmpty) ? [] : cm1 + [here];

  List<Point> cm2 = [here];
  if (room[here + m] == '[') cm2 = canMove(m, here + m + Point(1, 0), room);
  if (room[here + m] == ']') cm2 = canMove(m, here + m - Point(1, 0), room);

  return cm1.isEmpty || cm2.isEmpty ? [] : cm1 + cm2 + [here];
}
[–] mykl 2 points 3 days ago* (last edited 3 days ago)

Uiua

Pretty much just a transcription of my Dart solution.

Data  ← ⊜(⊜(βŠœβ‹•βŠΈβˆˆ"0123456789")βŠΈβ‰ @\n)⊸(Β¬β¦·"\n\n")"Button A: X+94, Y+34\nButton B: X+22, Y+67\nPrize: X=8400, Y=5400\n\nButton A: X+26, Y+66\nButton B: X+67, Y+21\nPrize: X=12748, Y=12176\n\nButton A: X+17, Y+86\nButton B: X+84, Y+37\nPrize: X=7870, Y=6450\n\nButton A: X+69, Y+23\nButton B: X+27, Y+71\nPrize: X=18641, Y=10279"
IsInt ← <0.00001⌡-⁅.
AB    ← Γ·Β°βŠ‚β‰‘(/-Γ—β‡ŒΒ°βŠŸ)⊏[0_1 2_1 0_2]
Cost  ← /+Γ—IsInt.Γ—3_1AB
&p /+≑Cost Data
&p /+≑(Cost⍜(⊑2|+1e13))Data
 
24
submitted 1 week ago* (last edited 1 week 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.

 

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 a kinda variant of Conway's Game of Life: we had to move elves around according to a set of rules while avoiding collisions and observe the size of the bounding box after 10 rounds. Part 2 asked us to run until the pattern stagnated. Nothing clever in my solution as most of the challenge seemed to be in understanding the rules correctly :-)

 

The [email protected] community is currently without an active mod: the original creator does not seem to have been active on Lemmy in months. I PM’d them to check whether they were still interested in the community and have received no reply.

I'm presently more or less the only active poster there, though that may change next month when this year's Advent of Code kicks off.

view more: next β€Ί