300x250
300x250

1. 제목

- 프로그래머스(17686) : 2018 KAKAO BLIND RECRUITMENT 오픈채팅방

문제 링크 : 코딩테스트 연습 - 오픈채팅방 | 프로그래머스 스쿨 (programmers.co.kr)

 


2. 풀이 과정

 

uidMap이라는 map 자료구조는 유저 아이디를 key로, 유저의 닉네임을 value로 저장합니다. uidRecord라는 벡터는 각 로그마다 유저 아이디와 해당 로그 메시지를 pair로 묶어 저장하는 용도로 사용됩니다.

for 루프에서는 record 벡터의 각 로그를 분리하여 command, uid, nickname 변수에 저장합니다. command 변수는 로그 유형을, uid 변수는 유저 아이디를, nickname 변수는 유저의 닉네임을 나타냅니다.

로그 유형에 따라 다음과 같은 처리를 수행합니다.

Enter: uidMap에 해당 유저 아이디가 이미 존재하는 경우에는 닉네임을 업데이트하고, 존재하지 않는 경우에는 새로 추가합니다. 그리고 uidRecord 벡터에 유저 아이디와 "님이 들어왔습니다."라는 메시지를 pair로 묶어 추가합니다.
Leave: uidRecord 벡터에 유저 아이디와 "님이 나갔습니다."라는 메시지를 pair로 묶어 추가합니다.
Change: uidMap에 해당 유저 아이디가 존재하는 경우에는 닉네임을 업데이트합니다.
uidRecord 벡터에 추가된 pair들은 마지막 for 루프에서 uidMap을 참조하여 출력 문자열을 생성하고, answer 벡터에 추가됩니다.


코드를 자세히 살펴보면, 우선 record라는 vector<string>을 입력값으로 받아들입니다. 이 record는 각 로그를 문자열 형태로 저장하고 있습니다. 이후 uidMap이라는 map 자료구조와 uidRecord라는 vector<pair> 자료구조를 선언해줍니다.

이후 record의 크기만큼 반복문을 실행하면서, split 함수를 이용하여 로그를 공백 단위로 분리해줍니다. 이 분리된 로그를 기반으로 각 로그에 따른 처리를 해줍니다.

만약 Enter 명령어가 들어온다면, 해당 유저가 기존에 존재하는 유저인지 신규 유저인지를 판단하여 uidMap에 저장합니다. 또한, uidRecord에는 해당 유저가 들어왔음을 기록해줍니다.

만약 Leave 명령어가 들어온다면, 해당 유저가 나갔음을 uidRecord에 기록해줍니다.

만약 Change 명령어가 들어온다면, uidMap에 해당 유저의 닉네임을 변경해줍니다.

이렇게 각 로그에 대한 처리를 마친 후, uidRecord에 저장된 로그 정보와 uidMap에 저장된 유저 정보를 기반으로 최종적인 출력 값을 만들어냅니다. uidRecord의 크기만큼 반복문을 실행하면서, uidMap에서 해당 유저의 닉네임을 가져와 로그와 함께 출력 값을 만들어 answer vector에 push_back 해줍니다.

 

 


3. 코드

#include <string>
#include <vector>
#include <map>
#include <sstream>

using namespace std;

//https://www.lifencoding.com/language/22#post?p=1
vector<string> split(string str, char Delimiter) 
{
    istringstream iss(str);             // istringstream에 str을 담는다.
    string buffer;                      // 구분자를 기준으로 절삭된 문자열이 담겨지는 버퍼

    vector<string> result;

    // istringstream은 istream을 상속받으므로 getline을 사용할 수 있다.
    while (getline(iss, buffer, Delimiter)) 
    {
        result.push_back(buffer);               // 절삭된 문자열을 vector에 저장
    }

    return result;
}

vector<string> solution(vector<string> record) 
{
    vector<string> answer;

    map<string, string> uidMap;
    vector<pair<string, string>> uidRecord;

    int recordSize = record.size();
    for (int i = 0; i < recordSize; i++)
    {
        vector<string> splitStr = split(record[i], ' ');
        string command = splitStr[0];
        string uid = splitStr[1]; 
        
        if (command == "Enter")
        {
            string nickname = splitStr[2];
            // 기존 유저
            if (uidMap.find(uid) != uidMap.end())
                uidMap[uid] = nickname;
            // 신규 유저
            else
                uidMap.insert({uid, nickname});

            uidRecord.push_back({uid, "님이 들어왔습니다."});
        }
        else if (command == "Leave")
        {
            uidRecord.push_back({ uid, "님이 나갔습니다." });
        }
        else if (command == "Change")
        {
            string nickname = splitStr[2];
            if (uidMap.find(uid) != uidMap.end())
                uidMap[uid] = nickname;
        }
    }

    int uidRecordSize = uidRecord.size();
    for (int i = 0; i < uidRecordSize; i++)
    {
        answer.push_back(uidMap[uidRecord[i].first] + uidRecord[i].second);
    }

    return answer;
}
300x250
300x250

1. XLua란?

 

XLua는 C#과 Lua 스크립트를 결합하여 Unity 프로젝트에서 스크립트를 작성할 수 있도록 지원하는 라이브러리예요. XLua는 Lua 스크립트 엔진과 C# 언어 간의 인터페이스를 제공하여, Lua 스크립트와 C# 클래스 간의 데이터 전송 및 상호 작용을 쉽게 할 수 있도록 도와줘요. 이를 통해 C# 언어의 장점과 Lua 스크립트 언어의 장점을 모두 활용하여 Unity 게임 개발을 더욱 효율적으로 할 수 있어요.

 

 

2.Unity에서 XLua를 사용하기 위한 설치 및 설정하기

 

2.1 xLua 프로젝트 다운로드하기

Tencent/xLua: xLua is a lua programming solution for C# ( Unity, .Net, Mono) , it supports android, ios, windows, linux, osx, etc. (github.com)

 

GitHub - Tencent/xLua: xLua is a lua programming solution for C# ( Unity, .Net, Mono) , it supports android, ios, windows, linu

xLua is a lua programming solution for C# ( Unity, .Net, Mono) , it supports android, ios, windows, linux, osx, etc. - GitHub - Tencent/xLua: xLua is a lua programming solution for C# ( Unity, .N...

github.com

 

xLua 페이지에서 프로젝트를 다운받아요.

 

 

2.2 다운로드한 프로젝트 Unity로 열기

 

압축파일을 풀고 해당프로젝트를 Unity로 열면

이렇게 에디터 버전이 누락됐다고 나오는데 전 그냥 설치되어 있는 2021.3.5f1 버전으로 열었어요.

 

'Continue' 클릭

 

이렇게 잘 열렸다면 성공이에요.

 

2.3 프로젝트 유니티패키지로 뽑아내기

 

에셋 폴더에서 우클릭을 해서 'Export Package...' 을 클릭해서 유니티 패키지를 생성해요.

준비는 끝났어요. 이제 저희 프로젝트에 이 패키지를 임포트 하면 돼요.

 

 

2.4 새 프로젝트 생성

저는 3D 프로젝트를 만들어서 테스트했어요.

 

 

2.5 xLua 패키지 임포트 하기

생성된 프로젝트에 아까 뽑아낸 유니티 패키지를 임포트 해줘요.

 

상단에 XLua가 표시되면 정상적으로 설치가 된 거예요.

 

 

 

3. Lua 스크립트 작성하기

 

이제 Lua 스크립트를 작성해야 하는데 마우스 우클릭 했을 때 C# 스크립트처럼 Create 메뉴에 Lua스크립트가 없어서 이거 먼저 메뉴버튼을 만들어 줬어요.

 

 

XLuaMenuEditor.cs

using UnityEditor;
using System.IO;

public class XLuaMenuEditor : Editor
{
    [MenuItem("Assets/Create/Lua Script")]
    private static void CreateLuaScript()
    {
        string path = AssetDatabase.GetAssetPath(Selection.activeObject);
        if (path == "")
        {
            path = "Assets";
        }
        else if (Path.GetExtension(path) != "")
        {
            path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), "");
        }

        // 새 텍스트 파일을 생성
        string fileName = "NewLuaScript.lua.txt";
        int fileIndex = 0;
        while (File.Exists(Path.Combine(path, fileName)))
        {
            fileIndex++;
            fileName = "NewLuaScript" + fileIndex + ".lua.txt";
        }

        // 파일 생성 후 이름 변경
        string newFilePath = Path.Combine(path, fileName);
        File.Create(newFilePath).Dispose();
        AssetDatabase.Refresh();
    }
}

이제 'Lua Script'를 클릭하면 루아 스크립트 파일이 생성돼요. 

간단하게 유니티 Debug.Log()를 찍는 코드를 작성해볼게요.

 

 

HelloWorld.cs와, HelloWorld.lua.txt 파일을 만들어요.

 

Helloworld.cs

using UnityEngine;
using XLua;
using System;

[LuaCallCSharp]
public class HelloWorld : MonoBehaviour
{
    public TextAsset luaScript;
    internal static LuaEnv luaEnv = new LuaEnv(); //all lua behaviour shared one luaenv only!

    private Action luaStart;
    private Action luaUpdate;
    private Action luaOnDestroy;
    float delta = 0;
    private LuaTable scriptEnv;

    void Start()
    {
        Init();
    }

    
    void Update()
    {
        if (luaUpdate == null && Input.GetKeyDown(KeyCode.G))
        {
            Init();
        }
        if (luaUpdate != null)
        {
            delta += Time.deltaTime;
            luaUpdate();
        }

        if (luaUpdate != null && delta >= 3)
        {
            DestroyLua();
        }
    }

    void Init()
    {
        delta = 0;
        scriptEnv = luaEnv.NewTable();

        LuaTable meta = luaEnv.NewTable();
        meta.Set("__index", luaEnv.Global);
        scriptEnv.SetMetaTable(meta);
        meta.Dispose();

        scriptEnv.Set("self", this);

        luaEnv.DoString(luaScript.text, "HelloWorld", scriptEnv);

        scriptEnv.Get("start", out luaStart);
        scriptEnv.Get("update", out luaUpdate);
        scriptEnv.Get("ondestroy", out luaOnDestroy);


        if (luaStart != null)
        {
            luaStart();
        }
    }

    void DestroyLua()
    {
        if (luaOnDestroy != null)
        {
            luaOnDestroy();
        }
        luaOnDestroy = null;
        luaUpdate = null;
        luaStart = null;
        scriptEnv.Dispose();
    }
}

 

저도 공부하는 중이라 간단하게 공부한 바로는

 

 

[LuaCallCSharp] : 이 클래스가 Lua 스크립트에서 호출될 수 있는 C# 메서드를 가지고 있음을 XLua에 알려줘요.


TextAsset luaScript: 이 변수는 실행할 Lua 스크립트를 저장하는 텍스트 파일을 참조해요.


