TIL(65)

이채원·2026년 10월 6일

TIL

목록 보기
67/72

오늘의 할일
1. 코드카타 정리
2. 세션 정리


코드카타 정리

  1. 소수 만들기
    문제 설명
    주어진 숫자 중 3개의 수를 더했을 때 소수가 되는 경우의 개수를 구하려고 합니다. 숫자들이 들어있는 배열 nums가 매개변수로 주어질 때, nums에 있는 숫자들 중 서로 다른 3개를 골라 더했을 때 소수가 되는 경우의 개수를 return 하도록 solution 함수를 완성해주세요.
#include <vector>

using namespace std;

// 어떤 숫자가 소수인지 판별해 주는 도와주는 함수
bool isPrime(int num) {
    if (num < 2) return false; // 0과 1은 소수 X
    
    // 2부터 시작해서 자기 자신보다 작은 수로 나누어 떨어지는지 확인
    for (int i = 2; i * i <= num; i++) {
        if (num % i == 0) {
            return false;
        }
    }
    return true; // 끝까지 나누어 떨어지지 않았다면 소수
}

int solution(vector<int> nums) {
    int answer = 0;
    int n = nums.size();

    // 중복 없이 3개의 숫자를 고름
    // 첫 번째 숫자 고르기
    for (int i = 0; i < n - 2; i++) {
        // 두 번째 숫자 고르기
        for (int j = i + 1; j < n - 1; j++) {
            // 세 번째 숫자 고르기
            for (int k = j + 1; k < n; k++) {
                
                // 3개 숫자 +
                int sum = nums[i] + nums[j] + nums[k];
                
                // 아까 만들어둔 isPrime
                if (isPrime(sum)) {
                    answer++;
                }
                
            }
        }
    }

    return answer;
}

AI가 작성한 코드

#include <vector>

using namespace std;

int solution(vector<int> nums) {
    // 문제의 조건에서 숫자는 1,000 이하입니다. 
    // 가장 큰 수 3개를 더해도 합이 3,000을 넘지 않으므로 한계치를 고정합니다.
    const int MAX_SUM = 3000;
    
    // 1. 에라토스테네스의 체 (Sieve of Eratosthenes)
    // 0부터 2999까지의 '소수 판별 수첩'을 미리 만들고 전부 true로 채웁니다.
    vector<bool> is_prime(MAX_SUM, true);
    is_prime[0] = is_prime[1] = false; // 0과 1은 소수가 아님

    // 2부터 시작해서 자기 자신의 배수들을 모조리 지워버립니다.
    for (int i = 2; i * i < MAX_SUM; i++) {
        if (is_prime[i]) {
            for (int j = i * i; j < MAX_SUM; j += i) {
                is_prime[j] = false; // i의 배수들은 소수가 아니므로 탈락
            }
        }
    }

    int answer = 0;
    int n = nums.size();

    // 2. 3중 for문 탐색
    for (int i = 0; i < n - 2; i++) {
        for (int j = i + 1; j < n - 1; j++) {
            for (int k = j + 1; k < n; k++) {
                // 이전 코드처럼 함수를 부르지 않고, 만들어둔 수첩에서 O(1)로 확인만 합니다.
                if (is_prime[nums[i] + nums[j] + nums[k]]) {
                    answer++;
                }
            }
        }
    }

    return answer;
}

에라토스테네스의 체 (미리 체로 걸러두기)
이전 코드에서는 숫자가 주어질 때마다 2부터 일일이 나누어 보며 소수인지 확인했습니다. 하지만 이 코드는 고대 그리스 수학자 에라토스테네스의 방식을 빌려, "2의 배수 지우고, 3의 배수 지우고, 5의 배수 지우면... 남는 건 전부 소수네?"라는 원리로 3,000까지의 소수 지도를 프로그램 시작과 동시에 단숨에 완성해 버립니다.

공간과 시간의 교환 (Space-Time Tradeoff)
nums 배열에 숫자가 50개 꽉 차 있다면, 3중 for문은 약 2만 번을 돌아야 합니다.
이전 코드에서는 2만 번마다 isPrime 함수를 호출해서 또 반복문을 돌았기 때문에 연산량이 수십만 번으로 폭증했습니다. 하지만 이 코드에서는 3,000칸짜리 가벼운 수첩(is_prime)을 메모리(공간)에 하나 만들어둔 덕분에, 2만 번의 함수 계산을 단 2만 번의 단순 조회(시간 복잡도 O(1))로 퉁쳐버립니다.

  1. 덧칠하기

