반응형

 

 

 

using System;

public class Solution {
    public int[] solution(int[] array, int[,] commands) {
        int[] answer = new int[commands.GetLength(0)];
        for(int i = 0; i < answer.Length; i++)
        {
            int start = commands[i, 0];
            int end = commands[i, 1];
            int[] splitArr = SplitArray(array, start, end);
            Array.Sort(splitArr);
            answer[i] = splitArr[commands[i, 2] - 1];
        }
        return answer;
    }
    
    private int[] SplitArray(int[] arr, int start, int end)
    {
        int[] returnArr = new int[end - start + 1];
        Array.Copy(arr, start - 1, returnArr, 0, returnArr.Length);
        return returnArr;
    }
}

 

 

이차원 배열

- 2차원 배열은 [행, 열]로 이루어져있다.

- 각 차원에 대한 길이(Length)는 GetLength(N)으로 가져올 수 있는데 N의 값은 행(0), 열(1)이다.

 

 

 

배열의 복사

- Array.Copy(sourceArray(A), startIndex(B), destArray(C), copyStartIndex(D), copyLength(E))

- 원본 배열(A)의 인덱스(B)부터 원하는 갯수(E)만큼의 배열값을 복사될 배열(C)의 인덱스(D)부터 복사를 진행한다.

- 여기서 (E)의 값은 인덱스값이 아니라 원소의 갯수를 의미한다. 즉 (D)와 (E)의 값이 destArray의 범위를 벗어나면 안된다. (복사될 원소의 갯수가 배열의 범위를 벗어나므로 에러 발생)

 

 

 

배열 정렬

- 기본적으로 오름차순으로 정렬되게 되어있다.

- 내림차순으로 정렬을 하려면 Sort한 뒤 Reverse함수를 호출해주거나 IComparer를 지정해서 내림차순 정렬이 가능하다.

using System;

public class ReverseComparer : IComparer
{
    public int Compare(object x, object y)
    {
        return (new CaseInsensitiveComparer()).Compare(y, x);
    }
}

void main()
{
    int[] arr = new int[] { 3, 7, 43, 7, 32, 5, 9, 11, 4, 2 };
    
    //case 1
    Array.Sort(arr);
    Array.Reverse(arr);
    
    //case 2
    IComparer revCompare = new ReverseComparer();
    Array.Sort(arr, revCompare);
    
    //case 3
    Array.Sort(arr, (a, b) =>
    {
        return b.CompareTo(a);
    });
}

 

 

 

 

 

 

반응형

'Stack > Coding test' 카테고리의 다른 글