static LuaEnv luaEnv: 이 변수는 모든 Lua 동작이 하나의 LuaEnv를 공유할 수 있도록 하는 XLua의 LuaEnv 클래스의 인스턴스를 저장해요. internal로 선언되어 있기 때문에, 같은 어셈블리 내에서만 사용 가능해요.


Action luaStart, luaUpdate, luaOnDestroy: 이 변수들은 각각 Lua 스크립트에서 가져온 start(), update(), ondestroy() 함수를 저장해요. 이 변수들은 Lua 스크립트에서 해당 함수가 정의되어 있지 않은 경우에도 null 값을 가질 수 있으며, 이러한 경우에는 해당 함수를 호출하지 않아요.


LuaTable scriptEnv: 스크립트가 실행될 때 생성된 Lua 환경을 저장하기 위한 LuaTable 변수를 선언해요

 

 

HelloWorld.lua.txt

function start()
	print("lua start...")
end

function update()
	print("lua update...")
end

function ondestroy()
    print("lua destroy")
end

 

 

4. 테스트해보기

 

이제 씬에 게임오브젝트를 하나 만들고 HelloWorld.cs를 넣어준 다음 인스팩터 창에서 Lua Script부분에 작성한 Lua Scirpt를 어싸인해줘요.

 

 

그리고 프로젝트를 실행하면

아래처럼 Lua스크립트에서 print 메서드를 잘 호출하고 있음을 확인할 수 있어요.

또한 루아가 가진 타입스크립트에 특징인데 중간에 루아 스크립트를 수정하고 저장하면 프로젝트를 껐다키지 않아도 바로 반영이 돼요.

이걸 확인하려고 'G'키를 누를 때마다 루아스크립트가 실행 중이지 않다면 실행되게 코드를 작성했어요.

 

이 기능이 루아가 가진 큰 장점 중 하나라서 지금은 간단한 디버그만 찍어봤지만 다양한 작업을 할 수 있을 거라 공부해 볼 가치가 있다고 생각해요.

 

 

출처 및 참고문헌

Tencent/xLua: xLua is a lua programming solution for C# ( Unity, .Net, Mono) , it supports android, ios, windows, linux, osx, etc. (github.com)

300x250
300x250

안녕하세요.

 

오늘은 오브젝트 풀링에 대해서 알아볼게요.

 

1. 왜 오브젝트 풀링을 써야 할까요?

 

유니티에서 오브젝트를 생성하기 위해서는 Instantiate를 사용하고 삭제할 때는 Destroy를 사용해요.

하지만 Instantiate, Destroy 이 두 함수는 상당히 비용을 크게 먹는다는 것을 구글링을 해보면 알 수 있어요.

Instantiate(오브젝트 생성)은 메모리를 새로 할당하고 리소스를 로드하는 등의 초기화 과정이 필요하고, Destroy(오브젝트 파괴)는 파괴 이후에 발생하는 가비지 컬렉팅으로 인한 프레임 드랍이 발생할 수 있어요.

 

그렇다면 플레이어의 총알과 같이 자주 생성되고 삭제되는 오브젝트들이 있을 경우 치명적일 수 있는 거죠. 이때 사용할 수 있는 것이 오브젝트 풀링이에요. 자주 사용하는 오브젝트를 미리 생성해 놓고 이걸 사용할 때마다 새로 생성 삭제 하는 것 이 아닌 사용할 때는 오브젝트풀한테 빌려서 사용하고 삭제할 때는 오브젝트풀한테 돌려줌으로써 단순하게 오브젝트를 활성화 비활성화 만 하는 개념이에요.

 

 

유니티에서 오브젝트 풀링을 처음부터 지원하진 않았어요. 그렇기에 오브젝트 풀링을 구현하는 방법이 사람마다 달랐으며 성능도 달랐을 거라고 생각돼요. 하지만 지금은 유니티에서 오브젝트 풀링을 공식적으로 지원해요. 이 글은 유니티에서 공식적으로 제공하는 오브젝트 풀링 사용방법에 대해서 정리하는 글이에요.

 

 

2. 기본 프로젝트 세팅

 

간단하게 플레이어가 움직이고 플레이어가 총알을 발사하는 프로젝트를 만들어줘요.

 

Player.cs

using UnityEngine;

public class Player : MonoBehaviour
{
    public float speed = 1f;
    public Transform bulletSpawnPoint;
    public GameObject bulletPrefab;

    void Update()
    {

        if (Input.GetKeyDown(KeyCode.Space))
        {
            var bulletGo = Instantiate(bulletPrefab);

            bulletGo.transform.position = this.bulletSpawnPoint.position;
        }

        // 가로 이동 반환값 : LeftArrow = -1 RightArrow = 1
        var h = Input.GetAxisRaw("Horizontal");
        // 세로 이동 반환값 : DownArrow = -1 UpArrow = 1        
        var v = Input.GetAxisRaw("Vertical");

        //단위 벡터 (크기가 1인 벡터)
        var dir = new Vector3(h, v, 0).normalized;

        this.transform.Translate(dir * this.speed * Time.deltaTime);
    }
}

플레이어가 이동하면서 space키를 누르면 총알을 발사하는 간단한 코드예요. 

 

 

Bullet.cs

using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float speed = 5f;

    void Update()
    {

        // 총알이 많이 날라가면 삭제 해주기
        if (this.transform.position.y > 5)
        {
            Destroy(this.gameObject);
        }

        this.transform.Translate(Vector3.up * this.speed * Time.deltaTime);
    }
}

총알이 생성되고 나서 위로 계속 올라가다가 일정 위치 이상으로 올라가면 삭제되는 간단한 코드예요.

 

 

 

간단하게 Circle 오브젝트에 빨간색을 넣어 만든 Bullet오브젝트는 프리팹으로 만들고 씬에선 삭제해 줘요

 

다음으로 Square 오브젝트에 검은색을 넣고 총알이 스폰될 위치를 잡아주고 이 위치와 총알 프리팹을 어싸인해 줘요.

 

 

 

 

3. 유니티 오브젝트 풀링 사용하기

이제 이 기존 코드를 유니티에서 제공하는 오브젝트 풀링을 사용하는 방식으로 바꿔볼게요.

 

바꾸기 전 오브젝트 풀링을 구현해 줘요.

ObjectPoolManager.cs

using UnityEngine;
using UnityEngine.Pool;

public class ObjectPoolManager : MonoBehaviour
{

    public static ObjectPoolManager instance;

    public int defaultCapacity = 10;
    public int maxPoolSize = 15;
    public GameObject bulletPrefab;

    public IObjectPool<GameObject> Pool { get; private set; }

    private void Awake()
    {
        if (instance == null)
            instance = this;
        else
            Destroy(this.gameObject);


        Init();
    }

    private void Init()
    {
        Pool = new ObjectPool<GameObject>(CreatePooledItem, OnTakeFromPool, OnReturnedToPool,
        OnDestroyPoolObject, true, defaultCapacity, maxPoolSize);

        // 미리 오브젝트 생성 해놓기
        for (int i = 0; i < defaultCapacity; i++)
        {
            Bullet bullet = CreatePooledItem().GetComponent<Bullet>();
            bullet.Pool.Release(bullet.gameObject);
        }
    }

    // 생성
    private GameObject CreatePooledItem()
    {
        GameObject poolGo = Instantiate(bulletPrefab);
        poolGo.GetComponent<Bullet>().Pool = this.Pool;
        return poolGo;
    }

    // 사용
    private void OnTakeFromPool(GameObject poolGo)
    {
        poolGo.SetActive(true);
    }

    // 반환
    private void OnReturnedToPool(GameObject poolGo)
    {
        poolGo.SetActive(false);
    }

    // 삭제
    private void OnDestroyPoolObject(GameObject poolGo)
    {
        Destroy(poolGo);
    }
}

 

 

이제 Bullet 오브젝트는 ObjectPoolManager가 생성하고 사용할 때마다 ObjectPoolManager가 관리하고 있는 Bullet 오브젝트를 받아서 사용할 수 있는 코드예요.

 

기존 Bullet 코드를 바꿔줘요.

Bullet.cs

using UnityEngine;
using UnityEngine.Pool;

public class Bullet : MonoBehaviour
{
    public IObjectPool<GameObject> Pool { get; set; }
    public float speed = 5f;

    void Update()
    {

        // 총알이 많이 날라가면 삭제 해주기
        if (this.transform.position.y > 5)
        {
            // 이제 자신이 Destroy를 하지 않는다.
            //Destroy(this.gameObject);

            // 오브젝트 풀에 반환
            Pool.Release(this.gameObject);
        }

        this.transform.Translate(Vector3.up * this.speed * Time.deltaTime);
    }
}

 

Player도 바꿔줘요.

using UnityEngine;

public class Player : MonoBehaviour
{
    public float speed = 1f;
    public Transform bulletSpawnPoint;

    void Update()
    {

        if (Input.GetKeyDown(KeyCode.Space))
        {
            // 총알생성을 플레이어가 하지 않는다.
            //var bulletGo = Instantiate(bulletPrefab);

            // 오브젝트풀 에서 빌려오기
            var bulletGo = ObjectPoolManager.instance.Pool.Get();

            bulletGo.transform.position = this.bulletSpawnPoint.position;
        }

        // 가로 이동 반환값 : LeftArrow = -1 RightArrow = 1
        var h = Input.GetAxisRaw("Horizontal");
        // 세로 이동 반환값 : DownArrow = -1 UpArrow = 1        
        var v = Input.GetAxisRaw("Vertical");

        //단위 벡터 (크기가 1인 벡터)
        var dir = new Vector3(h, v, 0).normalized;

        this.transform.Translate(dir * this.speed * Time.deltaTime);
    }
}

 

이제 프로젝트를 시작하면 오브젝트를 미리 최소 정해진 수만큼 만들어 놓고 플레이어가 요청할떄마다 대여해서 사용할 거예요.

 

 

 

4. 오브젝트 여러 개 관리하기

 

지금까지는 총알 1개만 관리하는 거였다면 여러 종류의 오브젝트를 관리하는 오브젝트풀을 관리할 수 있어야 실용성이 있을 거예요.

 

그래서 저는 오브젝트풀을 관리하는 딕셔너리를 선언해서 외부에서는 오브젝트풀매니저에게 오브젝트를 요청만 하면 매니저가 해당 오브젝트에 해당하는 오브젝트풀에 접근해서 오브젝트를 받아서 넘겨주는 방식으로 구현했어요.

 

먼저 오브젝트풀을 관리하기 위해선 오브젝트풀에서 사용할 프리팹과 몇 개를 생성할지 count를 알아야 해요. 이걸 딕셔너리로 관리할 거 기 때문에 key값으로 사용할 오브젝트네임까지 받는 클래스를 선언했어요.

    [System.Serializable]
    private class ObjectInfo
    {
        // 오브젝트 이름
        public string objectName;
        // 오브젝트 풀에서 관리할 오브젝트
        public GameObject perfab;
        // 몇개를 미리 생성 해놓을건지
        public int count;
    }

 

