https://school.programmers.co.kr/learn/courses/30/lessons/12927

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

처음에는 형태와 예시가 총 작업량에서 n만큼 빼주고 적당히 분배해주면 되는게 아닌지 생각했었는데

그렇게하면 정형화된 식으로 나타내기도 어렵고 계산자체도 너무 복잡해질 것 같아서 다른 방향으로 생각해봤을 때

많은 일부터 하나씩 빼준다음에 남은 것은 야근지수로 계산하면 되지 않을까했다.

이때 우선 순위 큐를 사용하여 항상 가장 큰 작업만을 가져오도록 하였다.

#include <vector>
#include <queue>
#include <numeric>

using namespace std;

long long solution(int n, vector<int> works) {
    long long total = accumulate(works.begin(), works.end(), 0LL);
    
    // 모든 일을 다 처리할 수 있으면 피로도 0
    if (total <= n) return 0;
    
    priority_queue<int> pq;
    for (int work : works) {
        pq.push(work);
    }
    
    while (n--) {
        int cur = pq.top();
        pq.pop();
        pq.push(cur - 1);
    }
    
    long long answer = 0;
    while (!pq.empty()) {
        long long x = pq.top();
        pq.pop();
        answer += x * x;
    }
    
    return answer;
}

 

2026년이 오고 다시 언리얼을 복습하는 겸해서 강의를 보려고 했는데 언리얼 5.6강의가 새로 나와서 기존에 배웠던 것을 정리해보는 겸 새로운 강의를 들어보려고한다. 

 이번에 들어볼 강의는 

https://www.udemy.com/share/101Weu3@rPR-gr4fO6mk1GkipMBqlqKzw0tZK1Z8O-DdcuI0Q6AC6FrnsnJ62kt-XoQij4TbYw==/

이 강의이다.

1.Geometry Brushes

 Geometry Brushes는 레벨 공간 자체를 생성하거나 깎는 구조 제작도구 로 레벨 디자인이나 모델링에서 사용된다. 기본 도형을 

월드에 배치할 때 디테일 창에 브러시 세팅이 있는데 이 x y z값을 조절하여 기존 레벨을 기하학적으로 “깎고( subtract )” 채우고( add ) 하면서 기본 구조물을 만드는 용도로 사용할 수 있다.

하지만 Geometry Brush는 최적화도 안 좋고 렌더링 방식도 효율적이지 않아서 최종 게임 출시용으로는 권장되지 않아서 디자인 후 엔진 메뉴에 있는 Convert To Static Mesh를통해 static mesh로 보통 바꾼다. 

 

예를 들어 사각형 도형을 배치해두고 다른 도형의 브러시 타입을 subtract 방식으로 적용해두면 해당 도형이 있는 자리가 비워져 보이게 된다. 

2. 라이브코딩

라이브 코딩은 언리얼에서 프로젝트를 진행하면서 핫 리로드와 같이 변경 사항을 바로 적용할 수 있는 방법이다. 단축키는 ctrl +alt+ f11이다. 하지만 .h 파일을 많이 바꾸었을 때는 프로젝트를 다시 빌드하는 것이 오류 없이 잘 작동한다. 그리고 프로젝트를 수정할 때 시작과 끝에는 빌드를 해주는 것이 좋다.

 

cocos2d-x를 한번 사용해보자 

우선 cocos2d-x파일을 설치 및 자동 환경설정을 하기 위해서는 홈페이지에서 zip파일을 다운받은 다음 setup.py를 실행시켜야한다. 이를 위해 python을 설치해주자. 

검색해봤을 때 python 버전은 2버전대를 설치해주자 

이 버전을 설치하는 이유는 cocos2d-console 도구가 Python 2 문법 기반으로 작성되어있기 때문이다.

 

https://www.python.org/downloads/release/python-2718/

 

Python Release Python 2.7.18

The official home of the Python Programming Language

www.python.org

 

이 링크에 접속한 다음 자신의 컴퓨터에 맞는 실행파일을 다운 받아 설치해주면된다. 설치할 때 add to path를 추가해주어야 정상적으로 작동한다.

 

이렇게 설치해준 다음, cocos2d-x 파일을 다운받아주자. cocos2dx는 버전은 3.17.2로 진행하였다. 

https://www.cocos.com/en/cocos2dx-download

 

이렇게 다운 받은 다음 cmd창이나 bash창을 통해 cocos2d-x파일의 압축을 푼 경로로 이동하여 다음 명령어를 입력해주자.

python setup.py

 

이렇게 해주면 입력하라는 창이 나올텐데 이것은 SDK 경로를 입력하는 창으로 당장은 필요하지 않기 때문에 Enter를 눌러 넘어가주자.

 