[C++ / Lv2] 전화번호 목록  (0) 2022.02.05
[C++ / Lv1] 완주하지 못한 선수  (0) 2022.02.04
[C++ / Lv2] 문자열 압축  (0) 2022.02.02
[C# / Lv2] H-Index  (0) 2022.01.19
[C# / Lv2] 가장 큰 수  (0) 2022.01.18
반응형

 

 

 

※ isometric 타일맵을 기준으로 작성된 포스팅입니다.

 

타일맵 컴포넌트에서 타일을 그리면 그려진 타일의 배열의 크기를 가져올 수 있다. isometric 기준으로 타일맵의 배열은 다음과 같이 구성된다.

 

 

주의할 점이 있는데 타일맵 자체에서 배열을 제공하는것이 아니라 타일맵이 그려진 사이즈(Vector2) 값을 가지고있는것이기 때문에 타일맵의 사이즈 값과 타일맵의 셀 바운드값을 가져와서 직접 배열을 초기화해줘야한다.

이 때, 셀 바운드값은 최하단 타일을 기준으로 값이 정해지기 때문에 이 값이 무조건 0에서부터 시작하지 않는다.

 

 

  • 타일맵 초기화 코드
private void InitTileArray()
{
    Tilemap tilemap = GetComponent<Tilemap>();
    Vector2[,] tileArray = new Vector2[m_cTilemap.size.y, m_cTilemap.size.x];
    
    Vector3Int tilePos;
    for (int y = tilemap.cellBounds.yMin; y < tilemap.cellBounds.yMax; y++)
    {
        for (int x = tilemap.cellBounds.xMin; x < tilemap.cellBounds.xMax; x++)
        {
            tilePos = new Vector3Int(x, y, 0);
            if (tilemap.HasTile(tilePos))
            {
                int arrX = x - tilemap.cellBounds.xMin;
                int arrY = y - tilemap.cellBounds.yMin;
                Vector3 tileWorldPos = tilemap.CellToWorld(tilePos);
                tileArray[arrY, arrX] = tileWorldPos;
            }
        }
    }
}

  ※ tileWorldPos의 값은 타일의 중앙좌표가 아닌 하단 모서리의 좌표이기 때문에, 중앙좌표를 위한 추가값을 넣어줘야한다.

 

 

 

 

  • 추가 주의사항

- 특정 위치에 타일맵을 한번 그리게되면 그 위치의 타일맵을 지운다고해도 그 위치는 바운드값에 포함이 된다. 그렇기때문에 타일맵 컴포넌트에서 옵션(…)을 눌러서 'Compress Tilemap Bounds'를 눌러주거나 코드에 CompressBounds()를 호출해준다.

 

 

 

 

반응형
반응형

 

 

 

가비지 컬렉터(GC, Garbage Collector)

- C/C++와 다르게 CLR(Common Language Runtime)이 자동 메모리 관리 기능을 제공한다.

- 객체를 힙에 할당을 하면 가비지 컬렉터는 이 중 쓰레기인것을 분리해서 수거해건다.

- 가비지 컬렉터 역시 소프트웨어이기 때문에 CPU와 메모리 자원을 소모한다.

   ※ 이 때, 별도의 공간에서 자원을 소모하는것이 아닌 사용자가 사용하는 자원을 같이 사용한다.

- unsafe 키워드를 사용한 비관리형 코드는 CLR이 제공하는 서비스를 받을 수 없다.

 

 

 

CLR의 메모리 관리

- C#으로 작성한 실행파일을 실행하면, CLR은 프로그램을 위한 일정 크기의 메모리(Managed Heap)를 마련한다.

- 그 뒤, CLR은 Managed Heap의 첫번째 주소에 객체를 할당할 메모리의 포인터를 위치시킨다.

- 힙에 객체를 할당하면 메모리 포인터를 할당된 객체가 차지하고 있는 공간 바로 뒤로 위치시킨다.

- Heap에 할당된 object 객체를 참조하고 있는 변수 obj가 Stack에서 소멸되버리면 object 객체는 어디에서도 접근할 수 없기 때문에 쓰레기가 되어버린다. (이런 객체들을 가비지 컬렉터가 수거해간다.)

- 위치 참조 객체(obj)를 Root라고 하고, 이 Root는 스택, 힙 아무데서나 생성될수 있다.

- .Net 어플리케이션이 실행되면 JIT 컴파일러(Just-In-Time Compile)가 이 Root들을 목록으로 만들고, CLR이 Root목록을 관리하면서 상태를 갱신한다.

   ※ 가비지 컬렉터가 CLR이 관리하는 루트의 목록을 참조해서 쓰레기를 수거해간다.

 

 

 

 

가비지 컬렉터 객체 정리 과정

1. 작업을 시작하기 전, 가비지 컬렉터는 모든 객체를 수거 대상으로 가정한다.

2. 루트 목록을 순회하면서 각 루트가 참조하고 있는 힙 객체와의 관계 여부를 조사한다. 만약 루트가 참조하고 있는 힙의 객체가 또다른 힙을 참조하고 있다면, 또 다른 힙 역시 루트와 관계있는것으로 판단하고 수거 대상에서 제외된다.

3. 쓰레기 객체가 차지하고 있던 메모리 공간은 비어있는 공간이 된다.

4. 루트 목록에 대한 조사가 끝나면, 가비지 컬렉터는 힙을 순회하면서 비어있는 공간에 쓰레기의 인접 객체들을 이동시켜서 채워넣는다.

 

 

 

 

세대별 가비지 컬렉션

- CLR의 메모리는 구역을 나누어 메모리에서 바로 없어질 객체와 오래 있을 객체를 따로 담아 관리한다.

- 메모리를 0, 1, 2의 3개 세대로 나누고 0세대에서는 빨리 사라질 객체, 마지막 세대에서는 오래 있을 객체로 채워진다.

- 객체에 대한 세대를 나누는 기준은 가비지 컬렉션을 겪은 횟수이다.

- 각 세대는 가비지 컬렉션 수행을 위한 임계치가 있으며, 이 임계치에 도달하면 가비지 컬렉터가 해당 세대에 대해 가비지 컬렉션을 수행하고, 살아남은 객체들을 다음 세대로 옮긴다.

- 상위 세대의 가비지 컬렉션이 수행되면 가비지 컬렉터는 해당 세대를 포함한 하위 세대에 대해서도 가비지 컬렉션을 수행한다.

  • 1세대 가비지 컬렉션 : 0, 1세대 가비지 컬렉션 수행.
  • 2세대(전체) 가비지 컬렉션 : 0, 1, 2세대 가비지 컬렉션 수행.

- 2세대 힙이 가득차게 되면, CLR은 어플리케이션의 실행을 잠시 멈추고 전체 가비지 컬렉션(Full GC)을 수행하여 메모리 확보하기 때문에, 어플리케이션의 메모리가 크면 클수록 Full GC시간이 길어지므로 유의해야한다.

 

 

 

 

가비지 컬렉션을 위한 효율적인 코드 작성법

1. 객체를 너무 많이 할당하지 말 것.

 - 객체 생성 코드를 작성할 때 곡 필요한 객체인지, 필요 이상으로 많은 객체를 생성하는 코드가 아닌지의 여부를 고려해야한다.

2. 너무 큰 객체 할당을 피한다.

 - CLR은 보통 크기의 객체를 할당하는 힙과는 별도로 85kb이상의 대형 객체를 할당하기 위한 대형 객체 힙(LOH : Large Object Heap)을 따로 유지한다. (사용자가 평소에 사용하는 힙은 소형 객체 힙(SOH : Small Object Heap)이다.)

 - SOH는 객체를 할당할 포인터가 위치한 메모리에 바로 객체를 할당하지만 LOH는 객체의 크기만큼의 여유 공간이 있는지 힙을 탐색하여 할당한다.

 - LOH는 메모리 정리를 위한 복사 비용이 비싸기 때문에, SOH처럼 정리된 힙에 객체들을 차곡차곡 모으지 않고 해제된 공간을 그대로 둔다.

 - 또한 LOH는 2세대 가비지 컬렉션이 수행되어야 쓰레기 객체가 수거되기 때문에 어플리케이션의 순간 정지를 불러온다.

3. 너무 복잡한 참조 관계는 만들지 말 것.

 - 객체끼리 너무 복잡한 참조관계를 만들어두면, 가비지 컬렉터는 가비지 컬렉션 수행 이후, 살아님은 객체의 세대를 옮기기 위해 메모리 복사를 수행하는 과정에서 객체를 구성하고 있는 각 필드 객체 간의 참조를 일일이 조사해서 참조 메모리 수조를 전부 수정하기 때문에 자원이 많이 소모된다.

 - 2세대의 객체의 멤버에 새로은 객체를 만들게될 경우 이 새로운 객체은 0세대 가비지 컬렉션에 의해 수거될 가능성이 있다. 이때 쓰기 장벽(Write barrier)이라는 장치를 통해 가비지 컬렉션의 대상에서 제외가 되는데, 여기서 발생하는 오버헤드가 크므로 참조관계를 최소한으로 하면 이러한 오버헤드를 줄일 수 있다.

4. 루트를 너무 많이 만들지 말 것.

 - 가비지 컬렉터는 루트 목록을 돌면서 쓰레기를 찾아내기 때문에, 루트 목록이 작아지면 그만큼 검사 수행 횟수가 줄어들므로 가비지 컬렉션이 빨리 끝나게된다.

 

 

 

 

 

반응형

'Stack > C#' 카테고리의 다른 글

C# 인덱서  (0) 2022.02.14
반응형

 

※ 연산자 오버로딩 기본 개념 참고

 

C++ 연산자 오버로딩

※ firend의 개념을 모른다면 다음 포스팅을 먼저 숙지할 것. C++ friend friend 선언 - 접근지시자에 관계없이 모든 멤버의 접근을 허용하는 선언이다. ※ 그렇기 때문에, 클래스의 friend 선언은 객체

srdeveloper.tistory.com


 

 

대입 연산자

- 정의하지 않으면 디폴트 대입 연산자가 삽입된다.

- 복사 생성자와 마찬가지로 멤버 대 멤버의 복사가 일어난다.

- 디폴트 대입 연산자는 얕은 복사이기 때문에 깊은 복사가 필요하다면 직접 정의해야한다.

#include <iostream>
using namespace std;

class SomeClass
{
private:
    int n1;
    
public:
    SomeClass(int n1 = 0) : n1(n1) { }
    void ShowData()
    {
        cout << n1 << endl;
    }
    SomeClass& operator=(const SomeClass& ref)
    {
        cout << "SomeClass operator=()" << endl;
        this->n1 = ref.n1;
        return *this;
    }
};

void main()
{
    SomeClass sc1(10);
    SomeClass sc2;
    sc2 = sc1;
    sc2.ShowData();
    
    //결과
    //SomeClass operator=()
    //10
}
  • sc2 선언시 바로 대입 연산자를 사용하면 대입 연산자가 호출 되는것이 아닌 복사 생성자가 호출이 된다.

 

 

 

디폴트 대입 연산자의 문제점

- 복사 생성자에서 생기는 문제와 유사하다. (얕은 복사의 문제)

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;

class Name
{
private:
    char* name;
    
public:
    Name(const char* name) 
    {
        int len = strlen(name) + 1;
        this->name = new char[len];
        strcpy(this->name, name);
    }
    void ShowData()
    {
        cout << name << endl;
    }
    Name& operator=(const Name& ref)
    {
        delete[] name;
        int len = strlen(ref.name) + 1;
        this->name = new char[len];
        strcpy(this->name, ref.name);
        return *this;
    }
    ~Name()
    {
        delete[] name;
    }
};

void main()
{
    Name n1("Lee");
    Name n2("Yoon");
    n2 = n1;
    n2.ShowData();
    
    //결과 : Lee
}
  • 대입 연산자를 통한 얕은 복사에서 일어나는 메모리 누수를 막기위해 name 멤버 변수에 할당된 기존 데이터를 할당 해제하고 새롭게 할당하여 깊은 복사를 진행한다.

 

 

 

상속 구조에서의 대입 연산자

- 자식 클래스에서 대입 연산자를 오버로딩 하는 경우, 부모 클래스의 대입 연산자를 명시해 주지 않으면 부모 클래스의 대입 연산자는 호출이 되지 않는다.

- 따라서, 디폴트 대입 연산자의 경우 부모 클래스의 대입 연산자까지 호출되는 것을 확인 할 수 있다.

#include <iostream>
using namespace std;

class Parent
{
private:
    int n1, n2;
    
public:
    Parent(int n1 = 0, int n2 = 0) : n1(n1), n2(n2) { }
    void ShowData()
    {
        cout << n1 << " / " << n2 << endl;
    }
    Parent& operator=(const Parent& ref)
    {
        cout << "Parent operator=()" << endl;
        this->n1 = ref.n1;
        this->n2 = ref.n2;
        return *this;
    }
};

class Child : public Parent
{
private:
    int n1, n2;
    
public:
    Child(int n1, int n2, int n3, int n4) : Parent(n1, n2), n1(n3), n2(n4) { }
    void ShowData()
    {
        Parent::ShowData();
        cout << n1 << " / " << n2 << endl;
    }
    Child& operator=(const Child& ref)
    {
        cout << "Child operator=()" << endl;
        //부모 클래스 대입 연산자 호출
        Parent::operator=(ref);
        this->n1 = ref.n1;
        this->n2 = ref.n2;
        return *this;
    }
};

void main()
{
    Child c1(1, 2, 3, 4);
    Child c2(0, 0, 0, 0);
    c2 = c1;
    c2.ShowData();
    
    //결과
    //Child operator=()
    //Parent operator=()
    //1 / 2
    //3 / 4
}
  • Child 클래스에서 대입 연산자 오버로딩에 Parent::operator(ref)를 주석처리하고 실행해보면 부모 클래스의 멤버는 대입 연산자가 작용하지 않은것을 확인할 수 있다.

 

 

 

인덱스 연산자

- C, C++의 배열은 '경계검사를 하지 않는다'는 단점을 가지고 있다.

- 즉 배열의 범위에 벗어난 인덱스 값을 넣어도 실행하는데 무리없이 진행이 되기 때문에, 안정성을 고려해서 배열 클래스를 만든다.

#include <iostream>
using namespace std;

class IntArray
{
private:
    int* arr;
    int arrLen;

public:
    IntArray(int len) : arrLen(len)
    {
        arr = new int[len];
    }
    int& operator[](int index)
    {
        if (index < 0 || index >= arrLen)
        {
            cout << "Arr index out of range" << endl;
            exit(1);
        }
        return arr[index];
    }
    ~IntArray()
    {
        delete[] arr;
    }
};

void main()
{
    IntArray ia(3);
    for(int i = 0; i <= 3; i++)
    {
        ia[i] = i + 1;
    }
    
    //결과 : Arr index out of range
}
  • 결과와 같이 배열에 대한 잘못된 접근을 차단함으로써 안정성을 보장받을 수 있다.

 

 

 

※ 배열 클래스의 복사에 대해서..

- 배열은 저장소의 일종이고, 저장소에 저장된 데이터는 '유일성'이 보장되어야 하기 때문에, 저장소의 복사는 잘못된 일로 간주된다.

- 따라서, 복사 생성자와 대입 연산자는 private 멤버로 지정하여 복사를 원칙적으로 막는것이 좋은 선택이 되기도 한다.

//IntArray 클래스의 private에 복사 생성자와 대입 연산자를 멤버로 둔다.
IntArray(const IntArray& ref) { }
IntArray& operator=(const IntArray& ref) { }

 

 

 

 

const를 통한 배열 안정성 보장하기

- 어떠한 함수를 정의할 때, 매개변수를 const로 지정하면 해당 함수 내에서는 값의 변경이 불가능하므로 안정성을 보장받을 수 있지만, 위와 같은 코드에서는 인덱스 연산자에서 const 선언이 되어있지 않기 때문에 const로 지정된 매개변수로 받으면 컴파일 에러가 난다.

//전역함수
void ShowData(const IntArray& ref)
{
    for(int i = 0; i < 3; i++)
    {
        cout << ref[i] << endl;	//컴파일 에러 발생
    }
}

 

- 해당 컴파일 에러를 해결하려면 const함수를 오버로딩하면 해결이 된다.

#include <iostream>
using namespace std;

class IntArray
{
private:
    int* arr;
    int arrLen;
    
    IntArray(const IntArray& ref) { }
    IntArray& operator=(const IntArray& ref) { }

public:
    IntArray(int len) : arrLen(len)
    {
        arr = new int[len];
    }
    //배열 안에 값을 넣을 때 호출됨.
    int& operator[](int index)
    {
        if (index < 0 || index >= arrLen)
        {
            cout << "Arr index out of range" << endl;
            exit(1);
        }
        return arr[index];
    }
    //const 매개변수로 선언된 배열 안의 값을 찾을 때 호출된
    int operator[](int index) const
    {
        if (index < 0 || index >= arrLen)
        {
            cout << "Arr index out of range" << endl;
            exit(1);
        }
        return arr[index];
    }
    ~IntArray()
    {
        delete[] arr;
    }
};

void ShowData(const IntArray& ref)
{
    for(int i = 0; i < 3; i++)
    {
        cout << ref[i] << endl;
    }
}

void main()
{
    IntArray ia(3);
    for(int i = 0; i < 3; i++)
    {
        ia[i] = i + 1;
    }
    ShowData(ia);
    
    //결과
    //1
    //2
    //3
}
  • 기존의 인덱스 연산자에서 const를 붙이는게 아닌 오버로딩을 한 이유는, 배열 멤버에 대한 저장이 불가능해지기 때문이다.
  • 따라서 const 선언을 이용해 인덱스 연산자를 오버로딩하여 해결한다.

 

 

 

반응형

'Stack > C++' 카테고리의 다른 글

C++ 네임스페이스(namespace), using  (0) 2021.12.12
C++ 연산자 오버로딩  (0) 2021.12.05
C++ friend  (0) 2021.12.05
C++ 다중상속  (0) 2021.11.22
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
반응형

 

 

어플리케이션

  •  앱의 출시 또는 업데이트 후 크래시가 발생할 경우 약 58%의 유저가 평점 1점을 주고 유저 유입률이 약 8% 하락함.
  • A/B 테스트를 적극 활용한다 : 일반 스크린샷보다 포토샵으로 꾸민 스크린샷이 설치율 증가에 도움됨.
  • 사전등록기능 : 최대 90일 이내에 출시해야함.

 

 

마케팅

  • 광고 문구를 기억에 남게하려면 다음의 순서를 지켜야한다.
  • 소개 → 관심 유도 → 혜택 알림 → 액션 유도 (지금 바로 설치하세요!)
  • 가능한 글자를 크게하여 광고 주체를 정확히 알림
  • 유저별로 유형이 다르므로 광고 이미지를 여러개로 만든다.
  • 비디오 광고의 경우, 여러 해상도를 고려할 것.
  • 비디오 광고는 초반 2~5초 안에 관심을 끌어야 하며 15~30초가 적당함.
  • 노출 20만 → 클릭 4천 → 실행 6백 → 가입 5백 → 플레이 1백 → 인앱구매 7의 비율을 가진다.
  • 배터리가 5% 이하면 기대효과가 없으므로 광고를 노출하지 않는다.

 

 

광고

  • 광고의 시작점을 게임의 흐름에 자연스럽게 포함시킨다.
  • 보상형 광고의 보상을 유저의 레벨에 맞게 지급한다. (전부 일정하면 고레벨 유저가 이탈한다.)
  • 광고를 게제할 때, 유머를 같이 섞어 유도할 것. ('앞으로 조금만 더하면 돼'와 같은 느낌의 위트있는 멘트로 유도한다.)

 

 

수익

  • 사용자의 40%는 지출이 불가능, 50%는 지출을 하지 않음.
  • 즉, 지출 가능성이 0인 유저는 어떠한 수단을 써도 기대매출이 0이다.
  • 광고의 빈도는 유저 1명당 7회가 가장 이상적인 이익을 만들어낸다. (따라서, 일 평균 6~7회 노출할 수 있게 설계한다.)
  • 어떤 앱은 1일 7회의 서로 다른 광고를 모두 보면 인앱결제의 보상을 제공하여 효과를 극대화하는 경우도 있다.
  • 이어하기, N배 보상, 시간 보상, 재화 보상 등 일반적인 보상기능이 효과적이다.
  • 소수의 고액사용자보다 다수의 소액사용자가 있는 게임이 지속가능성이 높다.
  • 보상형 광고, 인앱, 구독 기능을 골고루 같이 사용하는 것이 수익안정성을 높인다.
반응형

+ Recent posts