1
0

5 Commity 6e0e22182e ... b973ca5c35

Autor SHA1 Správa Dátum
  Aleksei Dorokhov b973ca5c35 Solved day12a 10 mesiacov pred
  Aleksei Dorokhov 7f7bad41f9 added filling in small spaces 10 mesiacov pred
  Aleksei Dorokhov cd39576076 Converted to i32 for some calculations 10 mesiacov pred
  Aleksei Dorokhov f58a1f3634 day12 works on example input 10 mesiacov pred
  Aleksei Dorokhov 246d7ac7a6 Started solving day12 10 mesiacov pred
9 zmenil súbory, kde vykonal 784 pridanie a 0 odobranie
  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);
+    }
+};