[Programmers Lv. 1] C#: 모의고사
프로그래머스 Lv.1에 있는 "모의고사" 문제에 대한 풀이 및 탐구에 대한 기록.
목 차
모의고사
문제 해설
문제 풀이 과정을 순차적으로 정리했다.
#1차 시도
if (a1 >= a2)
{
if (a3 > a1)
{
result = new int[1];
result[0] = 1;
}
else if (a3 == a1)
{
if (a1 == a2)
{
result = new int[3];
result[0] = 1;
result[1] = 2;
result[2] = 3;
}
else
{
result = new int[2];
result[0] = 1;
result[1] = 3;
}
}
else
{
if (a1 == a2)
{
result = new int[2];
result[0] = 1;
result[1] = 2;
}
else
{
result = new int[1];
result[0] = 1;
}
}
}
else
{
if (a3 > a2)
{
result = new int[1];
result[0] = 3;
}
else
{
result = new int[1];
result[0] = 2;
}
}
1차시도의 코드블럭은 잘못된 부분만 발췌해서 작성했다. 첫 시도에서는 위과 같이 if문만을 통해 조건을 나누려 했다. 하지만, 가장 큰 수 중 크기가 같을 수도 있는 것들을 단 하나도 빼먹지 않고 모두 산출해내기가 너무 힘들었고, 가독성도 매우 떨어졌다. 따라서 다음 부분을 아래와 같이 바꾸게 되었다.
#2차 시도
using System;
using System.Collections.Generic;
public class Solution
{
public int[] solution(int[] answers)
{
int[] p1 = { 1, 2, 3, 4, 5 };
int[] p2 = { 2, 1, 2, 3, 2, 4, 2, 5 };
int[] p3 = { 3, 3, 1, 1, 2, 2, 4, 4, 5, 5 };
int a1 = 0;
int a2 = 0;
int a3 = 0;
for (int i = 0; i < answers.Length; i++)
{
int temp = i % p1.Length;
if (answers[i] == p1[temp]) { a1++; }
temp = i % p2.Length;
if (answers[i] == p2[temp]) { a2++; }
temp = i % p3.Length;
if (answers[i] == p3[temp]) { a3++; }
}
int max = Math.Max(a1, Math.Max(a2, a3));
List<int> aList = new List<int>();
if (a1 == max) { aList.Add(1); }
if (a2 == max) { aList.Add(2); }
if (a3 == max) { aList.Add(3); }
return aList.ToArray();
}
}

우선, for문을 통해 세 사람이 맞춘 정답의 개수를 얻었다. 그 후, 세 사람 중 누구인지 무관하게 최다 정답이 몇 개인이 max 변수와 Math.Max를 통해 알아냈다. 그 후, if문을 통해 1, 2, 3 중 원하는 숫자만 숫자열에 추가하기 위해 List를 만든 후 정답의 개수가 max인 것만 Add해주었다. 마지막으로 이 List를 배열의 형태로 return함으로써 답을 완성했다. 왼쪽의 사진은 위의 코드 연산에 걸린 시간에 대한 기록이다.
#더 나은 답안에 대한 탐구 - GPT 버전
using System;
using System.Collections.Generic;
using System.Linq;
public class Solution
{
public int[] solution(int[] answers)
{
int[][] patterns = new int[][]
{
new int[] { 1, 2, 3, 4, 5 },
new int[] { 2, 1, 2, 3, 2, 4, 2, 5 },
new int[] { 3, 3, 1, 1, 2, 2, 4, 4, 5, 5 }
};
int[] scores = new int[3];
for (int i = 0; i < answers.Length; i++)
{
for (int j = 0; j < patterns.Length; j++)
{
if (answers[i] == patterns[j][i % patterns[j].Length])
{
scores[j]++;
}
}
}
int maxScore = scores.Max();
return scores
.Select((score, index) => new { score, index })
.Where(x => x.score == maxScore)
.Select(x => x.index + 1)
.ToArray();
}
}

