Day 2-1
This commit is contained in:
parent
1c6c068e44
commit
b4fb2f0643
@ -10,6 +10,9 @@
|
||||
<None Update="day1.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="day2.txt">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</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
|
||||
|
||||
using System.Diagnostics;
|
||||
using AdventOfCode;
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
var day1Result = await Day1.ExecuteAsync("day1.txt");
|
||||
Console.WriteLine($"Advent of code: Day 1 result: {day1Result}");
|
||||
sw.Stop();
|
||||
Console.WriteLine($"Advent of code: Day 1 time: {sw.Elapsed.TotalMilliseconds} ms.");
|
||||
var day2Result = await Day2.ExecuteAsync("day2.txt", new Day2.GameSettings(12, 13, 14));
|
||||
Console.WriteLine($"Advent of code: Day 2 result: {day2Result}");
|
Loading…
Reference in New Issue
Block a user