이렇게 해준다음 cocos 명령어를 통해 프로젝트를 생성하면 된다. 명령어는 위와 동일하게 cmd창이나 bash창에 입력하면 된다. 중요한 점은 이때 프로젝트는 cocos2d-x의 파일을 압축푼 곳에 생성해야 올바르게 작동한다는 점이다. 나는 projects라는 새로운 폴더를 만들고 이곳에 프로젝트를 생성해주었다.

C:\cocos2d-x-3.17.2\projects

cocos new [프로젝트 이름] -p [패키지 이름] -l [언어] -d [경로]

 

이렇게 만들어준 다음 proj.win32 폴더로 가서 sln파일을 열어주고 실행해보았을 때 cocos2d-x 아이콘과 화면이 등장하면 성공이다.

간단한 비행기 슈팅게임을 만들어보자. 

1.움직이기

움직이는 것을 new Input System을 통해 구현해보자.

우선 InputAction에서 Generate C# class를 켜주고 Apply해서 코드로 불러낼 수 있도록 해주자.

 

이렇게 해준다음 Player 클래스를 생성하여 매핑된 Actions가 작동될 때 실행될 함수를 할당해주자.

 

우선 입력을 받아올 때 입력 이벤트의 context를 Vector2로 읽어드려서 어떤 방향에서의 입력인지 알아내고 이를 저장한다. 저장한 값이 0보다 크다면 해당 방향으로 이동을 시작하게 한다. 이때 키 입력이 끝났을 때 다시 입력값을 0으로 초기화 해주어야 정상적으로 작동한다.

 

Player.cs

public class Player : MonoBehaviour
{

	private PlayerInputActions _playerInputAtions;
    
     void Update()
 	{
    
         if (_movementInput.magnitude > 0)
         {
             Vector2 direction = new Vector2(_movementInput.x, _movementInput.y);
             direction.Normalize();
             transform.Translate(direction * _speed * Time.deltaTime);
         }

    
 	}
    
    
     private void PlayerMove(InputAction.CallbackContext context)
     {
         _movementInput = context.ReadValue<Vector2>();
     }
     
       private void PlayerStop(InputAction.CallbackContext context)
      {
          _movementInput = Vector2.zero;
      }
    
	private void OnEnable()
 	{
         _playerInputAtions = new PlayerInputActions();
         _playerInputAtions.Player.Move.performed += PlayerMove;
         _playerInputAtions.Enable();
 	}
}

 

그리고 슈팅게임이기 때문에 경계선 check를 해주어서 만약 viewport 밖으로 나간다면 반대쪽으로 돌아오도록 구현해주자.

void Update()
{
    transform.position = BoundaryCheck(transform.position);
    if (_movementInput.magnitude > 0)
    {
        Vector2 direction = new Vector2(_movementInput.x, _movementInput.y);
        direction.Normalize();
        transform.Translate(direction * _speed * Time.deltaTime);
    }

   
}

public Vector2 BoundaryCheck(Vector2 position)
{
    Vector2 newposition = position;
    Vector2 viewPort = _mainCamera.WorldToViewportPoint(position);

    if (viewPort.x < 0)
    {
        newposition.x = _mainCamera.ViewportToWorldPoint(new Vector2(1, 0)).x;
    }
    else if (viewPort.x > 1)
    {
        newposition.x = _mainCamera.ViewportToWorldPoint(new Vector2(0, 0)).x;

    }

    newposition.y = Mathf.Clamp(newposition.y, _mainCamera.ViewportToWorldPoint(new Vector2(0, 0)).y, _mainCamera.ViewportToWorldPoint(new Vector2(0, 1)).y);

    return newposition;
}

 

5줄의 문자들을 입력받아야하는데 이때 한줄에 문자는 15개가 최대로 올 수 있다. 빈칸없이 연속으로 주어져서 어떻게 풀어야할지 고민을 했는데 생각해보면 문자열로 배열처럼 접근할 수 있기 때문에 한줄씩 받은 다음 접근을 세로로 하면 된다.

 

정답코드

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main()
{
    vector<string> v(5, ""); // 5줄의 문자열 벡터 생성

    // 입력 받기
    for (int i = 0; i < 5; i++)
    {
        getline(cin, v[i]); // 한 줄씩 입력받아 벡터에 저장
    }

    // 세로로 읽어서 출력
    for (int i = 0; i < 15; i++) // 최대 15개의 열
    {
        for (int j = 0; j < 5; j++) // 5개의 행
        {
            if (i < v[j].size()) // 현재 행의 i번째 문자가 존재하면 출력
            {
                cout << v[j][i];
            }
        }
    }

    return 0;
}