보다 더 코드를 발전시킬 방안이 있을까 궁금하여, 내 코드를 보여주며 더 향상시킬 방법을 GPT에게 물었을 때 나온예제 코드이다. 정답 패턴을 2차원 배열로 구성하고, 정답의 개수를 int 배열로 만든 후 LINQ와 람다식을 통해 최다정답자 배열을 반환하도록 하였다.
하지만, 이 코드는 내가 만든 코드에 비해 정답 연산에 걸리는 시간이 약 3배 오래걸리는 것을 확인할 수 있었다.
GPT가 이 코드를 추천한 근거는 "반복 연산 제거" 와 "일반화" , 즉 주로 가독성에 있었다.
어느 쪽이 더 좋은 코드인가에 대한 판단은 아직 잘 모르겠지만, 나는 2차원 배열의 사용을 지양하고 싶기에 내 코드가 나아보인다. 또한, 굳이 LINQ를 사용할 이유도 느끼지 못했다.
변수의 선언 위치
코드를 분석하던 중, 변수의 선언 위치에 대한 궁금증이 생겼다. 이때, 이 변수는 반복문 안에서만 그 사용이 한정되어 있다고 가정한다.
- 반복문의 바로 윗줄에 변수를 선언하기 : 메모리 재할당 방지, 안전성 저하
- 반복문 안에 변수를 선언하기: 안전성 보장, 반복적인 메모리 재할당
# 변수 선언을 반복문 밖에 하는 경우
int sum = 0;
for (int i = 0; i < 10; i++)
{
sum += i;
}
장점:
- 메모리 효율성: 변수는 for문 밖에서 한 번만 선언되며, 메모리 할당은 한 번만 이루어진다.
단점:
- 가독성 저하: 변수가 꼭 루프 내에서만 사용되지 않는 것처럼 보일 수 있다. 이로 인해 코드의 의도를 명확히 파악하는 데 방해가 된다.
- 부주의한 사용: 변수를 잘못 사용하거나 다른 코드에서 실수로 변경할 위험이 있다.
# 변수 선언을 반복문 안에 하는 경우
for (int i = 0; i < 10; i++)
{
int temp = i * 2;
}
장점:
- 가독성 향상: 변수의 사용 범위가 명확하다. 반복문 내에서만 사용되며, 루프가 끝나면 변수가 사라진다. 이로써 코드가 더 직관적이고 이해하기 쉬워진다.
- 안전성: 루프 밖에서는 해당 변수에 접근할 수 없어, 의도치 않은 수정이나 잘못된 사용을 방지할 수 있다.
단점:
- 메모리 재할당: 루프가 반복될 때마다 변수가 새로 선언되고 메모리가 할당되므로, 아주 많은 반복을 수행할 때는 약간의 메모리 및 성능 오버헤드가 있을 수 있다. 다만, 현대 컴파일러는 이러한 상황을 최적화할 수 있다.
# 결론
만약 for문 밖에서 특정 변수를 사용할 일이 전무하다면, 이 변수는 for문 내부에서 선언되는 방향이 적합하다. 그 이유는 다음과 같다.
우선, 현대 C# 컴파일러는 변수 재할당에 따른 오버헤드를 최소화하는 최적화를 수행한다. 이에 따라, 이미 자동으로 특정 변수를 작은 스코프에서만 재사용하는 기법이 적용되어 있다.
또한 메모리 할당 비용은 기본 타입이라는 전제 하에 매우 저렴하기에, 반복문이 수백만 회 반복되지 않는 한 성능 저하를 체감하기는 어렵다. 설령 수백만 회 이상 반복된다 해도 성능에 유의미한 영향을 끼치는 경우는 드물다.
따라서 앞으로 C#을 사용할 때는 기본 자료형의 변수 선언은 가독성과 사용 범위에 따라 필요한 곳에 선언하기로 결론지었다.
대제목 설명