이렇게 하고 오브젝트 매니저에게 할당하면 아래처럼 오브젝트풀에 등록할 오브젝트들을 등록할 수 있어요.

 

그리고 저는 딕셔너리를 2개와 String 변수 하나를 선언했는데

    // 생성할 오브젝트의 key값지정을 위한 변수
    private string objectName;
    
    // 오브젝트풀들을 관리할 딕셔너리
    private Dictionary<string, IObjectPool<GameObject>> 
    ojbectPoolDic = new Dictionary<string, IObjectPool<GameObject>>();

    // 오브젝트풀에서 오브젝트를 새로 생성할때 사용할 딕셔너리
    private Dictionary<string, GameObject> poolGoDic = new Dictionary<string, GameObject>();

 

objectPoolDic 은 오브젝트풀을 관리하기 위한 딕셔너리라면 poolGoDIc은 왜 필요하냐면요

 

    // 생성
    private GameObject CreatePooledItem()
    {
        GameObject poolGo = Instantiate(poolGoDic[objectName]);
        poolGo.GetComponent<PoolAble>().Pool = ojbectPoolDic[objectName];
        return poolGo;
    }

처음에 오브젝트풀에서 오브젝트를 생성할 때 어떤 오브젝트를 생성해야 하는지를 알 수가 없어요. 그렇기 때문에 생성해야 하는 오브젝트들을 objecgtName을 key값으로 갖게 하고 프리팹들을 저장해 놓고 풀에서 오브젝트를 새로 생성할 때 생성할 objecgtName key값에 해당하는 poolGoDic에 접근해서 오브젝트를 생성하는 거예요.

 

또한 보시면 이젠 Bullet 스크립트가 아니라 PoolAble 스크립트에 접근해서 Pool을 지정해주고 있는데 PoolAble 스크립트는 오브젝트풀을 사용할 오브젝트들이 모두 상속받게 해서 이 스크립트를 상속받은 오브젝트들만 오브젝트풀에 등록할 수 있게 했어요.

그래서 PoolAble 스크립트엔 그냥 자신이 돌려줘야 할 Pool을 저장할 프로퍼티와 자신의 게임 오브젝트를 넘겨줄 ReleaseObject 메서드만 정의를 해두면 돼요.

 

 

PoolAble.cs

using UnityEngine;
using UnityEngine.Pool;

public class PoolAble : MonoBehaviour
{
    public IObjectPool<GameObject> Pool { get; set; }

    public void ReleaseObject()
    {
        Pool.Release(gameObject);
    }
}

 

Bullet.cs

using UnityEngine;

public class Bullet : PoolAble
{
    public float speed = 5f;

    void Update()
    {
        // 총알이 많이 날라가면 삭제 해주기
        if (this.transform.position.y > 5)
        {
            // 오브젝트 풀에 반환
            ReleaseObject();
        }

        this.transform.Translate(Vector3.up * this.speed * Time.deltaTime);
    }
}

 

ObjectPoolManager.cs

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;

public class ObjectPoolManager : MonoBehaviour
{
    [System.Serializable]
    private class ObjectInfo
    {
        // 오브젝트 이름
        public string objectName;
        // 오브젝트 풀에서 관리할 오브젝트
        public GameObject perfab;
        // 몇개를 미리 생성 해놓을건지
        public int count;
    }


    public static ObjectPoolManager instance;

    // 오브젝트풀 매니저 준비 완료표시
    public bool IsReady { get; private set; }

    [SerializeField]
    private ObjectInfo[] objectInfos = null;

    // 생성할 오브젝트의 key값지정을 위한 변수
    private string objectName;

    // 오브젝트풀들을 관리할 딕셔너리
    private Dictionary<string, IObjectPool<GameObject>> ojbectPoolDic = new Dictionary<string, IObjectPool<GameObject>>();

    // 오브젝트풀에서 오브젝트를 새로 생성할때 사용할 딕셔너리
    private Dictionary<string, GameObject> goDic = new Dictionary<string, GameObject>();

    private void Awake()
    {
        if (instance == null)
            instance = this;
        else
            Destroy(this.gameObject);

        Init();
    }


    private void Init()
    {
        IsReady = false;

        for (int idx = 0; idx < objectInfos.Length; idx++)
        {
            IObjectPool<GameObject> pool = new ObjectPool<GameObject>(CreatePooledItem, OnTakeFromPool, OnReturnedToPool,
            OnDestroyPoolObject, true, objectInfos[idx].count, objectInfos[idx].count);

            if (goDic.ContainsKey(objectInfos[idx].objectName))
            {
                Debug.LogFormat("{0} 이미 등록된 오브젝트입니다.", objectInfos[idx].objectName);
                return;
            }

            goDic.Add(objectInfos[idx].objectName, objectInfos[idx].perfab);
            ojbectPoolDic.Add(objectInfos[idx].objectName, pool);

            // 미리 오브젝트 생성 해놓기
            for (int i = 0; i < objectInfos[idx].count; i++)
            {
                objectName = objectInfos[idx].objectName;
                PoolAble poolAbleGo = CreatePooledItem().GetComponent<PoolAble>();
                poolAbleGo.Pool.Release(poolAbleGo.gameObject);
            }
        }

        Debug.Log("오브젝트풀링 준비 완료");
        IsReady = true;
    }

    // 생성
    private GameObject CreatePooledItem()
    {
        GameObject poolGo = Instantiate(goDic[objectName]);
        poolGo.GetComponent<PoolAble>().Pool = ojbectPoolDic[objectName];
        return poolGo;
    }

    // 대여
    private void OnTakeFromPool(GameObject poolGo)
    {
        poolGo.SetActive(true);
    }

    // 반환
    private void OnReturnedToPool(GameObject poolGo)
    {
        poolGo.SetActive(false);
    }

    // 삭제
    private void OnDestroyPoolObject(GameObject poolGo)
    {
        Destroy(poolGo);
    }

    public GameObject GetGo(string goName)
    {
        objectName = goName;

        if (goDic.ContainsKey(goName) == false)
        {
            Debug.LogFormat("{0} 오브젝트풀에 등록되지 않은 오브젝트입니다.", goName);
            return null;
        }

        return ojbectPoolDic[goName].Get();
    }
}

 

 

 

Player.cs

using UnityEngine;

public class Player : MonoBehaviour
{
    public float speed = 1f;
    public Transform bulletSpawnPoint;

    void Update()
    {

        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            // 오브젝트풀 에서 빌려오기
            var bulletGo = ObjectPoolManager.instance.GetGo("BulletRed");

            bulletGo.transform.position = this.bulletSpawnPoint.position;
        }
        if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            // 오브젝트풀 에서 빌려오기
            var bulletGo = ObjectPoolManager.instance.GetGo("BulletGreen");

            bulletGo.transform.position = this.bulletSpawnPoint.position;
        }
        if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            // 오브젝트풀 에서 빌려오기
            var bulletGo = ObjectPoolManager.instance.GetGo("BulletBlue");

            bulletGo.transform.position = this.bulletSpawnPoint.position;
        }

        // 가로 이동 반환값 : LeftArrow = -1 RightArrow = 1
        var h = Input.GetAxisRaw("Horizontal");
        // 세로 이동 반환값 : DownArrow = -1 UpArrow = 1        
        var v = Input.GetAxisRaw("Vertical");

        //단위 벡터 (크기가 1인 벡터)
        var dir = new Vector3(h, v, 0).normalized;

        this.transform.Translate(dir * this.speed * Time.deltaTime);
    }
}

 

 

5. 완성

 

이렇게 한번 만들어두면 나중엔 ObjectPoolManager.instance.GetGo(가져올 오브젝트이름)으로 접근해서 사용할 수 있어요

 

 

 

 

참고자료 :

Unity - Scripting API: ObjectPool<T0> (unity3d.com)

유니티 - 오브젝트 풀링(Object pooling) (tistory.com)

300x250
300x250

유니티에서 파일을 생성하면 meta파일이 같이 생성돼요.

아래 사진처럼 Bulles.cs 파일을 생성하면 Bulles.cs.meta파일이 같이 생성되는 거처럼요.

 

편집기에선 굳이 볼 필요가 없는데 보이니까 난잡한 거 같아서 숨기는 방법을 찾아봤어요. 대부분 영문버전을 사용하고 있으셔서 한글버전용으로 올렸어요.

 

 

 

 

 

1. 파일 -> 기본 설정 -> 설정

 

 

2. 텍스트 편집기 -> 파일 -> 'Exclude' -> '패턴 추가'

 

3. '**/*.meta' 입력 -> '확인'

 

 

4. 확인

 

훨씬 보기 편해졌네요.

300x250

'잡동사니' 카테고리의 다른 글

[Visual Code] 열려있던 탭 저장  (0) 2023.03.03
Animal Rescue(동물구조대)  (0) 2022.12.12
300x250

1. 제목

- 프로그래머스(17686) : 2018 KAKAO BLIND RECRUITMENT [3차] 파일명 정렬 

문제 링크 : 코딩테스트 연습 - [3차] 파일명 정렬 | 프로그래머스 스쿨 (programmers.co.kr)


2. 풀이 과정

 

쉽게 풀려고 하면 쉽게 풀 수 있는 쉬운 정렬 문제이지만 제가 푼 방법이 좋은 방법은 아닌 거 같네요.

저는 문제에서 정렬 문제이지만 구현문제처럼 문제에서 요구하는 대로 그대로 풀었어요.

문자열을 Head, Number 부분을 나눠서 저장한 다음 이 둘을 먼저 각각 비교하고 같다면 들어온 순서를 유지했어요.

구조체를 만들어서 작업을 했는데 

struct File
{
	string fullName;
	string head;
	int number;
	int inputNumber;
};

그냥 처음부터 들어온 문자열을 그대로 저장하는 fullName 변수

숫자를 만나기 전까지를 저장할 head 변수

head 이후에 숫자를 저장하는 number 변수

마지막으로 들어온 순서를 유지하기 위해 들어온 순서를 기억하는 inputNumber 변수 에요.

 

처음에 입력으로 받는 files 벡터를 순회하며 fullName을 바로 저장해요.

for (auto file : files)
{
    File tempFile;
    tempFile.fullName = file;
}

 

이다음부터 file 문자열의 index를 돌며 head 부분을 분리해 냈어요.

int idx = 0;
for (; idx < file.size(); idx++)
{
    // 문자열 체크
    if (!IsNumber(file[idx]))
    {
        if (file[idx] >= 'A' && file[idx] <= 'Z')
            tempFile.head += (file[idx] + 32);
        else
            tempFile.head += file[idx];
    }
    else
    {
        break;
    }
}

 

그다음 number 부분을 분리하기 위해 숫자가 아닌 문자를 만날 때까지 strNumber 임시 변수에 넣어놓고 stoi() 함수로 문자를 숫자로 바꿔 저장한 후에 tempFile을 sortFiles 벡터에 넣어 줘요.

string strNumber;
for (; idx < file.size(); idx++)
{
    if (IsNumber(file[idx]))
        strNumber += file[idx];
    else
        break;
}
tempFile.number = stoi(strNumber);
tempFile.inputNumber = cnt;

