5 Commity 6e0e22182e ... b973ca5c35

Autor SHA1 Wiadomość Data
  Aleksei Dorokhov b973ca5c35 Solved day12a 7 miesięcy temu
  Aleksei Dorokhov 7f7bad41f9 added filling in small spaces 7 miesięcy temu
  Aleksei Dorokhov cd39576076 Converted to i32 for some calculations 7 miesięcy temu
  Aleksei Dorokhov f58a1f3634 day12 works on example input 7 miesięcy temu
  Aleksei Dorokhov 246d7ac7a6 Started solving day12 7 miesięcy temu
9 zmienionych plików z 784 dodań i 0 usunięć
  1. 3 0
      day12/.gitignore
  2. 109 0
      day12/area.zig
  3. 158 0
      day12/areacache.zig
  4. 13 0
      day12/build.zig
  5. 4 0
      day12/coord.zig
  6. 177 0
      day12/day12a.zig
  7. 101 0
      day12/input.zig
  8. 110 0
      day12/shape.zig
  9. 109 0
      day12/unionfind.zig

+ 3 - 0
day12/.gitignore

@@ -0,0 +1,3 @@
+input.txt
+.zig-cache
+zig-out

+ 109 - 0
day12/area.zig

@@ -0,0 +1,109 @@
+const std = @import("std");
+
+const shapeModule = @import("shape.zig");
+const Shape = shapeModule.Shape;
+const kShapeSize = shapeModule.kShapeSize;
+
+pub const Area = struct {
+    rows: i32 = 0,
+    cols: i32 = 0,
+    data: []u8 = undefined,
+    dataAllocator: std.mem.Allocator,
+
+    pub fn init(allocator: std.mem.Allocator, rows: i32, cols: i32) !Area {
+        var area: Area = .{
+            .rows = rows,
+            .cols = cols,
+            .data = try allocator.alloc(u8, @intCast(rows*cols)),
+            .dataAllocator = allocator,
+        };
+
+        for (0 .. area.data.len) |i| {
+            area.data[i] = '.';
+        }
+        return area;
+    }
+
+    pub fn deinit(self: *const Area) void {
+        self.dataAllocator.free(self.data);
+    }
+
+    pub fn copy(self: *const Area, allocator: std.mem.Allocator) !Area {
+        const area: Area = .{
+            .rows = self.rows,
+            .cols = self.cols,
+            .data = try allocator.alloc(u8, @intCast(self.rows*self.cols)),
+            .dataAllocator = allocator,
+        };
+        std.mem.copyForwards(u8, area.data, self.data);
+        return area;
+    }
+
+    pub fn totalFreeSize(self: *const Area) usize {
+        var freeSize:usize = 0;
+        for (self.data) |d| {
+            if (d == '.') {
+                freeSize += 1;
+            }
+        }
+        return freeSize;
+    }
+
+    pub fn getCell(self: *const Area, row: i32, col: i32) u8 {
+        return self.data[@intCast(row*self.cols + col)];
+    }
+
+    pub fn setCell(self: *Area, row: i32, col: i32, cell: u8) void {
+        self.data[@intCast(row*self.cols + col)] = cell;
+    }
+
+    pub fn print(self: *const Area, allocator: std.mem.Allocator) !void {
+        const rows:usize = @intCast(self.rows);
+        const cols:usize = @intCast(self.cols);
+        for (0..rows) |row| {
+            for (0..cols) |col| {
+                const cell = self.getCell(@intCast(row), @intCast(col));
+                try std.fs.File.stdout().writeAll(
+                  try std.fmt.allocPrint(allocator, "{c}", .{cell}));
+        
+            }
+            try std.fs.File.stdout().writeAll("\n");
+        }
+    }
+
+    pub fn appendShape(self: * const Area, allocator: std.mem.Allocator,
+                       shape: *const Shape, row: i32, col: i32, shapeSymbol: u8) !?Area {
+        var area = try self.copy(allocator);
+        for (0..kShapeSize) |shapeRow| {
+            for (0..kShapeSize) |shapeCol| {
+                if (shape.getCell(@intCast(shapeRow), @intCast(shapeCol)) == '.') {
+                    continue;
+                }
+
+                var areaRow:i32 = @intCast(shapeRow);
+                areaRow += row;
+                if (areaRow >= area.rows) {
+                    area.deinit();
+                    return null;
+                }
+
+                var areaCol:i32 = @intCast(shapeCol);
+                areaCol += col;
+                if (areaCol >= area.cols) {
+                    area.deinit();
+                    return null;
+                }
+
+                if (area.getCell(areaRow, areaCol) != '.') {
+                    area.deinit();
+                    return null;
+                }
+
+                area.setCell(areaRow, areaCol, shapeSymbol);
+            }
+        }
+
+        return area;
+    }
+};
+

