Day 2-1
This commit is contained in:
parent
1c6c068e44
commit
b4fb2f0643
@ -10,6 +10,9 @@
|
|||||||
<None Update="day1.txt">
|
<None Update="day1.txt">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="day2.txt">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
376
src/AdventOfCode/AdventOfCode/Day2.cs
Normal file
376
src/AdventOfCode/AdventOfCode/Day2.cs
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
using System.Buffers;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace AdventOfCode;
|
||||||
|
|
||||||
|
public static class Day2
|
||||||
|
{
|
||||||
|
public enum TokenType
|
||||||
|
{
|
||||||
|
Undefined,
|
||||||
|
Invalid,
|
||||||
|
Space,
|
||||||
|
Comma,
|
||||||
|
Colon,
|
||||||
|
Semicolon,
|
||||||
|
Identifier,
|
||||||
|
DecimalLiteral,
|
||||||
|
IntegerLiteral,
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct Token(TokenType type, string value)
|
||||||
|
{
|
||||||
|
public TokenType Type { get; } = type;
|
||||||
|
public string Value { get; } = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct GameSettings(int reds, int greens, int blues)
|
||||||
|
{
|
||||||
|
public int Reds { get; } = reds;
|
||||||
|
public int Greens { get; } = greens;
|
||||||
|
public int Blues { get; } = blues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct GameSet
|
||||||
|
{
|
||||||
|
public int Red { get; set; }
|
||||||
|
public int Green { get; set; }
|
||||||
|
public int Blue { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct GameData
|
||||||
|
{
|
||||||
|
public Dictionary<int, GameSet[]> Games { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Result
|
||||||
|
{
|
||||||
|
public int Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Regex containsSpaceOrPunctuatorRegex = new("[ ,:;]");
|
||||||
|
|
||||||
|
private static readonly Regex spacesRegex = new("^ *$");
|
||||||
|
private static readonly Regex commaRegex = new("^,$");
|
||||||
|
private static readonly Regex colonRegex = new("^:$");
|
||||||
|
private static readonly Regex semicolonRegex = new("^;$");
|
||||||
|
private static readonly Regex integerRegex = new("^[0-9]+$");
|
||||||
|
private static readonly Regex decimalRegex = new("^[0-9]+(\\.[0-9]+)?$");
|
||||||
|
private static readonly Regex identifierRegex = new("^[a-zA-Z][a-zA-Z0-9]*$");
|
||||||
|
|
||||||
|
private static readonly List<(Regex regex, TokenType tokenType)> matchers =
|
||||||
|
[
|
||||||
|
(spacesRegex, TokenType.Space),
|
||||||
|
(commaRegex, TokenType.Comma),
|
||||||
|
(colonRegex, TokenType.Colon),
|
||||||
|
(semicolonRegex, TokenType.Semicolon),
|
||||||
|
(integerRegex, TokenType.IntegerLiteral),
|
||||||
|
(decimalRegex, TokenType.DecimalLiteral),
|
||||||
|
(identifierRegex, TokenType.Identifier),
|
||||||
|
];
|
||||||
|
|
||||||
|
public static async Task<int> ExecuteAsync(string filePath, GameSettings gameSettings)
|
||||||
|
{
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
await using var fs = new FileStream(filePath, FileMode.Open);
|
||||||
|
return await ExecuteAsync(fs, gameSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<int> ExecuteAsync(Stream s, GameSettings gameSettings)
|
||||||
|
{
|
||||||
|
Result result = new();
|
||||||
|
var pipe = new Pipe();
|
||||||
|
var writing = FillPipeAsync(s, pipe.Writer);
|
||||||
|
var reading = ReadPipeAsync(pipe.Reader, gameSettings, result);
|
||||||
|
|
||||||
|
await Task.WhenAll(writing, reading);
|
||||||
|
|
||||||
|
return result.Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task FillPipeAsync(Stream fs, PipeWriter writer)
|
||||||
|
{
|
||||||
|
const int minBufferSize = 512;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var memory = writer.GetMemory(minBufferSize);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bytesRead = await fs.ReadAsync(memory, CancellationToken.None);
|
||||||
|
if (bytesRead == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
writer.Advance(bytesRead);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await writer.FlushAsync();
|
||||||
|
if (result.IsCompleted)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await writer.CompleteAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<int> ReadPipeAsync(PipeReader reader, GameSettings gameSettings, Result result)
|
||||||
|
{
|
||||||
|
var sum = 0;
|
||||||
|
result.Sum = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var readResult = await reader.ReadAsync();
|
||||||
|
var buffer = readResult.Buffer;
|
||||||
|
|
||||||
|
while (TryReadLine(ref buffer, out var line))
|
||||||
|
{
|
||||||
|
sum += ProcessLine(line, gameSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.AdvanceTo(buffer.Start, buffer.End);
|
||||||
|
|
||||||
|
if (readResult.IsCompleted)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await reader.CompleteAsync();
|
||||||
|
result.Sum = sum;
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryReadLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> line)
|
||||||
|
{
|
||||||
|
var position = buffer.PositionOf((byte)'\n');
|
||||||
|
|
||||||
|
if (position == null)
|
||||||
|
{
|
||||||
|
line = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
line = buffer.Slice(0, position.Value);
|
||||||
|
buffer = buffer.Slice(buffer.GetPosition(1, position.Value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ProcessLine(ReadOnlySequence<byte> line, GameSettings gameSettings)
|
||||||
|
{
|
||||||
|
var str = Encoding.UTF8.GetString(line);
|
||||||
|
if (string.IsNullOrWhiteSpace(str))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
var game = ParseString(str);
|
||||||
|
var sum = 0;
|
||||||
|
foreach (var (gameId, gameSets) in game.Games)
|
||||||
|
{
|
||||||
|
var possibleGame = true;
|
||||||
|
|
||||||
|
foreach (var gameSet in gameSets)
|
||||||
|
{
|
||||||
|
if (gameSet.Red <= gameSettings.Reds && gameSet.Blue <= gameSettings.Blues && gameSet.Green <= gameSettings.Greens)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
possibleGame = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (possibleGame)
|
||||||
|
{
|
||||||
|
sum += gameId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameData ParseString(string str)
|
||||||
|
{
|
||||||
|
var strSpan = str.AsSpan();
|
||||||
|
var tokens = TokenizeString(strSpan);
|
||||||
|
var gameData = new GameData()
|
||||||
|
{
|
||||||
|
Games = new Dictionary<int, GameSet[]>()
|
||||||
|
};
|
||||||
|
|
||||||
|
var headerSeparator = 0;
|
||||||
|
foreach (var token in tokens)
|
||||||
|
{
|
||||||
|
if (token.Type == TokenType.Colon)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
headerSeparator++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headerSeparator != 2)
|
||||||
|
{
|
||||||
|
throw new Exception("Header contains more than 2 tokens");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokens[0].Type != TokenType.Identifier ||
|
||||||
|
tokens[1].Type != TokenType.IntegerLiteral)
|
||||||
|
{
|
||||||
|
throw new Exception($"Header has wrong tokens: {tokens[0].Type} {tokens[1].Type}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!int.TryParse(tokens[1].Value, out var gameId))
|
||||||
|
{
|
||||||
|
throw new Exception($"Unable to parse game id: {tokens[1].Type} {tokens[1].Value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GameSet> dataList = [];
|
||||||
|
string? identifier = default;
|
||||||
|
int? value = default;
|
||||||
|
GameSet data = default;
|
||||||
|
|
||||||
|
for (var i = headerSeparator; i < tokens.Length; i++)
|
||||||
|
{
|
||||||
|
var endOfTokens = i == tokens.Length - 1;
|
||||||
|
var addToken = tokens[i].Type == TokenType.Comma ||
|
||||||
|
tokens[i].Type == TokenType.Semicolon ||
|
||||||
|
endOfTokens;
|
||||||
|
|
||||||
|
switch (tokens[i].Type)
|
||||||
|
{
|
||||||
|
case TokenType.Identifier:
|
||||||
|
{
|
||||||
|
identifier = tokens[i].Value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TokenType.IntegerLiteral:
|
||||||
|
{
|
||||||
|
value = int.Parse(tokens[i].Value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addToken)
|
||||||
|
{
|
||||||
|
FillDataStruct(identifier, value, ref data);
|
||||||
|
value = default;
|
||||||
|
identifier = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokens[i].Type == TokenType.Semicolon || endOfTokens)
|
||||||
|
{
|
||||||
|
dataList.Add(data);
|
||||||
|
data = default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gameData.Games.Add(gameId, dataList.ToArray());
|
||||||
|
return gameData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FillDataStruct(string? identifier, int? value, ref GameSet data)
|
||||||
|
{
|
||||||
|
if (identifier is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (identifier)
|
||||||
|
{
|
||||||
|
case "red":
|
||||||
|
{
|
||||||
|
data.Red = value ?? 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "green":
|
||||||
|
{
|
||||||
|
data.Green = value ?? 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "blue":
|
||||||
|
{
|
||||||
|
data.Blue = value ?? 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Token[] TokenizeString(ReadOnlySpan<char> strSpan)
|
||||||
|
{
|
||||||
|
List<Token> result = [];
|
||||||
|
|
||||||
|
var tail = 0;
|
||||||
|
for (var head = 0; head < strSpan.Length; head++)
|
||||||
|
{
|
||||||
|
var prevTestString = strSpan[tail .. head];
|
||||||
|
var testString = strSpan[tail .. (head + 1)];
|
||||||
|
var ch = strSpan[head];
|
||||||
|
|
||||||
|
var endOfLine = head == strSpan.Length - 1;
|
||||||
|
var spaceOrPunctuator = containsSpaceOrPunctuatorRegex.IsMatch(testString);
|
||||||
|
var prevTestStringContainsOnlySpaces = !prevTestString.IsEmpty && spacesRegex.IsMatch(prevTestString);
|
||||||
|
var separatorType = ch switch
|
||||||
|
{
|
||||||
|
',' when spaceOrPunctuator => TokenType.Comma,
|
||||||
|
':' when spaceOrPunctuator => TokenType.Colon,
|
||||||
|
';' when spaceOrPunctuator => TokenType.Semicolon,
|
||||||
|
' ' when spaceOrPunctuator => TokenType.Space,
|
||||||
|
_ => TokenType.Undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
var foundToken = (prevTestStringContainsOnlySpaces && separatorType != TokenType.Space) ||
|
||||||
|
(!prevTestStringContainsOnlySpaces && separatorType != TokenType.Undefined) ||
|
||||||
|
endOfLine;
|
||||||
|
|
||||||
|
if (!foundToken)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var punctuator = separatorType != TokenType.Undefined && separatorType != TokenType.Space;
|
||||||
|
var isSeparator = punctuator || (separatorType == TokenType.Space && !prevTestStringContainsOnlySpaces);
|
||||||
|
|
||||||
|
var targetSpan = endOfLine && !isSeparator ? testString : prevTestString;
|
||||||
|
|
||||||
|
var foundMatch = false;
|
||||||
|
foreach (var (regex, tokenType) in matchers)
|
||||||
|
{
|
||||||
|
if (!regex.IsMatch(targetSpan))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenType != TokenType.Space)
|
||||||
|
{
|
||||||
|
result.Add(new Token(tokenType, new string(targetSpan)));
|
||||||
|
}
|
||||||
|
foundMatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundMatch)
|
||||||
|
{
|
||||||
|
result.Add(new Token(TokenType.Invalid, new string(targetSpan)));
|
||||||
|
}
|
||||||
|
tail = head;
|
||||||
|
|
||||||
|
if (endOfLine && isSeparator && separatorType != TokenType.Space)
|
||||||
|
{
|
||||||
|
result.Add(new Token(separatorType, $"{ch}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,6 @@
|
|||||||
// See https://aka.ms/new-console-template for more information
|
// See https://aka.ms/new-console-template for more information
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using AdventOfCode;
|
using AdventOfCode;
|
||||||
|
|
||||||
var sw = Stopwatch.StartNew();
|
var day2Result = await Day2.ExecuteAsync("day2.txt", new Day2.GameSettings(12, 13, 14));
|
||||||
var day1Result = await Day1.ExecuteAsync("day1.txt");
|
Console.WriteLine($"Advent of code: Day 2 result: {day2Result}");
|
||||||
Console.WriteLine($"Advent of code: Day 1 result: {day1Result}");
|
|
||||||
sw.Stop();
|
|
||||||
Console.WriteLine($"Advent of code: Day 1 time: {sw.Elapsed.TotalMilliseconds} ms.");
|
|
Loading…
Reference in New Issue
Block a user