부제 : 입력받은 정수에 해당하는 enum 멤버로 할당 in 방향 of 시뮬레이션
0. 한 줄 요약
1. 발단 : java에서 갑분 enum?
2. 전개 : 어디에 써먹을까
3. 위기 : 이거 외않되?
4. 절정 : 도와줘요 구글에몽! (작성중)
5. 결말
요약
입력 받은 번호 위치(index)에 해당하는 enum객체의 원소를 바로 가져다 쓰고 싶으면
객체이름.values()[번호]을 사용하자
ex)
enum eDir{
empty, up, down, right, left;
}
eDir dir = eDir.values()[idx];
발단 : java에서 갑분 enum?
SW Expert Academy의 5653. [모의 SW 역량테스트] 줄기세포배양을 풀다가 세포들 상태를 enum으로 구분해서 풀어봤다.
(결과적으로 3개 케이스가 시간초과나서 실패함 => 반복을 줄여서 성공. 관련 글은 여기로)
이때 이후로 간단한 숫자나 문자를 입력받아 케이스를 나누는 부분을 enum으로 쓰면 어떨까 생각했다.
전개 : 어디에 써먹을까
"유지보수할 필요없는, 문제 하나 풀 때 쓰는 용도인데 그런거 쓸 필요가 있을까? 시간초과 안 남?"
스스로에게도 하는 말이고 남한테도 들은 말이지만, 그럼에도 enum으로 쓰려는 이유는 심플했다. 입력값에 따라 switch나 if-else로 케이스 나누기가 귀찮았다.
알고리즘 문제 중 시뮬레이션에서 자주보이는 방향 이동이 특히 그랬다.
많고 많은 예제문제들이 있지만 당장 풀고 있는 [백준]17143. 낚시왕에서는 다음과 같다.
위 = 1, 아래 = 2, 오른쪽 = 3, 왼쪽 = 4
이런 경우 나는 대략 다음 코드와 같이 구현하는 편이었다.
- ⅰ) 먼저 기본 입력을 Shark라는 클래스에 입력을 받는다.
static class Shark {
int r, c, s, d, z; // 상어위치, 속력, 이동 방향, 크기
char num; // 몇 번째 상어인지(알파벳)
public Shark(char num, int r, int c, int s, ind d, int z) {
super();
this.num = num;
this.r = r;
this.c = c;
this.s = s;
this.d = d;
this.z = z;
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int r = Integer.parseInt(st.nextToken());
int c = Integer.parseInt(st.nextToken());
int s = Integer.parseInt(st.nextToken());
int d = Integer.parseInt(st.nextToken()); // 상어의 이동 방향
int z = Integer.parseInt(st.nextToken());
Shark shark = new Shark(num, r, c, s, d, z);
}
- ⅱ) 이제 여기에 '낚시왕' 문제 기준으로 상어별로 이동 방향 값을 넣어주거나 이동시킨다.
switch (d) {
case 1: // 위
// 상어를 위로 이동시킴
// 벽이랑 만나면 d = 2로 바꾸고 계속 이동시킴
break;
case 2: // 아래
// 상어를 아래로 이동시킴
// 벽이랑 만나면 d = 1로 바꾸고 계속 이동시킴
break;
case 3: // 오른쪽
// 상어를 오른쪽으로 이동시킴
// 벽이랑 만나면 d = 4로 바꾸고 계속 이동시킴
break;
case 4: // 왼쪽
// 상어를 왼쪽으로 이동시킴
// 벽이랑 만나면 d = 3로 바꾸고 계속 이동시킴
break;
}
💬 if/else || switch 으로 방향 전환시 문제점
1. 주석 하나하나 달아줘야한다. 엄청 귀찮다
2. 주석 안 달면 가독성이 쓰레기다.
3. 주석 달아도 코드리뷰할 때 집중 안하면 기억 안 난다.
물론 어차피 enum으로 해도 나중에 조건문에서 값에 따라 분리하긴 해야한다.
switch (dir) {
case up:
// 상어를 위로 이동시킴
// 벽이랑 만나면 d = 2로 바꾸고 계속 이동시킴
break;
case down:
// 상어를 아래로 이동시킴
// 벽이랑 만나면 d = 1로 바꾸고 계속 이동시킴
break;
case right:
// 상어를 오른쪽으로 이동시킴
// 벽이랑 만나면 d = 4로 바꾸고 계속 이동시킴
break;
case left:
// 상어를 왼쪽으로 이동시킴
// 벽이랑 만나면 d = 3로 바꾸고 계속 이동시킴
break;
}
이런 식으로.
그래도 1이 위, 2가 아래, 3이 오른쪽, 4가 왼쪽이라고 굳이 표시할 필요가 없다.
위기 : 이거 외않되?
목표는 입력받은 숫자(1~4)에 해당하는 enum 값(방향: 위, 아래, 오, 왼)을 자동할당하는 것이다.
그러면 방향 전환 같이 방향 값을 찾을 때 정수값의 의미를 아는 사람만 파악하는 수고도 덜고, 주석을 안달아도 되므로 가독성과 귀찮음을 해결할 수 있을 것이다.
enum을 접한게 Unity C#이었던지라 비슷한 방식으로 접근했다.
그런데 이게 문제였다.
Microsoft 문서 중 열거형-C# 참조에서 enum 타입 사용법으로 다음 방식을 알려준다.
비슷하게 eDir dir = (eDir) d; 로 하면 되겠지?
금방 하겠는데?
.. 라고 생각했다. 플래그였다
전혀 안됐다.
그딴 거 안된다고 이클립스가 경고해준다(56warnings 는 무시해주길.. 알고 풀면서 패키지에 사용 안한 변수가 많아서 그래요..)
이때부터는 오라클에서 제공하는 java 레퍼런스를 뒤졌다.
valueOf 라는 게 있던데 d를 string으로 받아서 넘겨볼까?
안된단다.
Enum.java의 238번째 줄에 가보니 문제는 d의 값(문자열 형태인 1, 2, 3, 4)에 해당하는게 enum eDir에 없다는 의미였다.(eDir의 값은 up, down, right, left니까)
이 에러를 해결하기 위해 현재 코드를 조금 수정하면 이렇게 된다.
(좋은 코드가 아니니까 코드 블록 대신 사진으로 올렸다. 이런 거 복붙하지 마세요)
switch에 1, 2, 3, 4를 쓰기 싫어서 하는 건데 쓰게 만든다.
심지어 저건 입력 코드라서 구현에서 또 switch로 up 등등 을 써야하는게 이게 무슨 멍청한 짓일까.
절정 : 도와줘요 구글에몽!
분명 C# 처럼 한 줄로 캐스팅하거나 바로 대입할 수 있는 방법이 있을 것 같았다.
구글에도 물어보고, 오라클에서 제공하는 java 레퍼런스의 다른 메서드도 찾아보고, 야생의 핑프가 되어 지인들에게도 물어봤다.
이때 약간 주변 사람들이 이걸 게으르다고 해야 할지 부지런하다고 해야 할지 모르겠는 미친놈으로 바라봤다.
아무튼 물어보면서 내 목적을 좀 더 분명하게 정리해봤다.
입력(=대입 혹은 매개변수) | 정수형 |
return | String 혹은 enum형 (내 코드에서는 eDir로 return) |
그리고 정답은 킹갓제너럴 스택오버플로우에 있었다.
찬양하라 스택오버플로우
스택오버플로우 줄임말은 뭘까? SO? 스오플? 스플? 이거 줄임말 아는 분 댓글 좀 부탁드립니다. 쓰는데 너무 길다
첫 줄에도 말했던 객체이름.values()[정수]를 쓰면 된단다!
댓글에서는 캐시와 성능 관련해서 뭔가 있었지만 코드 돌려보고 한 줄로 바로 넣을 수 있다는게 완전 신났었다.
사진 속 코드는 아래 더보기 클릭
!주의: 전체 코드 중 일부이므로 단순 복붙할 경우 에러가 발생할 수 있습니다.
static class Shark {
int r, c, s, z; // 상어위치, 속력, 크기
eDir dir; // 이동 방향
char num; // 상어 번호(지만 알파벳)
public Shark(char num, int r, int c, int s, eDir dir, int z) {
super();
this.num = num;
this.r = r;
this.c = c;
this.s = s;
this.dir = dir;
this.z = z;
}
}
static enum eDir {
empty, up, down, right, left;
}
// 상어들 정보 넣기
for (int m = 0; m < M; m++) {
char num = (char) (m + 'A');
st = new StringTokenizer(br.readLine());
int r = Integer.parseInt(st.nextToken());
int c = Integer.parseInt(st.nextToken());
int s = Integer.parseInt(st.nextToken());
int d = Integer.parseInt(st.nextToken());
int z = Integer.parseInt(st.nextToken());
Shark shark = new Shark(num, r, c, s, eDir.values()[d], z);
sharkList.add(shark);
map[r][c] = num;
}
// 3. 상어가 이동한다.
for (int sIdx = 0; sIdx < sharkList.size(); sIdx++) {
if (sharkList.get(sIdx).s != -1) {
for (int s = 1; s <= sharkList.get(sIdx).s; s++) {
eDir dir = sharkList.get(sIdx).dir;
switch (dir) {
case up:
// 구현
break;
case down:
// 구현
break;
case right:
// 구현
break;
case left:
// 구현
break;
}
}
}
} // ========== 이동 종료
크게 달라진 건 티끌이고 오히려 코드 줄 수도 늘었다. 1, 2, 3, 4 대신 알파벳으로 치는게 더 귀찮을 수 있다.
그래도 누군가에게 코드를 설명할 때,
"1이니까 위로 갈거고 조건에 해당하면 2로 바꿔서 방향을 아래로 가도록할거야" 대신
"up이던 걸 조건에 해당하면 down으로 바꿀거야" 라고 말할 수 있게 되었다.
결론 : 객체이름.values()[번호]을 사용하자
수미상관
static enum eDir{
empty, up, down, right, left;
}
public static void main(String[] args) throws IOException {
// -- 중략 --
Shark shark = new Shark(num, r, c, s, eDir.values()[d], z);
// -- 중략 --
실제 프로젝트에서는 캐시 사용 때문에 느려진다는 얘기가 있다. 하지만 작은 규모나 시뮬레이션 문풀 때는 주석보다 가독성도 높고, 정수에 따라 switch나 if-else의 조건을 매번 바꿔주지 않아도 되니 편한 만큼 높은 만큼 자주 써먹을 것 같다.
+) 이번 글을 쓰면서 클래스의 멤버, 객체, 요소, 원소가 조금 혼재되어 사용한 것 같다. 관련 용어를 한 번 정리할 필요를 느꼈다. 정리하게 되면 블로깅해야지(티스토리 제발 주식처럼 터지지 말아줘)
++) 개인적으로 자바 레퍼런스는 메서드가 일목요연하게 정리되어있어서 어떤 메서드가 있는지 찾기는 쉽지만 이를 활용하는 방법은 아직 모르겠다. C++ 레퍼런스는 예시도 제공해줘서 좋은데 아쉽다.
'프로젝트 개발 관련 공부 > JAVA' 카테고리의 다른 글
[JAVA, Spring] 강한 결합, 느슨한 결함 (0) | 2022.10.25 |
---|