[BOJ] 1174.줄어드는 수

백준 알고리즘 문제 풀이

해당 문제는 백트래킹 방식으로 풀어보았습니다.

링크 : image


1. 문제

문제 보기
시간 제한 메모리 제한
2 초 128 MB

음이 아닌 정수를 십진법으로 표기했을 때, 왼쪽에서부터 자리수가 감소할 때, 그 수를 줄어드는 수라고 한다. 예를 들어, 321와 950은 줄어드는 수이고, 322와 958은 아니다.

N번째로 작은 줄어드는 수를 출력하는 프로그램을 작성하시오. 만약 그러한 수가 없을 때는 -1을 출력한다. 가장 작은 줄어드는 수가 1번째 작은 줄어드는 수이다.

입력(Input)

N이 주어진다. N은 1,000,000보다 작거나 같은 자연수이다.

출력(Output)

첫째 줄에 N번째 작은 줄어드는 수를 출력한다.


2. 문제 풀이

이 문제는 백트래킹을 활용하여 풀어보았다.

오늘은 포스트를 작성 할 수 없어서 추후 업데이트 하겠다.

백트래킹 방식

비트마스킹 방식

그럼 이것을 활용하여 코드로 표현해보자. 또한 비트마스킹으로도 풀이가 가능하기 때문에 함께 소스코드를 작성해 본다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;

public class Decreasenum_1174 {
    public static int[] num = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
    public static ArrayList<Long> al = new ArrayList<Long>();
    public static void solutionByBitmask(int n) {
        ArrayList<Long> list = new ArrayList<Long>();
        // 2^10의 모든 경우의수를 만든다.
        for(int i = 1; i < (1<<10); i++) {
            long sum = 0;
            // 10 자리만큼 탐색
            for(int j = 0; j < 10; j++) {
                // i가 0^2, 1^2, 2^2, .. , j^2 보다 크다면
                // i에 해당하는 비트가 각각 어느위치에 비트를 가지고 있는지 체크
                // if문을 만족한다면 해당 자릿수가 만족한다는 것이므로 sum*10 + num[j]를 추가.
                if( (i & (1 << j)) > 0 ) {
                    sum = sum * 10 + num[j];
                }
            }
            list.add(sum);
        }

        Collections.sort(list);
        if(n > list.size()) {
            System.out.println("-1");
            return;
        }
        System.out.println(list.get(n - 1));
    }

    public static void solutionByRecursive(long sum, int idx) {
        if(!al.contains(sum)) {
            al.add(sum);
        }
        if(idx >= 10) {
            return;
        }
        solutionByRecursive( (sum * 10) + num[idx], idx + 1);
        solutionByRecursive(sum, idx + 1);
    }
    public static void main(String[] args) throws NumberFormatException, IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(reader.readLine());

        int result = 0;
        if(result == 0) {
            solutionByBitmask(N);
        } else {
            solutionByRecursive(0, 0);
            al.sort(null);
            if(N > 1023)
                System.out.println("-1");
            else
                System.out.println(al.get(N - 1));
        }
    }
}

[BOJ] 15681.트리와 쿼리

백준 알고리즘 문제 풀이

해당 문제는 트리와 탐색 문제로 개인적으로 부족하다고 많이 느끼는 부분이라 뒤에 있는 번호들도 추후 풀어볼 예정입니다.

링크 : image


1. 문제

문제 보기
시간 제한 메모리 제한
1 초 128 MB

간선에 가중치와 방향성이 없는 임의의 루트 있는 트리가 주어졌을 때, 아래의 쿼리에 답해보도록 하자.

  • 정점 U를 루트로 하는 서브트리에 속한 정점의 수를 출력한다.

만약 이 문제를 해결하는 데에 어려움이 있다면, 하단의 힌트에 첨부한 문서를 참고하자.

입력(Input)

트리의 정점의 수 N과 루트의 번호 R, 쿼리의 수 Q가 주어진다. (2 ≤ N ≤ 105, 1 ≤ R ≤ N, 1 ≤ Q ≤ 105)

이어 N-1줄에 걸쳐, U V의 형태로 트리에 속한 간선의 정보가 주어진다. (1 ≤ U, V ≤ N, U ≠ V)

이는 U와 V를 양 끝점으로 하는 간선이 트리에 속함을 의미한다.

이어 Q줄에 걸쳐, 문제에 설명한 U가 하나씩 주어진다. (1 ≤ U ≤ N)

입력으로 주어지는 트리는 항상 올바른 트리임이 보장된다.

출력(Output)

Q줄에 걸쳐 각 쿼리의 답을 정수 하나로 출력한다.

문제 힌트 보기

image

그래프란, 정점들과 정점 둘을 잇는 간선들로 이루어진 집합을 의미한다.

위는 9개의 정점(원 모양)과, 10개의 간선(실선) 들로 이루어진 그래프이다. 각 원의 내부에 쓰여 있는 숫자는 편의상 정점에 매긴 번호를 의미한다.

붉은 간선은 차후 설명의 편의상 색칠해 둔 것으로, 우선은 다른 검은 간선과 동일한 것으로 간주하도록 하자.

간선은 항상 두 정점을 잇게 된다. 이제부터의 설명에서는, 각 정점을 번호로(1번 정점, 2번 정점.. ), 간선을 양 끝점의 정점의 번호로(1-3, 3-2… ) 표기하도록 하자.

그래프의 간선에는 가중치가 있을 수도 있다. 만일 특별한 언급이 없다면 모든 간선의 가중치가 1인 그래프로 간주할 수 있으며, 가중치가 존재한다면, 예를 들어 1-3 간선의 가중치가 3이라면, 1번 정점에서 3번 정점으로 가기 위해선 길이 3인 간선을 지나야 한다고 표현한다. 위의 그래프는 모든 간선의 길이가 1인 예시라고 보면 된다.

그래프의 간선에는 방향성이 있을 수도 있다. 예를 들어, 1번과 3번 정점 사이에 놓인 1-3 간선의 경우, 1->3 또는 3->1의 방향성을 가지는 것이 가능하다. 방향성 간선을 갖고 있는 그래프를 ‘유향 그래프’, 위의 그림처럼 방향성이 없는 간선만으로 이루어진 그래프를 ‘무향 그래프’ 라 한다. 간선의 방향성은 그래프에서 탐색을 진행할 때 결과를 달리할 수 있다. 예를 들어, 현재 위의 그래프에서 1번 정점에서 4번 정점까지 가면서, 간선을 최소한 거치는 경로는 1->3->4로, 총 2개의 간선을 거친다. 우리는 이것을 ‘1번 정점과 4번 정점의 최단 경로는 2다’ 라고 표현한다. 하지만 만약 3번 정점과 4번 정점 사이의 간선이 4->3의 방향성을 가진다면, 1번 정점에서 4번 정점으로 가는 최단 경로는 1->3->6->5->4 로, 총 4개의 간선을 지나야 한다. 즉, 최단 경로가 4가 된다.

그래프에서는 ‘사이클’ 을 정의할 수 있다. 무향 그래프에서의 사이클이란, 어떤 정점에서 출발해 시작점을 제외한 어떤 정점도, 어떤 간선도 두 번 이상 방문하지 않고 시작점으로 돌아올 수 있는 경로를 의미한다. 예를 들어, 위의 그림에서는 3-6-5-4-3 사이클과, 6-7-9 사이클이 존재한다. 1-3-1은 1-3 간선을 두 번 지났으므로 사이클이 될 수 없으며, 1-3-6-5-4-3은 시작점으로 돌아오지 않는 경로이므로 사이클이 아니다.

만일 그래프에 단 하나의 사이클도 없다면, 해당 그래프는 ‘트리’ 라고 부른다. 이는 그래프가 마치 하나의 정점에서 출발해 피어난 나무 모양과도 같음에 붙여진 이름으로, 예를 들어 위의 그림에서 빨간 간선 두 개를 제거한다면 위의 그래프는 트리가 된다. 예를 들어, 상단에 주어진 그래프에서 빨간 간선 두 개를 제거한 뒤 만들어진 트리의 모습은 아래와 같다.

image

일반적으로 그래프에서는 정점의 위치나 간선의 모양 등에 대한 조건은 전혀 고려하지 않으며, 오직 연결성만을 고려하므로, 간선의 집합이 변하지 않는다는 가정 하에 그래프를 얼마든지 다시 그릴 수가 있다. 위의 트리에서 5번 정점을 잡고 위로 들어올리는 예시를 생각해 보자. 아래쪽에 중력이 작용한다고 생각하고 5번 정점을 위쪽으로 들어올리게 되면 트리의 모양은 아래와 같이 변할 것이다.

image

간선의 집합에 변함이 없는 한, 그래프는 얼마든지 원하는 대로 다시 그릴 수가 있다. 예를 들어, 위의 트리를 거울에 비추어 좌우를 바꿀 경우에도 동일한 트리가 된다.

트리에는 루트(root)가 있을 수도 없을 수도 있지만, 편의를 위해서라면 아무 정점이나 루트로 선택할 수 있다. 5번 정점을 루트로 하였다고 생각한 뒤 위의 트리를 다시 보도록 하자.

트리는 항상 루트를 기준으로 다시 그릴 수 있기 때문에, 루트가 고정되지 않는 한 어떤 정점이 ‘위에’ 있는지 판정할 수는 없다. 하지만 루트가 고정된다면, 우리는 정점 간에 ‘부모’ 와 ‘자식’ 의 관계를 정의할 수가 있다. 예를 들어, 위의 트리에서는 4번 정점의 부모는 5번 정점이며, 3번 정점은 4번 정점의 자식이 된다. 5번 정점의 부모는 없으며, 4, 6번 정점을 두 자식으로 갖게 될 것이다.

트리에는 몇 가지 중요한 성질이 있는데, 그 중 두 가지만 추려보자면 아래와 같다.

  • 임의의 두 정점 U와 V에 대해, U에서 V로 가는 최단경로는 유일하다.
  • 아무 정점이나 잡고 부모와의 연결을 끊었을 때, 해당 정점과 그 자식들, 그 자식들의 자식들… 로 이루어진 부분그래프는 트리가 된다.

둘 모두 직관적이며 자명한 사실이므로 증명은 생략한다. 두 번째 성질에서, 끊어진 부분그래프로 만들어진 트리를 ‘서브트리’ 라고 부른다.

만약 트리에 대한 문제 하나가 출제되었다고 가정해보자. 입력이 위처럼 루트와 그 자식들로 이루어진다면 좋지만, 루트가 없는 일반 트리의 형태(두 번째 그림)의 형태로 입력이 주어질 수도 있다. 예를 들어, 정점의 개수와 간선의 목록만이 주어진다면, 어떻게 트리를 구성할 수 있을까?

예를 들어, 위의 트리에 대해 정점의 개수와 간선의 목록이 아래와 같이 입력된다고 하자.

9
1 3
4 3
5 4
5 6
6 7
2 3
9 6
6 8

첫 줄의 9는 정점의 개수이며, 나머지 8쌍의 두 정수는 간선의 양 끝점 번호를 의미한다. 트리에서의 간선의 개수는 항상 정점의 수 - 1이라는 것은 익히 알려진 사실이며, 증명 또한 어렵지 않으므로 설명을 생략한다.

위와 같은 데이터를 트리로 구성하기 위해서는, 우선 루트 하나를 임의로 정의하는 것이 편하다. 5번 정점을 루트로 정해보도록 하자.

트리에는 부모와 자식 관계가 있으므로, 각 정점별로 부모가 누구인지, 자식들의 목록은 어떻게 되는지를 저장해 두면 요긴하게 쓰일 것이다. 이를 아래와 같이 구현할 수 있다.

def makeTree(currentNode, parent) :
    for(Node in connect[currentNode]) :
        if Node != parent:
            add Node to currentNodes child
            set Nodes parent to currentNode
            makeTree(Node, currentNode)

currentNode는 현재 탐색 중인 정점이며, parent는 해당 정점의 부모 정점이다.

트리에서는 (눈치챘을 수도 있지만) 어떤 정점의 부모는 하나이거나 없다. 따라서, 어떤 정점에 대해 연결된 모든 정점은 최대 한 개의 정점을 제외하면 모두 해당 정점의 자식들이 될 것이다. 이에 따라, 부모 정점의 정보를 가져가면서, 부모 정점이 아니면서 자신과 연결되어 있는 모든 정점을 자신의 자식으로, 자신의 자식이 될 정점들의 부모 정점을 자신으로 연결한 뒤 재귀적으로 자식 정점들에게 트리 구성을 요청하는 형태의 함수이다.

