Just another Swedish programming sysadmin person.
Coffee is always the answer.

And beware my spaghet.

  • 54 Posts
  • 230 Comments
Joined 2 years ago
cake
Cake day: June 11th, 2023

help-circle



  • Again, it works until it requires reloading, i.e. the next update of any component or the next restart of the server.

    I’m also running an inode cache on the client side, on top of the persistent opcache, but due to the sheer number of files that Nextcloud consists of it still generates a frankly ridiculous amount of calls when it needs to invalidate the cache. If you’re running on local drives then that’s likely much less of an issue, regardless of what kind of drive it is, but this is hosted on machines that do not have any local storage.















  • Ended up oversleeping somewhat, so I did the first part on the way to work using flood fills over a global visited set, and now that work’s over I’ve sat down to expand that solution to do corner counting for part two as well.

    C#
    char[] map = new char[0];
    (int X, int Y) size = (0, 0);
    
    public void Input(IEnumerable<string> lines)
    {
      map = string.Concat(lines).ToCharArray();
      size = (lines.First().Length, lines.Count());
    }
    
    Dictionary<HashSet<(int,int)>,int> areas = new Dictionary<HashSet<(int,int)>,int>();
    public void PreCalc()
    {
      HashSet<(int, int)> visited = new HashSet<(int, int)>();
      for (int y = 0; y < size.Y; ++y)
        for (int x = 0; x < size.X; ++x)
        {
          var at = (x, y);
          if (visited.Contains(at))
            continue;
    
          var area = Flood((x, y), visited);
          areas[area.Area] = area.Perim;
        }
    }
    
    public void Part1()
    {
      int sum = areas.Select(kv => kv.Key.Count * kv.Value).Sum();
    
      Console.WriteLine($"Fencing total: {sum}");
    }
    public void Part2()
    {
      int sum = areas.Select(kv => kv.Key.Count * countCorners(kv.Key)).Sum();
    
      Console.WriteLine($"Fencing total: {sum}");
    }
    
    readonly (int dX, int dY)[] links = new[] { (1, 0), (0, 1), (-1, 0), (0, -1) };
    (HashSet<(int,int)> Area, int Perim) Flood((int X, int Y) from, HashSet<(int X, int Y)> visited)
    {
      char at = map[from.Y * size.X + from.X];
    
      (HashSet<(int,int)> Area, int Perim) ret = (new HashSet<(int,int)>(), 0);
      visited.Add(from);
      ret.Area.Add(from);
    
      foreach (var link in links)
      {
        (int X, int Y) newAt = (from.X + link.dX, from.Y + link.dY);
        char offset ;
        if (newAt.X < 0 || newAt.X >= size.X || newAt.Y < 0 || newAt.Y >= size.Y)
          offset = '\0';
        else
          offset = map[newAt.Y * size.X + newAt.X];
    
        if (offset == at)
        {
          if (visited.Contains(newAt))
            continue;
    
          var nextArea = Flood(newAt, visited);
          ret.Area.UnionWith(nextArea.Area);
          ret.Perim += nextArea.Perim;
        }
        else
        {
          ret.Perim += 1;
        }
      }
    
      return ret;
    }
    
    readonly (int dX, int dY)[] cornerPoints = new[] { (0, 0), (1, 0), (1, 1), (0, 1) };
    readonly int[] diagonalValues = new[] { (2 << 0) + (2 << 2), (2 << 1) + (2 << 3) };
    int countCorners(HashSet<(int X, int Y)> points)
    {
      int corners = 0;
      var bounds = findBounds(points);
      for (int y = bounds.minY - 1; y < bounds.maxY + 1; ++y)
        for (int x = bounds.minX - 1; x < bounds.maxX + 1; ++x)
        {
          var atPoint = cornerPoints.Select(c => points.Contains((x + c.dX, y + c.dY)));
          var before = corners;
          if (atPoint.Where(c => c).Count() % 2 == 1)
            corners++;
          else if (diagonalValues.Contains(atPoint.Select((c, i) => c ? (2 << i) : 0).Sum()))
            corners += 2;
        }
    
      return corners;
    }
    
    (int minX, int minY, int maxX, int maxY) findBounds(HashSet<(int X, int Y)> points)
    {
      (int minX, int minY, int maxX, int maxY) ret = (int.MaxValue, int.MaxValue, int.MinValue, int.MinValue);
      foreach (var point in points)
      {
        ret.minX = Math.Min(ret.minX, point.X);
        ret.minY = Math.Min(ret.minY, point.Y);
        ret.maxX = Math.Max(ret.maxX, point.X);
        ret.maxY = Math.Max(ret.maxY, point.Y);
      }
    
      return ret;
    }
    


  • And now we get into the days where caching really is king. My first attempt didn’t go so well, I tried to handle the full list result as one cache step, instead of individually caching the result of calculating each stone per step.

    I think my original attempt is still calculating at home, but I finished up this much better version on the trip to work.
    All hail public transport.

    C#
    List<long> stones = new List<long>();
    public void Input(IEnumerable<string> lines)
    {
      stones = string.Concat(lines).Split(' ').Select(v => long.Parse(v)).ToList();
    }
    
    public void Part1()
    {
      var expanded = TryExpand(stones, 25);
    
      Console.WriteLine($"Stones: {expanded}");
    }
    public void Part2()
    {
      var expanded = TryExpand(stones, 75);
    
      Console.WriteLine($"Stones: {expanded}");
    }
    
    public long TryExpand(IEnumerable<long> stones, int steps)
    {
      if (steps == 0)
        return stones.Count();
      return stones.Select(s => TryExpand(s, steps)).Sum();
    }
    Dictionary<(long, int), long> cache = new Dictionary<(long, int), long>();
    public long TryExpand(long stone, int steps)
    {
      var key = (stone, steps);
      if (cache.ContainsKey(key))
        return cache[key];
    
      var result = TryExpand(Blink(stone), steps - 1);
      cache[key] = result;
      return result;
    }
    
    public IEnumerable<long> Blink(long stone)
    {
      if (stone == 0)
      {
        yield return 1;
        yield break;
      }
      var str = stone.ToString();
      if (str.Length % 2 == 0)
      {
        yield return long.Parse(str[..(str.Length / 2)]);
        yield return long.Parse(str[(str.Length / 2)..]);
        yield break;
      }
      yield return stone * 2024;
    }
    

  • Nice to have a really simple one for a change, both my day 1 and 2 solutions worked on their very first attempts.
    I rewrote the code to combine the two though, since the implementations were almost identical for both solutions, and also to replace the recursion with a search list instead.

    C#
    int[] heights = new int[0];
    (int, int) size = (0, 0);
    
    public void Input(IEnumerable<string> lines)
    {
      size = (lines.First().Length, lines.Count());
      heights = string.Concat(lines).Select(c => int.Parse(c.ToString())).ToArray();
    }
    
    int trails = 0, trailheads = 0;
    public void PreCalc()
    {
      for (int y = 0; y < size.Item2; ++y)
        for (int x = 0; x < size.Item1; ++x)
          if (heights[y * size.Item1 + x] == 0)
          {
            var unique = new HashSet<(int, int)>();
            trails += CountTrails((x, y), unique);
            trailheads += unique.Count;
          }
    }
    
    public void Part1()
    {
      Console.WriteLine($"Trailheads: {trailheads}");
    }
    public void Part2()
    {
      Console.WriteLine($"Trails: {trails}");
    }
    
    int CountTrails((int, int) from, HashSet<(int,int)> unique)
    {
      int found = 0;
    
      List<(int,int)> toSearch = new List<(int, int)>();
      toSearch.Add(from);
    
      while (toSearch.Any())
      {
        var cur = toSearch.First();
        toSearch.RemoveAt(0);
    
        int height = heights[cur.Item2 * size.Item1 + cur.Item1];
        for (int y = -1; y <= 1; ++y)
          for (int x = -1; x <= 1; ++x)
          {
            if ((y != 0 && x != 0) || (y == 0 && x == 0))
              continue;
    
            var newAt = (cur.Item1 + x, cur.Item2 + y);
            if (newAt.Item1 < 0 || newAt.Item1 >= size.Item1 || newAt.Item2 < 0 || newAt.Item2 >= size.Item2)
              continue;
    
            int newHeight = heights[newAt.Item2 * size.Item1 + newAt.Item1];
            if (newHeight - height != 1)
              continue;
    
            if (newHeight == 9)
            {
              unique.Add(newAt);
              found++;
              continue;
            }
    
            toSearch.Add(newAt);
          }
      }
    
      return found;
    }