• 최초 작성일: 2022-12-03
  • 최종 수정일: 2022-12-03
  • 조회수: 2,498 회
  • 작성자: 권현욱 (엑셀러)
  • 강의 제목: VB0259 ㅡ 최장 연승 기록 집계하기

엑셀러 권현욱

들어가기 전에 ㅡ 그 지긋지긋하던 '경우의 수' 징크스를 깨다

우리나라의 월드컵 16강 도전 역사를 보면 흔히 이런 패턴으로 진행되곤 했다.

  • 조별 예선 1차전은 대체로 해볼만 한 상대와 붙는데 꼭 비기거나 진다(우루과이 전).
  • 2차전은 무조건 이겨야 하는데 1차전보다 못한 경기력을 펼친 끝에 또 비기거나 진다(가나 전).
  • 이제 3차전 밖에 기회가 없는데 하필이면 상대는 같은 조 최강팀이다(이번에는 포르투갈). 무조건 이기고 봐야 하는데 막상 뚜껑을 열어보면 '어라?' 의외로 잘 한다. 비기거나 심지어는 1점 차로 이기기도 한다.
  • 하지만 그 지긋지긋한 '경우의 수' 때문에 골득실에서 뒤져서 16강 진출 실패! 탄식과 자조, 개운치 않은 뒷맛을 남기며 4년 후를 기약하곤 했다.
로딩 중...

(이미지 출처: 서울신문)

이번에는 달랐다. 내 기억으로 월드컵 본선 무대에서는 한 번도 우리 손을 들어주지 않던 '경우의 수' 징크스가 이번에는 우리를 향해 미소 지었다. 빌드업 축구의 완성도가 높아졌고 투지만큼은 세계 톱 클래스였던 것 같다. 누구 말처럼 공은 둥글고 월드컵에서는 어떤 일이든 일어날 수 있다. 상대가 브라질이든 그 할아버지든 마찬가지다. 남은 경기는 '덤'이라 생각하고 후회 없는 승부를 펼쳤으면 한다.

(PS) 우리 경기 마치고 남의 나라 경기까지 이렇게나 가슴 졸이며 본 적이 있었나 싶다. 두 경기 보고나니 어깨가 다 아프다. 일각이 여삼추라는 말이 이래서 생겼구나, 실감할 수 있는 순간이었다.



질문 하나 & 완성 예

아이엑셀러 닷컴에서 열심히 공부하는 어느 분의 질문입니다. 아마도 테니스 동호회에서 총무 역할을 담당하시나 봅니다. 질문 내용을 요약하면 이렇습니다.

각 회원 별로 10게임씩 경기를 하며 경기마다 승/패를 기록한 시트가 있습니다. N열에는 회원 별로 몇 연승했는지 기록하고 싶다는 겁니다. 지금까지는 매번 일일이 카운팅해서 입력하고 있는데 이걸 간단히 해결할 방법이 없을까 하는 질문입니다.

로딩 중...

질문 내용

수식으로도 가능은 해 보입니다만 아주 길고 복잡한 수식이 될 것 같습니다. 그런 수식을 만들 노력과 정성이라면 VBA로 코딩하는 것이 한결 편리하고 효율적이리라 생각합니다. '연승기록' 시트에서 '연승 기록 체크' 버튼을 클릭하면 원하는 대로 결과가 나옵니다.

로딩 중...

완성 예

뿐만 아니라 승리 내역(O)에는 빨간 글자색으로, 연승 기록에는 빨간 배경색에 흰 글씨로 하이라이팅 표시되어 있습니다. 이런 것은 함수로는 도저히 할 수 없는 일이지요.

코드 작성

워크시트에서 ALT + F11 키를 눌러서 Visual Basic Editor 창을 엽니다. VB Editor에서 [삽입] - [모듈] 메뉴를 선택하여 모듈Module을 삽입하고 코드를 작성합니다. 코드에 대한 자세한 설명은 이어서 나옵니다. 길고 복잡한 코드라도 나누고 쪼개어(같은 말이네...) 접근하면 이해하기 쉬워집니다.