위와 같이 정의한 뒤엔, 메인 함수에서 한 차례 makeTree(5, -1) 을 호출할 경우 5번 정점을 루트로 하는 트리를 구성할 수 있다. -1은 부모가 없음을 의미한다.

그렇다면, 일반적인 형태의 트리에서 루트가 주어진 뒤 여러 질의가 주어지는 상황을 생각해 보자. 예를 들어, 5번 정점을 루트로 하는 트리에 대해, ‘정점 U를 루트로 하는 서브트리의 정점의 수는 얼마인가?’ 라는 질의가 다수 주어진다고 해 보자. U를 루트로 하는 서브트리란, 위에도 언급하였지만 정점 U와 그 부모의 연결을 끊고 정점 U를 기준으로 그 자식들, 자식들의 자식들… 로 만든 트리를 말한다. 예를 들어, 5번 정점이 루트일 때 4번 정점을 루트로 하는 서브트리에서의 정점의 수는 4개이며, 8번 정점을 루트로 하는 서브트리에서의 정점의 수는 1개가 된다.

물론 직접 연결을 끊은 뒤 다시 정점의 수를 세는 방법도 가능하겠지만, 트리의 정점 수가 많고, 질의 또한 많다면 프로그램이 제한시간 내에 수행될 수 없을 확률이 높다. 아마 미리 모든 정점을 각각 루트로 하는 서브트리에서의 정점의 수를 빠르게 구해 둘 방법이 있다면 좋을 것이다.

이를 구현하기 위해, 트리를 구성하던 코드의 동작 과정을 살펴보도록 하자. 루트에서 출발하여, 자식 정점들에 대해 한 번씩 트리 구성을 요청하게 된다. 여기에서 알 수 있는 사실은, 자식 정점들에 대한 makeTree가 호출된 뒤엔, 해당 자식 정점을 서브트리로 하는 트리가 구성이 완료된다는 것이다. 이와 같은 원리로 모든 정점에 대해 해당 정점을 루트로 하는 서브트리에 속한 정점의 수를 계산하는 함수를 만들어보도록 하자.

def countSubtreeNodes(currentNode) :
    size[currentNode] = 1 // 자신도 자신을 루트로 하는 서브트리에 포함되므로 0 아닌 1에서 시작한다.
    for Node in currentNodes child:
        countSubtreeNode(Node)
        size[currentNode] += size[Node]

자식 정점들에 대해 모두 서브트리에 속한 정점의 수를 계산하게 만든 뒤 각각의 정점 수를 더해 자신을 루트로 하는 서브트리에 속한 정점의 수를 만들게 된다. 이제 메인 함수 내에서 makeTree(5, -1)과 countSubtreeNodes(5) 를 차례대로 한 번씩 호출할 경우, 5번을 루트로 하는 트리에서 모든 정점에 대해 각 정점을 루트로 하는 서브트리에 속한 정점의 수를 계산해둘 수가 있다. 이를 이용하면, 모든 질의 U에 대해 size[U] 를 출력하기만 하면 되므로, 정점이 10만 개, 질의가 10만 개인 데이터에서도 충분히 빠른 시간 내에 모든 질의를 처리할 수가 있게 될 것이다.


2. 문제 풀이

이 문제는 각 루트마다 그래프를 각 독립적으로 보고, 하위 노드들의 개수를 따로 표기하여 최초 탐색 한 번만으로 각 노드의 간선의 개수를 구할 수 있었다.

ArrayList<Integer>를 가진 배열을 통해 각 노드들을 추가하며 무방향, 무가중치 그래프 구조를 만들 수 있다.

[0][1][2][3][4][5] ...
 1  3     0  5  4
    0

그리고 루트 R이 정해졌으므로 이것을 들어올린다고 생각하면 트리가 된다.
이 때, 이진 트리가 아님에는 주의하자.

매 쿼리마다 독립된 트리를 돌면 시간초과가 날 수 있다는 것을 알 수 있다.
( 2 ≤ N ≤ 10^5 , 1 ≤ R ≤ N, 1 ≤ Q ≤ 10^5 ) 때문에 나도 처음에 생각없이 매 번 돌면서 확인하도록 만들어서 시간 초과가 났었다.

List 배열과 동일하게 integer 배열을 만들어서 각 하위 노드들의 개수를 저장해둠으로써 이를 해결하였다.

이전에 방문한 노드는 재 방문하지 않도록하여 하위 노드의 개수를 상위 노드의 개수에 추가해주면 각 노드의 하위 노드 개수를 파악할 수 있다. 즉, 덕분에 매 쿼리마다 트리를 탐색할 필요없이 배열의 쿼리 인덱스만 호출하면 하위 노드의 개수 값을 알 수 있다.

그럼 이것을 활용하여 코드로 표현해보자.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;

public class TreenQuery_15681 {
    static int N, R, Q;
    static int[] arrq, cnt;
    static ArrayList<Integer>[] tree;

    public static void search(int x, int prevNode) {
        cnt[x] = 1;     // 현 루트 또한 간선의 개수로 포함

        for(int a : tree[x]) {
            if(a == prevNode) continue;     // 이전에 방문한 노드는 넘어간다.
            search(a, x);
            cnt[x] += cnt[a];               // 하위 노드의 간선 갯수도 더해준다.
        }
    }

    public static void solution() {
        search(R, -1);

        StringBuilder sb = new StringBuilder();
        for(int i : arrq) {
            sb.append(cnt[i] + "\n");
        }
        System.out.println(sb.toString());
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        R = Integer.parseInt(st.nextToken());
        Q = Integer.parseInt(st.nextToken());
        
        cnt = new int[N + 1];
        arrq = new int[Q];

        tree = new ArrayList[N + 1];
        for(int i = 1; i <= N; i++) tree[i] = new ArrayList<>();

        for(int i = 1; i < N; i++) {    // 가중치 없는 무방향 그래프
            if(!st.hasMoreTokens()) st = new StringTokenizer(br.readLine());
            int U = Integer.parseInt(st.nextToken());
            int V = Integer.parseInt(st.nextToken());
            tree[U].add(V);
            tree[V].add(U);
        }

        for(int i = 0; i < Q; i++) {
            arrq[i] = Integer.parseInt(br.readLine());
        }
        solution();
    }
}

게시글 테스트

일상 기록


slick image

Slick slick 사이트에 들어가서 get it now를 눌러 다운로드 받아주어 slick 폴더를 /assets/css/slick에 복사>붙여넣기 해준다.

게시글 원하는 위치에 아래와 같이 넣어주면 된다.

<div class="main_center">
    <div><img src= "/assets/img/blog/hydejack-8.jpg" style="width: auto; height: 500px;"></div>
    <div><img src="/assets/img/blog/hydejack-8.png" style="width: auto; height: 500px;"></div>
    <div><img src= "/assets/img/blog/hydejack-9-dark.jpg" style="width: auto; height: 500px;"></div>
</div>
<script>
    $(document).ready(function() {
        $('.main_center').slick({
            autoplay : true, /*자동으로 슬라이딩됨*/
            dots : true, /* 하단 점 버튼 */
            speed : 300 /* 이미지가 슬라이딩시 걸리는 시간 */,
            infinite : true,
            autoplaySpeed : 30000 /* 이미지가 다른 이미지로 넘어 갈때의 텀 */,
            arrows : true,
            slidesToShow : 1,
            slidesToScroll : 1,
            touchMove : true, /* 마우스 클릭으로 끌어서 슬라이딩 가능여부 */
            nextArrows : true, /* 넥스트버튼 */
            prevArrows : true,
            arrow : true, /*false면 좌우 버튼 없음, true면 좌우 버튼 보임*/
            fade : false
        });
    });
</script>

.slick() 안의 옵션을 원하는대로 설정하여 사용 할 수 있다.

그리고 scss를 수정하여 좌우로 화살표를 넣어서 표출 하는것도 가능하도록 변경 할 수 있다.
이미지는 별도로 구해서 사용하시면 됩니다.

assests/css/slick/slick-theme.css

.slick-prev:before
{
    content: url(left.png);
}
[dir='rtl'] .slick-prev:before
{
    content: url(right.png);
}

.slick-next:before
{
    content: url(right.png);
}
[dir='rtl'] .slick-next:before
{
    content: url(left.png);
}

게시글 목차 만들기

h1 타이틀 바로 아래에

* toc
{:toc .large-only}

를 추가하여 헤더를 기준으로 목차 생성


페이지 버튼 만들기

_include/components/page-button.html

<div class="page-control">
    <div>
        
        <a id="prev" class="w-btn-outline w-btn-gray-outline" href="/devlog/flutter-dart.html">&laquo; 플러터를 위한 필수! 다트를 알자!</a>
        
    </div>
    <div>
        
        <a id="next" class="w-btn-outline w-btn-gray-outline" href="/devlog/algo-baekjoon-15681.html">[BOJ] 15681.트리와 쿼리 &raquo;</a>
        
    </div>
</div>

_layouts/post.html

<div class="page-control">
    <div>
        
        <a id="prev" class="w-btn-outline w-btn-gray-outline" href="/devlog/flutter-dart.html">&laquo; 플러터를 위한 필수! 다트를 알자!</a>
        
    </div>
    <div>
        
        <a id="next" class="w-btn-outline w-btn-gray-outline" href="/devlog/algo-baekjoon-15681.html">[BOJ] 15681.트리와 쿼리 &raquo;</a>
        
    </div>
</div>

About 위에 위치하여 작성자 소개 위에 위치하도록 한다.

원하는 스타일대로 변경을 위해서 다음과 같이 작업해준다.

_sass/my-style.scss

.page-control {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.page-control > div {
  max-width: 50%;
}.page-control {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.page-control > div {
  max-width: 50%;
}

.w-btn-outline {
  position: relative;
  padding: 15px 30px;
  border-radius: 15px;
  font-family: "paybooc-Light", sans-serif;
  box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
  text-decoration: none;
  font-weight: 600;
  transition: 0.25s;
}

.w-btn-outline:hover {
  letter-spacing: 2px;
  transform: scale(1.2);
  cursor: pointer;
}

.w-btn-outline:active {
  transform: scale(1.5);
}

.w-btn-gray-outline {
  border: 3px solid #a3a1a1;
  color: #6e6e6e;
}

.w-btn-gray-outline:hover {
  background-color: #a3a1a1;
  color: #e3dede;
}

게시글에 유튜브플레이어 보기

_includes/components/youtubePlayer.html

<style>
    .embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; }
    .embed-container iframe,
    .embed-container object,
    .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>

<div class="embed-container" >
    <iframe src="https://www.youtube.com/embed/" frameborder="0" allowfullscreen="" onclick="ga('send', 'event', 'post', 'click', 'youtubePlayer');">
    </iframe>
</div>

위 처럼 youtube 영상의 id를 가져와서 표출 할 수 있도록 만들고

`<style>
    .embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; }
    .embed-container iframe,
    .embed-container object,
    .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>

<div class="embed-container" >
    <iframe src="https://www.youtube.com/embed/{youtube ID}" frameborder="0" allowfullscreen="" onclick="ga('send', 'event', 'post', 'click', 'youtubePlayer');">
    </iframe>
</div>`

게시글 원하는 위치에 youtube id를 넣어주면 된다.

image

위와 같이 watch?v= 이후에 오는 id를 가져다가 사용한다.


게시물 조회수 보이기

Hits 이용

Hits

TARGET URL에 나의 블로그 주소를 입력하고, Options에서 여러 설정들을 조정하여 내가 원하는 스타일의 hits를 만들 수 있다. 미리보기를 통해 표시되는것을 볼 수 있으니 참고해서 만들면 된다.

그렇게 생성된 아래의 HTML Link를 복사해서

_layouts/post.html

에 넣어서 각 게시물 별로 조회수가 보이는 위치를 조정할 수 있다.

_includes/body/sidebar-sticky.html

에 넣으면 사이드바는 어떤 게시물을 봐도 같이 보이는것이기 때문에 전체 조회수를 체크할 수 있다.

만약 조회수 가장 오른쪽에 생성되는 링크모양 아이콘이 싫다면

_sass/my-style.scss

a.external::after, a::after {
  display: none;
}

를 추가하여 제거 할 수 있다.


플러터를 위한 필수! 다트를 알자!

Flutter를 개발하기 위한 구글 Dart 언어에 대해서 알아보자

플러터는 다트(Dart)라는 프로그래밍 언어로 개발됐습니다. 따라서 플러터를 사용하려면 다트라는 새로운 언어를 알아야 합니다.
다트 홈페이지에는 "자바나 C# 개발자라면 하루면 배울 수 있다." 라고 소개하는 것처럼 다트는 생각보다 어렵지 않은 언어입니다.

다트 언어의 철학은 “간단하게 배워서 다양한 플랫폼에 써먹자!” 입니다. 지금 다트를 익혀 두면 모바일 앱뿐만 아니라 서버와 웹 프런트엔드, 데스크톱 앱을 만들 수도 있습니다.
다만, 플러터가 주제이므로 다트를 전문적으로 배우지는 않고 핵심 내용만 간략하게 다룰 예정입니다.

다트(Dart)!

다트는 구글이 Web Front-end 구현을 목적으로 개발한 프로그래밍 언어로 2011년 10월에 공개되었습니다.
다트는 마치 카멜레온처럼 어떻게 활용하느냐에 따라 서버나 웹, 앱을 만들 때 사용할 수 있습니다.

다트 언어의 9가지 특징

다트는 다른 언어와 비교해 9가지 두드러진 특징이 있습니다.

  1. 다트는 main() 함수로 시작합니다.
  2. 다트는 어디에서나 변수를 선언하고 사용할 수 있습니다.
  3. 다트에서는 모든 변수가 객체입니다. 그리고 모든 객체는 Object 클래스를 상속받습니다.
  4. 다트는 자료형이 엄격한 언어입니다. 이 말은 변수에 지정한 자료형과 다른 유형의 값을 저장하면 오류가 발생한다는 의미입니다. 만약 여러 자료형을 허용하려면 dynamic 타입을 이용할 수 있습니다.
  5. 다트는 제네릭 타입을 이용해 개발할 수 있습니다. 그리고 List<int>처럼 int형을 넣을 수도 있고, List<dynamic>처럼 다양한 데이터를 넣을 수도 있습니다.
  6. 다트는 public, protected 같은 키워드가 없습니다. 만약 외부로 노출하고 싶지 않다면 변수나 함수 이름 앞에 언더스코어(_)를 이용해 표시할 수 있습니다.
  7. 변수나 함수의 시작은 언더스코어 또는 문자열로 시작하고 그 이후에 숫자를 입력할 수 있습니다.
  8. 다트는 삼항 연산자를 사용할 수 있습니다.
  9. Null safety를 지원합니다. 이는 2.0에서 새롭게 추가된 기능으로, Null safety를 이용하면 컴파일 전에 널 예외(Null Exception)를 알 수 있으므로 널에 대한 오류가 발생하지 않도록 작업할 수 있습니다.

8) 삼항연산자

