Advent of Code – második nap
A 2. nap történetében az első folytatása, ott folytatódik, ahol az első nap abbamaradt. Fent vagyunk a tengeralattjárón és rá kell jönnünk, hogy hogyan kell vezetni.
Van egy előre megadott navigációs utasítássorozat, ami a következőképpen néz ki:
forward 5
down 5
forward 8
up 3
down 8
forward 2
A forward utasítás a horizontális tengelyen mozgat bennünket a megadott egységgel, az up és down utasítás pedig a mélységet szabályozza. Az első feladatban egy ilyen navigációs utasítássort kapunk, amit végrehajtva hozzájutunk egy horizontális és egy mélység koordinátához. Az első feladat helyes megoldása ennek a két számnak a szorzata lesz.
A feladat megoldásához készítettem egy új ReadLinesOfFile változatot, ami egy szöveges fájl egy sorából egy objektum létrehozását támogatja. A metódus tokenizer paramétere az elválasztó karakter, a objectFactory meg egy olyan lambda metódus, aminek a bemenete egy string tömb, amiből majd T típust állítunk elő.
public static IEnumerable<T> ReadLinesOfFile<T>(string filename, char tokenizer, Func<string[], T> objectFactory)
{
using (var file = File.OpenText(filename))
{
string? line;
do
{
line = file.ReadLine();
if (!string.IsNullOrEmpty(line))
{
string [] parts = line.Split(tokenizer, StringSplitOptions.RemoveEmptyEntries);
yield return objectFactory.Invoke(parts);
}
}
while (line != null);
}
}
A fenti metódus előnye, hogy objektumorientált leírást tesz lehetővé és van egy olyan tippem, hogy későbbi napi feladatok esetén jól fog jönni.
A megoldáshoz további domain osztályokat és típusokat definiáltam. A Day2Instruction a lehetséges utasításokat tartalmazza, míg a Day2PositionData a fájl egy sorát írja le.
public enum Day2Instruction
{
Forward,
Down,
Up,
}
public class Day2PositionData
{
public Day2Instruction Instruction { get; set; }
public int Value { get; set; }
}
Ezeknek a segítségével a megoldás a következő:
internal class Day02 : IPuzzleSolution
{
public void Execute()
{
var positions = FileReader.ReadLinesOfFile<Day2PositionData>("inputs\\02.txt", ' ', (tokens) =>
{
return new Day2PositionData
{
Instruction = Enum.Parse<Day2Instruction>(tokens[0], true),
Value = int.Parse(tokens[1])
};
}).ToArray();
int finalDepth = 0;
int finalHorizontal = 0;
foreach (var position in positions)
{
(int depth, int horizontal) = DecodePosition(position);
finalDepth += depth;
finalHorizontal += horizontal;
}
Console.WriteLine(finalDepth * finalHorizontal);
}
private static (int depth, int horizontal) DecodePosition(Day2PositionData position)
{
return position.Instruction switch
{
Day2Instruction.Forward => (0, position.Value),
Day2Instruction.Down => (position.Value, 0),
Day2Instruction.Up => (-position.Value, 0),
_ => (0, 0),
};
}
}
A megoldás lényegi része abból áll, hogy soronként végigmegyünk a fájlon és eldöntjük, hogy az utasítás milyen hatással lesz a végső mélységre és horizontális koordinátára. Ehhez készítettem egy DecodePosition metódust, ami egy ValueTuple-ban visszaadja, hogy mennyivel kell növelni a sor feldolgozása után a mélység és horizontális koordinátákat. Végezetül kiírja a két szám szorzatát, ami a megoldás lesz. A DecodePosition egy switch kifejezéssel lett megoldva, de megoldható lett volna egy komplexebb if-else utasításpárossal is.
2. feladat
A második feladat lényegében az első variánsa. Kiderül, hogy az utasítások nem azt jelentik, mint amit gondoltunk. Az up és down utasítások nem kilencven fokban emelik a tengeralattjárót, hanem az orr cél szögét (aim) állítják. A forward utasítás meg valójában két dolgot végez: először is növeli a horizontális pozíciót, a mélységet pedig az cél szög és a horizontális elmozdulás szorzata alapján módosítja.
A tengeralattjáró ebben a feladatban is 0,0 pozícióról indul és 0 a célszög. A feladatban az új információk alapján ki kell számolni a végső mélység és horizontális pozíciót és a megoldás ismét a kettőnek a szorzata lesz.
A megoldás nagyon hasonlít az első feladatra:
internal class Day02B : IPuzzleSolution
{
public void Execute()
{
var positions = FileReader.ReadLinesOfFile<Day2PositionData>("inputs\\02.txt", ' ', (tokens) =>
{
return new Day2PositionData
{
Instruction = Enum.Parse<Day2Instruction>(tokens[0], true),
Value = int.Parse(tokens[1])
};
}).ToArray();
int finalDepth = 0;
int finalHorizontal = 0;
int currentAim = 0;
foreach (var position in positions)
{
(int depth, int horizontal, int newAim) = DecodePosition(position, currentAim);
finalDepth += depth;
finalHorizontal += horizontal;
currentAim = newAim;
}
Console.WriteLine(finalDepth * finalHorizontal);
}
private static (int depth, int horizontal, int newAim) DecodePosition(Day2PositionData position, int currentAim)
{
return position.Instruction switch
{
Day2Instruction.Forward => (currentAim * position.Value, position.Value, currentAim),
Day2Instruction.Down => (0, 0, currentAim + position.Value),
Day2Instruction.Up => (0, 0, currentAim - position.Value),
_ => (0, 0, 0),
};
}
}
Igazából a lényegi különbség annyi, hogy a DecodePosition metódus másként néz ki. Két bemenete van: a sor és a jelenlegi cél szög. Kimenete pedig három érték: a mélység, a horizontális pozíció és az új célszög. A mélység és horizontális pozíció összegzésre kerül, míg a célszöget szimplán felülírjuk.
Mindenkinek kellemes Advent of Code időszakot kívánunk. Holnap folytatása következik. 🎄