# 99클럽 코테 스터디 10일차 TIL - 당연하다고 생각했던 문제 이해의 함정 (평행선 개수 세기)

피터·2025년 4월 11일
0

오늘은 좌표 평면 위의 점들로 만들 수 있는 x축 또는 y축 평행선 개수를 세는 문제를 풀었다. 문제는 간단해 보였는데, 정답까지 가는 길은 생각보다 험난했다. 당연하다고 생각했던 문제 이해가 발목을 잡을 줄이야... 오늘의 삽질은 정말 값진 교훈을 남겼다.


🤔 오해의 시작: 왜 조합을 계산했을까?

문제를 처음 읽었을 때, "두 개 이상의 점을 지나면서 x축 또는 y축에 평행한 직선이 몇 개인지" 라는 문구를 보고, 자연스럽게 '같은 x좌표를 가진 점들이 N개 있다면, 그 점들 중 2개를 골라 만들 수 있는 모든 y축 평행선'을 세어야 한다고 생각했다. y축 평행선도 마찬가지였다.

그래서 각 x좌표와 y좌표의 빈도수를 센 다음, 빈도수가 N인 경우 C(N, 2), 즉 N * (N - 1) / 2 (등차수열의 합과 유사한 형태)를 계산해서 모두 더하면 되겠다고 판단했다. 코드는 금방 작성했지만... 결과는 계속 "틀렸습니다" 였다.

머릿속은 복잡해졌다.

  • "좌표 범위 때문에 오버플로우가 났나? Int64로 바꿔야 하나?"
  • "입력 처리에서 ! 강제 언래핑이 문제인가? guard let을 써야 하나?"
  • "딕셔너리 사용법이 틀렸나?"
  • "혹시 엄청난 엣지 케이스가 있나?"

온갖 문법적, 기술적 측면만 파고들며 시간을 보냈다. 정작 가장 중요한 '문제 이해'를 의심하지 못했던 것이다.


🔍 문제 다시 읽기: "고유한" 직선!

계속된 실패에 지쳐 혹시나 하는 마음에 문제 설명을 다시 꼼꼼히 읽어보았다.

"평면에 n개의 점이 있다. 그중 두 개 이상의 점을 지나면서 x축 또는 y축에 평행한 직선이 몇 개인지 알아내는 프로그램을 작성하시오."

순간 머리를 한 대 맞은 듯한 느낌이 들었다. 문제는 "점들의 쌍" 이나 "만들 수 있는 모든 선분" 의 개수를 묻는 것이 아니었다. 점이 아무리 많아도, 예를 들어 x=5 위에 점이 100개가 있어도, 그 점들을 지나는 y축 평행선은 오직 x=5 하나 뿐이다. 즉, "고유한 직선" 의 개수를 세라는 의미였다!

그동안 C(N, 2)를 계산했던 것은 완전히 잘못된 방향이었다. 허탈함과 동시에 '아!' 하는 탄식이 나왔다.


✅ 첫 번째 시도 (잘못된 접근): 점들의 쌍 세기

나의 첫 번째 코드는 다음과 같았다. 같은 좌표를 공유하는 점들로 만들 수 있는 모든 직선(실제로는 점 쌍)의 개수를 세려고 했다.

// 초기 잘못된 코드 (제출했던 코드)
import Foundation

let count = Int(readLine()!)!

var xPoints = [Int: Int]()
var yPoints = [Int: Int]()

for _ in 0..<count {
    var dot = readLine()!.split(separator: " ").compactMap { Int($0) }
    // y좌표와 x좌표 순서로 읽도록 수정 (이전 질문 기반)
    let yCoord = dot.removeLast()
    let xCoord = dot.removeLast()
    yPoints[yCoord, default: 0] += 1
    xPoints[xCoord, default: 0] += 1
}

// 점 N개로 만들 수 있는 선분(쌍)의 개수 계산 함수 (오해의 근원)
func calculateParrellaLines(_ value: Int) -> Int {
    guard value > 1 else { return 0 }
    return Int(((value - 1)*value)/2) // C(value, 2)
}