다음 코드에서 첫 번째 줄을 보면 isPublic이 참이면 “public”, 참이 아니면 “private”를 반환하여 visibility에 지정합니다. 두 번째 줄은 매개변수로 전달받은 name이 null이면 “Guest”를 반환하고, 아니면 매개변수로 전달 받은 값을 그대로 반환합니다.

var visibility = isPublic ? 'public' : 'private';
String playerName(String name) => name ?? 'Guest';

간단한 코드로 다트의 특징 이해하기

다트로 만든 프로그램의 시작점은 자바나 C처럼 main() 함수입니다.

// 함수 정의
printInteger(int aNumber) {
    print('The number is $aNumber.'); // 콘솔에 출력
}

// main() 함수에서 시작
main() {
    var number = 42;        // 동적 타입 변수 지정
    printInteger(number);   // 함수 호출
}
결과 >
The number is 42.

var 키워드로 변수를 선언하면 해당 변수에 저장되는 값의 유형에 따라 자료형이 정해집니다. 이것을 자료형 추론(type inference)이라고 합니다.

다트에서는 문자열을 표현할 때는 큰따옴표나 작은따옴표를 이용하는데, 이때 따옴표 안에 ${표현식}과 같은 형태로 사용하면 표현식에 변수를 직접 넣을 수 있습니다.

구분 자료형 설명
숫자 int 정수형 숫자
  double 실수형 숫자
  num 정수형 또는 실수형 숫자
문자열 String 텍스트 기반 문자
논리형 bool True나 false
자료형 추론 var 입력받은 값에 따라 자료형 결정. 한 번 결정된 자료형은 변경 불가
  dynamic 입력받은 값에따라 자료형 결정. 다른 변수 입력하면 자료형 변경 가능

Null safety

Null safety를 사용하려면 pubspec.yaml 파일의 SDK 환경을 변경해야 합니다. 다음과 같이 2.12.0 버전 이상부터 Null safety를 지원합니다.

enviroment:
    sdk: ">=2.12.0 <3.0.0"

버전 표기 이해

변수를 선언할 때 사용하는 것으로, 자료형 다음에 ?를 붙이면 Null이 가능하고 붙이지 않으면 Null이 불가능합니다. 그리고 식 다음에 !를 붙이면 Null이 아님을 직접 표시합니다.

int? couldReturnNullButDoesnt() => -3;

void main() {
    int? couldBeNullButIsnt = 1;            // null로 변경 가능
    List<int?> listThatCouldHoldNulls = [2, null, 4];   // List의 int에 null 값 포함 가능
    List<int>? nullsList;                   // List 자체가 null일 수 있음
    int a = couldBeNullButIsnt;             // null을 넣으면 오류
    int b = listThatCouldHoldNulls.first;   // int b는 ?가 없으므로 오류
    int b = listThatCouldHoldNulls.first!;  // null이 아님을 직접 표시
    int c = couldReturnNullButDoesnt().abs();   // null일 수도 있으므로 abs()에서 오류
    int c = couldReturnNullButDoesnt()!.abs();  // null이 아님을 직접 표시

    print('a is $a.');
    print('b is $b.');
    print('c is $c.');
}

Null safety를 사용하는 이유는 프로그램 실행 중 널 예외가 발생하면 프로그램이 중지되는데, 이를 코드 단계에서 구분하여 작성할 수 있도록 하기 위해서입니다. 이런 이유로 요즘 나오는 언어 중에는 Null safety를 제공하는 언어가 많습니다.

다트가 제공하는 키워드

| 키워드 | - | - | - | | :–: |:–:|:–:|:–:| | abstract | dynamic | implements | show | | as | else | import | static | | assert | enum | in | super | | async | export | interface | switch | | await | extends | is | sync | | break | external | library | this | | case | factory | mixin | throw | | catch | false | new | true | | class | final | null | try | | const | finally | on | typedef | | continue | for | operator | var | | convariant | Function | part | void | | default | get | rethrow | while | | deferred | hide | return | with | | do | if | set | yield |

다트가 제공하는 키워드


비동기 처리 방식

다트는 비동기 처리를 지원하는 언어입니다. 비동기(asynchronous)란 언제 끝날지 모르는 작업을 기다리지 않고 다음 작업을 처리하게 하는 것을 의미합니다. 만약 비동기를 지원하지 않고 동기(synchronous)로만 처리한다면 어떤 작업이 오래 걸릴 경우 사용자는 실행이 멈춘 것으로 생각하고 프로그램을 종료할 수 있습니다. 일반적으로 네트워크에서 데이터를 가져오거나 데이터베이스 쓰기, 파일 읽기 등의 작업은 상황에 따라 언제 끝날지 알 수 없으므로 비동기로 처리합니다.

synchronous 1 1: 출처: DEV.to

비동기 프로세스의 작동 방식