cnt++;
sortFiles.push_back(tempFile);

 

마지막으로 sortFiles를 정렬한 후에 fullName만 따로 빼서 return 하면 정답처리가 돼요.

bool Compare(File file1, File file2)
{
	// 헤드 먼저 비교
	if (file1.head > file2.head)
		return false;
	else if (file1.head < file2.head)
		return true;

	// 헤드가 같다면 number 비교
	if (file1.number > file2.number)
		return false;
	else if (file1.number < file2.number)
		return true;

	// number 까지 같다면 들어온 순서 유지
	return file1.inputNumber < file2.inputNumber;
}

3. 함수 설명

bool Compare(File file1, File file2)

문제에서 요구하는 정렬 순서를 정의하기 위한 함수예요.

 

bool IsNumber(char c)

해당 문자가 숫자인지 체크하는 함수예요.


4. 코드

#include <algorithm>
#include <string>
#include <vector>

using namespace std;

struct File
{
	string fullName;
	string head;
	int number;
	int inputNumber;
};

bool Compare(File file1, File file2)
{
	// 헤드 먼저 비교
	if (file1.head > file2.head)
		return false;
	else if (file1.head < file2.head)
		return true;

	// 헤드가 같다면 number 비교
	if (file1.number > file2.number)
		return false;
	else if (file1.number < file2.number)
		return true;

	// number 까지 같다면 들어온 순서 유지
	return file1.inputNumber < file2.inputNumber;
}

// 문자가 숫자인지 체크하는 함수
bool IsNumber(char c)
{
	if (c >= '0' && c <= '9')
		return true;
	else
		return false;
}

vector<string> solution(vector<string> files)
{
	vector<File> sortFiles;
	vector<string> answer;

	int cnt = 0;
	for (auto file : files)
	{
		File tempFile;
		tempFile.fullName = file;

		int idx = 0;
		for (; idx < file.size(); idx++)
		{
			// 문자열 체크
			if (!IsNumber(file[idx]))
			{
				if (file[idx] >= 'A' && file[idx] <= 'Z')
					tempFile.head += (file[idx] + 32);
				else
					tempFile.head += file[idx];
			}
			else
			{
				break;
			}
		}

		string strNumber;
		for (; idx < file.size(); idx++)
		{
			if (IsNumber(file[idx]))
				strNumber += file[idx];
			else
				break;
		}
		tempFile.number = stoi(strNumber);
		tempFile.inputNumber = cnt;


		cnt++;
		sortFiles.push_back(tempFile);
	}
	

	sort(sortFiles.begin(), sortFiles.end(), Compare);

	for (auto sortFile : sortFiles)
		answer.push_back(sortFile.fullName);

	return answer;
}
300x250
300x250

코드편집툴로 Visual Studio community를 정말 오래 썼는데 회사에선 Rider나 VSCode를 사용하더라고요.

익숙한 Visual Studio community를 쓰려고 하였지만 개인이 사용할 때는 무료인데 회사에선 완전무료가 아니고 조건이 붙어요. 아래는 'MICROSOFT VISUAL STUDIO COMMUNITY 2022' 라이선스 설명이에요.

 

 

개별 라이선스 : 판매 또는 다른 목적으로 고유한 응용 프로그램에 개별적으로 작업하는 경우 이러한 응용 프로그램의 개발 및 테스트를 목적으로 소프트웨어를 사용할 수 있습니다.

 

조직 라이선스 :  조직인 경우 귀사의 사용자는 다음과 같이 소프트웨어를 사용할 수 있습니다:

i. 귀하의 사용자는 사용자의 수와 상관없이 OSI(오픈 소스 이니셔티브)에서 승인한 오픈 소스 소프트웨어 라이선스에 따라 출시된 응용 프로그램의 개발 및 테스트를 목적으로 소프트웨어를 사용할 수 있습니다.

ii. 귀사의 사용자는 사용자의 수와 상관없이 Visual Studio에 대한 확장 기능의 개발 및 테스트를 목적으로 소프트웨어를 사용할 수 있습니다.

iii. 귀사의 사용자는 사용자의 수와 상관없이 Windows 운영 체제에 대한 장치 드라이버의 개발 및 테스트를 목적으로 소프트웨어를 사용할 수 있습니다.

iv. 귀사의 사용자는 사용자의 수와 상관없이 Microsoft SQL Server Data Tools 또는 "Microsoft Analysis Services Projects", "Microsoft Reporting Services Projects" 또는 "SQL Server Integration Services Projects" 확장 기능을 사용하여 Microsoft SQL Server 데이터베이스 프로젝트 또는 Analysis Services, Reporting Services, Power BI Report Server 또는 Integration Services 프로젝트를 개발할 때 Microsoft SQL Server 개발에만 해당 소프트웨어를 사용할 수 있습니다.

v. 귀사의 사용자는 사용자의 수와 상관없이 강의실 교육(온라인 또는 직접)의 일환으로 응용 프로그램의 개발 및 테스트를 목적으로 또는 교육 연구 수행을 목적으로 소프트웨어를 사용할 수 있습니다.

vi. 위의 사항에 해당하지 않으며 귀사가 기업(아래에 정의됨)이 아닌 경우 최대 5명의 귀사의 개별 사용자가 응용 프로그램의 개발 및 테스트를 목적으로 동시에 소프트웨어를 사용할 수 있습니다.

vii. 귀하가 기업인 경우에는 그 직원 및 계약자가 응용 프로그램의 개발 또는 테스트를 목적으로 소프트웨어를 사용할 수 없습니다. 단, 상기 허용된 범위 내에서 (i) 오픈 소스, (ii) Visual Studio 확장, (iii) Windows 운영 체제를 위한 장치 드라이버, (iv) SQL Server 개발, 그리고 (v) 교육 목적은 여기서 제외됩니다.

"기업"이란 통칭 (a) 250대보다 많은 PC 또는 250명보다 많은 사용자가 있거나 (b) 연간 매출이 미화 1백만 달러(또는 다른 통화에서 이와 동등한 금액) 이상을 초과하는 조직 및 그 계열사이며, "계열사"란 조직을 통제하거나(과반수 소유권을 통해) 조직의 통제를 받거나 조직과 공동 통제권을 가지고 있는 모든 업체를 의미합니다.

License Terms | Microsoft Visual Studio Community 2022 - Visual Studio

 

 

이왕이면 회사와 집에서 같은 툴을 쓰는게 좋을 거 같아서 무료 코드 편집툴인 VSCode에 익숙해지기 위해서 유니티 스크립트 편집을 VsCode로 해보기로 했어요. 

 

근데 작업을 하고 난 후에 

이렇게 열린 탭 들 이 있는 상태로 VSCode를 닫기를 하면 

 

나중에 다시 열 때 기존에 열려 있던 창들이 다 닫혀 있더라고요.

 

 

찾아내느라 정말 힘들었어요.

 

1. '파일' -> '기본설정' -> '설정'

 

 

2. 창 -> 'Restore Windows' (모든 프로필에 적용) 찾기

기본값 'all'에서 'preserve'로 바꿔주기

 

 

 

이제 VSCode를 껐다 켜도 탭들이 잘 저장돼요.

 

 

출처 및 참고문헌

License Terms | Microsoft Visual Studio Community 2022 - Visual Studio

 

License Terms | Microsoft Visual Studio Community 2022 - Visual Studio

 

visualstudio.microsoft.com

 

300x250
300x250

 

저는 Visual Studio Code로 작업했어요.

 

 

1. Express 서버 열기 

 

1-1. Visual Studio Code 실행

 

1-2. 작업 폴더 설정

 

1-3. 'Open Folder' 클릭

 

1-4. App.js 파일 생성

 

1-5. Express 서버 설정

커맨드창에 아래 명령어 입력

npm init –y 
npm install express
npm i nodemon

 

커맨드 창이 없다면?

'Terminal' -> 'New Terminal' 클릭

 

파워쉘 말고 

 

'Command Prompt' 추가해주기

 

이제 여기서 명령어들 실행해주면 돼요.

 

1-6. 설치확인

1-7. App.js

const express = require('express');

const app = express();
const port = 3000;

app.use(express.json());

app.get('/', (req, res)=>
{
    res.send(`<h2>welcome to server</h2>`);
});

app.listen(port, ()=>
{
   console.log(`SERVER 실행됨 ${port}`); 
});

command

node app.js

 

1-8. 서버 열린거 확인

 

2. DB(MySQL) 연동

 

2.1 mysql2 모듈 설치

 

npm install -S mysql2

 

 

2.2 userDBC.js 추가

userDBC.js

const mysql = require('mysql2');