문제 설명
어느 학교에 페인트가 칠해진 길이가 n미터인 벽이 있습니다. 벽에 동아리 · 학회 홍보나 회사 채용 공고 포스터 등을 게시하기 위해 테이프로 붙였다가 철거할 때 떼는 일이 많고 그 과정에서 페인트가 벗겨지곤 합니다. 페인트가 벗겨진 벽이 보기 흉해져 학교는 벽에 페인트를 덧칠하기로 했습니다.

넓은 벽 전체에 페인트를 새로 칠하는 대신, 구역을 나누어 일부만 페인트를 새로 칠 함으로써 예산을 아끼려 합니다. 이를 위해 벽을 1미터 길이의 구역 n개로 나누고, 각 구역에 왼쪽부터 순서대로 1번부터 n번까지 번호를 붙였습니다. 그리고 페인트를 다시 칠해야 할 구역들을 정했습니다.

벽에 페인트를 칠하는 롤러의 길이는 m미터이고, 롤러로 벽에 페인트를 한 번 칠하는 규칙은 다음과 같습니다.

롤러가 벽에서 벗어나면 안 됩니다.
구역의 일부분만 포함되도록 칠하면 안 됩니다.
즉, 롤러의 좌우측 끝을 구역의 경계선 혹은 벽의 좌우측 끝부분에 맞춘 후 롤러를 위아래로 움직이면서 벽을 칠합니다. 현재 페인트를 칠하는 구역들을 완전히 칠한 후 벽에서 롤러를 떼며, 이를 벽을 한 번 칠했다고 정의합니다.

한 구역에 페인트를 여러 번 칠해도 되고 다시 칠해야 할 구역이 아닌 곳에 페인트를 칠해도 되지만 다시 칠하기로 정한 구역은 적어도 한 번 페인트칠을 해야 합니다. 예산을 아끼기 위해 다시 칠할 구역을 정했듯 마찬가지로 롤러로 페인트칠을 하는 횟수를 최소화하려고 합니다.

정수 n, m과 다시 페인트를 칠하기로 정한 구역들의 번호가 담긴 정수 배열 section이 매개변수로 주어질 때 롤러로 페인트칠해야 하는 최소 횟수를 return 하는 solution 함수를 작성해 주세요.

#include <string>
#include <vector>

using namespace std;

int solution(int n, int m, vector<int> section) {
    int answer = 0; // 롤러로 페인트칠을 한 횟수
    
    // 페인트가 묻어있는 마지막 지점
    // 처음엔 아무 곳도 칠하지 않았으므로 0으로 시작
    int painted_end = 0; 

    for (int i = 0; i < section.size(); i++) {
        
        // 만약 이번에 칠해야 할 구역이 아직 페인트가 묻지 않은 벽이면
        if (section[i] > painted_end) {
            
            answer++; // 롤러 한 번 굴리기
            
            // 롤러를 한 번 굴렸으니 페인트가 어디까지 묻었는지 저장
            // section[i]부터 시작해서 롤러 길이(m)만큼 칠해지니까 현재 위치 + m - 1
            painted_end = section[i] + m - 1;
        }
    }

    return answer;
}

AI가 작성한 코드

#include <vector>

using namespace std;

int solution(int n, int m, vector<int> section) {
    int answer = 0;
    int painted_end = 0;

    // 인덱스 변수(i)를 없애고, 범위 기반 for문(Range-based for loop)을 사용합니다.
    for (int pos : section) {
        if (pos > painted_end) {
            answer++;
            painted_end = pos + m - 1;
        }
    }

    return answer;
}

범위 기반 for문을 사용해서도 코드를 작성할 수 있다는 게 인상 깊었음


Unreal Engine 멀티플레이어 게임 개발 : 1회차 정리

챕터 1-1 : 채팅 프로젝트 생성

일단 먼저 CXGameModeBase와 CXPlayerController 만들기
그리고 블프 생성 -> 새 폴더 Game, Player에 생성해주기

이때 ChatX.Build.cs에 추가해줘야함.

// ChatX.Build.cs

using UnrealBuildTool;

public class ChatX : ModuleRules
{
	public ChatX(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[]
		{
			// Initial Dependencies
			"Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput",
			
		});

		PrivateDependencyModuleNames.AddRange(new string[] {  });

		PublicIncludePaths.AddRange(new string[] { "ChatX" });
	}
}

그리고 World Settings에 방금 만든 GameModeBase랑 PlyerController 지정

채팅 입력창 위젯 구현

UserWidget을 상속받는 CXChatInput클래스 생성 (Path : UI)

  • ChatX.Build.cs에 "UMG", 추가
// CXChatInput.h

#pragma once

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

class UEditableTextBox;

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

