Post

2026-06-09 TIL (72일차)

2026-06-09 TIL (72일차)

알고리즘: 3진 탐색(조합)과 소수 판별 (프로그래머스 - 소수 만들기)

1. 3개의 수를 고르는 방법 (조합의 사고법)

배열에서 ‘서로 다른 3개의 수’를 순서에 상관없이 고르는 모든 경우의 수를 찾아야 합니다. 예를 들어 인덱스가 0, 1, 2 다음엔 0, 1, 3 이런 식으로 어떻게 차례대로 겹치지 않게 접근할 수 있을까요?

사고의 흐름: 3중 for문

카드를 3장 뽑는다고 상상해 봅시다.

  1. 첫 번째 사람(i)이 먼저 카드를 한 장 고릅니다. (0번 인덱스부터 시작)
  2. 두 번째 사람(j)은 첫 번째 사람이 고른 다음 카드부터 한 장 고릅니다. (i + 1부터 시작)
  3. 세 번째 사람(k)은 두 번째 사람이 고른 다음 카드부터 한 장 고릅니다. (j + 1부터 시작)

이 논리를 코드로 옮기면 자연스럽게 3중 for문이 완성됩니다.

1
2
3
4
5
6
7
8
9
// 배열의 길이가 n일 때 (n = nums_len)
for (int i = 0; i < n - 2; i++) {           // 첫 번째 수는 뒤에 2개를 남겨둬야 함
    for (int j = i + 1; j < n - 1; j++) {   // 두 번째 수는 뒤에 1개를 남겨둬야 함
        for (int k = j + 1; k < n; k++) {   // 세 번째 수는 끝까지 갈 수 있음
            int sum = nums[i] + nums[j] + nums[k];
            // 여기서 sum이 소수인지 판별!
        }
    }
}

2. 소수(Prime Number) 판별 로직

소수는 “1과 자기 자신으로만 나누어떨어지는 1보다 큰 자연수”입니다. 이 정의를 코드로 어떻게 옮길까요?

직관적인 방법 (전부 나누어 보기)

어떤 숫자 num이 소수인지 확인하려면, 2부터 num - 1까지의 모든 숫자로 num을 나누어보면 됩니다. 나누다가 단 한 번이라도 나머지가 0이 나오면? 1과 자기 자신 외에 약수가 있다는 뜻이므로 소수가 아닙니다. (시간 복잡도: O(N))

시간 단축 비법 (루트까지만 검사하기)

2부터 num - 1까지 전부 검사하면 숫자가 커질수록 너무 비효율적입니다. 수학적 성질을 이용하면 num제곱근(루트)까지만 검사해도 충분히 소수 여부를 알 수 있습니다.

  • 이유: 약수는 짝을 이룹니다. (예: 16의 약수는 1, 2, 4, 8, 16)
  • 중간값인 4(16의 제곱근)를 넘어가면 8 x 2 = 16처럼 앞서 검사했던 짝꿍들의 반복이기 때문에 굳이 더 검사할 필요가 없습니다. (시간 복잡도: O(√N))
  • 코드 적용 (sqrt 함수 활용): C++의 <cmath> 라이브러리에서 제공하는 sqrt() 함수를 사용하여 루트 값을 직관적으로 구합니다. 이때 매 반복마다 루트 값을 다시 계산하지 않도록, int limit = sqrt(num); 처럼 미리 변수에 저장해 두고 반복문의 조건식(i <= limit)에 사용하는 것이 성능 최적화에 좋습니다.

예시 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 숫자를 하나 던져주면, 소수인지 아닌지 true/false로 대답해주는 함수
bool isPrime(int num) {
    // 1. 0이나 1은 소수가 아니므로 무조건 컷!
    if (num < 2) return false;

    // 2. 2부터 루트 num 까지만 나누어봅니다.
    // (sqrt 함수를 사용하여 수학적으로 명확하게 표현)
    int limit = sqrt(num);
    for (int i = 2; i <= limit; i++) {
        // 3. 만약 num이 i로 나누어 떨어진다면? (약수가 존재함)
        if (num % i == 0) {
            return false; // 소수가 아닙니다!
        }
    }

    // 4. 중간에 한 번도 안 나누어 떨어지고 무사히 반복문을 빠져나왔다면?
    return true; // 소수가 맞습니다!
}

Unreal: UMG C++ UI 바인딩과 meta=(BindWidget) 정리

1. meta=(BindWidget)이 없을 때의 불편함

과거 언리얼 초기 버전이나 이 기능을 모를 때는 C++ 클래스에서 블루프린트(디자이너)에 만들어둔 버튼이나 텍스트를 가져오기 위해 다음과 같은 하드코딩 방식을 써야 했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// .h 파일
class UButton* StartButton;