// 각 좌표 그룹별 쌍의 개수를 모두 합산
let xCount = xPoints.values.reduce(0, { $0 + calculateParrellaLines($1) })
let yCount = yPoints.values.reduce(0, { $0 + calculateParrellaLines($1) })

print(xCount + yCount)

왜 틀렸는가: calculateParrellaLines 함수는 점 N개로 만들 수 있는 직선의 '쌍'의 개수를 계산한다. 하지만 문제는 '고유한' 직선의 개수를 원했다. 예를 들어 x=0 위에 점이 3개 있으면, 위 코드는 3을 결과에 더하지만, 정답은 1만 더해야 했다.


✅ 두 번째 시도 (정답 접근): 고유한 직선 세기

문제의 요구사항을 정확히 파악한 후 코드를 수정하는 것은 매우 간단했다. 조합 계산은 전혀 필요 없었다.

핵심 아이디어:

  • 어떤 x좌표든 2번 이상 등장하면, 그 x좌표는 y축에 평행한 고유한 직선 하나를 만든다.
  • 어떤 y좌표든 2번 이상 등장하면, 그 y좌표는 x축에 평행한 고유한 직선 하나를 만든다.
// 최종 올바른 코드
import Foundation

let n = Int(readLine()!)!

var xPoints = [Int: Int]()
var yPoints = [Int: Int]()

for _ in 0..<n {
    let coordinates = readLine()!.split(separator: " ").compactMap { Int($0) }
    if coordinates.count == 2 {
        let xCoord = coordinates[0]
        let yCoord = coordinates[1]
        // ✨ 오늘의 이득: default 서브스크립트로 간결하게 빈도수 증가!
        xPoints[xCoord, default: 0] += 1
        yPoints[yCoord, default: 0] += 1
    }
}

// x좌표가 2번 이상 등장한 '고유한' x좌표의 개수 (수직선 개수)
let verticalLines = xPoints.values.filter { $0 >= 2 }.count

// y좌표가 2번 이상 등장한 '고유한' y좌표의 개수 (수평선 개수)
let horizontalLines = yPoints.values.filter { $0 >= 2 }.count

print(verticalLines + horizontalLines)

왜 맞는가: filter { $0 >= 2 }.count를 사용하여, 단순히 같은 좌표를 공유하는 점이 2개 이상인 경우를 딱 1번만 카운트한다. 이것이 문제에서 요구한 "고유한 직선의 개수"와 정확히 일치한다. 정말 어이없을 정도로 간단한 수정이었다!


💡 오늘의 이득: 딕셔너리 default: 서브스크립트

이번 삽질 과정에서 그나마 얻은 작은 수확은 Swift 딕셔너리의 default: 서브스크립트를 제대로 활용해본 것이다. 이전에는 if let 등으로 키 존재 여부를 확인하고 값을 업데이트했지만,

xPoints[xCoord, default: 0] += 1

이렇게 한 줄로 쓰니 코드가 훨씬 간결해졌다. xCoord 키가 딕셔너리에 없으면 default 값인 0을 사용하고, 거기에 1을 더해서 저장해준다. 키가 이미 있다면 기존 값에 1을 더한다. 빈도수 계산할 때 정말 유용하다!


✨ 오늘 배운 점 정리

  • 문제를 꼼꼼히, 정확히 읽는 것이 코딩보다 더 중요할 수 있다. 특히 "개수"를 세라는 요구가 나오면, 그것이 '모든 경우의 수'인지 '고유한 항목의 수'인지 명확히 파악해야 한다.
  • '당연히 이렇겠지' 하는 선입견이 문제 해결의 가장 큰 방해물이 될 수 있다. 코드가 틀렸을 때는 로직이나 문법뿐만 아니라, 문제 이해 자체를 다시 점검해보자.
  • Swift 딕셔너리의 default: 서브스크립트는 빈도수 계산 등에 매우 유용하고 코드를 간결하게 만들어준다.
  • 비록 삽질이었지만, 왜 틀렸는지 고민하고 깨닫는 과정 자체가 중요한 공부가 된다. 오늘의 어이없는 실수는 절대 잊지 못할 것 같다!

profile
iOS 개발자입니다.

0개의 댓글