public:
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UEditableTextBox> EditableTextBox_ChatInput;
	
};
  • [UE] UPROPERTY 메타 지정자: BindWidget과 순수 가상 함수의 유사성

💡요약
meta=(BindWidget)은 C++ 부모 클래스에서 선언한 변수와 정확히 동일한 이름의 UI 컴포넌트가 자식 위젯 블루프린트에 존재하도록 강제하는 키워드

1. BindWidget의 핵심 동작

예를 들어 CXChatInput 클래스에 EditableTextBox_ChatInput이라는 변수에 BindWidget 메타를 붙였다고 가정하면

// CXChatInput.h
UPROPERTY(meta = (BindWidget))
class UEditableTextBox* EditableTextBox_ChatInput;

이 클래스를 상속받아 위젯 블루프린트를 만들게 되면 에디터는 블루프린트의 계층 구조 내에 변수명과 완벽하게 똑같은 이름을 가진 EditableTextBox가 존재할 것을 요구함. 일치하는 위젯이 없다면 블루프린트 컴파일 에러가 발생.

2. 순수 가상 함수와의 비교

이러한 BindWidget의 특성은 순수 가상 함수 동작 방식과 비슷함

  • 순수 가상 함수: 부모 클래스에서 함수를 선언하면 상속받는 자식 클래스에서 반드시 해당 함수를 Override하도록 강제.
  • BindWidget: 부모 클래스에서 변수를 선언하면 상속받는 자식 위젯 블루프린트에서 반드시 동일한 이름의 UI 위젯 컴포넌트를 배치하도록 강제.

즉, 순수 가상 함수가 코드 레벨에서의 구현을 강제한다면 BindWidget은 'UI(블루프린트) 레벨'에서 특정 컴포넌트의 존재를 자식에게 강제. 이를 통해 C++ 로직과 블루프린트 UI 사이에 안전하게 연결.

  • WBP_ChatInput 설정

그리고 Anchors를 하단으로 해주고, 아래 사진처럼 설정

채팅 위젯 연결

InputModeUIOnly -> InputMode를 UI 쪽으로만 흘러가게끔 할건지, 아니면 게임이랑 UI 둘다 흘러가게 할 것인지, 아니면 게임에만 흘러가게 할 건지 결정
채팅이니까 UIOnly로 설정해주기

// CXPlayerController.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "CXPlayerController.generated.h"

class UCXChatInput;

UCLASS()
class CHATX_API ACXPlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	virtual void BeginPlay() override;

protected:
	UPROPERTY(EditDefaultsOnly)
	TSubclassOf<UCXChatInput> ChatInputWidgetClass;

	UPROPERTY()
	TObjectPtr<UCXChatInput> ChatInputWidgetInstance;

};
// CXPlayerController.cpp


#include "Player/CXPlayerController.h"

#include "UI/CXChatInput.h"

void ACXPlayerController::BeginPlay()
{
	Super::BeginPlay();
	
	FInputModeUIOnly InputModeUIOnly;
	SetInputMode(InputModeUIOnly);

	if (IsValid(ChatInputWidgetClass) == true)
	{
		ChatInputWidgetInstance = CreateWidget<UCXChatInput>(this, ChatInputWidgetClass);
		if (IsValid(ChatInputWidgetInstance) == true)
		{
			ChatInputWidgetInstance->AddToViewport();
		}
	}
}

그리고 컨트롤러 디테일 창에서 Chat Input Widget Class에 WBP_ChatInput 지정

채팅 메세지 관리

ChatX.Build.cs에 "UMG", "Slate", "SlateCore", 추가

// CXPlayerController.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "CXPlayerController.generated.h"

class UCXChatInput;

UCLASS()
class CHATX_API ACXPlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	virtual void BeginPlay() override;

	void SetChatMessageString(const FString& InChatMessageString);

protected:
	UPROPERTY(EditDefaultsOnly)
	TSubclassOf<UCXChatInput> ChatInputWidgetClass;

	UPROPERTY()
	TObjectPtr<UCXChatInput> ChatInputWidgetInstance;

	FString ChatMessageString;
};
// CXPlayerController.cpp


#include "Player/CXPlayerController.h"
#include "UI/CXChatInput.h"

void ACXPlayerController::BeginPlay()
{
	Super::BeginPlay();

	FInputModeUIOnly InputModeUIOnly;
	SetInputMode(InputModeUIOnly);

	if (IsValid(ChatInputWidgetClass) == true)
	{
		ChatInputWidgetInstance = CreateWidget<UCXChatInput>(this, ChatInputWidgetClass);
		if (IsValid(ChatInputWidgetInstance) == true)
		{
			ChatInputWidgetInstance->AddToViewport();
		}
	}
}

