오 이런 것이 있었군요.
1번 부터 25번까지 알고리즘 문제가 열리며 지금은 14번째 문제가 게시되어 있습니다.
ANDREA ANGELLA님이 C#을 이용해 Advent of Code 2022
문제를 풀고 있습니다!
오 이런 것이 있었군요.
1번 부터 25번까지 알고리즘 문제가 열리며 지금은 14번째 문제가 게시되어 있습니다.
ANDREA ANGELLA님이 C#을 이용해 Advent of Code 2022
문제를 풀고 있습니다!
저의 Day1 문제 풀입니다. 문제를 응용할 수 있는 문제가 새로 열립니다. 고전적으로 풀었습니다.
var lines = File.ReadAllLines("day1_input.txt");
var (max, sum) = (0, 0);
foreach (var line in lines)
{
if (line is "")
{
if (sum > max)
max = sum;
sum = 0;
continue;
}
sum += int.Parse(line);
}
Console.WriteLine(max);
Day1의 첫 번째 문제를 풀면 응용하는 두 번째 문제가 열리는데 상위 3개의 합을 구하는 것입니다. 슬슬 LINQ로 할껄 약간의 후회를 하게 되네요 ^^;
위의 코드는 다음처럼 LINQ로도 표현 가능합니다.
var max = File.ReadAllText("day1_input.txt")
.Split(Environment.NewLine + Environment.NewLine)
.Select(x =>
x.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)
.Sum(y => int.Parse(y))
)
.Max();
Console.WriteLine(max);
가독성은? 글쎄요… 첫 번째 코드가 좋아 보입니다.
Day1의 두번째 문제의 풀이입니다. LINQ를 사용했으므로 정렬을 한 후 상위 3개의 합을 구하는 것으로 풀 수 있습니다.
var max = File.ReadAllText("day1_input.txt")
.Split(Environment.NewLine + Environment.NewLine)
.Select(x =>
x.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)
.Sum(y => int.Parse(y))
)
.OrderByDescending(x => x)
.Take(3)
.Sum();
Console.WriteLine(max);
Day2 문제를 풀어보았습니다.
이해하면 간단한 문제인데 영어라 처음 이해하기가 어려웠네요.
다음은 풀이입니다.
internal class Day2
{
/// <summary>
/// 가위바위보 게임에서 승리하자
/// </summary>
public static void Solve1()
{
var lines = File.ReadAllLines("day2_input.txt");
var sum = 0;
foreach (var line in lines)
{
var temp = line.Split();
var y = temp[0] switch { "A" => Kind.바위, "B" => Kind.보, "C" => Kind.가위, _ => throw new InvalidOperationException() };
var i = temp[1] switch { "X" => Kind.바위, "Y" => Kind.보, "Z" => Kind.가위, _ => throw new InvalidOperationException() };
var score = (int)i;
// 이긴 경우
if ((i, y) is (Kind.바위, Kind.가위) ||
(i, y) is (Kind.보, Kind.바위) ||
(i, y) is (Kind.가위, Kind.보))
score += 6;
// 비긴 경우
else if (i == y)
score += 3;
sum += score;
}
Console.WriteLine(sum);
}
}
internal enum Kind
{
바위 = 1,
보 = 2,
가위 = 3
}
만들고 나니 영 마음에 들지 않습니다.
이긴 경우에 대한 패턴을 좀 더 분석한 후 다음의 코드를 생성하였습니다.
var lines = File.ReadAllLines("day2_input.txt");
var sum = 0;
foreach (var line in lines)
{
var y = line[0] - 'A';
var i = line[2] - 'X';
var score = 0;
// 비긴경우
if (i == y)
score = 3;
// 이긴경우
else if (i == (y + 1) % 3)
score = 6;
sum += score + i + 1;
}
Console.WriteLine(sum);
Day2의 심화 문제도 비슷한 패턴으로 풀 수 있습니다.
var lines = File.ReadAllLines("day2_input.txt");
var sum = 0;
foreach (var line in lines)
{
var y = line[0] - 'A'; // 0:바위, 1:보, 2:가위
var r = line[2] - 'X'; // 0:짐, 1:비김, 2:이김
// 0, 0 : 바위, i = 2
// 0, 1 : 바위, i = 0
// 0, 2 : 바위, i = 1
// 1, 0 : 보, i = 0
// 1, 1 : 보, i = 1
// 1, 2 : 보, i = 2
// 2, 0 : 가위, i = 1
// 2, 1 : 가위, i = 2
// 2, 2 : 가위, i = 0
var i = (y + r + 2) % 3;
sum += r * 3 + i + 1;
}
Console.WriteLine(sum);
그런데 코드는 간결해 졌지만 가독성은 떨어지는군요.
Day3 문제를 풀어봤습니다.
vJrwpWtwJgWrhcsFMMfFFhFp
jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
PmmdzqPrVvPwwTWBwg
wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
ttgJtRGJQctTZtZT
CrZsJsPPZsGzwwsLwLmpwMDw
각 문자열을 절반으로 자른 후 왼쪽 문자열과 오른쪽 문자열 중 같은 첫 문자를 우선순위 값으로 치환해서 합계를 구하면 풀 수 있습니다.
var lines = File.ReadAllLines("day3_input.txt");
//var lines = new[]
//{
// "vJrwpWtwJgWrhcsFMMfFFhFp",
// "jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL",
// "PmmdzqPrVvPwwTWBwg",
// "wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn",
// "ttgJtRGJQctTZtZT",
// "CrZsJsPPZsGzwwsLwLmpwMDw",
//};
var sum = 0;
foreach (var line in lines)
{
var rucksackSize = line.Length / 2;
var aList = line[..rucksackSize];
var bList = line[rucksackSize..];
//var result = from a in aList
// join b in bList
// on a equals b
// select a;
//var c = result.FirstOrDefault();
var c = aList
.Join(bList, x => x, y => y, (x, y) => x)
.FirstOrDefault();
var priority = GetPriorityNumber(c);
sum += priority;
}
Console.WriteLine(sum);
static int GetPriorityNumber(char c) => c switch
{
>= 'a' and <= 'z' => c - 'a' + 1,
>= 'A' and <= 'Z' => c - 'A' + 27,
_ => 0
};
LINQ를 사용하면 쉽게 풀리는 문제네요.
Join()
대신 Intersect()
로 좀 더 간단히 표현할 수 있습니다.
var c = aList
.Intersect(bList)
.FirstOrDefault();
Day3의 심화 문제입니다. 심화 문제는 3줄에 공통으로 들어간 문자를 찾아 우선순위 값으로 변환한 후 그 합계를 구하는 것입니다.
이런 문제는 LINQ를 이용하면 쉽게 풀립니다.
var lines = File.ReadAllLines("day3_input.txt");
//var lines = new[]
//{
// "vJrwpWtwJgWrhcsFMMfFFhFp",
// "jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL",
// "PmmdzqPrVvPwwTWBwg",
// "wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn",
// "ttgJtRGJQctTZtZT",
// "CrZsJsPPZsGzwwsLwLmpwMDw",
//};
var sum = lines.Chunk(3)
.Select(x => x[0].Intersect(x[1]).Intersect(x[2]).FirstOrDefault())
.Sum(x => GetPriorityNumber(x));
Console.WriteLine(sum);
static int GetPriorityNumber(char c) => c switch
{
>= 'a' and <= 'z' => c - 'a' + 1,
>= 'A' and <= 'Z' => c - 'A' + 27,
_ => 0
};
Day4의 문제는 두 개의 범위 중 포함되는 범위의 개수를 구하는 것입니다.
public static void Solve1()
{
var lines = File.ReadAllLines("day4_input.txt");
//var lines = new[]
//{
// "2-4,6-8",
// "2-3,4-5",
// "5-7,7-9",
// "2-8,3-7",
// "6-6,4-6",
// "2-6,4-8",
//};
var count = lines.Select(x => x.Split(','))
.Select(x => new
{
A = x[0].Split('-').Select(y => int.Parse(y)).ToArray(),
B = x[1].Split('-').Select(y => int.Parse(y)).ToArray()
})
.Count(x => IsInclude(x.A, x.B));
Console.WriteLine(count);
static bool IsInclude(int[] r1, int[] r2) => (r1, r2) switch
{
_ when r1[0] <= r2[0] && r1[1] >= r2[1] => true,
_ when r2[0] <= r1[0] && r2[1] >= r1[1] => true,
_ => false
};
결과가 나오면 평균은 하는 것이겠지만 ANDREA ANGELLA님의 풀이도 궁금해지는군요.
var assignments = File.ReadAllLines("Input.txt")
.Select(pair => pair.Split(","))
.Select(x => (Assignment.Parse(x[0]), Assignment.Parse(x[1])));
var fullyContainedAssignments = assignments.Where(a => a.First.FullyContains(a.Second) || a.Second.FullyContains(a.First));
Assert.That(fullyContainedAssignments.Count(), Is.EqualTo(518));
private record Assignment(int Start, int End)
{
public bool Overlap(Assignment a) => End < a.Start || Start > a.End;
public bool FullyContains(Assignment a) => Start <= a.Start && End >= a.End;
public static Assignment Parse(string a) => new(int.Parse(a.Split("-")[0]), int.Parse(a.Split("-")[1]));
}
깔끔하군요.
Day4의 응용문제는 두개의 범위가 겹칠 경우를 카운트 하는 것입니다. 다양한 풀이가 있겠지만 이번에는 좀 더 무식하게 짜봤습니다.
public static void Solve2()
{
var lines = File.ReadAllLines("day4_input.txt");
//var lines = new[]
//{
// "2-4,6-8",
// "2-3,4-5",
// "5-7,7-9",
// "2-8,3-7",
// "6-6,4-6",
// "2-6,4-8",
//};
var count = lines.Select(x => x.Split(','))
.Select(x => new
{
A = x[0].Split('-').Select(y => int.Parse(y)).ToArray(),
B = x[1].Split('-').Select(y => int.Parse(y)).ToArray()
})
.Count(x => IsOverlap(x.A, x.B));
Console.WriteLine(count);
static bool IsOverlap(int[] r1, int[] r2)
{
var r1List = Enumerable.Range(r1[0], r1[1] - r1[0] + 1);
var r2List = Enumerable.Range(r2[0], r2[1] - r2[0] + 1);
return r1List.Intersect(r2List).Any();
}
}
Day5 문제는 텍스트 정보에서 스택 목록을 만들고 이동 명령을 해석해서 스택을 조정한 뒤 스택에 쌓인 가장 위의 물건을 얻는 문제입니다.
[D]
[N] [C]
[Z] [M] [P]
1 2 3
move 1 from 2 to 1
move 3 from 1 to 3
move 2 from 2 to 1
move 1 from 1 to 2
최대한 LINQ로 풀려고 노력해봤고 아는 LINQ 명령이 많지 않다는 것을 느꼈습니다. 저에게 AoC 문제는 LINQ에 좀 더 익숙해질 수 있는 경험이 되곘군요.
public static void Solve1()
{
var lines = File.ReadAllText("day5_input.txt");
//var lines = """
// [D]
// [N] [C]
// [Z] [M] [P]
// 1 2 3
// move 1 from 2 to 1
// move 3 from 1 to 3
// move 2 from 2 to 1
// move 1 from 1 to 2
// """;
// 스택부와 명령부 라인 얻음
var (stacksLines, commandLines) = lines
.Split(Environment.NewLine + Environment.NewLine)
.Chunk(2)
.Select(x =>
(x[0].Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries),
x[1].Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)))
.First();
// 스택부에서 박스 정보 얻음
var stackBoxes = stacksLines
.Select(x => x
.Chunk(4)
.Select((y, i) => (Number: i + 1, Box: y[1]))
.Where(x => x.Box is not ' ') // 빈 슬롯 제외
)
.SkipLast(1) // 마지막 숫자부 제외
.SelectMany(x => x)
.Reverse(); // 아래부터 담아야 하므로 반전
var stackCount = stackBoxes.Max(x => x.Number);
// 박스를 스택에 담음
var stacks = new Stack<char>[stackCount];
foreach (var stackBox in stackBoxes)
{
ref var stack = ref stacks[stackBox.Number - 1];
if (stack is null)
stack = new Stack<char>();
stack.Push(stackBox.Box);
}
// 명령부 처리
foreach (var commandLine in commandLines)
{
var (moves, from, to) = commandLine.Split(' ')
.Chunk(6)
.Select(x => (int.Parse(x[1]), int.Parse(x[3]), int.Parse(x[5])))
.First();
foreach (var count in Enumerable.Range(1, moves))
{
var stack = stacks[from - 1].Pop();
stacks[to - 1].Push(stack);
}
}
var result = string.Concat(stacks.Select(x => x.First().ToString()));
Console.WriteLine(result);
}
문제를 계속 풀어가다 보면 문제 해결을 위해 문제를 연속된 정보 - Stream
으로 보고 풀어가는 문제 해결 방법에 대해 좀 더 익숙해지지 않을까 생각해봅니다.
Day5의 심화 문제입니다. 이제는 상자를 하나씩 옮기는 것이 아니라 옮겨야 하는 상자를 동시에 옮겼을 때 최종 구성된 스택에서 상단에 놓진 박스들을 구하는 문제입니다.
더이상 스택이 필요 없으므로 여러개를 옮길 수 있는 List<T>
로 컬렉션을 변경하고 명령 처리 후 최종 스택 상단의 박스를 결합하는 것을 string.Concat()
대신 LINQ Aggregate()
를 사용하였습니다.
public static void Solve2()
{
var lines = File.ReadAllText("day5_input.txt");
//var lines = """
// [D]
// [N] [C]
// [Z] [M] [P]
// 1 2 3
// move 1 from 2 to 1
// move 3 from 1 to 3
// move 2 from 2 to 1
// move 1 from 1 to 2
// """;
// 스택부와 명령부 라인 얻음
var (stacksLines, commandLines) = lines
.Split(Environment.NewLine + Environment.NewLine)
.Chunk(2)
.Select(x =>
(x[0].Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries),
x[1].Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)))
.First();
// 스택부에서 박스 정보 얻음
var stackBoxes = stacksLines
.Select(x => x
.Chunk(4)
.Select((y, i) => (Number: i + 1, Box: y[1]))
.Where(x => x.Box is not ' ') // 빈 슬롯 제외
)
.SkipLast(1) // 마지막 숫자부 제외
.SelectMany(x => x);
var stackCount = stackBoxes.Max(x => x.Number);
// 박스를 스택에 담음
var stacks = new List<char>[stackCount];
foreach (var stackBox in stackBoxes)
{
ref var stack = ref stacks[stackBox.Number - 1];
if (stack is null)
stack = new List<char>();
stack.Add(stackBox.Box);
}
// 명령부 처리
foreach (var commandLine in commandLines)
{
var (moves, from, to) = commandLine.Split(' ')
.Chunk(6)
.Select(x => (int.Parse(x[1]), int.Parse(x[3]), int.Parse(x[5])))
.First();
var fromStacks = stacks[from - 1];
var toStacks = stacks[to - 1];
var movingBoxes = fromStacks.GetRange(0, moves);
fromStacks.RemoveRange(0, moves);
toStacks.InsertRange(0, movingBoxes);
}
//var result = string.Concat(stacks.Select(x => x.First().ToString()));
var result = stacks.Aggregate("", (x, stack) => x + stack.First());
Console.WriteLine(result);
}
다음 문제부터는 문제가 유도하는 취지에 맞게 유닛 테스트로 구성해서 해봐야겠습니다.