단어를 입력으로 받았을 때, 몇개의 단어가 그룹단어인지 개수를 출력해주면 된다.

이때 그룹단어는 같은 단어가 연속해서 나타나는 경우를 의미한다.

소문자의 개수만큼 bool vector를 선언하고 단어의 알파벳을 하나씩 순회하는데 이전에 나온 알파벳과 비교하여 다를 

경우 방문했는지 체크하고 방문했다면 false를 반환해주고 다 체크했을 때 이상이 없으면 true를 반환해주는 함수를 통해 그룹함수인지를 체크하자.

 

정답코드

#include <iostream>
#include <vector>

using namespace std;

bool isGroup(string s);

int main()
{
	int n;
	cin >> n;

	int count = 0;

	for (int i = 0;i < n;i++)
	{
		string s;
		cin >> s;
		if (isGroup(s))
		{
			count++;
		}
	}

	cout << count;
}

bool isGroup(string s)
{
	vector<bool> visited(26, false);

	char pre_c = ' ';

	for (char c : s)
	{
		//만약 전에 나왔던 알파벳과 다르다면
		if (c != pre_c)
		{
			//방문하지 않았던 거라면
			if (!visited[c - 'a'])
			{
				visited[c - 'a'] = true;
				pre_c = c;
				continue;
			}
			else  //방문했었던거라면 그룹단어가 아님
			{
				return false;
			}
		}
	}

	return true;
}

 

문자를 입력받고 가장 많이 사용된 알파벳을 출력해주는데 만약 가장 많이 사용된 알파벳이 여러개 있다면 ?를 출력해주면 된다.

 

처음에 map을 사용하여 풀어서 정답이었지만 다음과 같은 문제점이 있어서 코드를 수정해보았다.

 

  • map<char, int> 대신 array 사용
    • map은 내부적으로 Red-Black Tree 구조를 사용하여 삽입과 조회가 O(log N) 소요됨.
    • 하지만 알파벳 개수는 26개로 고정되어 있으므로, 배열(array<int, 26>)을 사용하면 O(1)으로 빠르게 접근 가능.
  • tolower() 변환 시 불필요한 중복 연산 제거
    • 현재 tolower()를 여러 번 호출하고 있지만, 한 번 변환한 후 바로 인덱스로 사용하면 중복 연산 감소.
  • 최댓값 찾기 및 중복 체크를 하나의 반복문으로 수행
    • 현재는 두 개의 반복문을 사용하여 최댓값과 중복 여부를 검사 (O(N) + O(26))
    • 한 번의 반복문(O(26))으로 최댓값과 중복 여부를 처리하여 최적화 가능

 

 

정답코드(map사용)

#include <iostream>
#include <cctype>
#include <map>

using namespace std;

map<char, int> m;
int main()
{
	string s;
	cin >> s;
	
	for (int i = 0;i < s.length();i++)
	{
		m[tolower(s[i])]++;
	}
	
	int max_value = m[s[0]];
	char max_char = s[0];
	int cnt = 0;

	for (const auto& pair : m) {
		if (max_value < pair.second)
		{
			max_value = pair.second;
			max_char = pair.first;
		}
	}

	for (const auto& pair : m) {
		if (max_value == pair.second)
		{
			cnt++;
		}
	}

	if (cnt > 1)
	{
		cout << "?";
	}
	else
	{
		cout << char(toupper(max_char));
	}
}

 

정답코드(효율적, Array사용)

#include <iostream>
#include <array>

using namespace std;

int main()
{
    string s;
    cin >> s;

    array<int, 26> freq = { 0 }; // 알파벳 개수(26) 만큼 배열 생성 및 초기화

    // 문자 개수 세기
    for (char c : s)
    {
        freq[tolower(c) - 'a']++;
    }

    // 최댓값 찾기 & 중복 체크
    int max_value = 0, max_index = -1;
    bool is_duplicate = false;

    for (int i = 0; i < 26; i++)
    {
        if (freq[i] > max_value)
        {
            max_value = freq[i];
            max_index = i;
            is_duplicate = false; // 새로운 최댓값이 나오면 중복 여부 초기화
        }
        else if (freq[i] == max_value)
        {
            is_duplicate = true;
        }
    }

    // 출력
    if (is_duplicate)
        cout << "?";
    else
        cout << char(max_index + 'A'); // 대문자로 변환

    return 0;
}

 

100줄을 입력받아서 그대로 출력해줘야하기 때문에 ostringstream을 사용해보았다. getline을 통해 한줄씩 ostringstream에 추가하고 공백을 입력받으면 마지막에 한번에 출력해주는 방식을 사용하였다.

 

정답코드

#include <iostream>
#include <sstream>


using namespace std;