// .cpp 파일의 Initialize() 또는 NativeConstruct() 내부
bool UMyUserWidget::Initialize()
{
    Super::Initialize();
    
    // 1. 이름을 문자열(String)로 검색해서 위젯을 찾음
    UWidget* FoundWidget = GetWidgetFromName(TEXT("StartButton"));
    
    // 2. 찾은 위젯이 진짜 버튼인지 캐스팅(형변환)
    StartButton = Cast<UButton>(FoundWidget);
    
    // 3. 널 체크 (만약 못 찾았으면 크래시 나지 않게 방어)
    if (StartButton == nullptr)
    {
        return false; 
    }
    
    return true;
}

이 방식의 3가지 문제점

  1. 타이핑 실수(오타)의 위험: TEXT("StartButon")처럼 오타를 내면 C++ 컴파일은 정상적으로 성공해 버립니다. 하지만 게임을 실행하고 버튼을 누르는 순간 널 포인터 에러로 런타임 크래시(Runtime Crash)가 발생합니다.
  2. 끔찍한 반복 작업: UI 하나에 버튼 5개, 텍스트 10개, 이미지 5개가 있다면 저 길고 긴 검색+캐스팅+널체크 코드를 20번이나 반복해서 적어야 합니다.
  3. 디자이너와의 소통 단절: C++ 개발자는 코드로 "StartButton"을 찾고 있는데, UI 디자이너가 블루프린트에서 실수로 버튼 이름을 "Btn_Start"로 바꿔버리면 즉시 버그가 발생합니다.

2. meta=(BindWidget)이 존재하는 이유 (해결책)

이 매크로 하나만 붙여주면 위의 모든 문제가 단 한 줄로 해결됩니다.

  • 장점 1: 자동 매핑 (보일러플레이트 코드 제거) 이름으로 검색하고, 형변환하고, 널 체크하는 코드를 작성할 필요가 아예 사라집니다. C++ 변수 이름과 블루프린트 위젯 이름이 똑같기만 하면 엔진이 알아서 연결해 줍니다.
  • 장점 2: 강력한 안전성 보장 (컴파일 에러 유도) C++에 BindWidget이라고 선언해 뒀는데, 정작 블루프린트에 동일한 이름의 위젯이 없거나(오타), 위젯 타입이 다르면(C++은 Button인데 블루프린트는 Text일 때) 블루프린트 컴파일 자체가 막히면서 빨간색 에러를 뿜어냅니다. 게임을 켜보기도 전에 실수를 확실하게 잡아주는 엄청난 안전장치입니다.

3. 블루프린트 위젯 바인딩 시 주의사항 및 원리

BindWidget을 사용하고 블루프린트(WBP)를 만들 때 컴파일 에러가 난다면, 이는 C++과 BP 간의 강제적인 약속(Contract)이 지켜지지 않았기 때문입니다.

  • 위젯 이름은 스스로 덮어써야 합니다: 팔레트에서 위젯(예: Editable Text Box)을 끌어다 놓으면 이름이 EditableTextBox_23처럼 임의로 지어집니다. 이를 클릭하여 C++에서 선언한 변수 이름(대소문자 완벽 일치)으로 직접 수정해 주어야 바인딩이 성립합니다.
  • 적용 범위: 이 바인딩 강제성은 해당 C++ 클래스를 부모로 삼아 생성한 특정 위젯 블루프린트(WBP) 내에서만 요구됩니다. 다른 UI와는 무관합니다.

4. UE5 표준: TObjectPtr와 함께 사용하는 방법

언리얼 엔진 5부터는 메모리 최적화, 지연 로딩, 에디터 참조 추적 성능을 높이기 위해 원시 포인터(UButton*) 대신 스마트 포인터 래퍼인 TObjectPtr<T>를 사용하는 것을 강력히 권장합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "CXChatInput.generated.h"

// 전방 선언 (헤더 포함을 줄여 컴파일 속도 향상)
class UEditableTextBox;
class UProgressBar;

UCLASS()
class CHATX_API UCXChatInput : public UUserWidget
{
    GENERATED_BODY()

protected:
    // 반드시 블루프린트 디자이너에 'EditableTextBox_ChatInput'라는 이름의 UEditableTextBox가 있어야 함
    UPROPERTY(meta = (BindWidget))
    TObjectPtr<UEditableTextBox> EditableTextBox_ChatInput;


    // ==========================================
    // (선택적 바인딩)
    // ==========================================
    
    // 블루프린트에 'HP_Bar'가 있으면 연결하고, 없어도 에러를 내지 않음
    UPROPERTY(meta = (BindWidgetOptional))
    TObjectPtr<UProgressBar> HP_Bar;
};

팁: BindWidgetOptional

“이 UI에서는 텍스트 박스를 넣고 싶은데, 파생 UI에서는 빼고 싶을 수도 있다” 거나 “당장 위젯을 안 만들더라도 블루프린트 컴파일 에러는 피하고 싶다”면 meta=(BindWidgetOptional)을 사용하면 됩니다. 단, C++ 코드에서 이 위젯을 제어하기 전에는 반드시 Null 체크(if (HP_Bar))를 해주어야 런타임 크래시를 막을 수 있습니다.

This post is licensed under CC BY 4.0 by the author.