Sub successiveWin()
    ''' ---------- (1단계) ----------
    Dim shtX As Worksheet
    Dim rTbl As Range
    Dim rAreas As Range
    Dim rArea As Range
    Dim rA As Range
    Dim rWin As Range
    Dim rRow As Range
    Dim rX As Range
    
    On Error Resume Next
    Set shtX = Worksheets("연승기록")
    Set rTbl = shtX.Range("myTable").CurrentRegion
    Set rTbl = rTbl.Offset(1, 1).Resize(rTbl.Rows.Count - 1, rTbl.Columns.Count - 1)
    
    ''' ---------- (2단계) ----------
    With rTbl
        .Interior.ColorIndex = xlNone
        .Font.ColorIndex = 1
        With .Cells(.Columns.Count)
            .Offset(, 2).CurrentRegion.Clear
            .Offset(, 2).EntireColumn.HorizontalAlignment = xlCenter
            .Offset(-1, 2) = "연승 기록"
        End With
    End With

    ''' ---------- (3단계) ----------
    For Each rRow In rTbl.Rows
        For Each rX In rRow.Cells
            If rX = "O" Then
                If rArea Is Nothing Then
                    Set rArea = rX
                Else
                    Set rArea = rArea.Resize(, rArea.Cells.Count + 1)
                End If
            Else
KKK:
                If rAreas Is Nothing Then
                    Set rAreas = rArea
                Else
                    Set rAreas = Union(rAreas, rArea)
                End If
                Set rArea = Nothing
            End If
        Next
        If Not rArea Is Nothing Then GoTo KKK
        
    ''' ---------- (4단계) ----------
        For Each rA In rAreas.Areas
            rA.Font.ColorIndex = 3
            rA.Font.Bold = True
            If rWin Is Nothing Then
                Set rWin = rA
            Else
                If rWin.Cells.Count <= rA.Cells.Count Then Set rWin = rA
            End If
        Next
        
    ''' ---------- (5단계) ----------
        rWin.Interior.ColorIndex = 3
        rWin.Font.ColorIndex = 2
        rRow.Cells(rRow.Cells.Count).Offset(, 2) = rWin.Cells.Count
        Set rWin = Nothing
        Set rAreas = Nothing
    Next
End Sub

CODE

코드 해설

1. 첫 번째 단계는 변수를 선언하고 필요한 값이나 영역을 지정하는 작업입니다.

Dim shtX As Worksheet        ''' 워크시트 개체에 접근할 변수
Dim rTbl As Range            ''' 대진 결과표에 접근할 변수
Dim rAreas As Range          ''' 승리(O)한 영역 전체에 접근할 변수
Dim rArea As Range           ''' 승리한 영역에 개별적으로 접근할 변수
Dim rA As Range              ''' 승리 영역에 접근할 순환 변수
Dim rWin As Range            ''' 최장 연속 승리 영역에 접근할 변수
Dim rRow As Range            ''' 대진 결과표의 행 단위로 접근할 변수
Dim rX As Range              ''' 개별 셀에 접근할 순환 변수

On Error Resume Next         ''' 실행 도중 오류가 발생하더라도 다음 명령 수행
Set shtX = Worksheets("연승기록")
Set rTbl = shtX.Range("myTable").CurrentRegion        ''' myTable의 인접 영역을 rTbl 변수에 할당
Set rTbl = rTbl.Offset(1, 1).Resize(rTbl.Rows.Count - 1, rTbl.Columns.Count - 1)
                                    ''' 대진 결과표의 본문 영역을 rTbl 변수에 할당

해설

2. 대진 결과표의 서식을 초기화합니다.