int main()
{
	ostringstream output;

	while (1)
	{
		string s;
		getline(cin, s);
		if (s.empty()) break;

		output << s << "\n";
	}

	cout << output.str();

	return 0;
}

 

문제의 규칙성을 찾아야한다. 일단 알파벳이 아닌 1까지 가는 시간이 2초가 걸리기 때문에 A부터 C까지는 3초가 걸리고 D~F까지는 4초가 걸린다. 이때 4개의 알파벳이 있는 수도 있기 때문에 예외처리를 해주어야 한다. 

 

 

정답코드

#include <iostream>

using namespace std;


int main()
{
	int sum = 0;
	string s;
	cin >> s;

	for (char c : s)
	{
		//기본이 2초 알파벳 3개가 지날 때마다 1초씩 증가
		sum += ((int)c-65) / 3 + 3;

		if (c == 'S' || c == 'V' || c == 'Y' || c == 'Z') sum--;
	}
	
	cout << sum;

	return 0;
}

1. istringstream (입력 스트림, string → cin처럼 사용)

✔ 문자열을 입력 스트림으로 변환하여 cin처럼 사용할 수 있음.
✔ 공백을 기준으로 문자열을 분리하여 읽을 때 유용함.

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main() {
    string input = "123 456 789";
    istringstream iss(input);  // 문자열을 입력 스트림으로 변환

    int num;
    while (iss >> num) {  // 공백 기준으로 숫자 추출
        cout << num << endl;
    }

    return 0;
}

2. ostringstream (출력 스트림, cout처럼 string에 출력)

✔ 문자열을 생성 및 조합할 때 사용.
✔ cout처럼 데이터를 추가할 수 있음.
✔ += 연산자보다 빠르게 문자열을 결합하는 방법 중 하나.

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main() {
    ostringstream oss;  // 출력 스트림 생성

    oss << "Hello, " << "World! " << 2024;  // 여러 개의 데이터를 추가

    string result = oss.str();  // 최종 문자열 변환
    cout << result << endl;  // 출력

    return 0;
}

 

3. stringstream (입출력 스트림, istringstream + ostringstream)

✔ istringstream과 ostringstream을 동시에 사용할 수 있음.
✔ 문자열을 읽고(>>) 쓰기(<<) 모두 가능.
✔ 한 번에 문자열을 처리할 때 유용.

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main() {
    stringstream ss;  // 문자열 스트림 생성

    ss << "100 200 300";  // 문자열 입력

    int num;
    while (ss >> num) {  // 문자열에서 숫자 추출
        cout << "읽은 숫자: " << num << endl;
    }

    ss.clear();  // 스트림 상태 초기화
    ss << "새로운 데이터 추가";  // 새로운 문자열 입력

    cout << "최종 문자열: " << ss.str() << endl;  // 전체 문자열 출력

    return 0;
}

 

📌 istringstream vs ostringstream vs stringstream 차이점

스트림 종류설명주요 기능

istringstream 문자열 → 입력 스트림(cin처럼 사용) >> 연산자로 값 추출
ostringstream 출력 스트림 → 문자열(cout처럼 사용) << 연산자로 문자열 결합
stringstream 입출력 모두 가능 (istringstream + ostringstream) >>와 << 모두 사용 가능

 

 

 

3.map / unordered_map 초기화 방식

,를 사용하여 초기화 가능

map보다는 unordered_map을 사용하는게 조회가 빠르다.

unordered_map<string, double> grade_map = {
   {"A+", 4.5}, {"A0", 4.0}, {"B+", 3.5}, {"B0", 3.0},
   {"C+", 2.5}, {"C0", 2.0}, {"D+", 1.5}, {"D0", 1.0}, {"F", 0.0}
};

 

 

4.#define보다는 const 

#define으로 상수를 정의하면 컴파일에서는 이 값을 상수로 만들어서 실행시키기 떄문에 오류가 발생했을 떄 디버깅에서 오류를 찾기 어려울 수도 있기 때문에 상수값을 정의할 때는 #define 보다는 const를 사용하는 것이 좋다.

또한 매크로를 사용하여 정의하게 되면 이 상수값이 등장하는 만큼 이 사본이 등장하게 되지만 const를 사용하면 사본은 단 하나만 존재하게 된다.

 

 

5. 벡터에서 인덱스 계산에서 주소값 활용하기

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {10, 20, 30, 40, 50};

    int* base = &vec[0];
    int* current = &vec[3];

    int index = current - base;

    std::cout << "인덱스: " << index << std::endl;  // 출력: 3
}

 

위의 코드처럼 주소값을 통해 인덱스를 도출해낼수있다. 이를 통해 for(auto) 반복문에서도 위치를 찾아 연산에 활용할 수 있다.

+ Recent posts