다트는 async와 await 키워드를 이용해 비동기 처리를 구현합니다.

  1. 함수 이름 뒤, 본문이 시작하는 중괄호 { 앞에 async 키워드를 붙여 비동기로 만든다.
  2. 비동기 함수 안에서 언제 끝날지 모르는 작업 앞에 await 키워드를 붙인다.
  3. 2번 작업을 마친 결과를 받기 위해 비동기 함수 이름 앞에 Future(값이 여러 개면 Stream) 클래스를 지정한다.

다음 예시를 보겠습니다.

void main() {
    checkVersion();
    print('end process');
}
Future checkVersion() async {
    var version = await lookUpVersion();
    print(version);
}

int lookUpVersion() {
    return 12;
}

일반적인 생각으로 코드를 보면 main() 함수에서 제일 먼저 checkVersion() 함수를 호출했으므로 checkVersion() 함수에 있는 lookUpVersion() 함수가 호출되어 12를 전달받아 출력한 다음, 다시 main()으로 돌아와서 ‘end process’가 출력될 것 같습니다. 하지만 결과는 다음과 같습니다.

실행 결과 >
end process
12

이러한 결과가 나오는 이유는 먼저 checkVersion() 함수를 보면 이름 앞뒤로 Future와 async가 붙었습니다. 이렇게 하면 checkVersion() 함수를 비동기로 만들겠다는 의미입니다. 즉, checkVersion() 함수 안에 await가 붙은 함수를 비동기로 처리한 다음 그 결과는 Future 클래스에 저장해 둘 테니 먼저 checkVersion() 함수를 호출한 main() 함수의 나머지 코드를 모두 실행하라는 의미입니다. 그리고 main() 함수를 모두 실행했으면 그때 Future 클래스에 저장해 둔 결과를 이용해서 checkVersion() 함수의 나머지 코드를 실행합니다.

앞선 코드에서 lookUpVersion() 함수 앞에 await 키워드가 붙었습니다. await 키워드는 처리를 완료하고 결과를 반환할 때까지 이후 코드의 처리를 멈춥니다. 따라서 lookUpVersion() 함수를 호출해 version 변수에 12가 저장된 다음에야 비로소 print(version) 문으로 이를 출력합니다.
이처럼 비동기 함수에서 어떤 결과값이 필요하다면 해당 코드를 await로 지정합니다. 그러면 네트워크 지연 등으로 제대로 된 값을 반환받지 못한 채 이후 과정이 실행되는 것을 방지할 수 있습니다.

이러한 비동기 처리를 이용하면 지연이 발생하는 동안 애플리케이션이 멈춰 있지 않고 다른 동작을 하게 할 수 있습니다.

비동기 함수가 반환하는 값 활용하기

비동기 함수가 반환하는 값을 처리하려면 then() 함수를 이용합니다.

void main() async {
    await getVersionName().then((value) => {
        print(value);
    });
    print('end process');
}

Future<String> getVersionName() async {
    var versionName = await lookUpVersionName();
    return versionName;
}

String lookUpVersionName() {
    return 'Android Q';
}
실행 결과 >
Android Q
end process

코드를 보면 Future이라는 반환값을 정해 놓은 getVersionName() 이라는 함수가 있습니다. 이 함수는 async 키워드가 붙었으므로 비동기 함수입니다. 이처럼 비동기 함수가 데이터를 성공적으로 반환하면 호출하는 쪽에서 then() 함수를 이용해 처리할 수 있습니다.

then() 이외에 error() 함수도 이용할 수 있습니다. error() 함수는 실행 과정에서 오류가 발생했을 때 호출되므로 이를 이용해 예외를 처리할 수 있습니다.

다트와 스레드

다트는 하나의 스레드(thread)로 동작하는 프로그래밍 언어입니다. 그래서 await 키워드를 잘 사용해야 합니다.

void main() {
    printOne();
    printTwo();
    printThree();
}

void printOne() {
    print('One');
}

void printThree() {
    print('Three');
}

void printTwo async {
    Future.delayed(Duration(seconds: 1), () {
        print('Future!!');
    });
    print('Two');
}
실행 결과 >
One
Two
Three
Future!!

‘One’ 출력 이후에 printTwo() 함수에 진입하면 Future를 1초 지연했으므로 async로 정의한 비동기 함수의 특징에 대해 ‘Two’가 먼저 출력됩니다. 그리고 ‘Three’를 출력하고 ‘Future!!’가 가장 늦게 출력됩니다.

이때, printTwo() 함수를 다음과 같이 수정한다면?

void printTwo() async {
    await Future.delayed(Duration(seconds: 2), () {
        print('Future Method');
    });
    print('Two');
}

Future.delayed() 코드 앞에 await 키워드를 붙였으므로 이후 코드의 실행이 멈춥니다.
따라서 printTwo() 함수를 벗어나 main() 함수의 나머지 코드를 모두 실행하고, 그 다음에 await가 붙은 코드부터 차례대로 실행합니다.

실행 결과 >
One
Three
Future Method
Two

이처럼 await 키워드를 이용하면 await가 속한 함수를 호출한 쪽의 프로세스가 끝날 때까지 기다리기 때문에 이를 잘 고려해서 프로그램을 작성해야 합니다.


JSON 데이터 주고 받기

애플리케이션을 개발하다 보면 서버와의 통신이 중요하다는 것을 알게 됩니다. 대부분 앱은 서버와 데이터를 주고 받으며 상호 작용하고 화면에 필요한 데이터를 출력합니다. 이러한 데이터를 교환할 때 가장 많이 쓰는 형식이 JSON입니다.
직접 문자열 형태나 XML을 이용해 데이터를 주고 받기도 하지만, 가장 편리하면서 파일 크기도 작은 JSON 형식을 주로 이용합니다. 다트에서는 이러한 JSON 통신을 간편하게 이용할 수 있습니다.
JSON을 사용하려면 소스에 convert라는 라이브러리를 포함해야 합니다.

import 'dart:conver';

void main() {
    var jsonString = '''
        [
            {"score": 40},
            {"score": 80}
        ]
    ''';
    var scores = jsonDecode(jsonString);
    print(scores is List);              // true
    var firstScore = scores[0];
    print(firstScore is Map);           // true
    print(firstScore['score'] == 40);   // true
}

위 코드에서 jsonString 변수에 저장된 데이터가 JSON을 형태의 문자열입니다. 이 데이터를 convert 라이브러리에 있는 jsonDecode() 함수에 전달한 후 그 결과를 scores 변수에 저장했습니다. jsonDecode() 함수는 JSON 형태의 데이터를 dynamic 형식의 리스트로 변환해서 반환해 줍니다.
scores 변수가 리스트인지는 True/False로 점검할 수 있습니다.

그리고 scores 리스트에서 첫 번째 값을 firstScore에 저장합니다. 이 값은 키(key)와 값(values)이 있는 Map 형태입니다. print(firstScore[‘score’] == 40) 코드는 firstScore 데이터의 score 키에 해당하는 값이 40이라는 것을 나타냅니다. 이처럼 jsonDecode() 함수를 이용하면 서버에서 JSON 데이터를 받아서 사용할 수 있습니다.

이제 애플리케이션에서 서버로 데이터를 보내는 예도 살펴보겠습니다. 이때는 jsonEncode() 함수를 이용해 JSON 형태로 변환한 데이터를 서버로 보낼 수 있습니다.

import 'dart:convert';

void main() {
    var scores = [
        {'score': 40},
        {'score': 80},
        {'score': 100, 'overtime': true, 'special_guest': null},
    ];

    var jsonText = jsonEncode(scores);
    print(jsonText == 
        '[{"score": 40},{"score": 80},'
        '{"score": 100, "overtime": true,'
        '"special_guest": null}]');     // true 출력
}

scores 데이터는 배열로 이루어졌고 각 항목은 score값으로 구성되며 마지막 항목에는 overtime과 special_guest값을 추가했습니다.
앞의 코드에서는 {“score”: 40}처럼 키에 큰따옴표를 사용해 JSON 데이터임을 표시했고, 지금 코드는 {‘score’: 40}처럼 작은따옴표를 이용해 변수임을 표시했습니다.

이 scores 데이터를 인자로 jsonEncode() 함수를 호출하면 키값이 큰따옴표로 묶이고 전체 데이터를 작은따옴표로 한 번 묶어서 JSON 형태의 데이터가 됩니다.
이처럼 다트는 간단하게 JSON을 만들고 파싱하여 데이터를 주고받는 기능을 제공합니다.


스트림 통신하기

애플리케이션을 개발하다 보면 데이터를 순서대로 주고받아야 할 때가 있습니다. 데이터를 순서대로 주고받을 것으로 생각해서 화면을 구성했는데 네트워크나 와이파이 연결이 끊기거나 특정 API 호출이 늦어져 순서가 달라지면 애플리케이션이 원하는 흐름대로 작동하지 않을 수도 있습니다.

이처럼 순서를 보장받고 싶을 때 스트림(stream)을 이용합니다.
스트림은 처음에 넣은 데이터가 꺼낼 때도 가장 먼저 나오는 데이터 구조로 생각할 수 있습니다. 따라서 스트림을 이용하면 데이터를 차례대로 주고받는 코드를 작성할 수 있습니다.

import 'dart:async';

Future<int> sumStream(Stream<int> stream) async {
    var sum = 0;
    await for (var value in stream) {
        print('sumStream : $value');
        sum += value;
    }
    return sum;
}

Stream<int> countStream(int to) async* {
    for (int i = 0; i <= to; i++) {
        print('countStream : $i');
        yield i;
    }
}

main() async {
    var stream = countStream(10);
    var sum = await sumStream(stream);
    print(sum);     // 55
}

main() 함수를 살펴보면 먼저 countStream(10) 함수를 호출합니다. 이 함수는 async*dhk yield 키워드를 이용해 비동기 함수로 만들었습니다. 이 함수는 for 문을 이용해 1부터 int형 매개변수 to로 전달받은 숫자까지 반복합니다.

async* 명령어는 앞으로 yield를 이용해 지속적으로 데이터를 전달하겠다는 의미입니다. 위 코드에서 yield는 int형 i를 반환하는데, return은 한 번 반환하면 함수가 끝나지만 yield는 반환 후에도 계속 함수를 유지합니다.

이렇게 받은 yield값을 인자ㅗ sumStream() 함수를 호출하면 이 값이 전달될 때마다 sum 변수에 누적해서 반환해 줍니다. 그리고 main() 함수에서 이 값을 받아서 출력하면 55가 나옵니다.
출력 결과를 보면 함수가 어떤 흐름으로 진행되는지 알 수 있을겁니다.

실행 결과 >
countStream : 1
sumStream : 1
countStream : 2
sumStream : 2
...
countcountStream : 9
sumStream : 9
countStream : 10
sumStream : 10
55

이처럼 스트림을 이용하면 데이터를 차례대로 받아서 처리할 수 있습니다.

아니면 then() 함수를 이용해 스트림 코드를 작성할 수도 있습니다.

main() {
    var stream = Stream.fromIterable([1, 2, 3, 4, 5]);

    // 가장 앞의 데이터 결과: 1
    stream.first.then((value) => print('first: $value'));
    // 가장 마지막 데이터의 결과 : 5
    stream.last.then((value) => print('last: $value'));
    // 현재 스트림이 비어 있는지 확인: false
    stream.isEmpty.then((value) => print('isEmpty: $value'));
    // 전체 길이: 5
    stream.length.then((value) => print('length: $value'));
}

코드를 보면 Stream클래스를 이용해 배열을 하나 만든 후 함수를 이용해서 값을 가져옵니다.
그런 다음 then() 함수로 가져다 사용합니다.
그런데 이 코드는 그대로 실행하면 오류가 발생합니다. 일단 스트림을 통해 데이터를 사용하면 데이터는 사라지기 때문입니다. 따라서 다음처럼 한 번만 실행하도록 변경해야 합니다.

main() {
    var stream = Stream.fromIterable([1, 2, 3, 4, 5]);

    // 가장 마지막 데이터의 결과: 5
    stream.last.then((value) => print('last: $value'));
}

스트림은 실시간으로 서버를 살펴보다가 서버에서 데이터가 변경되면 화면을 새로 고침하지 않더라도 자동으로 변경된 데이터가 반영되어야 할 때 사용할 수 있는 유용한 클래스입니다.


다트 프로그램 만들기

1. 구구단 프로그램 작성하기

void main() {
    for(int i = 0; i <= 9; i++) {
        for(int j = 0; j <=9; j++) {
            print('$i * $j = ${i * j}');
        }
    }
}

2. 자동차 클래스 구현하기

다음과 같은 속성을 포함하는 클래스를 만들어 봅니다. | 이름 | 자료형 | 의미 | |:—:|:—-:|:—:| |maxSpeed|int|최고 속도| |price|num|가격| |name|String|이름|

Car 클래스 안에 saleCar()라는 이름으로 함수를 작성합니다. 이 함수는 자동차 가격을 10% 할인해서 반환합니다. 그리고 main() 함수에서 Car 클래스를 이용해 다음과 같은 속성값으로 3종류의 자동차를 선언합니다. 새로운 객체를 선언할 때 자바에선는 new라는 키워드를 사용하지만 다트에서는 생략할 수 있습니다. 물론 사용해도 무방합니다.

maxSpeed price name
320 100000 BMW
250 70000 BENZ
200 80000 FORD

BMW를 3번 할인하는 함수를 호출한 뒤에 차량 가격을 출력해봅니다.

void main() {
    Car bmw = Car(320, 100000, BMW);
    Car benz = Car(250, 70000, BENZ);
    Car ford = Car(200, 80000, FORD);

    bmw.saleCar();
    bmw.saleCar();
    bmw.saleCar();
    print(bmw.price);
}

class Car {
    int maxSpeed;
    num price;
    String name;

    Car(int maxSpeed, num price, String name) {
        this.maxSpeed = maxSpeed;
        this.price = price;
        this.name = name;
    }

    num saleCar() {
        price = price * 0.9;
        return price;
    }
}

3. 로또 번호 생성기

무작위 수를 생성하는 랜덤 함수를 이용하려면 dart:math 라이브러리를 사용해야 합니다.
import 'dart:math' as math;
as math 코드는 import한 dart:math 라이브러리를 math라는 이름으로 사용하겠다는 의미입니다. 이 math를 이용해 6개의 무작위 수를 만드는 로또 번호 생성기를 만들어 봅시다. 만약, 생성한 번호가 중복일 경우에는 다시 생성합니다.

import 'dart:collection';
import 'dart:math' as math;

void main() {
    var rand = math.Random();
    HashSet<int> lotteryNumber  HashSet();

    while(lotteryNumber.length < 6) {
        lotteyNumber.add(rand.nextInt(45) + 1);
    }
    print(lotteryNumber);
}
실행 결과 >
{2, 9, 13, 24, 33, 42}

HashSet를 사용하려면 dart:collection이라는 라이브러리를 import해야 합니다.

[BOJ] 20166.문자열 지옥에 빠진 호석

백준 알고리즘 문제 풀이

해당 문제는 제한 조건이 있었기 때문이라도 완전 탐색 활용하여 풀어보았습니다.

링크 : image


1. 문제

문제 보기
시간 제한 메모리 제한
1 초 512 MB

하루 종일 내리는 비에 세상이 출렁이고 구름이 해를 먹어 밤인지 낮인지 모르는 어느 여름 날

잠 들기 싫어 버티던 호석이는 무거운 눈꺼풀에 패배했다. 정신을 차려보니 바닥에는 격자 모양의 타일이 가득한 세상이었고, 각 타일마다 알파벳 소문자가 하나씩 써있다더라. 두려움에 가득해 미친듯이 앞만 보고 달려 끝을 찾아 헤맸지만 이 세상은 끝이 없었고, 달리다 지쳐 바닥에 드러누우니 하늘에 이런 문구가 핏빛 구름으로 떠다니고 있었다.

  • 이 세상은 N행 M열의 격자로 생겼으며, 각 칸에 알파벳이 써있고 환형으로 이어진다. 왼쪽 위를 (1, 1), 오른쪽 아래를 (N, M)이라고 하자.
  • 너는 아무 곳에서나 시작해서 상하좌우나 대각선 방향의 칸으로 한 칸씩 이동할 수 있다. 이 때, 이미 지나 왔던 칸들을 다시 방문하는 것은 허용한다.
  • 시작하는 격자의 알파벳을 시작으로, 이동할 때마다 각 칸에 써진 알파벳을 이어 붙여서 문자열을 만들 수 있다.
  • 이 곳의 신인 내가 좋아하는 문자열을 K 개 알려줄 터이니, 각 문자열 마다 너가 만들 수 있는 경우의 수를 잘 대답해야 너의 세계로 돌아갈 것이다.
  • 경우의 수를 셀 때, 방문 순서가 다르면 다른 경우이다. 즉, (1,1)->(1,2) 로 가는 것과 (1,2)->(1,1) 을 가는 것은 서로 다른 경우이다.

호석이는 하늘을 보고서 “환형이 무엇인지는 알려달라!” 며 소리를 지르니 핏빛 구름이 흩어졌다가 모이며 아래와 같은 말을 그렸다.

  • 너가 1행에서 위로 가면 N 행으로 가게 되며 반대도 가능하다.
  • 너가 1열에서 왼쪽으로 가면 M 열로 가게 되며 반대도 가능하다.
  • 대각선 방향에 대해서도 동일한 규칙이 적용된다.
  • 하늘에 아래와 같은 그림을 구름으로 그려줄 터이니 이해해 돕도록 하여라.
  • 예를 들어서, 너가 (1, 1)에서 위로 가면 (N, 1)이고, 왼쪽으로 가면 (1, M)이며 왼쪽 위 대각선 방향으로 가면 (N, M)인 것이다.

image

세상을 이루는 격자의 정보와, K 개의 문자열이 주어졌을 때, 호석이가 대답해야 하는 정답을 구해주도록 하자.

입력(Input)

첫번째 줄에 격자의 크기 N, M과 신이 좋아하는 문자열의 개수 K 가 주어진다.

다음에 N개의 줄에 걸쳐서 M개의 알파벳 소문자가 공백없이 주어진다. 여기서의 첫 번째 줄은 1행의 정보이며, N 번째 줄은 N행의 정보이다.

이어서 K개의 줄에 걸쳐서 신이 좋아하는 문자열이 주어진다. 모두 알파벳 소문자로 이루어져 있다.

출력(Output)

K개의 줄에 걸쳐서, 신이 좋아하는 문자열을 만들 수 있는 경우의 수를 순서대로 출력한다.

제한

  • 3 ≤ N, M ≤ 10, N과 M은 자연수이다.
  • 1 ≤ K ≤ 1,000, K는 자연수이다.
  • 1 ≤ 신이 좋아하는 문자열의 길이 ≤ 5
  • 신이 좋아하는 문자열은 중복될 수도 있다.

2. 문제 풀이

신이 좋아하는 문자열 길이가 5 이하이고, N과 M, K의 수가 크지 않기 때문에 완전 탐색을 통해서 문제를 풀어볼 수 있을것 같았다.

우선 상, 하, 좌, 우 의 4방향뿐만 아니라 대각선으로도 가는 8방향으로 갈 수 있다고 나와 있다.
심지어 왔던 방향을 돌아가서 문자열을 만들 수 있으니 왔던 방향을 체크해주지 않아도 된다.

때문에 큐를 이용해서 반복적으로 큐에 넣고, 큐에 들어간 문자가 신이 좋아하는 문자열과 같으면 카운트를 올려주는 방식으로 풀 수 있다고 생각했다.

클래스를 하나 정의해서 큐에 넣어주고 다시 꺼낼 때 현재의 위치와 이동 가능 방향으로 이동하여 문자열을 만들 수 있도록 정의 한다.

// 큐에 들어가는 클래스
class Words

int x, y, len;
String str;

문자열의 길이가 5이기 때문에 길이 5가 넘어가면 더 이상 큐에 넣지 않고, 다시 큐에서 다음 Words를 꺼내는 식으로 조건을 건다.

a -> 길이가 5가 넘는지 체크 -> 신이 좋아하는 문자열이 있는지 체크, 있다면 카운팅 -> 8방향으로 보고 뒤에 문자열 추가 -> (큐에 넣기) -> 다시 차례가 와서 꺼내고 (반복)

위와 같은 반복을 통해 전부 다 돌려보도록해서 문제를 풀 수 있었다.

그럼 이것을 활용하여 코드로 표현해보자.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Hellhoseok_20166 {
    static int N, M, K;
    static char[][] cloud;
    static int[][] dir = { // 배열 상, 하, 좌, 우, 좌상, 우상, 좌하, 우하
        {-1, 1, 0, 0, -1, -1, 1, 1},
        {0, 0, -1, 1, -1, 1, -1, 1}
    };
    static int[] result;

    public static void solution(int x, int y, String[] keywords) {
        Queue<Words> q = new LinkedList<>();
        q.add(new Words(x, y, 1, cloud[x][y]+""));

        while(!q.isEmpty()) {
            Words tmpW = q.poll();

            if(tmpW.len > 5) continue;

            for(int j = 0; j < K; j++) {
                if(keywords[j].equals(tmpW.str)) {
                    result[j]++;
                }
            }

            for(int i = 0; i < 8; i++) {
                int nx = tmpW.x + dir[0][i];
                int ny = tmpW.y + dir[1][i];

                if(nx == 0) nx = N;
                if(ny == 0) ny = M;
                if(nx > N) nx = 1;
                if(ny > M) ny = 1;

                q.add(new Words(nx, ny, tmpW.len + 1, tmpW.str + cloud[nx][ny]));
            }
        }
    }
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        K = Integer.parseInt(st.nextToken());

        cloud = new char[N + 1][M + 1];
        result = new int[K];

        String tmp = "";
        for(int i = 1; i <= N; i++) {
            tmp = br.readLine();
            for(int j = 1; j <= M; j++) {
                cloud[i][j] = tmp.charAt(j - 1);
            }
        }

        String[] keywords = new String[K];
        for(int i = 0; i < K; i++) {
            tmp = br.readLine();
            keywords[i] = tmp;

        }

        for(int i = 1; i <= N; i++) {
            for(int j = 1; j <= M; j++) {
                solution(i, j, keywords);
            }
        }

        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < K; i++) {
            sb.append(result[i] + "\n");
        }

        System.out.println(sb.toString());
    }
}