// Create the connection pool. The pool-specific settings are the defaults
const pool = mysql.createPool
({
  host: 'localhost',
  user: 'root',
  database: 'test',
  password: 'q142753',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

const getUsers = async ()=>
{
    const promisePool = pool.promise();
    const [rows] = await promisePool.query('select * from users;');
    console.log(rows);
    return rows;
};

module.exports = 
{
    getUsers
};

 

2.3 usersRouter.js 추가

usersRouter.js

const express = require('express');
const userDBC = require('./usersDBC');
const router = express.Router();

router.get('/getUsers', async (req, res)=>
{
    let res_get_users = 
    {
        status_code : 500,
        users : [] 
    };

    try
    {
        const rows = await userDBC.getUsers();
        res_get_users.status_code = 200;
        if(rows.length > 0)
        {
            rows.forEach((user)=>
            {
                res_get_users.users.push
                ({
                    userId : user.userId,
                    userPassword : user.userPassword,
                    userName : user.userName,
                    userSignUpDate : user.userSignUpDate
                });
            });
        }
        else
        {
            console.log('사용자 없음');
        }
    }
    catch(error)
    {
        console.log(error.message);
    }
    finally
    {

        //응답 
        //res.json(res_get_users);
        var result = '';

        for(var i=0; i < res_get_users.users.length; i++)
        {
        result += res_get_users.users[i].userId;
        result += res_get_users.users[i].userPassword;
        result += res_get_users.users[i].userName;
        result += res_get_users.users[i].userSignUpDate;
        
        result += "<br>";
        }

        res.send(result);
    }
});


module.exports = router;

App.js

const express = require('express');
const usersRouter = require('./usersRouter');

const app = express();
const port = 3000;

app.use(express.json());

app.use(`/users`, usersRouter);

app.get('/', (req, res)=>
{
    res.send(`<h2>welcome to server</h2>`);
});

app.listen(port, ()=>
{
   console.log(`SERVER 실행됨 ${port}`); 
});

 

300x250

'[개인공부] > Node.Js' 카테고리의 다른 글

[Node.Js ERROR] connect ECONNREFUSED ::1:3306  (0) 2023.11.27
[Node.Js] 설치 (18.14.2 LTS)  (0) 2023.02.26
300x250

1-1. 사용할 데이터베이스 설정

USE TEST;

 

1-2. 'Tables' 우 클릭 'Create Table' 클릭

1-3. 테이블 명, Charset/Collation 설정

1-4. 컬럼설정하고 'Apply' 클릭

 

1-5. 'Apply' 클릭

 

1-6. 'Finish' 클릭

 

1-7. 쿼리창에서 'SHOW TABLES;' 입력

SHOW TABLES;

생성된 테이블 확인

 

 

2.1 데이터 추가

 

기본 문법

1. INSERT INTO 테이블이름(필드이름1, 필드이름2, 필드이름3, ...)

   VALUES (데이터값1, 데이터값2, 데이터값3, ...)

2. INSERT INTO 테이블이름

   VALUES (데이터값1, 데이터값2, 데이터값3, ...)

 

INSERT INTO users(userId, userPassword, userName)
VALUES('gildong', 'p1234', '홍길동');

 

2.2 추가된 데이터 확인

SELECT * FROM users;

 

2.3 정상작동 확인 했으면 다른 데이터도 추가

 

INSERT INTO users(userId, userPassword, userName)
VALUES('mango', 'q3214', '망고');
INSERT INTO users(userId, userPassword, userName)
VALUES('yuri', 'w1234', '유리');
INSERT INTO users(userId, userPassword, userName)
VALUES('human', 'e1234', '사람');
INSERT INTO users(userId, userPassword, userName)
VALUES('cat', 'P@ssw0rd', '고양이');
INSERT INTO users(userId, userPassword, userName)
VALUES('puppy', 'P@ssw0rd', '강아지');

SELECT * FROM users;

 

 

 

Reference : 

코딩의 시작, TCP School

300x250
300x250

Node.Js 홈페이지를 보시면 'Node.js®는 Chrome V8 JavaScript 엔진으로 빌드된 JavaScript 런타임입니다.' 라고 설명하고 있어요.

이러한 Node.Js를 이용하여 서버를 간단하게 만들 수 있어요.

 

Node.Js 18.14.2 LTS 버전 설치 방법입니다.

 

1. 사이트 접속

Node.js (nodejs.org)

 

Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

 

2.  버전선택

 

저는 안정적인 LTS 버전으로 설치했어요.

 

 

3. node-v18.14.2-x64.msi 실행

 

4. 'Next' 클릭

 

 

5. 라이센스 동의하고 'Next' 클릭

 

 

6. 설치 경로 지정하고 'Next' 클릭

 

 

7. 'Next' 클릭

 

8. 'Next' 클릭

네이티브 모듈용 도구

 

선택적으로 네이티브 모듈을 컴파일하는 데 필요한 도구를 설치합니다.

일부 npm 모듈은 설치 시 c/C++용으로 컴파일해야 합니다. 이러한 모듈을 설치하려면 일부 도구(Python 및 Visual Studio 빌드 도구)를 설치해야 합니다.

필요한 도구를 자동으로 설치할 거냐고 묻는 건데 저는 자동설치 안할거기 때문에 그냥 'Next' 눌렀어요.

 

 

9.  'Install' 클릭

 

10. 'Finish' 클릭

 

11. 설치확인

 

 

cmd 키신 후 nove -v를 입력했을 때 위 화면이 나오면 정상 설치 된 거예요.

300x250
300x250

 

1. 커넥션 추가하기

 

커넥션 세팅

테스트 버튼 눌렀을 때 정상적으로 되었다면 위 화면이 나와요.

생성한 커넥션으로 접속해 주세요.

2. 스키마 생성하기

아래에 'Schemas' 탭 클릭

왼쪽에 빈 공간 영역 우클릭 후 스키마 생성 버튼 클릭

 

스키마 세팅하고 'Apply' 클릭

 

'Apply' 클릭

 

'Finish' 클릭

 

생성된 스키마 확인

 

 

3. 생성된 데이터베이스 확인

쿼리탭 클릭 후 'SHOW DATABASES' 입력 후 CTRL + ENTER

아래쪽에 결과창에 생성한 test 있으면 정상이에요.

 

300x250
300x250

0. 설치하기 전 이전에 설치된 게 있다면 삭제하고 설치해 주세요.

[MySQL] MySQL 삭제 :: 별빛상자 (tistory.com)

 

[MySQL] MySQL 삭제

C:\Program Files (x86)\MySQL\MySQL Installer for Windows MySQLInstaller.exe 실행 다 삭제하면 됩니다. 'Next' 클릭 'Next' 클릭 'Execuute' 클릭 삭제 정상적으로 된 거 확인하셨으면 'Finish' 클릭 깨끗하네요. appwiz.cpl에서

starlightbox.tistory.com

 

 

1. MySQL 인스톨러 다운

MySQL :: Download MySQL Installer (Archived Versions)

 

 

2. 'Download' 클릭

 

 

3. 'No thanks, just start my download' 클릭

 

4. 'mysql-installer-web-community-5.7.41.0.msi' 실행

 

 

5. 'Sever only' 체크하고 'Next' 클릭

 

6. 'Execute' 클릭

 

7. 다운로드가 완료 됐다면 'Next' 클릭

 

 

8. 'Execute' 클릭

 

9. 완료 됐다면 'Next' 클릭

 

10. 'Next' 클릭

 

 

11. 저는 기본값으로 내버려 두고 'Next' 클릭했어요.

 

 

12. Root 비밀번호 설정 하시고 'Next' 클릭

이거 비밀번호 잊어버리면 안 돼요.

 

 

13. 서비스창에 표시될 이름

이름 입력 후 'Start the MySQL Server at System Startup 체크 해제 후 'Next' 클릭

 

14. MySQL Installer는 다음 위치에 있는 파일 및 폴더의 권한을 업데이트하여 서버의 데이터 디렉터리를 보호할 수 있습니다.

 

예, Windows 서비스를 실행하는 사용자(해당하는 경우) 및 관리자 그룹에만 전체 액세스 권한을 부여합니다. 다른 사용자 및 그룹은 액세스 권한이 없습니다.

예, 하지만 액세스 수준을 검토하고 구성하겠습니다.

아니요, 서버 구성 후 권한을 관리하겠습니다.

 

 

전 그냥 기본값으로 설정해 주고 'Next' 클릭했어요

 

 

15. 'Execute' 클릭

 

 

16. 'Finish' 클릭

 

 

17. 'Next' 클릭

 

 

18. 'Finish' 클릭

 

 

C:\Program Files (x86)\MySQL\MySQL Installer for Windows

 

19. MySQLInstaller.exe 실행

 

 

20. 설치된 거 확인하셨으면 우측에 'Add' 클릭

 

 

21. Applications -> MySQL Workbench -> MySQL Workbench 8.0 -> MySQL Workbench 8.0.32 체크 후 화살표 클릭

 

 

22. 'Next' 클릭

 

 

23. 'Execute' 클릭

 

 

24. 'Next' 클릭

 

 

25. 'Execute' 클릭

 

 

26. 'Next' 클릭

 

 

27. 'Finish' 클릭

 

 

28. 위 화면이 나오면 정상적으로 설치가 된 겁니다.

 

 

29. MySQL Workbench 8.0 CE 실행

 

30. 연결해 보기

 

위 커넥션을 클릭하면 

 

 

아까 입력한 root 비밀번호 입력해서 접속되는지 확인

 

잘되네요!

300x250
300x250

C:\Program Files (x86)\MySQL\MySQL Installer for Windows

 

 

MySQLInstaller.exe 실행

 

다 삭제하면 됩니다.

 

 

'Next' 클릭

 

 

'Next' 클릭

 

'Execuute' 클릭

 

 

삭제 정상적으로 된 거 확인하셨으면 'Finish' 클릭

 

 

깨끗하네요.

 

appwiz.cpl에서도 MySQL Installer - Community 삭제해 주세요.

 

 

 

삭제가 완료 됐으면

 

C:\Program Files -> mysql

C:\Program Files (x86) -> mysql

C:\ProgramData -> mysql

C:\Users\사용자\AppData\Roaming

이 경로들 확인해 보시고

혹시라도 이렇게 남아 있으면 폴더까지 삭제하시면 돼요.

 

마지막으로 혹시 모르니 재부팅 한 번 하시는 게 좋지 않을까 생각이 되네요.

300x250
300x250

 

SLG_ExcelToJsonPortable.zip
1.31MB

 

압축푸시고 SLG_ExcelToJsonConverter.exe 실행하시면

 

 

 

나와요. 폴더열기 누르셔서 변환할 엑셀파일 있는 경로를 선택해 주세요.

 

 

폴더만 선택할 수 있는 다이얼로그라서 아무것도 안보이실 텐데 정상이에요.

 

 

실제로는 이렇게 엑셀파일들이 있어야 해요.

 

폴더 선택을 하셨다면

 

이렇게 해당폴더에 있는 엑셀파일들이 리스트박스에 표시되는데 이 중 변환할 파일들만 선택해서 변환 버튼을 눌러주시면 돼요.

 

 

변환이 완료되면  

 

 

엑셀파일이 있던 폴더에. cs파일 하고 json파일이 생성된 걸 확인할 수 있어요.

 

student_data.xlsx

StudentData.cs

student_data.json

 

엑셀 양식은 1 번열에 데이터이름, 2 번열에 데이터타입, 3 번열부터 데이터들 넣어주시면 돼요.

데이터 타입은

 

이것들만 사용해 주세요.

 

 

 

 

 

2023-04-08 아래 오류 해결 했어요 안 보셔도 돼요.

 

 

가끔 가다가

이런 에러가 나올 때 가 있는데 이때는 당황하지 마시고 엑셀에서 빈칸에 공백 같은 게 들어갔다는 거니까

엑셀에서

이동 옵션 선택 하시고

빈 셀 옵션 선택하신 다음 확인 눌러보시면

이렇게 숨어있는 칸들이 있어요 이것들 Delete 해주시고 다시 변환 눌러주시면 잘돼요.

 

프로젝트 소스파일은 깃에 올려놨어요.

starlight97/SLG_ExcelToJsonConverter: ExcelToJsonConverter (github.com)

300x250
300x250

1. 제목

- 백준 1916 최소비용 구하기

- BOJ 1916 최소비용 구하기

문제 링크 : 1916번: 최소비용 구하기 (acmicpc.net)

 

1916번: 최소비용 구하기

첫째 줄에 도시의 개수 N(1 ≤ N ≤ 1,000)이 주어지고 둘째 줄에는 버스의 개수 M(1 ≤ M ≤ 100,000)이 주어진다. 그리고 셋째 줄부터 M+2줄까지 다음과 같은 버스의 정보가 주어진다. 먼저 처음에는 그

www.acmicpc.net


2. 풀이 과정

 

BOJ1753이랑 정말 똑같은 문제예요. 골드문제인데 다익스트라만 구현하면 그냥 바로 풀리는 문제예요. 풀이과정보다는 다익스트라 개념을 공부하는 게 이문제를 쉽게 푸는데 도움이 된다고 생각해요.

 

[알고리즘] 다익스트라 (C++) :: 별빛상자 (tistory.com)

 

[알고리즘] 다익스트라 (C++)

다익스트라 알고리즘은 음의 간선이 없을 때 사용할 수 있는 하나의 정점에서 다른 모드 정점으로 가는 최단 경로를 구할 때 사용하는 알고리즘이에요. 현실 세계에선 음의 간선이 없기 때문에

starlightbox.tistory.com


3. 코드

#define INF 1000000000

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

int N, M;
int startIndex, endIndex;
vector <pair<int, int>> graph[1001];
int distances[1001];

void Dijkstra(int start)
{
	distances[start] = 0;
	priority_queue<pair<int, int>> pq;

	pq.push(make_pair(start, 0));
	while (!pq.empty())
	{
		int current = pq.top().first;
		int distance = -pq.top().second;
		pq.pop();
		
		//최단 거리가 아닌 경우 스킵
		if (distances[current] < distance) continue;
		for (int i = 0; i < graph[current].size(); i++)
		{
			// 선택된 노드의 인접 노드
			int next = graph[current][i].first;
			// 선택된 노드를 인접 노드로 거쳐서 가는 비용
			int nextDistance = distance + graph[current][i].second;
			// 기존의 최소 비용보다 더 저렴하다면 교체.
			if (nextDistance < distances[next])
			{
				distances[next] = nextDistance;
				pq.push(make_pair(next, -nextDistance));
			}
		}
	}
}

void Solution()
{
	for (int i = 1; i <= N; i++)
		distances[i] = INF;

	Dijkstra(startIndex);

	cout << distances[endIndex] << endl;	
}


void Input()
{
	cin >> N >> M;

	for (int i = 0; i < M; i++)
	{
		int from;
		int to;
		int distance;
		cin >> from >> to >> distance;
		graph[from].push_back(make_pair(to, distance));
	}
	cin >> startIndex >> endIndex;
}

int main()
{
	Input();
	Solution();

	return 0;
}
300x250

'<C++ 백준 BOJ> > DP' 카테고리의 다른 글

[백준 BOJ1753] 최단경로 (C++)  (0) 2023.02.18
300x250

1. 제목

- 백준 1753 최단경로

- BOJ 1753 최단경로

문제 링크 : 1753번: 최단경로 (acmicpc.net)

 

1753번: 최단경로

첫째 줄에 정점의 개수 V와 간선의 개수 E가 주어진다. (1 ≤ V ≤ 20,000, 1 ≤ E ≤ 300,000) 모든 정점에는 1부터 V까지 번호가 매겨져 있다고 가정한다. 둘째 줄에는 시작 정점의 번호 K(1 ≤ K ≤ V)가

www.acmicpc.net


2. 풀이 과정

 

풀이과정이랄 게 따로 없네요. 골드문제인데 다익스트라만 구현하면 그냥 바로 풀리는 문제예요. 다익스트라 개념만 알고 있으면 매우 쉽게 풀 수 있을 거 같아요.

 

[알고리즘] 다익스트라 (C++) :: 별빛상자 (tistory.com)

 

[알고리즘] 다익스트라 (C++)

다익스트라 알고리즘은 음의 간선이 없을 때 사용할 수 있는 하나의 정점에서 다른 모드 정점으로 가는 최단 경로를 구할 때 사용하는 알고리즘이에요. 현실 세계에선 음의 간선이 없기 때문에

starlightbox.tistory.com


3. 코드

#define INF 1000000000

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

int V, E;
int startIndex;
vector <pair<int, int>> graph[20001];
int distances[20001];

void Dijkstra(int start)
{
	distances[start] = 0;
	priority_queue<pair<int, int>> pq;

	pq.push(make_pair(0, start));
	while (!pq.empty())
	{
		int distance = -pq.top().first;
		int current = pq.top().second;
		pq.pop();
		
		//최단 거리가 아닌 경우 스킵
		if (distances[current] < distance) continue;

		for (int i = 0; i < graph[current].size(); i++)
		{
			// 선택된 노드를 인접 노드로 거쳐서 가는 비용
			int nextDistance = distance + graph[current][i].first;
			// 선택된 노드의 인접 노드
			int next = graph[current][i].second;
			// 기존의 최소 비용보다 더 저렴하다면 교체.
			if (nextDistance < distances[next])
			{
				distances[next] = nextDistance;
				pq.push(make_pair(-nextDistance, next));
			}
		}
	}
}

void Solution()
{
	for (int i = 1; i <= V; i++)
		distances[i] = INF;

	Dijkstra(startIndex);
	
	for (int i = 1; i <= V; i++)
	{
		if (distances[i] != INF)
			cout << distances[i] << "\n";
		else
			cout << "INF" << "\n";
	}
}


void Input()
{
	cin >> V >> E;

	cin >> startIndex;

	for (int i = 0; i < E; ++i)
	{
		int from;
		int to;
		int distance;
		cin >> from >> to >> distance;
		graph[from].push_back(make_pair(distance, to));
	}
}

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	Input();
	Solution();

	return 0;
}
300x250

'<C++ 백준 BOJ> > DP' 카테고리의 다른 글

[백준 BOJ1916] 최소비용 구하기 (C++)  (0) 2023.02.18
300x250

코루틴은 은근히 쓰레기 메모리(가비지)를 자주 생성해요. 코루틴을 자주 사용하는 로직에서 이를 최적화하지 않는다면 GC의 collect에서 프레임이 상당히 떨어질 수 있어요..

코루틴에서 가비지가 생성되는 주요한 부분으로는 크게 두 가지가 있어요..

 

- StartCoroutine

- YieldInstruction

StartCoroutine  메서드를 호출할 때 유니티가 해당 코루틴을 관리하기 위해 엔진내부에서 인스턴스가 생성되며 이때 이것이 가비지가 된다고 해요. StartCoroutine은 유니티 엔진 내부의 코드이기 때문에 이를 최적화하는 것은 쉽지 않기 때문에 코루틴 기능을 직접 제작하거나 비슷한 기능을 제공하는 에셋 More Effective Coroutine을 사용해야 한다고 해요.

 

웬만하면 최소한으로 StartCoroutine을 사용해야 할 것 같아요.

 

그렇다면 저희가 최적화를 건드릴 수 있는 부분은 YieldInstruction가 남아있어요. YieldInstruction 이거는 아래와 같은 종류들이 있어요.

 

여기서 yield구문 자체는 가비지를 생성하지 않지만 new가 붙는 yield return new WaitForSeconds(float) 같은 것들이 가비지가 생성이 되기 때문에 캐싱을 해주는 게 좋아요.

 

using System.Collections;
using UnityEngine;

public class Test2Main : MonoBehaviour
{
    private void Start()
    {
        StartCoroutine(this.WaitForSecondsTestImpl());
        StartCoroutine(this.WaitForEndOfFrameTestImpl());
        StartCoroutine(this.WaitForFixedUpdateTestImpl());
    }

    private IEnumerator WaitForSecondsTestImpl()
    {
        while (true)
        {
            yield return new WaitForSeconds(0.01f);
        }
    }

    private IEnumerator WaitForEndOfFrameTestImpl()
    {
        while (true)
        {
            yield return new WaitForEndOfFrame();
        }
    }

    private IEnumerator WaitForFixedUpdateTestImpl()
    {
        while (true)
        {
            yield return new WaitForFixedUpdate();

        }
    }
}

스샷을 보시면 프레임이 끝날 때마다 WaitForEndOfFrameTestImpl에서 가비지가 생성되고, FixedFrame이 끝날 때마다 WaitForFixedUpdateTestImpl에서 가비지가 생성되고, 지정한 시간이 지날 때마다 WaitForSecondsTestImpl에서 가비지가 생성이 되고 있는 것을 볼 수 있어요.

 

이때 캐싱을 할 때 WaitForEndOfFrame, WaitForFixedUpdate은 고정된 값이기에 한 번만 생성해 놓으면 되지만 WaitForSeconds처럼 여러 개의 Seconds가 필요할 때엔 고정된 값이 아니어서 제가 참고한 사이트에선 딕셔너리에 Seconds를 Key로 넣어놓고 값에 Seconds에 해당하는 WaitForSeconds를 넣어놓는 형식으로 캐싱을 했어요.

 

이때 기존에 있는 Key인지 검사하는 과정에서 딕셔너리에 TryGetValue를 사용할 때 박싱이 일어나는 것 때문에 IEqualityComparer를 구현해서 딕셔너리를 생성할 때 object형식으로 비교하는 게 아닌 제네릭형식으로 비교하는 것을 직접 만드셨던데

 

[Unity] Dictionary.TryGetValue() 박싱테스트 :: 별빛상자 (tistory.com)

 

제가 테스트해본 결과 제가 작업하고 있는 환경에선 TryGetValue에서 박싱이 일어나고 있지 않기 때문에 IEqualityComparer는 구현을 하지 않아도 됐어요.

using System.Collections.Generic;
using UnityEngine;

public class YieldInstructionCache
{
    class FloatComparer : IEqualityComparer<float>
    {
        bool IEqualityComparer<float>.Equals(float x, float y)
        {
            return x == y;
        }
        int IEqualityComparer<float>.GetHashCode(float obj)
        {
            return obj.GetHashCode();
        }
    }

    public static readonly WaitForEndOfFrame WaitForEndOfFrame = new WaitForEndOfFrame();
    public static readonly WaitForFixedUpdate WaitForFixedUpdate = new WaitForFixedUpdate();

    private static readonly Dictionary<float, WaitForSeconds> timeInterval = new Dictionary<float, WaitForSeconds>();
    private static readonly Dictionary<float, WaitForSecondsRealtime> timeIntervalReal = new Dictionary<float, WaitForSecondsRealtime>();


    public static WaitForSeconds WaitForSeconds(float seconds)
    {
        WaitForSeconds wfs;
        
        if (!timeInterval.TryGetValue(seconds, out wfs))
            timeInterval.Add(seconds, wfs = new WaitForSeconds(seconds));

        return wfs;
    }

    public static WaitForSecondsRealtime WaitForSecondsRealTime(float seconds)
    {
        WaitForSecondsRealtime wfsReal;

        if (!timeIntervalReal.TryGetValue(seconds, out wfsReal))
            timeIntervalReal.Add(seconds, wfsReal = new WaitForSecondsRealtime(seconds));

        return wfsReal;
    }
}

 

이 부분이 전 필요 없었어요. 혹시라도 나중에 버전이 달라졌을 때 박싱이 발생한다면 IEqualityComparer를 사용해야 할 것 같네요.

 

using System.Collections;
using UnityEngine;

public class Test2Main : MonoBehaviour
{
    private void Start()
    {
        StartCoroutine(this.WaitForSecondsTestImpl());
        StartCoroutine(this.WaitForEndOfFrameTestImpl());
        StartCoroutine(this.WaitForFixedUpdateTestImpl());
    }


    private IEnumerator WaitForSecondsTestImpl()
    {
        while (true)
        {
            yield return YieldInstructionCache.WaitForSeconds(0.1f);
        }
    }

    private IEnumerator WaitForEndOfFrameTestImpl()
    {
        while (true)
        {
            yield return YieldInstructionCache.WaitForEndOfFrame;
        }
    }

    private IEnumerator WaitForFixedUpdateTestImpl()
    {
        while (true)
        {
            yield return YieldInstructionCache.WaitForFixedUpdate;

        }
    }

}

더 이상 가비지가 생성이 안되고 있는 모습을 볼 수 있어요.

 

저는 유니티 사용할 때 코루틴을 엄청 많이 사용했었는데 지금까지 이런 걸 하나도 고려 안 한 상태에서 했었어요. 많이 반성하게 되네요! 

 

저는 유니티 버전 2021.3.5f1, .NET Standard 2.1, Visual Studio 2019 환경에서 테스트했어요.

 

 

참고자료 :

유니티 코루틴 최적화 (ejonghyuck.github.io)

그때는 맞고 지금은 틀리다 - 제네릭 컬렉션도 박싱이 발생할 수 있다 | Overworks’ lab in GitHub

C# Dictionary ContainsKey와 TryGetValue 뭐가 더 효율적인가? (tistory.com)

[Unity] yield return 종류 — KKIMSSI (tistory.com)

 

300x250
300x250

이테스트를 한 이유는 코루틴 최적화를 알아보던 도중 WaitForSeconds(seconds)를 캐싱하려면 딕셔너리에 seconds를 넣어두고 기존에 사용한 적이 있으면 캐싱한 WaitForSeconds를 반환하고 사용한 적이 없으면 딕셔너리에 해당 seconds를 넣어서 캐싱하는 기법을 사용한다고 해요.

 

그때는 맞고 지금은 틀리다 - 제네릭 컬렉션도 박싱이 발생할 수 있다 | Overworks’ lab in GitHub

 

이때 사용한 적이 있는지를 확인하기 위해 딕셔너리에 TryGetValue를 사용하는 과정 중 박싱이 발생할 수 있다고 하여 IEqualityComparer 인터페이스를 새로 구현하여 딕셔너리에 비교 연산자로 넣어줘야 한다고 해서 object 형식이 아닌 비교하려는 자료형을 선언하여 박싱이 일어나지 않게 해야 한다고 하는데 이 부분이 이해가 가지 않아서 테스트를 해봤어요.

 

 

C# 도큐먼트를 봤을 때는 object가 아닌 제네릭형식 TKey를 받아서 하고 있기 때문에 박싱이 일어나지 않을까 생각했어요.

 

제네릭 컬렉션도 방식이 발생할 수 있다 글에서 테스트한 프로파일링 결과예요 해당결과를 보면

DefaultComparer를 사용할 때 가비지 컬렉션이 발생하는것을 알 수 있어요.

 

저도 동일한 코드를 작성해서 테스트 해봤어요.

using System.Collections.Generic;
using UnityEngine;

public class StructTest : MonoBehaviour
{
    public struct Struct
    {
        public int value;

        public Struct(int value)
        {
            this.value = value;
        }
    }

    private Dictionary<Struct, string> structDic = new Dictionary<Struct, string>();

    private void Start()
    {
        for (int i = 0; i < 5; ++i)
        {
            structDic.Add(new Struct(i), i.ToString());
        }
    }


    void Update()
    {
        string str;
        for (int i = 0; i < 5; ++i)
        {
            structDic.TryGetValue(new Struct(i), out str);
        }
    }
}

 

using System.Collections.Generic;
using UnityEngine;

public class IntTest : MonoBehaviour
{
    private Dictionary<int, string> intDic = new Dictionary<int, string>();

    void Start()
    {
        for (int i = 0; i < 5; ++i)
        {
            intDic.Add(i, i.ToString());
        }
    }

    void Update()
    {
        string str;
        for (int i = 0; i < 5; ++i)
        {
            intDic.TryGetValue(i, out str);
        }
    }
}

 

using System.Collections.Generic;
using UnityEngine;

public class EnumTest : MonoBehaviour
{
    public enum Enums
    {
        Enums0,
        Enums1,
        Enums2,
        Enums3,
        Enums4,
    }

    Dictionary<Enums, string> enumDic = new Dictionary<Enums, string>();

    void Start()
    {
        for (int i = 0; i < 5; ++i)
        {
            enumDic.Add((Enums)i, i.ToString());
        }
    }

    void Update()
    {
        string str;
        for (int i = 0; i < 5; ++i)
        {
            enumDic.TryGetValue((Enums)i, out str);
        }
    }
}

 

저 또한 가비지가 생기고 있으나 Enum에선 생기지 않고 있는 것을 볼 수 있고 DefaultComparer는 확인할 수 없었어요. 다만 이때 생기는 가비지 또한 박싱때문에 생긴 다기보단 

비교를 할 때 new 키워드를 사용해서 인스턴스를 새로 만들고 있어서 가비지가 생기는 게 아닌가란 생각이 들었어요. 아마 위에 테스트 당시보다 유니티 버전이 오르고 C# 버전도 올라서 그런 게 아닐까 조심스럽게 예상해 봐요.

이제 코루틴 최적화도 테스트해 볼 건데 제 예상엔 IEqualityComparer는 구현 안 해도 캐싱하는데 문제가 없지 않을까 미리 예상해 봐요.

 

저는 유니티 버전 2021.3.5f1, .NET Standard 2.1, Visual Studio 2019 환경에서 테스트했어요.

 

참고자료:

그때는 맞고 지금은 틀리다 - 제네릭 컬렉션도 박싱이 발생할 수 있다 | Overworks’ lab in GitHub

 

 

300x250
300x250

유니티를 쓰다 보면 C#은 

이런 식으로 내부 코드를 볼 수가 있는데 

 

유니티는 이렇게 가려져 있어요.

UnityEngine.CoreMoudule.dll을 봐야 할 거 같아요.

 

 

아래 링크에서 파일을 다운로드할 수 있어요.
https://marketplace.visualstudio.com/items?itemName=SharpDevelopTeam.ILSpy

 

 

 

2022도 깔려있는데 2022는 안 나오네요.

비주얼스튜디오 종료한 상태에서 설치하세요.

 

 

설치하고 나서 보려고 했던 dll 파일을 'Open in ILSpy'로 열어보면

 

아래처럼 디컴파일된 소스를 볼 수 있어요.

 

 

참고자료 : 

kjun.kr (kjcoder.tistory.com) :: [C#] ILSpay (ICSharpCode) 디컴파일러

300x250
300x250

다익스트라 알고리즘은 음의 간선이 없을 때 사용할 수 있는 하나의 정점에서 다른 모드 정점으로 가는 최단 경로를 구할 때 사용하는 알고리즘이에요. 현실 세계에선 음의 간선이 없기 때문에 현실세계에서도 사용하기 적합한 알고리즘 중 하나예요.

 

다익스트라 알고리즘은 시작 노드부터 인접노드들을 방문하며 최단 거리를 구할 때 이전에 구했던 최단 거리 정보를 재사용하는 특징이 있어요. 이때 인접노드가 여러 개가 있다면 가장 낮은 cost를 갖는 노드부터 방문하며 cost를 갱신하게 구현해 봤어요.

 

구현해야 하는 내용들은 다음과 같아요.

1. 출발 노드 세팅.

2. 출발 노드를 기준으로 각 노드의 최소 비용을 세팅.

3. 방문하지 않는 노드 중 가장 cost가 적은 노드 선택.

4. 해당 노드를 거쳐서 특정한 노드로 가는 경우를 고려하여 최소 비용을 갱신.

5. 3 ~ 4 반복

 

 

아래와 같은 그래프가 있다고 생각하고 진행해 볼게요.

1. 출발 노드는 1번 노드부터 한다고 가정했어요.

2. 출발노드를 기준으로 각 노드의 최소비용을 세팅해요.

 

 

현재 1번 노드는 2, 3, 4 하고만 연결이 되어 있기 때문에 다음과 같은 값을 갖고 있어요.

이 중 에서 이제 4번 노드의 cost가 가장 낮기 때문에 4번 노드부터 탐색합니다.

4번 노드에서 갈 수 있는 노드들은 1, 2, 3, 5번 노드가 있어요.

이것들을 1번에서 4번까지 가는 cost(1) + 각 노드까지 가는 cost가 기존에 등록되어 있는 값들보다 저렴하게 갈 수 있는지를 체크하는 거예요.

 

1번 노드 1 + 1 < 0        X

2번 노드 1 + 2 < 2        X

3번 노드 1 + 3 < 5        O

5번 노드 1 + 1 < INF    O

 

다음과 같이 더 낮은 값이 있으면 distances를 갱신해 줘요.

 

 

방문 한 노드는 파란색으로 표현할게요

이제 방문 안 한 노드 중 가장 낮은 cost값을

갖는 노드는 2번과 5번이 있는데 이때는 앞에 인덱스부터 찾아요.

2번은 1, 3, 4 노드랑 연결이 되어 있네요.

 

1번 노드 2 + 2 < 0        X

3번 노드 2 + 2 < 2        X

4번 노드 2 + 2 < 1        X

 

갱신되는 노드가 없네요.

 

 

이제 5번 노드를 탐색해요.

5번 노드는 3, 4, 6 하고 연결이 되어 있네요.

 

3번 노드 2 + 1 < 4        O

4번 노드 2 + 1 < 1        X

6번 노드 2 + 2 < INF    O

 

3, 6번 노드가 갱신이 일어났어요.

 

마찬가지로 3번과 6번 노드의 cost값이 같기 때문에 3번 노드부터 탐색을 해요.

 

3번 노드는 1, 2, 4, 5, 6 하고 연결이 되어 있네요. 모든 노드하고 연결이 되어 있어요.

 

1번 노드 3 + 5 < 0        X

2번 노드 3 + 2 < 2        X

4번 노드 3 + 3 < 1        X

5번 노드 3 + 1 < 2        X

6번 노드 3 + 5 < 4        X

 

갱신되는 노드가 없네요.

 

마지막 6번 노드는 3, 5 하고 연결이 되어 있네요.

 

3번 노드 4 + 5 < 0        X

5번 노드 4 + 2 < 2        X

 

이렇게 모든 노드 방문이 끝나면 1번 노드에서 각 노드까지 가는 최적의 cost를 구할 수 있어요.

 

#define INF 1000000000

#include <iostream>
#include <vector>

using namespace std;

int V, E;
int startIndex;
vector <pair<int, int>> graph[7];
int distances[7];
bool visit[7];

// 가장 최소 cost를 가지는 정점을 반환합니다.
int GetSmallIndex()
{
	int min = INF;
	int index = 0;
	for (int i = 1; i <= V; i++)
	{
		if (distances[i] < min && !visit[i])
		{
			min = distances[i];
			index = i;
		}
	}
	return index;
}

void Dijkstra(int startIndex)
{
	for (int i = 1; i <= V; i++)
	{
		distances[i] = INF;
	}

	// 2. 출발 노드를 기준으로 각 노드의 최소 비용을 세팅
	for (int i = 0; i < graph[startIndex].size(); i++)
	{
		int vertex = graph[startIndex][i].second;
		int cost = graph[startIndex][i].first;
		distances[vertex] = cost;
	}

	distances[startIndex] = 0;
	visit[startIndex] = true;

	for (int i = 1; i <= V-1; i++)
	{
		// 3. 방문하지 않는 노드 중 가장 cost가 적은 노드 선택.
		int current = GetSmallIndex();
		visit[current] = true;

		// 4. 해당 노드를 거쳐서 특정한 노드로 가는 경우를 고려하여 최소 비용을 갱신.
		// 현재 연결된 노드중 가장 낮은 cost를 갖는 노드의 연결된 노드들을 확인
		for (int j = 0; j < graph[current].size(); j++)
		{
			int vertex = graph[current][j].second;

			// distances[vertex] = 현재 기록된 최소 cost.
			// graph[current][j].first = 현재 연결된 노드의 인접 노드의 cost
			// 현재 기록된 최소 cost보다  (현재 연결된 노드에서 인접노드로 가기 위한 cost + 현재 노드까지 오기 위한 cost)가 크다면 cost 갱신해 주기
			if (distances[vertex] > graph[current][j].first + distances[current])
				distances[vertex] = graph[current][j].first + distances[current];

		}
	}
}

void Input()
{
	cin >> V >> E;
	for (int i = 0; i < E; i++)
	{
		int from;
		int to;
		int distance;

		cin >> from >> to >> distance;
		graph[from].push_back(make_pair(distance, to));
		graph[to].push_back(make_pair(distance, from));
	}

	// 1. 출발 노드 세팅.
	cin >> startIndex;
}

int main()
{
	Input();

	Dijkstra(startIndex);

	for (int i = 1; i <= V; i++)
	{
		cout << distances[i] << " ";
	}

	return 0;
}

// input
/*
6 10
1 2 2
1 3 5
1 4 1
2 3 3
2 4 2
3 4 3
3 5 1
3 6 5
4 5 1
5 6 2
1
*/

위코드는 다익스트라를 단순 구현 했을 경우예요.

 

하지만 이처럼 구현하게 되면 

int GetSmallIndex()
{
	int min = INF;
	int index = 0;
	for (int i = 1; i <= V; i++)
	{
		if (distances[i] < min && !visit[i])
		{
			min = distances[i];
			index = i;
		}
	}
	return index;
}

 

3. 방문하지 않는 노드 중 가장 cost가 적은 노드 선택 -> 과정에서 해당 부분처럼 간선이 없더라도 일단 모든 노드를 다 탐색하기에 

시간 복잡도가 O(N^2)가 나와요. 이 말이 무슨 말이냐면 노드가 10000개고 간선이 2개밖에 없더라도 일단 코드는 10000*10000번 탐색을 하는 비효율 적인 코드예요.

 

#define INF 1000000000

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

int V, E;
int startIndex;
vector <pair<int, int>> graph[7];
int distances[7];

void Dijkstra(int startIndex)
{
	for (int i = 1; i <= V; i++)
	{
		distances[i] = INF;
	}
	distances[startIndex] = 0;

	priority_queue<pair<int, int>> pq;
	pq.push(make_pair(0, startIndex));

	while (!pq.empty())
	{
		// 3. 방문하지 않는 노드 중 가장 cost가 적은 노드 선택.
		int distance = -pq.top().first;
		int current = pq.top().second;
		pq.pop();

		//최단 거리가 아닌 경우 스킵
		if (distances[current] < distance) continue;

		// 4. 해당 노드를 거쳐서 특정한 노드로 가는 경우를 고려하여 최소 비용을 갱신.
		// 현재 연결된 노드중 가장 낮은 cost를 갖는 노드의 연결된 노드들을 확인
		for (int i = 0; i < graph[current].size(); i++)
		{
			// 선택된 노드를 인접 노드로 거쳐서 가는 비용
			int nextDistance = distance + graph[current][i].first;
			// 선택된 노드의 인접 노드
			int next = graph[current][i].second;
			// 기존의 최소 비용보다 더 저렴하다면 교체.
			if (nextDistance < distances[next])
			{
				distances[next] = nextDistance;
				// 방문할 수 있는 노드이므로 이노드들에서도 탐색 시작
				pq.push(make_pair(-nextDistance, next));
			}

		}
	}
}

void Input()
{
	cin >> V >> E;
	for (int i = 0; i < E; i++)
	{
		int from;
		int to;
		int distance;

		cin >> from >> to >> distance;
		graph[from].push_back(make_pair(distance, to));
		graph[to].push_back(make_pair(distance, from));
	}

	// 1. 출발 노드 세팅.
	cin >> startIndex;
}

int main()
{
	Input();

	Dijkstra(startIndex);

	for (int i = 1; i <= V; i++)
	{
		cout << distances[i] << " ";
	}

	return 0;
}

// input
/*
6 10
1 2 2
1 3 5
1 4 1
2 3 3
2 4 2
3 4 3
3 5 1
3 6 5
4 5 1
5 6 2
1
*/

그래서 이렇게 연결이 된 부분들만 검사하는 즉 정점에 비해 간선이 적은 경우에도 대항할 수 있는 코드로 구현이 가능해요. 이렇게 될 때 시간 복잡도는 O(N * log N)이 나온다고 해요. priority_queue를 이용한 이유는 cost가 적은 노드부터 꺼내서 탐색하기 위해서 cost값에 -를 넣어서 넣게되면 cost가 작은 노드들이 가장 큰 값으로 정렬이 되기 때문에 사용했어요.

 

 

 

참고자료 : 

23. 다익스트라(Dijkstra) 알고리즘 : 네이버 블로그 (naver.com)

(51) 25강 - 다익스트라 알고리즘(Dijkstra Algorithm) [ 실전 알고리즘 강좌(Algorithm Programming Tutorial) #25 ] - YouTube

 

개인 공부 기록용 블로그예요.

오류나 틀린 부분이 있을 경우

언제든지 댓글 혹은 메일로 지적해 주세요!

300x250
300x250

개인 공부 기록용 블로그예요.

오류나 틀린 부분이 있을 경우

언제든지 댓글 혹은 메일로 지적해 주세요!

 

프로그램

- 컴퓨터가 실행할 수 있는 명령어들의 집합

- 어떤 문제를 해결하기 위하여 그 처리 방법과 순서를 기술하여 컴퓨터에 주어지는 일련의 명령문 집합체

 

 

프로세스

- 컴퓨터에서 메모리에 올라와 실행 중인 프로그램

- 각각의 프로세스는 독립된 메모리 공간을 할당받음

- 명령어들과 데이터를 가짐

 

 

단일 프로세스 시스템

컴퓨터가 한 번에 하나의 프로그램만 실행할 수 있는 시스템 

 

단점 : CPU 사용률이 좋지 않음

 

calc.cpp

#include <iostream>

using namespace std;

int main()
{
	string userName;
	int num1, num2;

	cout << "유저 이름을 입력 해주세요 : ";
	cin >> userName;

	cout << "더할 숫자 2개를 입력해 주세요." << endl;
	cin >> num1 >> num2;

	int result = num1 + num2;
	cout << "결과 : " << result << endl;

	return 0;
}

cin이 C++ 표준 입력 함수인데 이처럼 각각 입력을 받아야 하는 경우가 있을 경우 cpu는 밑에 작업을 처리하지 않고 대기하게 되며 cpu 낭비가 발생

 

해결책 : 여러 개의 프로그램을 메모리에 올려놓고 동시에 실행시키면 되지 않을까? 이처럼 IO 작업이 발생하면 다른 프로세스가 CPU에서 실행이 되게 해 보자 라는 아이디어가 나오게 됨 이렇게 해서 나오게 된 게 멀티프로그래밍 시스템

 

 

멀티프로그래밍

컴퓨터가 여러 개의 프로그램을 실행할 수 있는 시스템

단점 : 먼저 실행하고 있는 프로세스가 CPU 사용 시간이 길어지게 되면 다른 프로세스는 계속 대기를 하게 됨

해당 부분에서 사용자가 입력을 하고 있지 않으면 다른 프로세스는 계속 대기를 해야 됨

 

해결책 : 프로세스는 한번 CPU를 사용할 때 아주 짧은 시간 동안만 CPU에서 실행되도록 하자 라는 아이디어가 나오게 됨.

-> 멀티태스킹 시스템

 

멀티태스킹 시스템

- 프로세스의 응답 시간을 최소화시키는 게 목적

- 오늘날에는 프로세스뿐만 아니라 스레드 또한 아주 짧게 쪼개 된 cpu time을 나눠 갖는 것

- 여러 프로그램이 동시에 실행되는 것처럼 느끼게 됨

단점 :

- 하나의 프로세스가 동시에 여러 작업을 수행하지는 못함

- 프로세스의 컨텍스트 스위칭은 무거운 작업

(컨텍스트 스위칭이란 위에 사진에서 P1을 실행하다가 P2을 실행하기 위해서 기존에 실행되던 프로세스를 중단하고 다른 프로세스를 실행하는 것으로 CPU에서 실행할 프로세스를 교체하는 기술을 의미함)

- 프로세스끼리 데이터 공유가 쉽지 않음

- CPU에서 듀얼 코어가 등장했는데 잘 쓰고 싶다는 아이디어 등장

 

해결책 : 스레드를 사용하자

 

 

스레드

- 프로세스(process) 내에서 실제로 작업을 수행하는 주체

- 프로세스는 최소 한 개 이상의 스레드를 가질 수 있다.

- 이전에는 CPU에서 실행되는 단위가 프로세스였다면 오늘날에는 스레드가 실행되는 단위(unit of execution)

- 프로세스 끼리 하는 것보단 같은 프로세스의 스레드들끼리 하는 컨텍스트 스위칭 작업이 더 가볍다.

- 스레드들은 자신들이 속한 프로세스의 메모리 영역을 공유하게 됨

 

 

멀티스레딩 시스템

- 병렬처리가 가능해짐

- OS에서 프로세스가 생성되면 자원을 할당해주는데 이때 Stack 영역, Heap 영역, Data 영역, Code 영역을 할당받음.

스레드마다 Stack 영역은 고유한 영역이고 밑에 메모리 영역을 공유할 수 있으므로 데이터 공유가 쉽다.

 

 

멀티프로세싱 시스템

- 두 개 이상의 프로세서나 코어를 활용하는 시스템

 

 

참고 문헌  

(20) 프로세스, 스레드, 멀티태스킹, 멀티스레딩, 멀티프로세싱, 멀티프로그래밍, 이 모든 것을 한 방에 깔끔하게 설명합니다!! 콘텐츠 퀄리티 만족하실 겁니다! - YouTube

 

300x250

'[개인공부] > CS' 카테고리의 다른 글

[CS] 컴퓨터 구조 #1  (0) 2024.02.20
300x250