With rTbl
    .Interior.ColorIndex = xlNone        ''' 셀 배경색 초기화
    .Font.ColorIndex = 1                 ''' 글자색 검정색으로 지정
    With .Cells(.Columns.Count)
        .Offset(, 2).CurrentRegion.Clear ''' 연승 내역을 기록할 주변 영역 지우기
        .Offset(, 2).EntireColumn.HorizontalAlignment = xlCenter
        .Offset(-1, 2) = "연승 기록"
    End With
End With

해설

3. For Each ~ In ~ Next 문을 두 번 중첩합니다. 바깥쪽 순환문은 테이블의 행 단위로, 안쪽 순환문은 각 행의 셀 단위로 접근합니다. 이곳이 전체 코드의 핵심입니다. rArea와 rAreas 변수에 어떤 영역이 이미 할당되어 있는지 아닌지 여부에 따라 새로운 영역을 업데이트 해주는 부분을 눈여겨 보시기 바랍니다.

For Each rRow In rTbl.Rows        ''' 테이블의 개별 행 단위로 접근
  For Each rX In rRow.Cells       ''' 개별 행의 각 셀 단위로 접근
      If rX = "O" Then            ''' 셀 값이 O(승리)라면...

        ''' rArea 변수에 아무 것도 할당되어 있지 않다면 rX를,
        ''' 이미 어떤 영역이 할당되어 있다면 다음 셀까지로 범위 확장
        If rArea Is Nothing Then
              Set rArea = rX
          Else
              Set rArea = rArea.Resize(, rArea.Cells.Count + 1)
          End If
      Else
KKK:
          ''' rAreas 변수에 아무 것도 할당되어 있지 않다면 rArea 변수 값을,
          ''' 이미 어떤 영역이 할당되어 있다면 기존 rAreas 영역과 rArea 영역 통합
          If rAreas Is Nothing Then
              Set rAreas = rArea
          Else
              Set rAreas = Union(rAreas, rArea)
          End If
          Set rArea = Nothing               ''' rArea 변수 초기화
      End If
    Next
    If Not rArea Is Nothing Then GoTo KKK    ''' 마지막 rArea 영역을 처리하기 위해 KKK 레이블로 이동

해설

4. 승리한 셀이 담긴 영역에 접근하여 서식을 지정합니다. 또한 rWin과 rA 영역을 비교하여 가장 긴 연승 기록에 해당하는 영역을 rWin 변수에 담아둡니다.

    For Each rA In rAreas.Areas         ''' rAreas의 각 영역(Area)에 접근
      rA.Font.ColorIndex = 3            ''' rA, 즉 값이 O인 셀의 글자색을 빨간색으로 지정
      rA.Font.Bold = True               ''' 굵은 글꼴로 지정

      ''' rWin 변수에 아무 것도 할당되어 있지 않다면 rA 변수 값을,
      ''' 이미 어떤 영역이 할당되어 있다면 rWin과 rA 영역의 크기를 비교하여 큰 영역을 rWin 변수에 할당
      If rWin Is Nothing Then        
          Set rWin = rA
      Else
          If rWin.Cells.Count <= rA.Cells.Count Then Set rWin = rA
      End If
    Next

해설

여기까지 진행되었다면 테이블의 첫 번째 행에 대한 처리가 끝납니다. 그 결과는 아래 그림과 같습니다. 셀 값이 O라면 굵은 빨간색으로 표기해 줍니다. 빨간 점선으로 표기한 영역이 각각의 rA에 해당합니다.

로딩 중...

5. 마지막으로, 가장 긴 연승 기록 영역에 서식을 지정합니다.

    rWin.Interior.ColorIndex = 3      ''' 셀 배경색을 빨간색으로 지정
    rWin.Font.ColorIndex = 2          ''' 글자색을 흰색으로 지정
    rRow.Cells(rRow.Cells.Count).Offset(, 2) = rWin.Cells.Count    
                      ''' 각 행의 가장 오른쪽 셀에서 2열 더 오른쪽으로 이동하여 연승 기록 삽입
    Set rWin = Nothing                ''' rWin 변수 초기화
    Set rAreas = Nothing              ''' rAreas 변수 초기화
Next

해설

중첩 For ~ Next 문이 사용되어 처음 보면 좀 복잡해 보일 수 있습니다만 VBA 디버깅 툴을 이용하여 진행 과정을 몇 차례 추적해 보면 이해하시리라 생각합니다. 이와 관련해서는 다음 강의를 참고하세요.