+ 158 - 0
day12/areacache.zig

@@ -0,0 +1,158 @@
+const std = @import("std");
+
+const areaModule = @import("area.zig");
+
+const Area = areaModule.Area;
+
+pub const AreaHashContext = struct {
+    pub fn hash(ctx: AreaHashContext, key: Area) u64 {
+        _ = ctx;
+
+        var hasher = std.hash.Wyhash.init(0);
+        hasher.update(std.mem.asBytes(&key.rows));
+        hasher.update(std.mem.asBytes(&key.cols));
+        hasher.update(key.data);
+        return hasher.final();
+    }
+
+    pub fn eql(ctx: AreaHashContext, a: Area, b: Area) bool {
+        _ = ctx;
+
+        if (a.rows != b.rows) {
+            return false;
+        }
+
+        if (a.cols != b.cols) {
+            return false;
+        }
+
+        return std.mem.eql(u8, a.data, b.data);
+    }
+};
+
+const AreaCacheEntry = struct {
+    possibleShapeCounts: std.ArrayList([]i32) = std.ArrayList([]i32).empty,
+    impossibleShapeCounts: std.ArrayList([]i32) = std.ArrayList([]i32).empty,
+};
+
+const AreaHashMap = std.hash_map.HashMap(Area, AreaCacheEntry, AreaHashContext,
+                                         std.hash_map.default_max_load_percentage);
+
+pub const AreaCacheResult = enum {
+    kNotFound,
+    kPossible,
+    kImpossible
+};
+
+fn matchesPossible(shapeCounts: []i32, possibleShapeCounts: [][]i32) bool {
+    for (possibleShapeCounts) |possibleShapeCount| {
+        var isPossible = true;
+
+        for (0 .. shapeCounts.len) |i| {
+            if (shapeCounts[i] > possibleShapeCount[i]) {
+                isPossible = false;
+                break;
+            }
+        }
+
+        if (isPossible) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+fn matchesImpossible(shapeCounts: []i32, impossibleShapeCounts: [][]i32) bool {
+    for (impossibleShapeCounts) |impossibleShapeCount| {
+        var isImpossible = true;
+
+        for (0 .. shapeCounts.len) |i| {
+            if (shapeCounts[i] < impossibleShapeCount[i]) {
+                isImpossible = false;
+                break;
+            }
+        }
+
+        if (isImpossible) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+
+pub const AreaCache = struct {
+    childAllocator: std.mem.Allocator,
+    arenaAllocator: *std.heap.ArenaAllocator,
+    allocator: std.mem.Allocator,
+    context: AreaHashContext,
+    entries: AreaHashMap,
+    hitStat: u64,
+    totalStat: u64,
+
+
+
+    pub fn init(childAllocator: std.mem.Allocator) !AreaCache {
+        var areaCache: AreaCache = undefined;
+        areaCache.childAllocator = childAllocator;
+        areaCache.arenaAllocator = try childAllocator.create(std.heap.ArenaAllocator);
+        areaCache.arenaAllocator.* = std.heap.ArenaAllocator.init(childAllocator);
+        areaCache.allocator = areaCache.arenaAllocator.allocator();
+        areaCache.context = .{};
+        areaCache.entries = AreaHashMap.initContext(areaCache.allocator, areaCache.context);
+        areaCache.hitStat = 0;
+        areaCache.totalStat = 0;
+
+        return areaCache;
+    }
+
+    pub fn deinit(self: *const AreaCache) void {
+        self.arenaAllocator.deinit();
+        self.childAllocator.destroy(self.arenaAllocator);
+    }
+
+    pub fn addPossibleCacheEntry(self: *AreaCache, area: *const Area, shapeCounts: []i32) !void {
+        var result = try self.entries.getOrPut(area.*);
+        if (!result.found_existing) {
+            result.key_ptr.data = try self.allocator.alloc(u8, area.data.len);
+            std.mem.copyForwards(u8, result.key_ptr.data, area.data);
+            result.value_ptr.* = .{};
+        }
+
+        const newShapeCounts = try result.value_ptr.possibleShapeCounts.addOne(self.allocator);
+        newShapeCounts.* = try self.allocator.alloc(i32, shapeCounts.len);
+        std.mem.copyForwards(i32, newShapeCounts.*, shapeCounts);
+    }
+
+    pub fn addImpossibleCacheEntry(self: *AreaCache, area: *const Area, shapeCounts: []i32) !void {
+        var result = try self.entries.getOrPut(area.*);
+        if (!result.found_existing) {
+            result.key_ptr.data = try self.allocator.alloc(u8, area.data.len);
+            std.mem.copyForwards(u8, result.key_ptr.data, area.data);
+            result.value_ptr.* = .{};
+        }
+
+        const newShapeCounts = try result.value_ptr.impossibleShapeCounts.addOne(self.allocator);
+        newShapeCounts.* = try self.allocator.alloc(i32, shapeCounts.len);
+        std.mem.copyForwards(i32, newShapeCounts.*, shapeCounts);
+    }
+
+    pub fn check(self: *AreaCache, area: *const Area, shapeCounts: []i32) AreaCacheResult {
+        self.totalStat += 1;
+        if (self.entries.get(area.*)) |cacheEntry| {
+            if (matchesPossible(shapeCounts, cacheEntry.possibleShapeCounts.items)) {
+                self.hitStat += 1;
+                return AreaCacheResult.kPossible;
+            }
+
+            if (matchesImpossible(shapeCounts, cacheEntry.impossibleShapeCounts.items)) {
+                self.hitStat += 1;
+                return AreaCacheResult.kImpossible;
+            }
+        }
+
+        return AreaCacheResult.kNotFound;
+    }
+};

+ 13 - 0
day12/build.zig

@@ -0,0 +1,13 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+    const exe1 = b.addExecutable(.{
+        .name = "day12a",
+        .root_module = b.createModule(.{
+            .root_source_file = b.path("day12a.zig"),
+            .target = b.graph.host,
+        }),
+    });
+
+    b.installArtifact(exe1);
+}

+ 4 - 0
day12/coord.zig

@@ -0,0 +1,4 @@
+pub const Coord = struct {
+    row: i32,
+    col: i32,
+};

+ 177 - 0
day12/day12a.zig

@@ -0,0 +1,177 @@
+const std = @import("std");
+
+const shapeModule = @import("shape.zig");
+const inputModule = @import("input.zig");
+const areaModule = @import("area.zig");
+const coordModule = @import("coord.zig");
+const unionFindModule = @import("unionfind.zig");
+const areacacheModule = @import("areacache.zig");
+
+const Shape = shapeModule.Shape;
+const Area = areaModule.Area;
+const Coord = coordModule.Coord;
+const UnionFind = unionFindModule.UnionFind;
+const AreaCache = areacacheModule.AreaCache;
+const AreaCacheResult = areacacheModule.AreaCacheResult;
+const AreaHashContext = areacacheModule.AreaHashContext;
+
+
+const kShapeSize = shapeModule.kShapeSize;
+const readInputIntoString = inputModule.readInputIntoString;
+const parseInput = inputModule.parseInput;
+
+pub fn calculateShapeSizes(allocator: std.mem.Allocator, shapeTransforms: []const []const Shape) ![]usize{
+    var shapeSizes = try allocator.alloc(usize, shapeTransforms.len);
+    for (0..shapeTransforms.len) |i| {
+        shapeSizes[i] = 0;
+        for (shapeTransforms[i][0].data) |d| {
+            if (d != '.') {
+                shapeSizes[i] += 1;
+            }
+        }
+    }
+    return shapeSizes;
+}
+
+pub fn calcShapesTotalSize(shapeCounts: []i32, shapeSizes: []usize) usize {
+    var totalSize: usize = 0;
+
+    for (0..shapeCounts.len) |i| {
+        totalSize += shapeSizes[i] * @as(usize, @abs(shapeCounts[i]));
+    }
+    return totalSize;
+}
+
+
+pub fn canContainShapes(allocator: std.mem.Allocator, area: *const Area,
+                        shapeCounts: []i32, shapeTransforms: []const []const Shape,
+                        shapeSizes: []usize, minShapeSize: usize,
+                        areaCache: *AreaCache) !bool {
+
+    const cacheCheckResult = areaCache.check(area, shapeCounts);
+    if (cacheCheckResult == AreaCacheResult.kPossible) {
+        return true;
+    } else if (cacheCheckResult == AreaCacheResult.kPossible) {
+        return false;
+    }
+
+    if (calcShapesTotalSize(shapeCounts, shapeSizes) > area.totalFreeSize()) {
+        try areaCache.addImpossibleCacheEntry(area, shapeCounts);
+        return false;
+    }
+    
+    var allEmpty = true;
+    for (shapeCounts) |shapeCount| {
+        if (shapeCount != 0) {
+            allEmpty = false;
+            break;
+        }
+    }
+    if (allEmpty) {
+        return true;
+    }
+
+    for (0 .. shapeCounts.len) |shapeIndex| {
+        if (shapeCounts[shapeIndex] == 0) {
+            continue;
+        }
+
+        for (0..shapeTransforms[shapeIndex].len) |shapeVariant| {
+            for (0 .. @intCast(area.rows)) |row| {
+                for (0 .. @intCast(area.cols)) |col|  {
+                    if (try area.appendShape(
+                          allocator,
+                          &shapeTransforms[shapeIndex][shapeVariant],
+                          @intCast(row), @intCast(col), '#')) |*newArea| {
+                        defer newArea.deinit();
+                    
+                        var mutableArea: Area = newArea.*;
+                        var unionFind = try UnionFind.init(allocator, &mutableArea);
+                        for (0 .. @intCast(mutableArea.rows)) |unionRow| {
+                            for (0 .. @intCast(mutableArea.cols)) |unionCol| {
+                                if (mutableArea.getCell(@intCast(unionRow), @intCast(unionCol)) == '.') {
+                                    const nodeAIndex:usize = @intCast(unionRow*@abs(mutableArea.cols) + unionCol);
+                                    const nodeARootIndex = unionFind.Find(nodeAIndex);
+                                    if (unionFind.nodes[nodeARootIndex].size < minShapeSize) {
+                                        mutableArea.setCell(@intCast(unionRow), @intCast(unionCol), '#');
+                                    }
+                                }
+                            }
+                        }
+                        unionFind.deinit();
+                    
+                    
+                        var newShapeCounts = try allocator.alloc(i32, shapeCounts.len);
+                        defer allocator.free(newShapeCounts);
+                        std.mem.copyForwards(i32, newShapeCounts, shapeCounts);
+                        newShapeCounts[shapeIndex] -= 1;
+                    
+                        if (try canContainShapes(allocator, newArea, newShapeCounts,
+                                                 shapeTransforms, shapeSizes, minShapeSize,
+                                                 areaCache)) {
+                            try areaCache.addPossibleCacheEntry(area, newShapeCounts);
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    try areaCache.addImpossibleCacheEntry(area, shapeCounts);
+    return false;
+}
+
+pub fn main() !void {
+    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+    const allocator = gpa.allocator();
+
+    const inputBuffer = try readInputIntoString(allocator, "input.txt");
+    const input = try parseInput(allocator, inputBuffer);
+
+    var shapeTransforms: [][]Shape = try allocator.alloc([]Shape, input.shapes.len);
+    for (0..input.shapes.len) |i| {
+        var allTransforms = try input.shapes[i].getAllTransforms(allocator);
+        defer allTransforms.deinit();
+
+        shapeTransforms[i] = try allocator.alloc(Shape, allTransforms.count());
+
+        var j: usize = 0;
+        var it = allTransforms.keyIterator();
+        while (it.next()) |shapeTransform| {
+            shapeTransforms[i][j] = shapeTransform.*;
+            j += 1;
+        }
+    }
+
+    const shapeSizes = try calculateShapeSizes(allocator, shapeTransforms);
+    defer allocator.free(shapeSizes);
+
+    var minShapeSize = shapeSizes[0];
+    for (shapeSizes) |shapeSize| {
+        if (shapeSize < minShapeSize) {
+            minShapeSize = shapeSize;
+        }
+    }
+
+    var areaCache = try AreaCache.init(allocator);
+    defer areaCache.deinit();
+
+    var counter: usize = 0;
+    for (0..input.areas.len) |i| {
+        const area = try Area.init(allocator, input.areas[i].rows, input.areas[i].cols);
+        defer area.deinit();
+        
+        if(try canContainShapes(allocator, &area, input.areas[i].shapesCount,
+                                shapeTransforms, shapeSizes, minShapeSize, &areaCache)) {
+            counter += 1;
+            try std.fs.File.stdout().writeAll("can contain shapes\n");
+        } else {
+            try std.fs.File.stdout().writeAll("CAN't contain shapes\n");
+        }
+    }
+
+    try std.fs.File.stdout().writeAll(
+      try std.fmt.allocPrint(allocator, "Answer: {d}\n", .{counter}));
+}
+

+ 101 - 0
day12/input.zig

@@ -0,0 +1,101 @@
+const std = @import("std");
+const shapeModule = @import("shape.zig");
+const Shape = shapeModule.Shape;
+
+pub const AreaRequirements = struct {
+    rows: i32,
+    cols: i32,
+    shapesCount: []i32,
+};
+
+pub const Input = struct {
+    shapes: []Shape,
+    areas: []AreaRequirements,
+};
+
+fn parseShape(shapeText: []const u8) Shape {
+    var it = std.mem.tokenizeScalar(u8, shapeText, '\n');
+    _ = it.next(); // skip header
+    
+    var shape: Shape = .{
+        .data = undefined,
+    };
+    
+    for (0..3) |row| {
+        const rowText = it.next().?;
+        for (0..3) |col| {
+            shape.setCell(@intCast(row), @intCast(col), rowText[col]);
+        }
+    }
+
+    return shape;
+}
+
+fn parseAreaRequirements(allocator: std.mem.Allocator, line: []const u8) !AreaRequirements {
+    var it = std.mem.tokenizeSequence(u8, line, ": ");
+    const sizeText = it.next().?;
+    const shapesCountText = it.next().?;
+
+    var sizeTextIt = std.mem.tokenizeScalar(u8, sizeText, 'x');
+    const rowsText = sizeTextIt.next().?;
+    const colsText = sizeTextIt.next().?;
+
+    const rows = try std.fmt.parseInt(i32, rowsText, 10);
+    const cols = try std.fmt.parseInt(i32, colsText, 10);
+
+    var shapesCountTextIt = std.mem.tokenizeScalar(u8, shapesCountText, ' ');
+    var shapesCount = std.ArrayList(i32).empty;
+    while (shapesCountTextIt.next()) |shapeCountText| {
+      const shapeCount = try shapesCount.addOne(allocator);
+      shapeCount.* = try std.fmt.parseInt(i32, shapeCountText, 10);
+    }
+    
+    const area: AreaRequirements = .{
+        .rows = rows,
+        .cols = cols,
+        .shapesCount = shapesCount.items,
+    };
+
+    return area;
+}
+
+pub fn parseInput(allocator: std.mem.Allocator, inputBuffer: []u8) !Input {
+    var shapes = std.ArrayList(Shape).empty;
+    var areas = std.ArrayList(AreaRequirements).empty;
+
+    var i = std.mem.tokenizeSequence(u8, inputBuffer, "\n\n");
+    while (i.next()) |inputBlock| {
+        if (inputBlock.len >= 2 and inputBlock[1] == ':') {
+            const shape = try shapes.addOne(allocator);
+            shape.* = parseShape(inputBlock);
+        } else {
+            var it = std.mem.tokenizeScalar(u8, inputBlock, '\n');
+            while(it.next()) |line| {
+                const area = try areas.addOne(allocator);
+                area.* = try parseAreaRequirements(allocator, line);
+            }
+        }
+    }
+
+    const input: Input = .{
+        .shapes = shapes.items,
+        .areas = areas.items,
+    };
+
+    return input;
+}
+
+pub fn readInputIntoString(allocator: std.mem.Allocator, fileName: []const u8) ![]u8 {
+    const inputFile = try std.fs.cwd().openFile(
+        fileName, .{.mode = .read_only,});
+    defer inputFile.close();
+
+    var inputFileReader = inputFile.reader(
+      try allocator.alignedAlloc(u8, null, 1024));
+    const inputFileSize = try inputFileReader.getSize();
+
+    const inputBuffer = try allocator.alignedAlloc(u8, null, inputFileSize);
+    try inputFileReader.interface.readSliceAll(inputBuffer);
+    return inputBuffer;
+}
+

+ 110 - 0
day12/shape.zig

@@ -0,0 +1,110 @@
+const std = @import("std");
+
+pub const kShapeSize: i32 = 3;
+
+pub const Shape = struct {
+    data: [9]u8,
+
+    pub fn getCell(self: *const Shape, row: i32, col: i32) u8 {
+        return self.data[@abs(row*kShapeSize + col)];
+    }
+
+    pub fn setCell(self: *Shape, row: i32, col: i32, cell: u8) void {
+        self.data[@abs(row*kShapeSize + col)] = cell;
+    }
+
+    pub fn flipHor(self: *Shape) void {
+        for (0 .. kShapeSize) |row| {
+            for (0 .. kShapeSize/2) |col| {
+                const t = self.getCell(@intCast(row), @intCast(col));
+                self.setCell(@intCast(row), @intCast(col), 
+                             self.getCell(@intCast(row),
+                                          @intCast(kShapeSize - 1 - col)));
+                self.setCell(@intCast(row), @intCast(kShapeSize - 1 - col), t);
+            }
+        }
+    }
+
+    pub fn flipVer(self: *Shape) void {
+        for (0 .. kShapeSize) |col| {
+            for (0 .. kShapeSize/2) |row| {
+                const t = self.getCell(@intCast(row), @intCast(col));
+                self.setCell(@intCast(row), @intCast(col), 
+                             self.getCell(@intCast(kShapeSize - 1 - row),
+                                          @intCast(col)));
+                self.setCell(@intCast(kShapeSize - 1 - row), @intCast(col), t);
+            }
+        }
+    }
+
+
+    pub fn rotate(self: *Shape) void {
+        for (0 .. kShapeSize-1) |row| {
+            for (row+1 .. kShapeSize) |col| {
+                const t = self.getCell(@intCast(row), @intCast(col));
+                self.setCell(@intCast(row), @intCast(col),
+                             self.getCell(@intCast(col), @intCast(row)));
+                self.setCell(@intCast(col), @intCast(row), t);
+            }
+        }
+
+        self.flipHor();
+    }
+
+    pub fn getAllTransforms(self: * const Shape, allocator: std.mem.Allocator) !std.AutoHashMap(Shape, bool) {
+        var allTransforms = std.AutoHashMap(Shape, bool).init(allocator);
+
+        try allTransforms.put(self.*, true);
+
+
+        var shapeHFlip = self.*;
+        shapeHFlip.flipHor();
+
+        for (0 .. 4) |_| {
+            try allTransforms.put(shapeHFlip, true);
+            shapeHFlip.rotate();
+        }
+
+        var shapeVFlip = self.*;
+        shapeVFlip.flipVer();
+
+        for (0 .. 4) |_| {
+            try allTransforms.put(shapeVFlip, true);
+            shapeVFlip.rotate();
+        }
+
+
+        var shapeHVFlip = self.*;
+        shapeHVFlip.flipHor();
+        shapeHVFlip.flipVer();
+
+        for (0 .. 4) |_| {
+            try allTransforms.put(shapeHVFlip, true);
+            shapeHVFlip.rotate();
+        }
+
+        var shapeVHFlip = self.*;
+        shapeVHFlip.flipVer();
+        shapeVHFlip.flipHor();
+
+        for (0 .. 4) |_| {
+            try allTransforms.put(shapeVHFlip, true);
+            shapeVHFlip.rotate();
+        }
+
+        return allTransforms;
+    }
+
+    pub fn print(self: *const Shape, allocator: std.mem.Allocator) !void {
+        for (0..kShapeSize) |row| {
+            for (0..kShapeSize) |col| {
+                const cell = self.getCell(@intCast(row), @intCast(col));
+                try std.fs.File.stdout().writeAll(
+                  try std.fmt.allocPrint(allocator, "{c}", .{cell}));
+        
+            }
+            try std.fs.File.stdout().writeAll("\n");
+        }
+    }
+};
+

+ 109 - 0
day12/unionfind.zig

@@ -0,0 +1,109 @@
+const std = @import("std");
+
+const coordModule = @import("coord.zig");
+const areaModule = @import("area.zig");
+
+const Coord = coordModule.Coord;
+const Area = areaModule.Area;
+
+pub const UnionFindNode = struct {
+    parent: usize,
+    size: usize,
+};
+
+pub const UnionFind  = struct {
+    nodes: []UnionFindNode,
+    nodesAllocator: std.mem.Allocator,
+
+    pub fn Find(self: *UnionFind, nodeIndex: usize) usize {
+        if (self.nodes[nodeIndex].parent == nodeIndex) {
+            return nodeIndex;
+        }
+
+        self.nodes[nodeIndex].parent = self.Find(self.nodes[nodeIndex].parent);
+        return self.nodes[nodeIndex].parent;
+    }
+
+    fn Union(self: *UnionFind, nodeAIndex: usize, nodeBIndex: usize) bool {
+        var nodeARootIndex = self.Find(nodeAIndex);
+        var nodeBRootIndex = self.Find(nodeBIndex);
+
+        if (nodeARootIndex == nodeBRootIndex) {
+            return false;
+        }
+
+        if (self.nodes[nodeARootIndex].size < self.nodes[nodeBRootIndex].size) {
+            const t = nodeBRootIndex;
+            nodeBRootIndex = nodeARootIndex;
+            nodeARootIndex = t;
+        }
+
+        self.nodes[nodeBRootIndex].parent = nodeARootIndex;
+        self.nodes[nodeARootIndex].size += self.nodes[nodeBRootIndex].size;
+        return true;
+    }
+
+    fn UnionIteration(self: *UnionFind, area: *const Area) bool {
+        var performedUnion = false;
+        for (0 .. @intCast(area.rows)) |row| {
+            for (0 .. @intCast(area.cols)) |col| {
+                const nodeAIndex:usize = @intCast(row*@abs(area.cols) + col);
+                if (area.getCell(@intCast(row), @intCast(col)) == '.') {
+                    if (row > 0 and area.getCell(@intCast(row-1), @intCast(col)) == '.') {
+                        const nodeBIndex: usize = @intCast((row-1)*@abs(area.cols) + col);
+                        if (self.Union(nodeAIndex, nodeBIndex)) {
+                            performedUnion = true;
+                        }
+                    }
+
+                    if (col > 0 and area.getCell(@intCast(row), @intCast(col-1)) == '.') {
+                        const nodeBIndex: usize = @intCast(row*@abs(area.cols) + col - 1);
+                        if (self.Union(nodeAIndex, nodeBIndex)) {
+                            performedUnion = true;
+                        }
+                    }
+
+                    if (row < area.rows - 1 and area.getCell(@intCast(row+1), @intCast(col)) == '.') {
+                        const nodeBIndex: usize = @intCast((row+1)*@abs(area.cols) + col);
+                        if (self.Union(nodeAIndex, nodeBIndex)) {
+                            performedUnion = true;
+                        }
+                    }
+
+                    if (col < area.cols - 1 and area.getCell(@intCast(row), @intCast(col+1)) == '.') {
+                        const nodeBIndex: usize = @intCast(row*@abs(area.cols) + col + 1);
+                        if (self.Union(nodeAIndex, nodeBIndex)) {
+                            performedUnion = true;
+                        }
+                    }
+                }
+            }
+        }
+
+        return performedUnion;
+    }
+
+    pub fn init(allocator: std.mem.Allocator, area: *const Area) !UnionFind {
+        var unionFind: UnionFind  = .{
+            .nodes = try allocator.alloc(UnionFindNode, @intCast(area.rows*area.cols)),
+            .nodesAllocator = allocator,
+        };
+
+        for (0..unionFind.nodes.len) |i| {
+            unionFind.nodes[i].parent = i;
+            unionFind.nodes[i].size = 1;
+        }
+
+        while (true) {
+          if (!unionFind.UnionIteration(area)) {
+            break;
+          }
+        }
+
+        return unionFind;
+    }
+
+    pub fn deinit(self: *const UnionFind) void {
+        self.nodesAllocator.free(self.nodes);
+    }
+};