Day 8: Playground ## Megathread guidelines - Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever) - You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL ## FAQ - What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268 - Where do I participate?: https://adventofcode.com/ - Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465

  • janAkali
    link
    fedilink
    arrow-up
    6
    ·
    8 months ago

    Nim

    view code
    type
      AOCSolution[T,U] = tuple[part1: T, part2: U]
      Vec3 = tuple[x,y,z: int]
      Node = ref object
        pos: Vec3
        cid: int
    
    proc dist(a,b: Vec3): float =
      sqrt(float((a.x-b.x)^2 + (a.y-b.y)^2 + (a.z-b.z)^2))
    
    proc solve(input: string, p1_limit: int): AOCSolution[int, int] =
      let boxes = input.splitLines().mapIt:
        let parts = it.split(',')
        let pos = Vec3 (parseInt parts[0], parseInt parts[1], parseInt parts[2])
        Node(pos: pos, cid: -1)
    
      var dists: seq[(float, (Node, Node))]
      for i in 0 .. boxes.high - 1:
        for j in i+1 .. boxes.high:
          dists.add (dist(boxes[i].pos, boxes[j].pos), (boxes[i], boxes[j]))
    
      var curcuits: Table[int, HashSet[Node]]
      var curcuitID = 0
    
      dists.sort(cmp = proc(a,b: (float, (Node, Node))): int = cmp(a[0], b[0]))
    
      for ind, (d, nodes) in dists:
        var (a, b) = nodes
        let (acid, bcid) = (a.cid, b.cid)
    
        if acid == -1 and bcid == -1: # new curcuit
          a.cid = curcuitID
          b.cid = curcuitID
          curcuits[curcuitId] = [a, b].toHashSet
          inc curcuitID
        elif bcid == -1: # add to a
          b.cid = acid
          curcuits[acid].incl b
        elif acid == -1: # add to b
          a.cid = bcid
          curcuits[bcid].incl a
        elif acid != bcid: # merge two curcuits
          for node in curcuits[bcid]:
            node.cid = acid
          curcuits[acid].incl curcuits[bcid]
          curcuits.del bcid
    
        if ind+1 == p1_limit:
          result.part1 = curcuits.values.toseq.map(len).sorted()[^3..^1].prod
    
        if not(acid == bcid and acid != -1): result.part2 = a.pos.x * b.pos.x
    

    Runtime: 364 ms

    Part 1:
    I compute all pairs of Euclidean distances between 3D points, sort them, then connect points into circuits, using, what I think is called a Union‑Find algorithm (circuits grow or merge). After exactly 1000 connections (including redundant ones), I take the three largest circuits and multiply their sizes.

    Part 2:
    While iterating through the sorted connections, I also calculate the product of each pair x‑coordinates. The last product is a result for part 2.

    Problems I encountered while doing this puzzle:

    • I’ve changed a dozen of data structures, before settled on curcuitIDs and ref objects stored in a HashTable (~ 40 min)
    • I did a silly mistake of mutating the fields of an object and then using new fields as keys for the HashTable (~ 20 min)
    • I am stil confused and don’t understand why do elves count already connected junction boxes (~ 40 min, had to look it up, otherwise it would be a lot more)

    Time to solve Part 1: 1 hour 56 minutes
    Time to solve Part 2: 4 minutes

    Full solution at Codeberg: solution.nim

  • strlcpy
    link
    fedilink
    English
    arrow-up
    3
    ·
    8 months ago

    the trap

    Re. what connections to process or not? That seems to be like one of those that’s either completely obvious when you implement your solution one way, and a nasty pitfall when you do it another. In this case, pre-computing the list of pairs to process vs. finding the next one when you need it.

  • strlcpy
    link
    fedilink
    arrow-up
    3
    ·
    8 months ago

    C

    Got stuck for a bit on part 1 on a silly mistake in the group (circuit) merging code, where the nodes from one group are reassigned to the other:

    for (i=0; i < nnodes; i++)
            if (nodes[i].group == pair->b->group)
                    nodes[i].group = pair->a->group;
    

    At some point in the loop pair->b.group itself is updated, from then on the check is against the new group value. Oops.

    In the end, my solution’s runtime on my (10 year old!) PC is about 160 ms for both parts, which is more than I would like, so maybe I’ll look into better set representations.

    Code
    #include <stdio.h>
    #include <stdlib.h>
    #include <inttypes.h>
    #include <assert.h>
    
    #define LEN(a)		(sizeof(a)/sizeof(*(a)))
    #define NPAIRS(x)	((x)*((x)-1)/2)
    
    #define MAXN	1024
    
    struct node { int x,y,z, group; };
    struct pair { struct node *a, *b; int64_t dist_sq; };
    struct group { int count; };
    
    static struct node nodes[MAXN];
    static struct pair pairs[NPAIRS(MAXN)];
    static struct group groups[MAXN];
    
    static int nnodes;
    static int ngroups;
    
    static int64_t
    node_dist_sq(const struct node *a, const struct node *b)
    {
    	return
    	    (int64_t)(a->x - b->x) * (a->x - b->x) +
    	    (int64_t)(a->y - b->y) * (a->y - b->y) +
    	    (int64_t)(a->z - b->z) * (a->z - b->z);
    }
    
    static int
    cmp_pairs(const void *va, const void *vb)
    {
    	const struct pair *a = va;
    	const struct pair *b = vb;
    
    	return
    	    a->dist_sq < b->dist_sq ? -1 :
    	    a->dist_sq > b->dist_sq ?  1 : 0;
    }
    
    static int
    cmp_groups_asc(const void *va, const void *vb)
    {
    	const struct group *a = va;
    	const struct group *b = vb;
    
    	return b->count - a->count;
    }
    
    static void
    merge_groups(int group_a, int group_b)
    {
    	int i;
    
    	if (group_a == group_b)
    		return;
    
    	groups[group_a].count += groups[group_b].count;
    	groups[group_b].count = 0;
    
    	for (i=0; i<nnodes; i++)
    		if (nodes[i].group == group_b)
    			nodes[i].group = group_a;
    	
    	ngroups--;
    }
    
    int
    main()
    {
    	int p1=0,p2=0, p1_limit, i,j, n,p;
    
    	for (; ; nnodes++) {
    		assert(nnodes < MAXN);
    		n = scanf(" %d,%d,%d",
    		    &nodes[nnodes].x,
    		    &nodes[nnodes].y,
    		    &nodes[nnodes].z);
    		if (n < 3)
    			break;
    		nodes[nnodes].group = nnodes;
    		groups[nnodes].count = 1;
    	}
    
    	ngroups = nnodes;
    
    	for (p=0, i=0; i<nnodes-1; i++)
    	for (j=i+1; j<nnodes; j++, p++) {
    		pairs[p].a = &nodes[i];
    		pairs[p].b = &nodes[j];
    		pairs[p].dist_sq = node_dist_sq(&nodes[i], &nodes[j]);
    	}
    
    	qsort(pairs, NPAIRS(nnodes), sizeof(*pairs), cmp_pairs);
    
    	p1_limit = nnodes <= 100 ? 10 : 1000;
    
    	for (i=0; ngroups > 1; i++) {
    		merge_groups(pairs[i].a->group, pairs[i].b->group);
    
    		if (ngroups == 1)
    			p2 = pairs[i].a->x * pairs[i].b->x;
    
    		if (i == p1_limit) {
    			qsort(groups, LEN(groups), sizeof(*groups),
    			    cmp_groups_asc);
    
    			p1 = groups[0].count *
    			     groups[1].count *
    			     groups[2].count;
    		}
    	}
    
    	printf("08: %d %d\n", p1, p2);
    }