Compare commits

...

2 Commits

Author SHA1 Message Date
c5bf80a9b5 refactor testcases 2023-12-17 13:14:01 +02:00
a15d429279 solve day 7 2023-12-17 13:10:08 +02:00
10 changed files with 349 additions and 50 deletions

1
.gitignore vendored
View File

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

View File

@ -18,3 +18,10 @@ pub const Day = struct {
part1: ?Solver,
part2: ?Solver
};
pub fn expectAnswerUInt(solver: Solver, answer: u32, lines: []const []const u8) !void {
var input = Input{ .lines = lines, .allocator = std.testing.allocator };
var actual = try solver(&input);
var expected: u32 = answer;
try std.testing.expectEqual(expected, actual.uint);
}

View File

@ -94,20 +94,17 @@ pub fn part2(input: *aoc.Input) !aoc.Result {
}
test "part 1 example" {
var lines = [_][]const u8{
var example_input = [_][]const u8{
"1abc2",
"pqr3stu8vwx",
"a1b2c3d4e5f",
"treb7uchet",
};
var input = aoc.Input{ .lines = &lines, .allocator = std.testing.allocator };
var actual = try part1(&input);
var expected: u32 = 142;
try std.testing.expectEqual(expected, actual.uint);
try aoc.expectAnswerUInt(part1, 142, &example_input);
}
test "part 2 example" {
var lines = [_][]const u8{
var example_input = [_][]const u8{
"two1nine",
"eightwothree",
"abcone2threexyz",
@ -116,8 +113,5 @@ test "part 2 example" {
"zoneight234",
"7pqrstsixteen",
};
var input = aoc.Input{ .lines = &lines, .allocator = std.testing.allocator };
var actual = try part2(&input);
var expected: u32 = 281;
try std.testing.expectEqual(expected, actual.uint);
try aoc.expectAnswerUInt(part2, 281, &example_input);
}

View File

@ -111,15 +111,9 @@ const example_input = [_][]const u8{
};
test "part 1 example" {
var input = aoc.Input{ .lines = &example_input, .allocator = std.testing.allocator };
var actual = try part1(&input);
var expected: u32 = 8;
try std.testing.expectEqual(expected, actual.uint);
try aoc.expectAnswerUInt(part1, 8, &example_input);
}
test "part 2 example" {
var input = aoc.Input{ .lines = &example_input, .allocator = std.testing.allocator };
var actual = try part2(&input);
var expected: u32 = 2286;
try std.testing.expectEqual(expected, actual.uint);
try aoc.expectAnswerUInt(part2, 2286, &example_input);
}

View File

@ -121,15 +121,9 @@ const example_input = [_][]const u8{
};
test "part 1 example" {
var input = aoc.Input{ .lines = &example_input, .allocator = std.testing.allocator };
var actual = try part1(&input);
var expected: u32 = 4361;
try std.testing.expectEqual(expected, actual.uint);
try aoc.expectAnswerUInt(part1, 4361, &example_input);
}
test "part 2 example" {
var input = aoc.Input{ .lines = &example_input, .allocator = std.testing.allocator };
var actual = try part2(&input);
var expected: u32 = 467835;
try std.testing.expectEqual(expected, actual.uint);
try aoc.expectAnswerUInt(part2, 467835, &example_input);
}

View File

@ -105,15 +105,9 @@ const example_input = [_][]const u8{
};
test "part 1 example" {
var input = aoc.Input{ .lines = &example_input, .allocator = std.testing.allocator };
var actual = try part1(&input);
var expected: u32 = 13;
try std.testing.expectEqual(expected, actual.uint);
try aoc.expectAnswerUInt(part1, 13, &example_input);
}
test "part 2 example" {
var input = aoc.Input{ .lines = &example_input, .allocator = std.testing.allocator };
var actual = try part2(&input);
var expected: u32 = 30;
try std.testing.expectEqual(expected, actual.uint);
try aoc.expectAnswerUInt(part2, 30, &example_input);
}

View File

@ -249,16 +249,10 @@ const example_input = [_][]const u8{
};
test "part 1 example" {
var input = aoc.Input{ .lines = &example_input, .allocator = std.testing.allocator };
var actual = try part1(&input);
var expected: u32 = 35;
try std.testing.expectEqual(expected, actual.uint);
try aoc.expectAnswerUInt(part1, 35, &example_input);
}
test "part 2 example" {
var input = aoc.Input{ .lines = &example_input, .allocator = std.testing.allocator };
var actual = try part2(&input);
var expected: u32 = 46;
try std.testing.expectEqual(expected, actual.uint);
try aoc.expectAnswerUInt(part2, 46, &example_input);
}

View File

@ -101,15 +101,9 @@ const example_input = [_][]const u8{
};
test "part 1 example" {
var input = aoc.Input{ .lines = &example_input, .allocator = std.testing.allocator };
var actual = try part1(&input);
var expected: u32 = 288;
try std.testing.expectEqual(expected, actual.uint);
try aoc.expectAnswerUInt(part1, 288, &example_input);
}
test "part 2 example" {
var input = aoc.Input{ .lines = &example_input, .allocator = std.testing.allocator };
var actual = try part2(&input);
var expected: u32 = 71503;
try std.testing.expectEqual(expected, actual.uint);
try aoc.expectAnswerUInt(part2, 71503, &example_input);
}

325
src/day7.zig Normal file
View File

@ -0,0 +1,325 @@
const aoc = @import("./aoc.zig");
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const HandStrength = enum {
Nothing,
HighCard,
OnePair,
TwoPair,
ThreeOfAKind,
FullHouse,
FourOfAKind,
FiveOfAKind,
};
const CARD_VARIANTS = 13;
const CardIndex = u4;
const Hand = [5]CardIndex;
const HandBid = struct {
hand: Hand = undefined,
bid: u32
};
const CardCounts = [CARD_VARIANTS]CardIndex;
const IndexArray = std.ArrayList(usize);
const HandBids = std.ArrayList(HandBid);
fn hand_to_str(hand: Hand) [5]u8 {
var str = [1]u8{0} ** 5;
for (0..hand.len) |i| {
str[i] = switch (hand[i]) {
12 => 'A',
11 => 'K',
10 => 'Q',
9 => 'J',
8 => 'T',
7 => '9',
6 => '8',
5 => '7',
4 => '6',
3 => '5',
2 => '4',
1 => '3',
0 => '2',
else => unreachable
};
}
return str;
}
fn get_card_index(char: u8) CardIndex {
return switch (char) {
'A' => 12,
'K' => 11,
'Q' => 10,
'J' => 9,
'T' => 8,
'9' => 7,
'8' => 6,
'7' => 5,
'6' => 4,
'5' => 3,
'4' => 2,
'3' => 1,
'2' => 0,
else => unreachable
};
}
fn parse_input(allocator: Allocator, lines: []const []const u8) !HandBids {
var hand_bids = HandBids.init(allocator);
for (lines) |line| {
var tokens = std.mem.tokenizeScalar(u8, line, ' ');
const hand_str = tokens.next().?;
const bid_str = tokens.next().?;
const bid = try std.fmt.parseInt(u32, bid_str, 10);
assert(hand_str.len == 5);
var hand_bid = HandBid{ .bid = bid };
for (0..5) |i| {
hand_bid.hand[i] = get_card_index(hand_str[i]);
}
try hand_bids.append(hand_bid);
}
return hand_bids;
}
fn count_cards(hand: Hand) CardCounts {
var counts = [1]CardIndex{0} ** CARD_VARIANTS;
for (hand) |card| {
counts[card] += 1;
}
return counts;
}
fn determine_card_strength(hand: Hand) HandStrength {
var counts = count_cards(hand);
var ones_count: u4 = 0;
var two_count: u4 = 0;
var three_count: u4 = 0;
for (counts) |count| {
switch (count) {
5 => return .FiveOfAKind,
4 => return .FourOfAKind,
3 => three_count += 1,
2 => two_count += 1,
1 => ones_count += 1,
else => continue
}
}
if (ones_count == 5) {
return .HighCard;
}
if (two_count == 2) {
return .TwoPair;
}
if (two_count == 1 and three_count == 1) {
return .FullHouse;
}
if (ones_count == 2 and three_count == 1) {
return .ThreeOfAKind;
}
if (two_count == 1 and ones_count == 3) {
return .OnePair;
}
return .Nothing;
}
fn determine_card_strength_joker(hand: Hand) HandStrength {
const joker_card = comptime get_card_index('J');
var jokers = std.BoundedArray(usize, 5).init(0) catch unreachable;
var card_types = std.BoundedArray(CardIndex, 5).init(0) catch unreachable;
for (0.., hand) |i, card| {
if (card == joker_card) {
jokers.append(i) catch unreachable;
}
if (std.mem.indexOfScalar(CardIndex, card_types.slice(), card) == null) {
card_types.append(card) catch unreachable;
}
}
if (jokers.len == 0) {
return determine_card_strength(hand);
}
var joker_choices_container = [1]usize{0} ** 5;
var joker_choices = joker_choices_container[0..jokers.len];
var modified_hand: Hand = undefined;
@memcpy(&modified_hand, &hand);
var best_strength = HandStrength.Nothing;
const combination_count = std.math.powi(usize, card_types.len, jokers.len) catch unreachable;
for (0..combination_count) |_| {
for (0..joker_choices.len) |i| {
modified_hand[jokers.get(i)] = card_types.get(joker_choices[i]);
}
const strength = determine_card_strength(modified_hand);
best_strength = @enumFromInt(@max(@intFromEnum(best_strength), @intFromEnum(strength)));
joker_choices[0] += 1;
for (0..(joker_choices.len-1)) |i| {
if (joker_choices[i] == card_types.len) {
joker_choices[i] = 0;
joker_choices[i+1] += 1;
}
}
}
return best_strength;
}
fn compare_hands(a: *Hand, b: *Hand) bool {
inline for (0..a.len) |i| {
if (a[i] != b[i]) {
return a[i] > b[i];
}
}
return false;
}
fn compare_hands_joker(a: *Hand, b: *Hand) bool {
const joker_card = comptime get_card_index('J');
inline for (0..a.len) |i| {
if (a[i] != b[i]) {
const card_a = if (a[i] == joker_card) 0 else a[i]+1;
const card_b = if (b[i] == joker_card) 0 else b[i]+1;
return card_a > card_b;
}
}
return false;
}
fn sort_same_strength(hands: []Hand, indexes: []usize, cmp: *const fn(*Hand, *Hand) bool) void {
for (0..(indexes.len-1)) |i| {
for ((i+1)..indexes.len) |j| {
const hand_a = &hands[indexes[i]];
const hand_b = &hands[indexes[j]];
if (cmp(hand_a, hand_b)) {
std.mem.swap(usize, &indexes[i], &indexes[j]);
}
}
}
}
pub fn part1(input: *aoc.Input) !aoc.Result {
const allocator = input.allocator;
const hand_bids = try parse_input(allocator, input.lines);
defer hand_bids.deinit();
var hand_strengths = try allocator.alloc(HandStrength, hand_bids.items.len);
defer allocator.free(hand_strengths);
var hands = try allocator.alloc(Hand, hand_bids.items.len);
defer allocator.free(hands);
for (0..hand_bids.items.len) |i| {
hands[i] = hand_bids.items[i].hand;
hand_strengths[i] = determine_card_strength(hand_bids.items[i].hand);
}
var order = try IndexArray.initCapacity(allocator, hand_bids.items.len);
defer order.deinit();
var sub_order = try IndexArray.initCapacity(allocator, hand_bids.items.len);
defer sub_order.deinit();
inline for (@typeInfo(HandStrength).Enum.fields) |field| {
sub_order.clearRetainingCapacity();
const hand_strength: HandStrength = @enumFromInt(field.value);
for (0..hand_bids.items.len) |idx| {
if (hand_strengths[idx] == hand_strength) {
sub_order.appendAssumeCapacity(idx);
}
}
if (sub_order.items.len != 0) {
sort_same_strength(hands, sub_order.items, compare_hands);
order.appendSliceAssumeCapacity(sub_order.items);
}
}
var answer: u32 = 0;
for (0.., order.items) |i, idx| {
answer += @as(u32, @intCast(i+1)) * hand_bids.items[idx].bid;
}
return .{ .uint = answer };
}
pub fn part2(input: *aoc.Input) !aoc.Result {
const allocator = input.allocator;
const hand_bids = try parse_input(allocator, input.lines);
defer hand_bids.deinit();
var hand_strengths = try allocator.alloc(HandStrength, hand_bids.items.len);
defer allocator.free(hand_strengths);
var hands = try allocator.alloc(Hand, hand_bids.items.len);
defer allocator.free(hands);
for (0..hand_bids.items.len) |i| {
hands[i] = hand_bids.items[i].hand;
hand_strengths[i] = determine_card_strength_joker(hand_bids.items[i].hand);
}
var order = try IndexArray.initCapacity(allocator, hand_bids.items.len);
defer order.deinit();
var sub_order = try IndexArray.initCapacity(allocator, hand_bids.items.len);
defer sub_order.deinit();
inline for (@typeInfo(HandStrength).Enum.fields) |field| {
sub_order.clearRetainingCapacity();
const hand_strength: HandStrength = @enumFromInt(field.value);
for (0..hand_bids.items.len) |idx| {
if (hand_strengths[idx] == hand_strength) {
sub_order.appendAssumeCapacity(idx);
}
}
if (sub_order.items.len != 0) {
sort_same_strength(hands, sub_order.items, compare_hands_joker);
order.appendSliceAssumeCapacity(sub_order.items);
}
}
var answer: u32 = 0;
for (0.., order.items) |i, idx| {
answer += @as(u32, @intCast(i+1)) * hand_bids.items[idx].bid;
}
return .{ .uint = answer };
}
const example_input = [_][]const u8{
"32T3K 765",
"T55J5 684",
"KK677 28",
"KTJJT 220",
"QQQJA 483"
};
test "part 1 example" {
try aoc.expectAnswerUInt(part1, 6440, &example_input);
}
test "part 2 example" {
try aoc.expectAnswerUInt(part2, 5905, &example_input);
}

View File

@ -27,6 +27,7 @@ const Days = [_]aoc.Day{
create_day(@import("day4.zig")),
create_day(@import("day5.zig")),
create_day(@import("day6.zig")),
create_day(@import("day7.zig")),
};
fn kilobytes(count: u32) u32 {
@ -179,4 +180,5 @@ test {
_ = @import("day4.zig");
_ = @import("day5.zig");
_ = @import("day6.zig");
_ = @import("day7.zig");
}