class Words {
    int x;
    int y;
    int len;
    String str;
    public Words(int x, int y, int len, String str) {
        this.x = x;
        this.y = y;
        this.len = len;
        this.str = str;
    }
}

Emoji 이모티콘

Markdown 이모지에 대한 정리

  • 마크다운을 이용해 이모티콘을 표현 가능하다.
  • 깃허브도 문제없이 적용 가능하다.
  • 마크다운 문법에 :: 사이에 이모티콘명을 넣으면 자동으로 인식된다. :rocket:
GitHub supports emoji!

# :+1:

## :sparkles:

* :camel:

1. :tada:

:rocket: :metal:

마크다운 이모지 종류

[People]

1 2 3
:bowtie: :bowtie: 😄 :smile: 😆 :laughing:
😊 :blush: 😃 :smiley: ☺️ :relaxed:
😏 :smirk: 😍 :heart_eyes: 😘 :kissing_heart:
😚 :kissing_closed_eyes: 😳 :flushed: 😌 :relieved:
😆 :satisfied: 😁 :grin: 😉 :wink:
😜 :stuck_out_tongue_winking_eye: 😝 :stuck_out_tongue_closed_eyes: 😀 :grinning:
😗 :kissing: 😙 :kissing_smiling_eyes: 😛 :stuck_out_tongue:
😴 :sleeping: 😟 :worried: 😦 :frowning:
😧 :anguished: 😮 :open_mouth: 😬 :grimacing:
😕 :confused: 😯 :hushed: 😑 :expressionless:
😒 :unamused: 😅 :sweat_smile: 😓 :sweat:
😥 :disappointed_relieved: 😩 :weary: 😔 :pensive:
😞 :disappointed: 😖 :confounded: 😨 :fearful:
😰 :cold_sweat: 😣 :persevere: 😢 :cry:
😭 :sob: 😂 :joy: 😲 :astonished:
😱 :scream: :neckbeard: :neckbeard: 😫 :tired_face:
😠 :angry: 😡 :rage: 😤 :triumph:
😪 :sleepy: 😋 :yum: 😷 :mask:
😎 :sunglasses: 😵 :dizzy_face: 👿 :imp:
😈 :smiling_imp: 😐 :neutral_face: 😶 :no_mouth:
😇 :innocent: 👽 :alien: 💛 :yellow_heart:
💙 :blue_heart: 💜 :purple_heart: ❤️ :heart:
💚 :green_heart: 💔 :broken_heart: 💓 :heartbeat:
💗 :heartpulse: 💕 :two_hearts: 💞 :revolving_hearts:
💘 :cupid: 💖 :sparkling_heart: :sparkles:
⭐️ :star: 🌟 :star2: 💫 :dizzy:
💥 :boom: 💥 :collision: 💢 :anger:
❗️ :exclamation: :question: :grey_exclamation:
:grey_question: 💤 :zzz: 💨 :dash:
💦 :sweat_drops: 🎶 :notes: 🎵 :musical_note:
🔥 :fire: 💩 :hankey: 💩 :poop:
💩 :shit: 👍 :+1: 👍 :thumbsup:
👎 :-1: 👎 :thumbsdown: 👌 :ok_hand:
👊 :punch: 👊 :facepunch: :fist:
✌️ :v: 👋 :wave: :hand:
:raised_hand: 👐 :open_hands: ☝️ :point_up:
👇 :point_down: 👈 :point_left: 👉 :point_right:
🙌 :raised_hands: 🙏 :pray: 👆 :point_up_2:
👏 :clap: 💪 :muscle: 🤘 :metal:
🖕 :fu: 🚶 :walking: 🏃 :runner:
🏃 :running: 👫 :couple: 👪 :family:
👬 :two_men_holding_hands: 👭 :two_women_holding_hands: 💃 :dancer:
👯 :dancers: 🙆 :ok_woman: 🙅 :no_good:
💁 :information_desk_person: 🙋 :raising_hand: 👰 :bride_with_veil:
🙎 :person_with_pouting_face: 🙍 :person_frowning: 🙇 :bow:
:couplekiss: :couplekiss: 💑 :couple_with_heart: 💆 :massage:
💇 :haircut: 💅 :nail_care: 👦 :boy:
👧 :girl: 👩 :woman: 👨 :man:
👶 :baby: 👵 :older_woman: 👴 :older_man:
👱 :person_with_blond_hair: 👲 :man_with_gua_pi_mao: 👳 :man_with_turban:
👷 :construction_worker: 👮 :cop: 👼 :angel:
👸 :princess: 😺 :smiley_cat: 😸 :smile_cat:
😻 :heart_eyes_cat: 😽 :kissing_cat: 😼 :smirk_cat:
🙀 :scream_cat: 😿 :crying_cat_face: 😹 :joy_cat:
😾 :pouting_cat: 👹 :japanese_ogre: 👺 :japanese_goblin:
🙈 :see_no_evil: 🙉 :hear_no_evil: 🙊 :speak_no_evil:
💂 :guardsman: 💀 :skull: 🐾 :feet:
👄 :lips: 💋 :kiss: 💧 :droplet:
👂 :ear: 👀 :eyes: 👃 :nose:
👅 :tongue: 💌 :love_letter: 👤 :bust_in_silhouette:
👥 :busts_in_silhouette: 💬 :speech_balloon: 💭 :thought_balloon:
:feelsgood: :feelsgood: :finnadie: :finnadie: :goberserk: :goberserk:
:godmode: :godmode: :hurtrealbad: :hurtrealbad: :rage1: :rage1:
:rage2: :rage2: :rage3: :rage3: :rage4: :rage4:
:suspect: :suspect: :trollface: :trollface:  

[Nature]

☀️ :sunny: ☔️ :umbrella: ☁️ :cloud: ❄️ :snowflake: ⛄️ :snowman: ⚡️ :zap: 🌀 :cyclone: 🌁 :foggy: 🌊 :ocean: 🐱 :cat: 🐶 :dog: 🐭 :mouse: 🐹 :hamster: 🐰 :rabbit: 🐺 :wolf: 🐸 :frog: 🐯 :tiger: 🐨 :koala: 🐻 :bear: 🐷 :pig: 🐽 :pig_nose: 🐮 :cow: 🐗 :boar: 🐵 :monkey_face: 🐒 :monkey: 🐴 :horse: 🐎 :racehorse: 🐫 :camel: 🐑 :sheep: 🐘 :elephant: 🐼 :panda_face: 🐍 :snake: 🐦 :bird: 🐤 :baby_chick: 🐥 :hatched_chick: 🐣 :hatching_chick: 🐔 :chicken: 🐧 :penguin: 🐢 :turtle: 🐛 :bug: 🐝 :honeybee: 🐜 :ant: 🐞 :beetle: 🐌 :snail: 🐙 :octopus: 🐠 :tropical_fish: 🐟 :fish: 🐳 :whale: 🐋 :whale2: 🐬 :dolphin: 🐄 :cow2: 🐏 :ram: 🐀 :rat: 🐃 :water_buffalo: 🐅 :tiger2: 🐇 :rabbit2: 🐉 :dragon: 🐐 :goat: 🐓 :rooster: 🐕 :dog2: 🐖 :pig2: 🐁 :mouse2: 🐂 :ox: 🐲 :dragon_face: 🐡 :blowfish: 🐊 :crocodile: 🐪 :dromedary_camel: 🐆 :leopard: 🐈 :cat2: 🐩 :poodle: 🐾 :paw_prints: 💐 :bouquet: 🌸 :cherry_blossom: 🌷 :tulip: 🍀 :four_leaf_clover: 🌹 :rose: 🌻 :sunflower: 🌺 :hibiscus: 🍁 :maple_leaf: 🍃 :leaves: 🍂 :fallen_leaf: 🌿 :herb: 🍄 :mushroom: 🌵 :cactus: 🌴 :palm_tree: 🌲 :evergreen_tree: 🌳 :deciduous_tree: 🌰 :chestnut: 🌱 :seedling: 🌼 :blossom: 🌾 :ear_of_rice: 🐚 :shell: 🌐 :globe_with_meridians: 🌞 :sun_with_face: 🌝 :full_moon_with_face: 🌚 :new_moon_with_face: 🌑 :new_moon: 🌒 :waxing_crescent_moon: 🌓 :first_quarter_moon: 🌔 :waxing_gibbous_moon: 🌕 :full_moon: 🌖 :waning_gibbous_moon: 🌗 :last_quarter_moon: 🌘 :waning_crescent_moon: 🌜 :last_quarter_moon_with_face: 🌛 :first_quarter_moon_with_face: 🌔 :moon: 🌍 :earth_africa: 🌎 :earth_americas: 🌏 :earth_asia: 🌋 :volcano: 🌌 :milky_way: ⛅️ :partly_sunny: :octocat: :octocat: :shipit: :squirrel:

