#include<iostream> #include<vector> #include<queue> #define endl "\n" #define MAX 50 usingnamespacestd; int N, ans = 1e9; char land[MAX + 1][MAX + 1]; bool visited[MAX + 1][MAX + 1][2]; vector<pair<int, int>> start_point; vector<pair<int, int>> end_point; int dr[5] = { 0, -1, 1, 0, 0 }; int dc[5] = { 0, 0, 0, -1, 1 }; structLog { int type; // 0 : 가로, 1 : 세로 int r, c; // 중점 좌표 }; boolisIn(int r, int c, int type){ // 통나무가 평지 범위인지 if (type == 0) c--; else r--; for (int i = 0; i < 3; ++i) { if (r < 0 || c < 0 || r > N - 1 || c > N - 1) returnfalse; if (type == 0) c++; else r++; } returntrue; } boolisEnd(int r, int c, int type){ // EEE에 도착했는지 if (type == 0) c--; else r--; for (int i = 0; i < end_point.size(); i++) { if (end_point[i].first != r || end_point[i].second != c) returnfalse; if (type == 0) c++; else r++; } returntrue; } boolCheck(int r, int c, int type){ // 이동이 가능한지 if (type == 0) c--; else r--; for (int i = 0; i < 3; ++i) { if (land[r][c] == '1') returnfalse; if (type == 0) c++; else r++; } returntrue; } boolCheckRotate(int r, int c, int type){ // 회전이 가능한지 int sr = r - 1, sc = c - 1; for (int i = sr; i < sr + 3; ++i) { for (int j = sc; j < sc+ 3; ++j) { if (land[i][j] == '1') returnfalse; } } returntrue; } voidBFS(){ queue<Log> q; int type, r, c; if (start_point[0].first == start_point[1].first) { type = 0; r = start_point[0].first; c = start_point[1].second; } else { type = 1; c = start_point[0].second; r = start_point[1].first; } q.push({ type, r, c }); visited[r][c][type] = true; int cnt = 0; while (int s = q.size()) { while (s--) { int r = q.front().r, c = q.front().c; int type = q.front().type; if (isEnd(r, c, type)) { ans = cnt; return; } q.pop(); for (int dir = 0; dir < 5; ++dir) { if (dir == 0 || dir == 1) type = (type+1) % 2; // 회전 후 타입 원래대로 int nr = r + dr[dir]; int nc = c + dc[dir]; if (!isIn(nr, nc, type)) continue; if (visited[nr][nc][type]) continue; if (dir == 0) if (!CheckRotate(nr, nc, type)) continue; // 3 x 3 확인 후 회전 if (Check(nr, nc, type)) { visited[nr][nc][type] = true; q.push({ type, nr, nc }); } } } cnt++; } }
intmain(){ cin >> N; for (int i = 0; i < N; ++i) { for (int j = 0; j < N; ++j) { cin >> land[i][j]; if (land[i][j] == 'B') start_point.push_back({ i, j }); elseif (land[i][j] == 'E') end_point.push_back({ i, j }); } } BFS(); if (ans == 1e9) ans = 0; cout << ans < endl; return0; }
이어진 수 하나가 경우의 수라고 생각한다. (문제 목표는 123456789인 수(경우)를 찾는 것)
map<해당 경우(수), 이동 횟수>를 사용하여 해당 경우에 도달하기까지 걸리는 이동 횟수를 저장한다.
9(0)이 있는 위치에서 시작하여 BFS 탐색을 하고 탐색 시에 swap을 해야 한다. (이동을 할 때 인덱스 계산에 주의한다.)
swap을 위해 string을 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12
ex) 현재 193425786 (0대신 9를 해야 각 자릿 수가 모두 채워진다. 0123...으로 하면 0이 사라짐) 193425786 -> 913425786 (왼쪽 이동) -> 123495786 (아래쪽 이동) -> 149425786 (오른쪽 이동)
3 x 3 0 1 2 3 4 5 6 7 8 행 = 9번 위치(0~8 중) / 3 열 = 9번 위치 % 3
주의
아래 코드에서 dist.count(next_num) == 0 대신 dist[next_num] == 0 을 하면 틀리다. dist[해당 수]에는 이동 횟수가 들어있고 dist.count(해당 수)는 해당 경우의 수가 몇 번 나왔는지 알려주기 때문이다. map에서 해당 키, 값을 넣어주지 않았는데 바로 해당 키에 대한 값을 참조하려고(dist[next_num] == 0) 하면 제대로 연산이 수행되지 않을 것이다.
배포를 위해 scp를 사용하여 Jenkins에 있는 프로젝트 코드를 NAVER Cloud 서버에 복사해야 한다. Node.js 기반의 프로젝트는 Tomcat과 같은 WAS(웹 서버+웹 컨테이너)가 존재하지 않아 ssh로 접속, scp로 파일을 주고 받는 작업으로 비교적 간단히 배포할 수 있다.
2-1. NAVER Cloud 서버에도 ssh 키를 생성한다.
1
ssh-keygen -t rsa
키가 생성되면 authorized_keys, id_rsa, id_rsa.pub, known_hosts 가 생성되어 있는 것을 볼 수 있다.
scp로 파일을 주고 받는다 하였는데 이때 복사 받을 서버의 접속 비밀번호를 알아야 한다. 그렇게되면 복사할 때마다 매번 비밀번호를 요구하여 자동화 배포는 불가능하게 된다.
이를 해결하기 위해 위의 4개의 키를 이용한다. Jenkins에서는 NAVER Cloud 서버를 호스트로, NAVER Cloud 서버에서는 Jenkins 서버의 공개 키를 허가받은 키로 등록한다. 이러면 비밀번호를 요구하지 않고 자동화 배포가 가능하다.
마지막으로 sudo nginx -t 명령어로 문법 이상 유무를 확인하고 이상이 없을 경우, systemctl stop nginx 명령어로 NGINX를 종료한 후에 systemctl start nginx 로 다시 시작한다.
3-5. NAVER Cloud Platform ACG 설정하기.
여기서 마지막으로 한 가지 해야할 것은 ACG 설정이다.
ACG 설정하기
NAVER Cloud Platform 콘솔에서 1024포트를 열어준다. 위에서 blue, green 모두 다른 포트지만 NGINX를 통해 1024 포트로 로드 밸런싱 되기에 사용자는 이를 통해 접근할 수 있다. (보통 백엔드와 프론트엔드를 분리하면서 프로젝트를 진행하고 있다면 프론트엔드는 80포트를 사용하는게 좋다. 80이 기본 포트라 생략 가능하므로)
이것으로 NAVER Cloud 서버에서의 설정은 끝났다.
4. 마지막 작업
이제 마지막 Jenkins에서 빌드할 때 설정을 해주어야 위에서 설정한 무중단 배포가 자동화된다.
Jenkins 프로젝트 관리에서 “Execute managed script”를 클릭한다. 다만, 아직 작성한 스크립트가 없기에 Jenkins 관리 > Managed files > Add a new Config를 통해 스크립트를 작성한다.
포인터가 가리키는 곳을 변경할 수는 없습니다. 변경이 가능했다면 myCat은 Rowlf라는 이름일텐데 Max이기 때문입니다. 여기선 new 연산자를 통해 다른 주소값을 가진 다른 인스턴스가 생성된 것이고 메소드와 같은 라이프 사이클을 갖습니다. 즉, 메소드 리턴과 동시에 사라지게 됩니다.
1 2 3 4
publicvoidfoo(Cat someCat){ someCat = new Cat("Fifi"); someCat.setName("Rowlf"); }
이게 왜 값에 의한 호출인지 아직 명확하지 않아 보입니다.
다음 예시를 보시면 명확하게 이해가 되실 겁니다.
예제2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
publicclassMain{
publicstaticvoidmain(String[] args){ Foo f = new Foo("f"); changeReference(f); // It won't change the reference! modifyReference(f); // It will modify the object that the reference variable "f" refers to! }
publicstaticvoidchangeReference(Foo a){ Foo b = new Foo("b"); a = b; }
만약 wrapper 클래스 객체가 null이라면, autoboxing은 NullPointerException에 취약할 수 밖에 없습니다.
다음 코드에서 doyun이라는 객체가 phjone number가 없다면 null을 리턴하지 않도록 해야 합니다. 그렇지 않다면 NullPointerException을 보게 될 것입니다.
1 2
Person doyun = new Person("Doyun"); int phone = doyun.getPhone();
7. 객체 생성 시 규약을 따르고 합리적인 default 값을 정의하자.
NullPointerException은 대부분 불완전한 정보나 요구되는 의존성을 모두 충족시키지 않고 객체가 생성되었을 때 발생합니다.
그렇기에 이를 피하는 방법만으로도 null을 다룰 수 있습니다. 예를 들어 Employee라는 객체는 id와 name 값이 없으면 생성될 수 없게 하고 옵션으로 phone number 값을 가질 수 있습니다. 단 여기서 phone number 값이 없다면 null을 리턴하지 않고 0과 같은 default 값을 리턴하도록 해야 합니다.
8. DB에서 null 제약 조건을 유지하자.
도메인 객체(Customers, Order와 같은)를 저장하기위해 DB를 사용하는 경우 DB 자체에서 null 제약 조건을 정의해야 합니다. (데이터의 무결성 보장 때문에)
DB에서 이런 제약 조건을 유지하는 것만으로 Java code에서 null check를 줄일 수 있습니다. 이미 DB에서 해당 필드가 null 값을 가질 수 있는지 없는지 확인하기에 Java code 내에서 불필요한 null 체크를 최소화할 수 있습니다.
// 순서대로 1. Title: 2. Title: String 3. Title: String VS 4. Title: String VS StringBuffer 5. Title: String VS StringBuffer Content: 6. Title: String VS StringBuffer Content: Difference 7. Title: String VS StringBuffer Content: Difference between 8. Title: String VS StringBuffer Content: Difference between String 9. Title: String VS StringBuffer Content: Difference between String and 10. Title: String VS StringBuffer Content: Difference between String and StringBuffer 11. Title: String VS StringBuffer Content: Difference between String and StringBuffer is 12. Title: String VS StringBuffer Content: Difference between String and StringBuffer is ~~~
String 객체가 담고 있는 문자열은 변경이 불가능 하기에 모든 라인마다 JVM에 새로운 String 객체가 생성됩니다.
StringBuffer 객체는 문자열 버퍼가 존재하고 있어서 하나의 객체에서 작업이 이루어지므로 JVM 메모리를 보다 효율적으로 사용할 수 있습니다. 위와 같은 경우보다 훨씬 더 많은 작업을 요구한다면 StringBuffer를 사용하는게 성능면에서 상당히 유리할 것입니다.
JVM은 Java Virtual Machine으로 저희가 작성한 .class 자바 코드(애플리케이션)을 동작시켜줍니다.
각 OS에 맞는 JVM이 존재하여 다른 OS 환경에서도 동일하게 자바 코드가 실행되도록 합니다.
JVM은 어떻게 동작하나요?
JVM이 어떻게 동작하는지 보기 전에 JVM 구조를 먼저 살펴보겠습니다.
JVM 구조
크게 Class Loader, Execution Engine, Runtime Data Area로 구성됩니다. 좀 더 자세히 알아보겠습니다.
Class Loader
.class 파일(들)과 CLASSPATH 환경변수로 지정한 jdk/lib에 있는 class 파일들 그리고 .jar 파일까지 모두 JVM 위에 올려놓는 역할을 수행합니다.
Execution Engine
바이트 코드를 명령어 단위로 읽어서 실행하는 역할을 합니다. 여기서 바이트 코드는 Class Loader에 올려진 class 파일들이며 애초에 .java → .class(바이트 코드)로 컴파일한 이유는 JVM이 읽을 수 있게 바이트 코드로 변환한 것이고 실행을 위해서는 기계가 읽을 수 있게 변환하는 작업이 필요합니다.
*바이트 코드는 사람에 더 친숙하다고 볼 수 있습니다.(물론 기계어 보단…친숙하실 겁니다. 무려 hex editor로 바이트 코드를 읽을 수 있다구요!)
기계가 읽을 수 있도록 다시 번역을 해야 하는 작업이 여기서 이루어집니다. 두 가지 방식이 존재합니다.
인터프리터(Interpreter)
흔히 Python, JavaScript 등이 이 방식으로 실행됩니다. 명령어를 하나씩 읽어 해석하고 실행합니다. (한 번만 실행되는 코드는 보통 이 방식으로 진행됩니다.)
인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일하여 네이티브 코드로 변경하고 이후에는 인터프리팅하지 않고 네이티브 코드로 직접 실행합니다.
JIT(Just-In-Time)
적절한 시점은 이 코드가 자주 실행되는 체크를 하면서 판단하게 됩니다. 또한 네이티브 코드는 캐시에 보관하기에 여러 번 수행되는 코드는 이 방식으로 빠르게 실행시킬 수 있습니다.
모든 Thread가 공유하는 영역은 (JVM이 시작될 때 생성되는) Heap과 Method 영역입니다. 그외는 각자 자신만의 영역을 가지고 있습니다. (독립적으로 수행되는 thread를 생각하면 이해가 되실겁니다.)
PC register
현재 수행 중인 JVM 명령 주소가 담겨있습니다.
JVM Stack
Stack Frame이라는 구조체를 저장하는 스텍입니다.
Stack Frame은 메서드가 수행될 때마다 생성되고 종료될 때 제거됩니다. 그 안에는 local variable array, operand stack, reference of runtime constant pool이 담겨있습니다.
Local variable array는 0번째 인덱스에 메서드를 호출한 클래스(this)가 담겨있고 그 이후로는 메서드 파라미터, 지역변수가 담겨있습니다.
Operand stack은 메서드의 호출 결과를 push, pop하면서 실제 작업을 수행합니다.
Reference of runtime constant pool은 현재 실행 중인 메서드가 속한 클래스의 런타임 상수 풀에 대한 참조가 담겨있습니다.
Heap
객체나 인스턴스가 생성되어 저장되는 공간으로 GC의 대상이 되기에 성능 튜닝은 보통 이곳에서 이루어집니다.
Method
JVM이 읽어들인 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드, 메서드의 정보와 static 변수, 메서드의 바이트 코드 등 중요한 정보를 보관하는 영역입니다.
이 중 Runtime Constant Pool 영역이 내부에 따로 존재합니다. 핵심적인 역할을 담당하기 때문입니다. 이곳에서 각 클래스와 인터페이스의 상수뿐만 아니라 메서드와 필드에 대한 모든 레퍼런스까지 담고 있어 이곳을 통해 메소드나 필드의 주소를 찾고 참조를 합니다.
JVM 동작 과정
.java 파일이 javac(자바 컴파일러)를 통해 .class 파일(바이트코드)로 변환되고 이로써 JVM이 읽을 수 있게 됩니다. 이런 class 이외에 필요한 다른 class 들을 Class Loader가 올리면 Execution Engine이 이를 실행시킵니다. Runtime Data Area는 Class Loader와 Execution Engine과 상호작용을 합니다.