void ACXPlayerController::SetChatMessageString(const FString& InChatMessageString)
{
	ChatMessageString = InChatMessageString;
}
// CXChatInput.h

#pragma once

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

class UEditableTextBox;

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

public:
	virtual void NativeConstruct() override;

	virtual void NativeDestruct() override;

protected:
	UFUNCTION()
	void OnChatInputTextCommitted(const FText& Text, ETextCommit::Type CommitMethod);

public:
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UEditableTextBox> EditableTextBox_ChatInput;

};
// CXChatInput.cpp


#include "UI/CXChatInput.h"

#include "Components/EditableTextBox.h"
#include "Player/CXPlayerController.h"

// 
void UCXChatInput::NativeConstruct()
{
	Super::NativeConstruct();

	if (EditableTextBox_ChatInput->OnTextCommitted.IsAlreadyBound(this, &ThisClass::OnChatInputTextCommitted) == false)
	{
		EditableTextBox_ChatInput->OnTextCommitted.AddDynamic(this, &ThisClass::OnChatInputTextCommitted);
	}
}

void UCXChatInput::NativeDestruct()
{
	Super::NativeDestruct();

	if (EditableTextBox_ChatInput->OnTextCommitted.IsAlreadyBound(this, &ThisClass::OnChatInputTextCommitted) == true)
	{
		EditableTextBox_ChatInput->OnTextCommitted.RemoveDynamic(this, &ThisClass::OnChatInputTextCommitted);
	}
}

// 텍스트를 제출하면 GetOwningPlayer을 통해서 컨트롤러를 가져올 수 있는데 OwningPlayerController라고 있음. 결국에는 엔터를 친 그 컴퓨터에 PlayerController가 나오게 됨. PlayerController 가져온다고만 일단 이해해놓기.
// 캐스트를 성공하면 SetChatMessageString을 호출
void UCXChatInput::OnChatInputTextCommitted(const FText& Text, ETextCommit::Type CommitMethod)
{
	if (CommitMethod == ETextCommit::OnEnter)
	{
		APlayerController* OwningPlayerController = GetOwningPlayer();
		if (IsValid(OwningPlayerController) == true)
		{
			ACXPlayerController* OwningCXPlayerController = Cast<ACXPlayerController>(OwningPlayerController);
			if (IsValid(OwningCXPlayerController) == true)
			{
				OwningCXPlayerController->SetChatMessageString(Text.ToString());

				EditableTextBox_ChatInput->SetText(FText());
			}
		}
	}
}

채팅 메세지 출력

// CXPlayerController.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "CXPlayerController.generated.h"

class UCXChatInput;

UCLASS()
class CHATX_API ACXPlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	virtual void BeginPlay() override;

	void SetChatMessageString(const FString& InChatMessageString);
	void PrintChatMessageString(const FString& InChatMessageString);


protected:
	UPROPERTY(EditDefaultsOnly)
	TSubclassOf<UCXChatInput> ChatInputWidgetClass;

	UPROPERTY()
	TObjectPtr<UCXChatInput> ChatInputWidgetInstance;

	FString ChatMessageString;
};
// CXPlayerController.cpp


#include "Player/CXPlayerController.h"
#include "Kismet/KismetSystemLibrary.h"
#include "UI/CXChatInput.h"

void ACXPlayerController::BeginPlay()
{
	Super::BeginPlay();

	FInputModeUIOnly InputModeUIOnly;
	SetInputMode(InputModeUIOnly);

	if (IsValid(ChatInputWidgetClass) == true)
	{
		ChatInputWidgetInstance = CreateWidget<UCXChatInput>(this, ChatInputWidgetClass);
		if (IsValid(ChatInputWidgetInstance) == true)
		{
			ChatInputWidgetInstance->AddToViewport();
		}
	}
}

void ACXPlayerController::SetChatMessageString(const FString& InChatMessageString)
{
	ChatMessageString = InChatMessageString;
	PrintChatMessageString(ChatMessageString);
}

// 화면에 텍스트 출력
void ACXPlayerController::PrintChatMessageString(const FString& InChatMessageString)
{
	// 월드 컨텍스트 -> this를 넣어서 PlayerController를 넣은 거
    // ChatMessageString을 출력
    // 스크린에다가 출력할 건지? -> true
    // 로그도 찍을 건지? -> true
    // Linecolor = red
    // Duration = 5초
	UKismetSystemLibrary::PrintString(this, ChatMessageString, true, true, FLinearColor::Red, 5.0f);
}
profile
초보 개발자

0개의 댓글