[Objects]

🎍 :bamboo: 💝 :gift_heart: 🎎 :dolls: 🎒 :school_satchel: 🎓 :mortar_board: 🎏 :flags: 🎆 :fireworks: 🎇 :sparkler: 🎐 :wind_chime: 🎑 :rice_scene: 🎃 :jack_o_lantern: 👻 :ghost: 🎅 :santa: 🎄 :christmas_tree: 🎁 :gift: 🔔 :bell: 🔕 :no_bell: 🎋 :tanabata_tree: 🎉 :tada: 🎊 :confetti_ball: 🎈 :balloon: 🔮 :crystal_ball: 💿 :cd: 📀 :dvd: 💾 :floppy_disk: 📷 :camera: 📹 :video_camera: 🎥 :movie_camera: 💻 :computer: 📺 :tv: 📱 :iphone: ☎️ :phone: ☎️ :telephone: 📞 :telephone_receiver: 📟 :pager: 📠 :fax: 💽 :minidisc: 📼 :vhs: 🔉 :sound: 🔈 :speaker: 🔇 :mute: 📢 :loudspeaker: 📣 :mega: ⌛️ :hourglass::hourglass_flowing_sand::alarm_clock: ⌚️ :watch: 📻 :radio: 📡 :satellite::loop: 🔍 :mag: 🔎 :mag_right: 🔓 :unlock: 🔒 :lock: 🔏 :lock_with_ink_pen: 🔐 :closed_lock_with_key: 🔑 :key: 💡 :bulb: 🔦 :flashlight: 🔆 :high_brightness: 🔅 :low_brightness: 🔌 :electric_plug: 🔋 :battery: 📲 :calling: ✉️ :email: 📫 :mailbox: 📮 :postbox: 🛀 :bath: 🛁 :bathtub: 🚿 :shower: 🚽 :toilet: 🔧 :wrench: 🔩 :nut_and_bolt: 🔨 :hammer: 💺 :seat: 💰 :moneybag: 💴 :yen: 💵 :dollar: 💷 :pound: 💶 :euro: 💳 :credit_card: 💸 :money_with_wings: 📧 :e-mail: 📥 :inbox_tray: 📤 :outbox_tray: ✉️ :envelope: 📨 :incoming_envelope: 📯 :postal_horn: 📪 :mailbox_closed: 📬 :mailbox_with_mail: 📭 :mailbox_with_no_mail: 🚪 :door: 🚬 :smoking: 💣 :bomb: 🔫 :gun: 🔪 :hocho: 💊 :pill: 💉 :syringe: 📄 :page_facing_up: 📃 :page_with_curl: 📑 :bookmark_tabs: 📊 :bar_chart: 📈 :chart_with_upwards_trend: 📉 :chart_with_downwards_trend: 📜 :scroll: 📋 :clipboard: 📆 :calendar: 📅 :date: 📇 :card_index: 📁 :file_folder: 📂 :open_file_folder: ✂️ :scissors: 📌 :pushpin: 📎 :paperclip: ✒️ :black_nib: ✏️ :pencil2: 📏 :straight_ruler: 📐 :triangular_ruler: 📕 :closed_book: 📗 :green_book: 📘 :blue_book: 📙 :orange_book: 📓 :notebook: 📔 :notebook_with_decorative_cover: 📒 :ledger: 📚 :books: 🔖 :bookmark: 📛 :name_badge: 🔬 :microscope: 🔭 :telescope: 📰 :newspaper: 🏈 :football: 🏀 :basketball: ⚽️ :soccer: ⚾️ :baseball: 🎾 :tennis: 🎱 :8ball: 🏉 :rugby_football: 🎳 :bowling: ⛳️ :golf: 🚵 :mountain_bicyclist: 🚴 :bicyclist: 🏇 :horse_racing: 🏂 :snowboarder: 🏊 :swimmer: 🏄 :surfer: 🎿 :ski: ♠️ :spades: ♥️ :hearts: ♣️ :clubs: ♦️ :diamonds: 💎 :gem: 💍 :ring: 🏆 :trophy: 🎼 :musical_score: 🎹 :musical_keyboard: 🎻 :violin: 👾 :space_invader: 🎮 :video_game: 🃏 :black_joker: 🎴 :flower_playing_cards: 🎲 :game_die: 🎯 :dart: 🀄️ :mahjong: 🎬 :clapper: 📝 :memo: 📝 :pencil: 📖 :book: 🎨 :art: 🎤 :microphone: 🎧 :headphones: 🎺 :trumpet: 🎷 :saxophone: 🎸 :guitar: 👞 :shoe: 👡 :sandal: 👠 :high_heel: 💄 :lipstick: 👢 :boot: 👕 :shirt: 👕 :tshirt: 👔 :necktie: 👚 :womans_clothes: 👗 :dress: 🎽 :running_shirt_with_sash: 👖 :jeans: 👘 :kimono: 👙 :bikini: 🎀 :ribbon: 🎩 :tophat: 👑 :crown: 👒 :womans_hat: 👞 :mans_shoe: 🌂 :closed_umbrella: 💼 :briefcase: 👜 :handbag: 👝 :pouch: 👛 :purse: 👓 :eyeglasses: 🎣 :fishing_pole_and_fish: ☕️ :coffee: 🍵 :tea: 🍶 :sake: 🍼 :baby_bottle: 🍺 :beer: 🍻 :beers: 🍸 :cocktail: 🍹 :tropical_drink: 🍷 :wine_glass: 🍴 :fork_and_knife: 🍕 :pizza: 🍔 :hamburger: 🍟 :fries: 🍗 :poultry_leg: 🍖 :meat_on_bone: 🍝 :spaghetti: 🍛 :curry: 🍤 :fried_shrimp: 🍱 :bento: 🍣 :sushi: 🍥 :fish_cake: 🍙 :rice_ball: 🍘 :rice_cracker: 🍚 :rice: 🍜 :ramen: 🍲 :stew: 🍢 :oden: 🍡 :dango: 🥚 :egg: 🍞 :bread: 🍩 :doughnut: 🍮 :custard: 🍦 :icecream: 🍨 :ice_cream: 🍧 :shaved_ice: 🎂 :birthday: 🍰 :cake: 🍪 :cookie: 🍫 :chocolate_bar: 🍬 :candy: 🍭 :lollipop: 🍯 :honey_pot: 🍎 :apple: 🍏 :green_apple: 🍊 :tangerine: 🍋 :lemon: 🍒 :cherries: 🍇 :grapes: 🍉 :watermelon: 🍓 :strawberry: 🍑 :peach: 🍈 :melon: 🍌 :banana: 🍐 :pear: 🍍 :pineapple: 🍠 :sweet_potato: 🍆 :eggplant: 🍅 :tomato: 🌽 :corn:

[Places]

🏠 :house: 🏡 :house_with_garden: 🏫 :school: 🏢 :office: 🏣 :post_office: 🏥 :hospital: 🏦 :bank: 🏪 :convenience_store: 🏩 :love_hotel: 🏨 :hotel: 💒 :wedding: ⛪️ :church: 🏬 :department_store: 🏤 :european_post_office: 🌇 :city_sunrise: 🌆 :city_sunset: 🏯 :japanese_castle: 🏰 :european_castle: ⛺️ :tent: 🏭 :factory: 🗼 :tokyo_tower: 🗾 :japan: 🗻 :mount_fuji: 🌄 :sunrise_over_mountains: 🌅 :sunrise: 🌠 :stars: 🗽 :statue_of_liberty: 🌉 :bridge_at_night: 🎠 :carousel_horse: 🌈 :rainbow: 🎡 :ferris_wheel: ⛲️ :fountain: 🎢 :roller_coaster: 🚢 :ship: 🚤 :speedboat: ⛵️ :boat: ⛵️ :sailboat: 🚣 :rowboat: ⚓️ :anchor: 🚀 :rocket: ✈️ :airplane: 🚁 :helicopter: 🚂 :steam_locomotive: 🚊 :tram: 🚞 :mountain_railway: 🚲 :bike: 🚡 :aerial_tramway: 🚟 :suspension_railway: 🚠 :mountain_cableway: 🚜 :tractor: 🚙 :blue_car: 🚘 :oncoming_automobile: 🚗 :car: 🚗 :red_car: 🚕 :taxi: 🚖 :oncoming_taxi: 🚛 :articulated_lorry: 🚌 :bus: 🚍 :oncoming_bus: 🚨 :rotating_light: 🚓 :police_car: 🚔 :oncoming_police_car: 🚒 :fire_engine: 🚑 :ambulance: 🚐 :minibus: 🚚 :truck: 🚋 :train: 🚉 :station: 🚆 :train2: 🚅 :bullettrain_front: 🚄 :bullettrain_side: 🚈 :light_rail: 🚝 :monorail: 🚃 :railway_car: 🚎 :trolleybus: 🎫 :ticket: ⛽️ :fuelpump: 🚦 :vertical_traffic_light: 🚥 :traffic_light: ⚠️ :warning: 🚧 :construction: 🔰 :beginner: 🏧 :atm: 🎰 :slot_machine: 🚏 :busstop: 💈 :barber: ♨️ :hotsprings: 🏁 :checkered_flag: 🎌 :crossed_flags: 🏮 :izakaya_lantern: 🗿 :moyai: 🎪 :circus_tent: 🎭 :performing_arts: 📍 :round_pushpin: 🚩 :triangular_flag_on_post: 🇯🇵 :jp: 🇰🇷 :kr: 🇨🇳 :cn: 🇺🇸 :us: 🇫🇷 :fr: 🇪🇸 :es: 🇮🇹 :it: 🇷🇺 :ru: 🇬🇧 :gb: 🇬🇧 :uk: 🇩🇪 :de:

[Symbols]

1️⃣ :one: 2️⃣ :two: 3️⃣ :three: 4️⃣ :four: 5️⃣ :five: 6️⃣ :six: 7️⃣ :seven: 8️⃣ :eight: 9️⃣ :nine: 🔟 :keycap_ten: 🔢 :1234: 0️⃣ :zero: #️⃣ :hash: 🔣 :symbols: ◀️ :arrow_backward: ⬇️ :arrow_down: ▶️ :arrow_forward: ⬅️ :arrow_left: 🔠 :capital_abcd: 🔡 :abcd: 🔤 :abc: ↙️ :arrow_lower_left: ↘️ :arrow_lower_right: ➡️ :arrow_right: ⬆️ :arrow_up: ↖️ :arrow_upper_left: ↗️ :arrow_upper_right::arrow_double_down::arrow_double_up: 🔽 :arrow_down_small: ⤵️ :arrow_heading_down: ⤴️ :arrow_heading_up: ↩️ :leftwards_arrow_with_hook: ↪️ :arrow_right_hook: ↔️ :left_right_arrow: ↕️ :arrow_up_down: 🔼 :arrow_up_small: 🔃 :arrows_clockwise: 🔄 :arrows_counterclockwise::rewind::fast_forward: ℹ️ :information_source: 🆗 :ok: 🔀 :twisted_rightwards_arrows: 🔁 :repeat: 🔂 :repeat_one: 🆕 :new: 🔝 :top: 🆙 :up: 🆒 :cool: 🆓 :free: 🆖 :ng: 🎦 :cinema: 🈁 :koko: 📶 :signal_strength: 🈹 :u5272: 🈴 :u5408: 🈺 :u55b6: 🈯️ :u6307: 🈷️ :u6708: 🈶 :u6709: 🈵 :u6e80: 🈚️ :u7121: 🈸 :u7533: 🈳 :u7a7a: 🈲 :u7981: 🈂️ :sa: 🚻 :restroom: 🚹 :mens: 🚺 :womens: 🚼 :baby_symbol: 🚭 :no_smoking: 🅿️ :parking: ♿️ :wheelchair: 🚇 :metro: 🛄 :baggage_claim: 🉑 :accept: 🚾 :wc: 🚰 :potable_water: 🚮 :put_litter_in_its_place: ㊙️ :secret: ㊗️ :congratulations: Ⓜ️ :m: 🛂 :passport_control: 🛅 :left_luggage: 🛃 :customs: 🉐 :ideograph_advantage: 🆑 :cl: 🆘 :sos: 🆔 :id: 🚫 :no_entry_sign: 🔞 :underage: 📵 :no_mobile_phones: 🚯 :do_not_litter: 🚱 :non-potable_water: 🚳 :no_bicycles: 🚷 :no_pedestrians: 🚸 :children_crossing: ⛔️ :no_entry: ✳️ :eight_spoked_asterisk: ✴️ :eight_pointed_black_star: 💟 :heart_decoration: 🆚 :vs: 📳 :vibration_mode: 📴 :mobile_phone_off: 💹 :chart: 💱 :currency_exchange: ♈️ :aries: ♉️ :taurus: ♊️ :gemini: ♋️ :cancer: ♌️ :leo: ♍️ :virgo: ♎️ :libra: ♏️ :scorpius: ♐️ :sagittarius: ♑️ :capricorn: ♒️ :aquarius: ♓️ :pisces::ophiuchus: 🔯 :six_pointed_star::negative_squared_cross_mark: 🅰️ :a: 🅱️ :b: 🆎 :ab: 🅾️ :o2: 💠 :diamond_shape_with_a_dot_inside: ♻️ :recycle: 🔚 :end: 🔛 :on: 🔜 :soon: 🕐 :clock1: 🕜 :clock130: 🕙 :clock10: 🕥 :clock1030: 🕚 :clock11: 🕦 :clock1130: 🕛 :clock12: 🕧 :clock1230: 🕑 :clock2: 🕝 :clock230: 🕒 :clock3: 🕞 :clock330: 🕓 :clock4: 🕟 :clock430: 🕔 :clock5: 🕠 :clock530: 🕕 :clock6: 🕡 :clock630: 🕖 :clock7: 🕢 :clock730: 🕗 :clock8: 🕣 :clock830: 🕘 :clock9: 🕤 :clock930: 💲 :heavy_dollar_sign: ©️ :copyright: ®️ :registered: ™️ :tm::x: ❗️ :heavy_exclamation_mark: ‼️ :bangbang: ⁉️ :interrobang: ⭕️ :o: ✖️ :heavy_multiplication_x::heavy_plus_sign::heavy_minus_sign::heavy_division_sign: 💮 :white_flower: 💯 :100: ✔️ :heavy_check_mark: ☑️ :ballot_box_with_check: 🔘 :radio_button: 🔗 :link::curly_loop: 〰️ :wavy_dash: 〽️ :part_alternation_mark: 🔱 :trident::white_check_mark: 🔲 :black_square_button: 🔳 :white_square_button: ⚫️ :black_circle: ⚪️ :white_circle: 🔴 :red_circle: 🔵 :large_blue_circle: 🔷 :large_blue_diamond: 🔶 :large_orange_diamond: 🔹 :small_blue_diamond: 🔸 :small_orange_diamond: 🔺 :small_red_triangle: 🔻 :small_red_triangle_down: :shipit: :shipit:

출처: https://inpa.tistory.com/464 [👨‍💻 Dev Scroll]

Flutter?

Flutter를 공부하고 간단한 프로그램 개발을 통해 익혀본다

Flutter란 생소한 언어의 출현과 동시에 정말 흥미가 생겼다.
네이티브 앱에 근접한 속도를 내면서 하나의 코드로 Android, iOS를 동시에 개발할 수 있다니!
때문에 배워보면서, 개발해보면서 시대의 흐름을 따라가보려고 한다.

작성되는 글

이 디렉토리에는 플러터에 대해서 공부하고 내용을 정리하면서 내가 이해한 내용을 작성하는 저장소이다.

플러터는 다트를 기반으로 하는 애플리케이션 개발 언어이다. 여러 장점을 가지고 있으며 무엇보다도 안드로이드와 애플을 구분없이 개발 할 수 있다는 점에서 내 관심을 끌었다.

또한 네이티브 앱 못지 않은 빠른 속도를 자랑한다니 이런 어썸한 언어에 관심이 가는게 당연하지 않겠는가?

때문에 이 카테고리는 플러터를 배우며, 개발하며 익힌 지식이나 정리할 내용들을 작성하는 저장소이다.

지금 구상하고 있는 앱이 두 가지 정도 있는데, 이것을 실용화 할 수 있을 정도의 수준까지 더 노력해보려 한다.


플러터의 등장 배경

2007년 애플의 아이폰이 등장한 이후 스마트폰은 끊임없이 발전하고 있습니다. 애플은 iOS 운영체제를 발표하면서 오브젝트브-C(Objective-C)로 만든 아이폰용 앱을 출시할 수 있는 앱스토어를 만들었습니다. 그로부터 1년 뒤 구글도 안드로이드 운영체제를 발표하면서 자바(Java)를 사용해 안드로이드용 앱을 판매할 수 있는 구글 플레이를 만들었습니다.

다양한 기업이 잇따라 이 싸움에 뛰어들었지만 10년이 훨씬 지난 지금도 안드로이드와 iOS의 점유율이 거의 100%라고 해도 과언이 아닐 정도로 두 플랫폼이 모바일 시장을 장악하고 있습니다. image

애플과 구글은 각자의 앱 개발 생태계를 확장하려는 목적으로 새로운 언어를 내놓았습니다. 애플은 오브젝티브-C 대신에 스위프트(Swift)라는 언어를 만들었고, 구글은 코틀린(Kotlin)이라는 새로운 언어로 자바를 대체하려고 합니다. 새롭게 등장한 두 언어는 지금도 계속 진화하면서 앱 개발 환경을 더 나은 방향으로 이끌고 있습니다. 오브젝티브-C나 스위프트로 iOS 앱을 개발하거나 자바나 코틀린으로 안드로이드 앱을 개발하는 것처럼 각 모바일 운영체제에 맞는 언어로 개발한 앱을 네이티브 앱(native app)이라고 합니다.

웹앱, 하이브리드 앱의 등장

두 회사가 시장을 나눠 먹는 사이 개발자들은 더 많은 사용자가 앱을 사용하게 하려고 똑같은 앱을 iOS용과 안드로이드용으로 두 번 개발해야 했습니다. 개발자에게는 조금 피곤한 상황이지 않을 수 없었죠. 그래서 “하나의 소스로 안드로이드와 iOS 모두에서 실행할 수 있는 방법이 없을까?” 를 고민하게 되었고, 그 결과로 웹앱과 하이브리드 앱이 등장했습니다.

웹앱 (web apps)은 웹 기술을 이용해서 만든 앱입니다. 앱의 화면을 나타내는 뷰(view)를 모바일용 웹으로 만들어서 다양한 기종과 해상도에 대응하며 빠르게 개발할 수 있습니다. 요즘은 네이티브 앱처럼 알림도 보내고 오프라인에서도 동작하는 프로그레시브 웹앱 (progressive web apps, PWA)도 주목받고 있습니다.
그리고 하이브리드 앱 (hybride apps)은 웹앱을 만든 후 별도의 프레임워크를 이용해 운영체제별로 동작하는 앱을 만드는 기술입니다.

하지만 이러한 기술로 만든 앱은 네이티브 앱과 비교해 상대적으로 속도가 느리고 애니메이션 사용에도 제약이 있는 등 스마트폰의 성능을 충분히 활용할 수 없었습니다. 이때 리액트 네이티브가 등장했습니다.

리액트 네이티브와 플러터

페이스북에서 만든 리액트 네이티브 (React Native)는 여러 운영체제에서 동작하는 앱을 개발 할 수 있는 크로스 플랫폼 앱 개발 프레임워크입니다. 특히 웹 개발자에게 익숙한 자바스크립트를 사용하므로 웹 개발자가 새로운 언어를 배우지 않고서도 앱을 개발할 수 있는 길을 터주었습니다. 또한 네이티브 언어로 개발할 때는 사용자 인터페이스(UI, user interface)를 변경할 때마다 다시 빌드해야 했지만, 리액트 네이티브는 코드를 변경하면 화면에 바로 표시되므로 개발 효율도 좋습니다.

리액트 네이티브에서는 자바스크립트가 다리 역할을 하면서 안드로이드나 iOS의 네이티브 API에 접근합니다. 똑같은 자바스크립트 코드가 안드로이드와 iOS처럼 각기 다른 운영체제에서 실행되게 연결해 주는 거죠. 따라서 웹앱이나 하이브리드 앱보다는 속도가 빠르지만 화면에 표시할 내용이 많으면 느려질 수 있습니다. 그리고 운영체제가 업데이트되면 디자인이 의도한 바와 달라질 수 있습니다.

반면에 플러터 (Flutter)는 똑같이 크로스 플랫폼 앱 개발 프레임워크지만 구글에서 만든 다트(Dart)라는 언어를 사용합니다. 따라서 자바나 C# 같은 컴파일 언어가 가진 특징을 활용해 앱을 개발할 수 있습니다.

플러터는 크게 프레임워크와 엔진, 임베더 계층으로 구성되어 있습니다. 프레임워크 계층에는 다트 언어로 개발된 여러 가지 클래스가 있으며 이러한 클래스를 이용해 앱을 개발합니다. 그 다트 언어로 개발된 여러 가지 클래스가 있으며 이러한 클래스를 이용해 앱을 개발합니다. 그리고 엔진 계층은 플러터의 코어를 담당하는데 대부분 C와 C++ 언어로 만들어졌습니다. 여기서는 데이터 통신, 다트 컴파일, 렌더링 그리고 시스템 이벤트 등을 처리합니다.

image

마지막으로 임베더 계층에는 플러터 앱이 크로스 플랫폼에서 동작하도록 플러터 엔진이 렌더링한 결과를 플랫폼별 네이티브 언어로 뷰를 만들어 화면에 보여줍니다. 안드로이드 앱은 자바와 C, C++ 언어로 만들고, iOS 앱은 오브젝티브-C와 오브젝티브-C++ 언어로 만듭니다. 그리고 리눅스와 윈도우 앱은 C++ 언어로 만듭니다. 따라서 다트 언어로 소스 파일만 작성하면 플러터의 각 계층을 거쳐 플랫폼별 앱을 개발할 수 있습니다. 이 가운데 내부적인 처리는 신경 쓰지 않고서 플러터나 다트 언어를 업데이트만 하면 됩니다.

구분 리액트 네이티브 플러터
개발 주체 페이스북 구글
언어 자바스크립트 다트
출시 2015년도 2017년도
성능 빠르지만 네이티브 앱큼은 아님 네이티브 앱에 근접한 속도
학습 곡선 높음(네이티브 앱 개발자 기준) 낮음(네이티브 앱 개발자 기준)
대표 앱 페이스북, 인스타그램, 핀터레스트 등 알리바바, 구글 애드센스, 리플렉틀리 등
장점 - 저변이 넓은 자바스크립트 생태계 - 다양한 위젯
  - 웹 개발자의 접근성 - 강력한 애니메이션 기능
  - npm으로 많은 패키지 이용 가능 - 블루투스 등 네이티브 하드웨어와의 연결성
단점 - 기본 위젯이 부족해 커스텀해서 사용 - 플러터 SDK로 앱 크기가 큼(네이티브 대비)
  - 안드로이드/iOS 네이티브 위젯을 이용하기에 OS 판올림에 따른 업데이트 필요 - 아직 개발 생태계가 성숙하지 않아 빠른 피드백 얻기가 어려움
  - 블루투스 등 네이티브 커스텀해 통신하는 부분 개발이 어려움 - 업데이트 주가가 빠름(분기별)
최종 목표 자바스크립트로 웹, 앱, 데스크톱 모든 플랫폼을 개발할 수 있는 통합 솔루션 개발 안드로이드, iOS, 웹, 윈도우 10 앱을 같은 코드로 개발할 수 있는 플랫폼 개발

플러터가 주목받는 이유

플러터를 상징하는 대표적인 특징 3가지!

하나, 높은 개발 효율

플러터를 이용하면 안드로이드와 iOS 앱을 동시에 개발할 수 있어서 효율적입니다. 이렇게 개발된 앱은 어떤 운영체제에서도 똑같은 사용자 인터페이스와 사용자 경험(UX, user experience)을 제공합니다. 또한, 플러터의 핫 리로드 (hot reload) 기능은 소스 수정 후 번거로운 빌드 과정 없이 결과 화면에 바로 표시해 주므로 개발 시간을 줄일 수 있습니다.

둘, 유연한 사용자 인터페이스

역동적이면서도 유연한 사용자 인터페이스는 플러터의 큰 장점입니다. 다양한 위젯 (widget)을 제공하므로 사용자 맞춤형 앱을 쉽게 만들 수 있습니다. 만약 원하는 위젯이 없으면 선과 도형으로 직접 그려서 만들 수도 있습니다.
또한, 강력한 애니메이션 기능을 제공하여 복잡한 계산식 없이 적은 노력으로 만족스러운 사용자 경험을 줄 수 있습니다. 플로터의 위젯을 활용하면 iOS에서 구글의 머터리얼 디자인이 적용된 앱을 만들거나 반대로 안드로이드에서 iOS스타일 앱을 개발할 수도 있습니다.

플러터에서는 iOS 스타일의 위젯을 쿠퍼티노(cupertino)라고 부릅니다.

셋, 빠른 속도

플러터는 전체 화면을 그릴 때 스키아 (skia) 엔진을 이용합니다. 예를 들어 배경은 노란색으로 하고 그 위에 아이콘을 그린다고 했을 때, 플러터는 노란색을 전체 화면에 칠한 다음 아이콘을 그리는 두 번의 작업을 한 번에 해서 초당 60프레임 이상의 속도로 화면을 갱신합니다.
이처럼 빠르고 자연스러운 화면 전환 덕분에 네이티브 앱과 속도 차이를 거의 느낄 수 없습니다.

스키아는 C++로 개발된 오픈소스 2D 그래픽 엔진으로 플러터뿐만 아니라 크롬, 안드로이드, 파이어폭스, 리브레오피스 등 다양한 플랫폼과 제품에서 사용되고 있습니다.


플러터 갤러리

flutterGallery


플러터의 주목도가 최근 로켓성장하는 이유가 다 있는것 같다.
그래서 Dart 언어를 익히면서 플러터 공부를 해보려고 한다.

Flutter 개발 환경

Flutter를 개발하기 위한 안드로이드 스튜디오 프로젝트 생성

플러터 개발을 위해 Android Studio를 설치하고 개발 프로젝트를 만들어서 emulator를 실행시켰다.
image

생소하다면 생소한 디렉토리 구성과 환경을 구축해보고 나니 이제부터 시작이겠구나 막막하면서도 두근거림이 있다.

[BOJ] 2302.극장 좌석

백준 알고리즘 문제 풀이

해당 문제는 DP 알고리즘 기법을 활용하여 풀어보았습니다.

링크 : image


1. 문제

문제 보기
시간 제한 메모리 제한
2 초 128 MB

어떤 극장의 좌석은 한 줄로 되어 있으며 왼쪽부터 차례대로 1번부터 N번까지 번호가 매겨져 있다. 공연을 보러 온 사람들은 자기의 입장권에 표시되어 있는 좌석에 앉아야 한다. 예를 들어서, 입장권에 5번이 쓰여 있으면 5번 좌석에 앉아야 한다. 단, 자기의 바로 왼쪽 좌석 또는 바로 오른쪽 좌석으로는 자리를 옮길 수 있다. 예를 들어서, 7번 입장권을 가진 사람은 7번 좌석은 물론이고, 6번 좌석이나 8번 좌석에도 앉을 수 있다. 그러나 5번 좌석이나 9번 좌석에는 앉을 수 없다.

그런데 이 극장에는 “VIP 회원”들이 있다. 이 사람들은 반드시 자기 좌석에만 앉아야 하며 옆 좌석으로 자리를 옮길 수 없다.

오늘 공연은 입장권이 매진되어 1번 좌석부터 N번 좌석까지 모든 좌석이 다 팔렸다. VIP 회원들의 좌석 번호들이 주어졌을 때, 사람들이 좌석에 앉는 서로 다른 방법의 가짓수를 구하는 프로그램을 작성하시오.

예를 들어서, 그림과 같이 좌석이 9개이고, 4번 좌석과 7번 좌석이 VIP석인 경우에 <123456789>는 물론 가능한 배치이다. 또한 <213465789> 와 <132465798> 도 가능한 배치이다. 그러나 <312456789> 와 <123546789> 는 허용되지 않는 배치 방법이다.

image

입력(Input)

첫째 줄에는 좌석의 개수 N이 입력된다. N은 1 이상 40 이하이다. 둘째 줄에는 고정석의 개수 M이 입력된다. M은 0 이상 N 이하이다. 다음 M 개의 줄에는 고정석의 번호가 작은 수부터 큰 수의 순서로 한 줄에 하나씩 입력된다.

출력(Output)

주어진 조건을 만족하면서 사람들이 좌석에 앉을 수 있는 방법의 가짓수를 출력한다. 방법의 가짓수는 2,000,000,000을 넘지 않는다. (2,000,000,000 < 231-1)


2. 문제 풀이

이 문제를 DP 방식으로 풀기 위해서 점화식을 먼저 세워 보기로 했다.

좌석이 1개인 경우
 -> [1]
좌석이 2개인 경우
 -> [1][2]
 -> [2][1]
좌석이 3개인 경우
 -> [1][2][3]
 -> [1][3][2]
 -> [2][1][3]
좌석이 4개인 경우
 -> [1][2][3][4]
 -> [1][2][4][3]
 -> [1][3][2][4]
 -> [2][1][3][4]
 -> [2][1][4][3]
 -> [1][3][2][4]

좌석이 5개인 경우부터는 경우의 수가 많아지니 노가다로 쉽게 얻을 수 있는 여기까지만 알아보면,
수열의 합이 [1,2,3,5]를 얻을 수 있다.
따라서 dp[i] = dp[i - 1] + dp[i - 2]; 의 형태로 보인다.

이렇게 연속한 좌석 수에 따른 경우의 수를 알았으므로 VIP석으로 인해서 구분된 좌석의 연속 수를 저장해서 해당하는 모든 DP 값을 곱해주면 구하고자 하는 모든 경우의 수를 구할 수 있다.

여기서 5개인 경우의 수를 구할 때, DP를 구하는 방식처럼 쪼개서 생각해보면
아래는 좌석 5개를 4개 1개로 나누어 생각해 본 경우이다.

[1][2][3][4] [5]
DP[5] = DP[4] * DP[1] = 5 * 1 = 5

위의 경우처럼 쪼개서 구한 경우의 수와 중복되지 않을 수 있는 경우는 무엇이 있을지 생각해보자.

마지막 5번 좌석이 바로 앞으로 가는 경우를 생각해 볼 수 있다.

[1][2][3] [5][4]
뒤쪽의 2개의 좌석은 5 4 로 고정이다.
5가 맨 마지막에 오는 경우는 앞의 DP[5] = DP[4] * DP[1] 에서 구했다.

*DP[3] * DP[2]가 아님에 주의!!*

그럼 이것을 활용하여 코드로 표현해보자.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Theater_2302 {
    public static int seat[];
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());
        int M = Integer.parseInt(br.readLine());
        
        seat = new int[N + 1];

        seat[0] = 1;
        seat[1] = 1;
        seat[2] = 2;
        for(int i = 3; i <= N; i++) {
            seat[i] = seat[i - 1] + seat[i - 2];
        }
        
        int result = 1, prev = 0;
        for(int i = 0; i < M; i++) {
            int temp = Integer.parseInt(br.readLine());
            result *= seat[temp - prev - 1];
            prev = temp;
        }
        result *= seat[N - prev];

        System.out.println(result);
    }
}

JAVA의 메모리? JAVA의 메모리 구조에 대한 이해

JAVA 메모리 구조와 GC 등에 대한 이해

  • JAVA의 메모리 구조에 대해서 이해해보는 시간을 가져본다.

JVM이란?

JAVA의 메모리 구조에 대해서 설명하기 전에 JVM이 무엇인지 알아야 한다.
JVM은 Java Virtual Machine의 약자로, 자바 가상 머신이라고 불린다.
자바의 운영체제 사이에서 중개자 역할을 수행하며, 자바가 운영체제에 구애 받지 않고 프로그램을 실행할 수 있도록 도와주는 역할을 수행한다.
또한, GC(Garbage Collector)를 사용한 메모리 관리도 자동으로 수행하며 다른 하드웨어와 다르게 레지스터 기반이 아닌 스택 기반으로 동작한다.

image

먼저, 자바 컴파일러에 의해 자바 소스 파일은 바이트 코드로 변환된다. 그리고 이러한 바이트 코드를 JVM에서 읽어 들인 다음에 이것을 복잡한 과정을 거쳐서 어떤 운영체제든간에 프로그램을 실행할 수 있도록 만든다.

만약 자바 소스 파일은 리눅스에서 만들어졌고, 윈도우에서 이 파일을 실행하고 싶다면 윈도우용 JVM을 설치하기만 하면 되는 것이다! 여기서 JVM은 운영체제에 종송적이라는 특징을 알 수 있다.


JVM 메모리 구조

이제 JVM이 무엇인지, 자바 프로그램의 실행 단계에 대해서 간략하게 알아보았으니 JVM의 구체적인 수행 과정에 대해서 정확히 어떤 구조를 가지고 있고 어떻게 동작을 하는지 알아보자.

아래는 자바 프로그램의 실행 단계로, JVM은 크게 4가지의 구조로 나누어 볼 수 있다.

  • Garbage Collector
  • Execution Engine
  • Class Loader
  • Runtime Data Area

image

다시 한 번 언급하자면, 자바 소스 파일은 자바 컴파일러에 의해서 바이트 코드 형태인 클래스 파일이 된다. 그리고 이 클래스 파일은 클래스 로더가 읽어들이면서 JVM이 수행된다.

Garbage Collector

Garbage Collector(GC)는 힙(heap) 메모리 영역에 생성된 객체들 중에서 참조되지 않은 객체들을 탐색후 제거하는 역할을 한다. 이때, GC가 역할을 하는 시간은 언제인지 정확히 알 수 없다.

Execution Engine

클래스 로더를 통해 JVM 내의 Runtime Data Area에 배치된 바이트 코드들을 명령어 단위로 읽어서 실행한다. 최초 JVM이 나왔을 당시에는 인터프리터 방식이라고 하는데 속도가 느리다는 단점이 있었기 때문에 JIT 컴파일러 방식을 통해 이 점이 보완되었다.
JIT는 바이트 코드를 어셈블러 같은 네이티브 코드로 바꿈으로써 실행이 빠르지만 역시 변환하는데 비용이 발생했다. 이 같은 이유로 JVM은 모든 코드를 JIT 컴파일러 방식으로 실행하지 않고, 인터프리터 방식을 사용하다가 일정한 기준이 넘어가면 JIT 컴파일러 방식으로 실행한다.

Class Loader

JVM 내로 클래스 파일을 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈이다. 런타임 시에 동적으로 클래스를 로드한다.

Runtime Data Area

JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역이다.
이 영역은 크게 아래와 같이 나눌 수 있다.

  • Method Area
    • 모든 쓰레드가 공유하는 메모리 영역. 메소드 영역은 클래스, 인터페이스, 메소드, 필드, Static 변수 등의 바이트 코드를 보관한다.
  • Heap Area
    • 모든 쓰레드가 공유하며, new 키워드로 생성된 객체와 배열이 생성되는 영역이다.
    • 또한, 메소드 영역에 로드된 클래스만 생성이 가능하고 Garbage Collector가 참조되지 않는 메모리를 확인하고 제거하는 영역이다.
  • Stack Area
    • 메서드 호출 시마다 각각의 스택 프레임(그 메서드만을 위한 공간)을 생성한다.
    • 메서드 안에서 사용되는 값들을 저장하고, 호출된 메서드의 매개변수, 지역변수, 리턴 값 및 연산 시 일어나는 값들을 임시로 저장한다.
    • 메서드의 수행이 끝나면 프레임별로 삭제한다.
  • PC Register
    • 쓰레드가 시작될 때 생성되며, 생성될 때마다 생성되는 공간으로 쓰레드마다 하나씩 존재한다.
    • 쓰레드가 어떤 부분을 무슨 명령으로 실행해야할 지에 대한 기록을 하는 부분으로 현재 수행중인 JVM 명령의 주소를 갖는다.
  • Native Method Stack
    • 자바 외 언어로 작성된 네이티브 코드를 위한 메모리 영역이다.

Pagination