개요

평소 사용법만 알았던 어노테이션에 대해서 자세히 알아보고 직접 어노테이션을 만들어보자.

어노테이션이란

자바 소스 코드에 추가하여 사용할 수 있는 메타데이터의 일종

  • @ 기호를 붙여 사용한다.
  • JDK 1.5 이상 버전에서 사용가능하다.
  • 클래스 파일에 임베디드 되어 컴파일러에 의해 생성된 후 자바 가상머신에 포함되어 작동한다.

Annotation의 용도

  • compiler를 위한 정보 : Annotation은 컴파일러가 에러를 감지하는데 사용
  • 컴파일 시간 및 배포 시간 처리 : Annotation 정보를 처리해 코드, XML 파일 등을 생성
  • 런타임 처리 : 일부 Annotation은 런타임에 조사됨

Annotation의 종류

  • Built in Annotation : 자바에서 기본 제공하는 어노테이션 ex. @Override, @Deprecated
  • Meta Annotation : 커스텀 어노테이션을 만들 수 있게 제공된 어노테이션 ex. @Retention, @Target
  • Custom Annotation : 개발자의 의도에 의해 생성된 어노테이션

어노테이션 생성 방법

인터페이스 선언과 동일하나 @ 기호를 앞에 붙여준다.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME) // Runtime까지 해당 어노테이션을 삭제하지 않음
@Target(ElementType.TYPE) // 
public @interface MyAnnotation {
	int type() // 어노테이션 선언시에 필요한 데이터를 여기다 선언하기
	// 클래스, 메서드 또는 필드에 선언한다면, @MyAnnotation(type = 1) 이런 형식으로 사용됨
}

@Retention

어노테이션이 언제까지 살아있을 것인지를 정하는 어노테이션

  • RetentionPolicy.SOURCE : 소스코드(.java) 까지 남아있는다.
  • RetentionPolicy.CLASS : 바이트코드(.class) 까지 남아있는다.
  • RetentionPolicy.RUNTIME : 런타임까지 남아있는다.

어노테이션 활용하기

Annotation Processor를 통해 어노테이션을 처리할 수 있다.

컴파일 시점에 끼어들어 특정한 어노테이션이 붙어있는 소스코드를 참조해서 새로운 소스코드를 만들어 낼 수 있다

이를 확인해보기 위해 커스텀 어노테이션을 만들어 보자.

커스텀 어노테이션

필자의 경우 메서드의 성능 측정을 위한 어노테이션을 한번 만들어 봤다.

패키지를 생성하기에 앞서 패키지 구조를 아래와 같이 설정 했다. 한 프로젝트 내에서 사용가능한 자료를 찾지 못했다.

root
├─ annotation
└─ application

annotation 모듈 gradle 설정

동적으로 소스코드를 추가하기 위해 javapoet을 이용했다.

또한 autoservice도 함께 추가해줬다. autoservice는 아래와 같은 기능을 한다.

  • javax.annotation.processing.Processor 과같은 manifest 파일을 자동으로 생성

⇒ 컴파일 시점에 애노테이션 프로세서를 사용하여 javax.annotation.processing.Processor 파일을 자동으로 생성

plugins {
    id 'java'
}

group = 'org.example'
version = '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'com.squareup:javapoet:1.13.0'
    // <https://mvnrepository.com/artifact/com.google.auto.service/auto-service>
    implementation 'com.google.auto.service:auto-service:1.1.1'
    annotationProcessor 'com.google.auto.service:auto-service:1.1.1'

}

test {
    useJUnitPlatform()
}

PerformanceTest 어노테이션 생성

어노테이션 패키지 내에 PerformanceTest라는 이름의 어노테이션을 생성해줬다. 클래스를 대상으로 할 것이기 때문에, Target을 ElementType.TYPE으로 지정해줬다.

또한, 컴파일 이 후에는 어노테이션 정보가 필요없기 때문에 Retention을 RetentionPolicy.SOURCE로 지정해줬다.

package com.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface PerformanceTest {
}

어노테이션 처리부분

어노테이션 처리의 경우, 필드 값과 메서드를 그대로 가져오지만, 메서드에 한해 해당 메서드의 body 앞 뒤로 System.currentTimeMillis() 함수 실행부분을 넣고 마지막에 body를 실행하는데에 걸린 시간을 출력하도록 구성했다.

@AutoService(Processor.class)
public class PerformanceProcessor extends AbstractProcessor
{

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
    {
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(PerformanceTest.class);
        List<FieldSpec> fieldSpecList = new ArrayList<>();
        List<MethodSpec> methodSpecList = new ArrayList<>();

        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
            "list all : " + elements.toString());
        for (Element element : elements)
        {

            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
                "CustomGetter process : " + element.getSimpleName());

            TypeElement typeElement = (TypeElement) element;

            for (Element field : typeElement.getEnclosedElements())
            {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
                    "field : " + field.getKind());

                if (field.getKind() == ElementKind.METHOD) {
                    ExecutableElement executableElement = (ExecutableElement) field;
                    Trees trees = Trees.instance(processingEnv);
                    String methodBody = trees.getTree(executableElement).getBody().toString();
                    methodBody = methodBody.substring(2, methodBody.length());
                    methodBody = methodBody.substring(0, methodBody.length()-3);
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
                        "element body : " + methodBody);

                    String methodNm = String.format("p%s", field.getSimpleName());

                    MethodSpec methodSpec = MethodSpec.methodBuilder(methodNm)
                        .addModifiers(Modifier.PUBLIC)
                        .addStatement("long start = System.currentTimeMillis()")
                        .addStatement(methodBody)
                        .addStatement("long end = System.currentTimeMillis()")
                        .addStatement("System.out.println(String.format(\\"method running time : %d\\", end-start))")
                        .build();

                    methodSpecList.add(methodSpec);

                } else if (field.getKind() == ElementKind.FIELD) {
                    String fieldNm = field.getSimpleName().toString();
                    TypeName fieldTypeName = TypeName.get(field.asType());

                    FieldSpec fieldSpec = FieldSpec.builder(fieldTypeName, fieldNm)
                        .build();

                    fieldSpecList.add(fieldSpec);
                }
            }
            ClassName className = ClassName.get(typeElement);
            String getterClassName = String.format("P%s", className.simpleName());

            TypeSpec getterClass = TypeSpec.classBuilder(getterClassName)
                .addModifiers(Modifier.PUBLIC)
                .addFields(fieldSpecList)
                .addMethods(methodSpecList)
                .build();

            try
            {
                JavaFile.builder(className.packageName(), getterClass)
                    .build()
                    .writeTo(processingEnv.getFiler());
            }
            catch (IOException e)
            {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "ERROR : " + e);
            }
        }

        return true;
    }
}

application 모듈 gradle 설정

annotation 모듈을 dependency에 추가해준다.

compileOnly를 통해 해당 모듈이 컴파일시에만 포함하도록 하고

annotationProcessor를 통해 어노테이션 처리를 할 수 있도록 해준다.

plugins {
    id 'java'
}

group = 'org.example'
version = '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    compileOnly project(':annotation')
    annotationProcessor project(':annotation')
}

test {
    useJUnitPlatform()
}

annotation 테스트

테스트용으로 클래스를 하나 생성해주고, 아까 생성한 PerformanceTest 어노테이션을 붙여준다.

import com.annotation.PerformanceTest;

@PerformanceTest
public class AnnotationTest {
    public String s;
    public int a;
    public void run() {

        for (int i = 0; i < 1000000000; i++) {
            a+=i;
        }
    }
}

이후 빌드를 하게 되면 아래와 같이 어노테이션 프로세서에서 생성했던 클래스가 추가된다.

이후 클래스를 생성 할 때에는 AnnotationProcessor에 의해 생성된 클래스로 접근하여 사용할 수 있다.

package com.main;

import java.lang.String;

public class PAnnotationTest {
  String s;

  int a;

  public void prun() {
    long start = System.currentTimeMillis();

            for (int i = 0; i < 1000000000; i++) {
                a += i;
            };
    long end = System.currentTimeMillis();
    System.out.println(String.format("method running time : %d", end-start));
  }
}

 

package com.main;

public class Main {

    public static void main(String[] args) {
        PAnnotationTest pAnnotationTest = new PAnnotationTest();
        pAnnotationTest.prun();
    }
}

정리

annotation은 소스코드를 동적으로 수정할 수 있는 만큼 잘 다룰 경우 개발에 있어 편리함을 줄 수 있을 것 같다. 지금보니 QueryDSL도 비슷한 방식으로 구현이 되어있는 것 같은데, 잘 활용할 수 있도록 좀 더 공부해두는 것이 좋을 것 같다.

전체 코드는 https://github.com/gogoadl/gradle-annotation-processor 에 업로드 되어 있습니다.

Reference

https://jeong-pro.tistory.com/234

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=vefe&logNo=222072718782

https://catsbi.oopy.io/78cee801-bb9c-44af-ad1f-dffc5a541101

https://stackoverflow.com/questions/74546841/get-method-source-code-on-annotation-processor

'Java' 카테고리의 다른 글

[자바] 빌더패턴이란?  (0) 2020.07.16

문제

시간 제한 메모리 제한 제출 정답 맞힌 사람 정답 비율

1 초 256 MB 37117 9871 6578 24.587%

문제

상근이는 빈 공간과 벽으로 이루어진 건물에 갇혀있다. 건물의 일부에는 불이 났고, 상근이는 출구를 향해 뛰고 있다.

매 초마다, 불은 동서남북 방향으로 인접한 빈 공간으로 퍼져나간다. 벽에는 불이 붙지 않는다. 상근이는 동서남북 인접한 칸으로 이동할 수 있으며, 1초가 걸린다. 상근이는 벽을 통과할 수 없고, 불이 옮겨진 칸 또는 이제 불이 붙으려는 칸으로 이동할 수 없다. 상근이가 있는 칸에 불이 옮겨옴과 동시에 다른 칸으로 이동할 수 있다.

빌딩의 지도가 주어졌을 때, 얼마나 빨리 빌딩을 탈출할 수 있는지 구하는 프로그램을 작성하시오.

입력

첫째 줄에 테스트 케이스의 개수가 주어진다. 테스트 케이스는 최대 100개이다.

각 테스트 케이스의 첫째 줄에는 빌딩 지도의 너비와 높이 w와 h가 주어진다. (1 ≤ w,h ≤ 1000)

다음 h개 줄에는 w개의 문자, 빌딩의 지도가 주어진다.

  • '.': 빈 공간
  • '#': 벽
  • '@': 상근이의 시작 위치
  • '*': 불

각 지도에 @의 개수는 하나이다.

출력

각 테스트 케이스마다 빌딩을 탈출하는데 가장 빠른 시간을 출력한다. 빌딩을 탈출할 수 없는 경우에는 "IMPOSSIBLE"을 출력한다.

풀이

BFS로 풀 수 있는 문제였다.

  1. 배열을 벽, 불, 상근이, 빈 공간 으로 나눠서 값을 업데이트 해준다.
  2. 움직일 수 있는 사람이 있는지 확인
  3. 현재 시점에 불들을 4 방향으로 불을 퍼뜨린다 (시간이 없을 경우 한번에 불 다붙이고 끝나버림)
  4. 현재 시점에 사람을 4 방향으로 이동시킨다.
  5. 만약 사람이 배열의 끝 부분에 도착했다면 가장 빨리 도착한 경우 업데이트 해준다.

푸는데 중요했던 부분은 두가지가 있었다.

불이 2개가 붙어있다면, 주기마다 2개의 불을 다 이동시켜야 하는 것

빌딩을 탈출하는데에 가장 빠른 시간을 출력하는 것

 

package com.company.baekjoon;

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

public class B5427 {
    static final int WALL = -2;
    static final int FIRE = -1;
    static final int EMPTY_SPACE = 0;
    static final int PERSON = 1;

    static int[] dx = new int[] {-1,1,0,0};
    static int[] dy = new int[] {0,0,-1,1};
    static int[][] graph;
    static Queue<int[]> fires = new LinkedList<>();
    static Queue<int[]> person = new LinkedList<>();
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder();
        int T = Integer.parseInt(br.readLine());

        for (int i = 0; i < T; i++) {
            String[] size = br.readLine().split(" ");
            int w = Integer.parseInt(size[0]);
            int h = Integer.parseInt(size[1]);
            graph = new int[h][w];
            fires.clear();
            person.clear();
            int answer = Integer.MAX_VALUE;
            int time = 0;
            for (int j = 0; j < h; j++) {
                char[] s = br.readLine().toCharArray();
                for (int k = 0; k < s.length; k++) {
                    if (s[k] == '#') {
                        graph[j][k] = WALL;
                    } else if (s[k] == '.') {
                        graph[j][k] = EMPTY_SPACE;
                    } else if (s[k] == '*') {
                        graph[j][k] = FIRE;
                        fires.add(new int[] {j,k,0});
                    } else {
                        graph[j][k] = PERSON;
                        person.add(new int[]{j,k,0});
                    }
                }
            }
            // 불 먼저 움직이고 사람 움직이기
            while (!person.isEmpty()) {
                while (fires.peek() != null && fires.peek()[2] == time ) {
                    int[] fire = fires.poll();
                    for (int j = 0; j < dx.length; j++) {
                        int cy = fire[0] + dy[j];
                        int cx = fire[1] + dx[j];

                        if (cx < 0 || cy < 0 || cx >= w || cy >= h) continue;
                        if (graph[cy][cx] == FIRE) continue;
                        if (graph[cy][cx] != WALL) {
                            graph[cy][cx] = FIRE;
                            fires.add(new int[]{cy, cx, fire[2] + 1});
                        }
                    }
                }

                while (person.peek() != null && person.peek()[2] == time) {
                    int[] p = person.poll();
                    for (int j = 0; j < dx.length; j++) {
                        int cy = p[0] + dy[j];
                        int cx = p[1] + dx[j];

                        if (cx < 0 || cy < 0 || cx >= w || cy >= h) {
                            answer = Math.min(answer,p[2]);
                            break;
                        }
                        if (graph[cy][cx] == EMPTY_SPACE) {
                            graph[cy][cx] = PERSON;
                            person.add(new int[]{cy, cx, p[2] + 1});
                        }
                    }
                }
                time++;
            }
            if (answer == Integer.MAX_VALUE)
                sb.append("IMPOSSIBLE").append("\n");
            else
                sb.append(answer+1).append("\n");
        }
        System.out.println(sb);
    }
}
Android 장치에 블루투스 마우스 장치를 연결하면 화면에 마우스 커서가 표시됩니다.

이 커서를 Android 7.0 이상부터 커서를 숨기거나, 모양을 바꿀 수 있게 되었습니다.

필자의 경우 마우스 커서를 숨기는 기능에 사용했었는데, 이때 사용한 코드를 공유합니다.

블루투스 마우스 커서의 경우 setPointerIcon 메서드를 호출한 뷰 계층에서는 마우스 커서 모양이 변경되는것으로 보이며,

시스템 UI위에서는 기본 마우스 커서가 보이게 됩니다.

아래는 제가 사용했던 코드 일부를 공유합니다.
@Override
public boolean dispatchGenericMotionEvent(MotionEvent motionEvent) {
    int index = motionEvent.getActionIndex();

    if (motionEvent.getToolType(index) == MotionEvent.TOOL_TYPE_MOUSE) {
        if (rootLayout != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            PointerIcon pointerIcon = PointerIcon.getSystemIcon(this, -1);
            rootLayout.setPointerIcon(pointerIcon);
        }
    }
    return super.dispatchGenericMotionEvent(motionEvent);
}

 

  • 부족한 금액 계산하기

문제 설명

새로 생긴 놀이기구는 인기가 매우 많아 줄이 끊이질 않습니다. 이 놀이기구의 원래 이용료는 price원 인데, 놀이기구를 N 번 째 이용한다면 원래 이용료의 N배를 받기로 하였습니다. 즉, 처음 이용료가 100이었다면 2번째에는 200, 3번째에는 300으로 요금이 인상됩니다.
놀이기구를 count번 타게 되면 현재 자신이 가지고 있는 금액에서 얼마가 모자라는지를 return 하도록 solution 함수를 완성하세요.
단, 금액이 부족하지 않으면 0을 return 하세요.

제한사항

  • 놀이기구의 이용료 price : 1 ≤ price ≤ 2,500, price는 자연수
  • 처음 가지고 있던 금액 money : 1 ≤ money ≤ 1,000,000,000, money는 자연수
  • 놀이기구의 이용 횟수 count : 1 ≤ count ≤ 2,500, count는 자연수

입출력 예

pricemoneycountresult

3 20 4 10

입출력 예 설명

입출력 예 #1
이용금액이 3인 놀이기구를 4번 타고 싶은 고객이 현재 가진 금액이 20이라면, 총 필요한 놀이기구의 이용 금액은 30 (= 3+6+9+12) 이 되어 10만큼 부족하므로 10을 return 합니다.

참고 사항

  • 미션 언어는 Java, JavaScript, Python3, C++ 만 해당 됩니다.
  • 같은 코드를 제출한 사람이 여럿이라면 코드를 가장 먼저 제출한 분께 상품을 드립니다.
  • 좋아요 수가 동일할 경우 코드를 가장 먼저 제출한 분께 상품을 드립니다.

 

나의 풀이

 

문제는 간단했는데, 네가지 테스트가 계속 실패하는 바람에.. 도무지 모르겠어서 구글링 했더니 money값을 받을 때 

int형이 아닌 long형으로 받아야 한다고 해서 매개변수 타입을 long으로 변경하니 성공했다.. 문제를 보면 알 수 있는 부분인데 좀더 문제를 정확히 읽고 풀어야겠다 ㅠ-ㅠ

    public long solution(int price, long money, int count) {
        for(int i = 1; i <= count; i++)  
            money = money - price * i;
        return  money > 0 ? 0 : money*-1;
    }

문제 설명

당신은 폰켓몬을 잡기 위한 오랜 여행 끝에, 홍 박사님의 연구실에 도착했습니다. 홍 박사님은 당신에게 자신의 연구실에 있는 총 N 마리의 폰켓몬 중에서 N/2마리를 가져가도 좋다고 했습니다.
홍 박사님 연구실의 폰켓몬은 종류에 따라 번호를 붙여 구분합니다. 따라서 같은 종류의 폰켓몬은 같은 번호를 가지고 있습니다. 예를 들어 연구실에 총 4마리의 폰켓몬이 있고, 각 폰켓몬의 종류 번호가 [3번, 1번, 2번, 3번]이라면 이는 3번 폰켓몬 두 마리, 1번 폰켓몬 한 마리, 2번 폰켓몬 한 마리가 있음을 나타냅니다. 이때, 4마리의 폰켓몬 중 2마리를 고르는 방법은 다음과 같이 6가지가 있습니다.

  1. 첫 번째(3번), 두 번째(1번) 폰켓몬을 선택
  2. 첫 번째(3번), 세 번째(2번) 폰켓몬을 선택
  3. 첫 번째(3번), 네 번째(3번) 폰켓몬을 선택
  4. 두 번째(1번), 세 번째(2번) 폰켓몬을 선택
  5. 두 번째(1번), 네 번째(3번) 폰켓몬을 선택
  6. 세 번째(2번), 네 번째(3번) 폰켓몬을 선택

이때, 첫 번째(3번) 폰켓몬과 네 번째(3번) 폰켓몬을 선택하는 방법은 한 종류(3번 폰켓몬 두 마리)의 폰켓몬만 가질 수 있지만, 다른 방법들은 모두 두 종류의 폰켓몬을 가질 수 있습니다. 따라서 위 예시에서 가질 수 있는 폰켓몬 종류 수의 최댓값은 2가 됩니다.
당신은 최대한 다양한 종류의 폰켓몬을 가지길 원하기 때문에, 최대한 많은 종류의 폰켓몬을 포함해서 N/2마리를 선택하려 합니다. N마리 폰켓몬의 종류 번호가 담긴 배열 nums가 매개변수로 주어질 때, N/2마리의 폰켓몬을 선택하는 방법 중, 가장 많은 종류의 폰켓몬을 선택하는 방법을 찾아, 그때의 폰켓몬 종류 번호의 개수를 return 하도록 solution 함수를 완성해주세요.

제한사항

  • nums는 폰켓몬의 종류 번호가 담긴 1차원 배열입니다.
  • nums의 길이(N)는 1 이상 10,000 이하의 자연수이며, 항상 짝수로 주어집니다.
  • 폰켓몬의 종류 번호는 1 이상 200,000 이하의 자연수로 나타냅니다.
  • 가장 많은 종류의 폰켓몬을 선택하는 방법이 여러 가지인 경우에도, 선택할 수 있는 폰켓몬 종류 개수의 최댓값 하나만 return 하면 됩니다.

입출력 예

numsresult

[3,1,2,3] 2
[3,3,3,2,2,4] 3
[3,3,3,2,2,2] 2

입출력 예 설명

입출력 예 #1
문제의 예시와 같습니다.

입출력 예 #2
6마리의 폰켓몬이 있으므로, 3마리의 폰켓몬을 골라야 합니다.
가장 많은 종류의 폰켓몬을 고르기 위해서는 3번 폰켓몬 한 마리, 2번 폰켓몬 한 마리, 4번 폰켓몬 한 마리를 고르면 되며, 따라서 3을 return 합니다.

입출력 예 #3
6마리의 폰켓몬이 있으므로, 3마리의 폰켓몬을 골라야 합니다.
가장 많은 종류의 폰켓몬을 고르기 위해서는 3번 폰켓몬 한 마리와 2번 폰켓몬 두 마리를 고르거나, 혹은 3번 폰켓몬 두 마리와 3번 폰켓몬 한 마리를 고르면 됩니다. 따라서 최대 고를 수 있는 폰켓몬 종류의 수는 2입니다.

 

1. 최대한 많은 수의 포켓몬을 골라야 하기 때문에, 중복을 제거해준다.
2. 가질 수 있는 최댓값은 N/2 마리 이므로, 배열의 길이 / 2 가 최대값이다.
3. 중복을 제거한 포켓몬 수 보다 최댓값이 크면 중복을 제거한 포켓몬 수가 최대값이 된다.

 

풀이 코드

  public int solution(int[] nums) {
        int answer = nums.length / 2;

        Set<Integer> set = new HashSet<>();

        for (int i = 0; i < nums.length; i++) {
            set.add(nums[i]);
        }
        if(answer > set.size())
            answer = set.size();
        return answer;
    }

 

문제 설명

0과 1로 이루어진 어떤 문자열 x에 대한 이진 변환을 다음과 같이 정의합니다.

  1. x의 모든 0을 제거합니다.
  2. x의 길이를 c라고 하면, x를 "c를 2진법으로 표현한 문자열"로 바꿉니다.

예를 들어, x = "0111010"이라면, x에 이진 변환을 가하면 x = "0111010" -> "1111" -> "100" 이 됩니다.

0과 1로 이루어진 문자열 s가 매개변수로 주어집니다. s가 "1"이 될 때까지 계속해서 s에 이진 변환을 가했을 때, 이진 변환의 횟수와 변환 과정에서 제거된 모든 0의 개수를 각각 배열에 담아 return 하도록 solution 함수를 완성해주세요.


제한사항

  • s의 길이는 1 이상 150,000 이하입니다.
  • s에는 '1'이 최소 하나 이상 포함되어 있습니다.

입출력 예

sresult

"110010101001" [3,8]
"01110" [3,3]
"1111111" [4,1]

입출력 예 설명

입출력 예 #1

  • "110010101001"이 "1"이 될 때까지 이진 변환을 가하는 과정은 다음과 같습니다.

회차이진 변환 이전제거할 0의 개수0 제거 후 길이이진 변환 결과

1 "110010101001" 6 6 "110"
2 "110" 1 2 "10"
3 "10" 1 1 "1"
  • 3번의 이진 변환을 하는 동안 8개의 0을 제거했으므로, [3,8]을 return 해야 합니다.

입출력 예 #2

  • "01110"이 "1"이 될 때까지 이진 변환을 가하는 과정은 다음과 같습니다.

회차이진 변환 이전제거할 0의 개수0 제거 후 길이이진 변환 결과

1 "01110" 2 3 "11"
2 "11" 0 2 "10"
3 "10" 1 1 "1"
  • 3번의 이진 변환을 하는 동안 3개의 0을 제거했으므로, [3,3]을 return 해야 합니다.

입출력 예 #3

  • "1111111"이 "1"이 될 때까지 이진 변환을 가하는 과정은 다음과 같습니다.

회차이진 변환 이전제거할 0의 개수0 제거 후 길이이진 변환 결과

1 "1111111" 0 7 "111"
2 "111" 0 3 "11"
3 "11" 0 2 "10"
4 "10" 1 1 "1"
  • 4번의 이진 변환을 하는 동안 1개의 0을 제거했으므로, [4,1]을 return 해야 합니다.

나의 풀이

1. 문자열에서 0 제거하기

2. 이진 변환하기

3. 문자열의 길이가 1이 될때까지 반복하기

 

public int[] solution(String s) {
        int[] answer = new int[2];
        int zeroCount = 0;
        int binaryCount = 0;
        while (true) {

            if (s.length() == 1) { // 문자열의 길이가 1일 경우 break
                break;
            }

            for (int i = 0; i < s.length(); i++) {
                if (s.charAt(i) == '0') {
                    zeroCount++; // 문자가 0일경우 count 증가
                }
            }

            s = s.replaceAll("0", ""); // 0을 모두 공백으로 변경

            int binaryStr = s.length();

            s = Integer.toBinaryString(binaryStr); // 문자열의 길이를 이진수로 변환
            binaryCount++;
        }
        
        answer[0] = binaryCount;
        answer[1] = zeroCount;

        return answer;
    }

문제 설명

트럭 여러 대가 강을 가로지르는 일 차선 다리를 정해진 순으로 건너려 합니다. 모든 트럭이 다리를 건너려면 최소 몇 초가 걸리는지 알아내야 합니다. 트럭은 1초에 1만큼 움직이며, 다리 길이는 bridge_length이고 다리는 무게 weight까지 견딥니다.
※ 트럭이 다리에 완전히 오르지 않은 경우, 이 트럭의 무게는 고려하지 않습니다.

예를 들어, 길이가 2이고 10kg 무게를 견디는 다리가 있습니다. 무게가 [7, 4, 5, 6]kg인 트럭이 순서대로 최단 시간 안에 다리를 건너려면 다음과 같이 건너야 합니다.

경과 시간다리를 지난 트럭다리를 건너는 트럭대기 트럭

0 [] [] [7,4,5,6]
1~2 [] [7] [4,5,6]
3 [7] [4] [5,6]
4 [7] [4,5] [6]
5 [7,4] [5] [6]
6~7 [7,4,5] [6] []
8 [7,4,5,6] [] []

따라서, 모든 트럭이 다리를 지나려면 최소 8초가 걸립니다.

solution 함수의 매개변수로 다리 길이 bridge_length, 다리가 견딜 수 있는 무게 weight, 트럭별 무게 truck_weights가 주어집니다. 이때 모든 트럭이 다리를 건너려면 최소 몇 초가 걸리는지 return 하도록 solution 함수를 완성하세요.

제한 조건

  • bridge_length는 1 이상 10,000 이하입니다.
  • weight는 1 이상 10,000 이하입니다.
  • truck_weights의 길이는 1 이상 10,000 이하입니다.
  • 모든 트럭의 무게는 1 이상 weight 이하입니다.

입출력 예

bridge_lengthweighttruck_weightsreturn

2 10 [7,4,5,6] 8
100 100 [10] 101
100 100 [10,10,10,10,10,10,10,10,10,10] 110

출처

※ 공지 - 2020년 4월 06일 테스트케이스가 추가되었습니다.

 

나의 풀이

 

1. 한번의 반복문이 도는 것을 1초로 잡는다.

2. 더이상 남아있는 트럭이 없을 때 까지 반복한다.

3. 큐의 길이가 다리 길이보다 작을 경우 큐에 트럭을 올린다.

4. 큐에 들어가있는 트럭 무게의 합과 추가될 트럭 무게의 합이 다리의 무게한도 보다 작으면 큐에 트럭을 추가하고, 

   아닐 경우 큐에 0을 추가함 (앞에 추가되어 있는 값들을 한칸 씩 밀어내기 위해!)

5. 큐의 길이가 다리의 길이보다 같거나 커질 경우 큐에 있는 트럭을 삭제하고 위의 로직을 반복한다. 

6. 트럭이 모두 큐에 add 되면, 마지막 트럭이 큐에서 삭제 되는 수를 더해준다. (큐의 길이를 더해서 리턴)

 

    public int solution(int bridge_length, int weight, int[] truck_weights) {
        int answer = 0;

        Queue<Integer> bridgeQueue = new ArrayDeque<>();
        Queue<Integer> waitQueue = new ArrayDeque<>();

        for (int i: truck_weights) {
            waitQueue.add(i);
        }
        int sum = 0;
        while (!waitQueue.isEmpty()){
            answer++;
            if(bridgeQueue.size() < bridge_length){
                if(sum + waitQueue.peek() <= weight){
                    sum += waitQueue.peek();
                    bridgeQueue.add(waitQueue.poll());

                }else{
                    bridgeQueue.add(0);
                }
            }else{
                sum -= bridgeQueue.poll();
                if(sum + waitQueue.peek() < weight){
                    sum += waitQueue.peek();
                    bridgeQueue.add(waitQueue.poll());

                }else{
                    bridgeQueue.add(0);
                }
            }
        }
        bridgeQueue.size();
        return answer + bridge_length;
    }

 

문제 설명

게임개발자인 죠르디는 크레인 인형뽑기 기계를 모바일 게임으로 만들려고 합니다.
죠르디는 게임의 재미를 높이기 위해 화면 구성과 규칙을 다음과 같이 게임 로직에 반영하려고 합니다.

게임 화면은 1 x 1 크기의 칸들로 이루어진 N x N 크기의 정사각 격자이며 위쪽에는 크레인이 있고 오른쪽에는 바구니가 있습니다. (위 그림은 5 x 5 크기의 예시입니다). 각 격자 칸에는 다양한 인형이 들어 있으며 인형이 없는 칸은 빈칸입니다. 모든 인형은 1 x 1 크기의 격자 한 칸을 차지하며 격자의 가장 아래 칸부터 차곡차곡 쌓여 있습니다. 게임 사용자는 크레인을 좌우로 움직여서 멈춘 위치에서 가장 위에 있는 인형을 집어 올릴 수 있습니다. 집어 올린 인형은 바구니에 쌓이게 되는 데, 이때 바구니의 가장 아래 칸부터 인형이 순서대로 쌓이게 됩니다. 다음 그림은 [1번, 5번, 3번] 위치에서 순서대로 인형을 집어 올려 바구니에 담은 모습입니다.

만약 같은 모양의 인형 두 개가 바구니에 연속해서 쌓이게 되면 두 인형은 터뜨려지면서 바구니에서 사라지게 됩니다. 위 상태에서 이어서 [5번] 위치에서 인형을 집어 바구니에 쌓으면 같은 모양 인형 두 개가 없어집니다.

크레인 작동 시 인형이 집어지지 않는 경우는 없으나 만약 인형이 없는 곳에서 크레인을 작동시키는 경우에는 아무런 일도 일어나지 않습니다. 또한 바구니는 모든 인형이 들어갈 수 있을 만큼 충분히 크다고 가정합니다. (그림에서는 화면표시 제약으로 5칸만으로 표현하였음)

게임 화면의 격자의 상태가 담긴 2차원 배열 board와 인형을 집기 위해 크레인을 작동시킨 위치가 담긴 배열 moves가 매개변수로 주어질 때, 크레인을 모두 작동시킨 후 터트려져 사라진 인형의 개수를 return 하도록 solution 함수를 완성해주세요.

[제한사항]

  • board 배열은 2차원 배열로 크기는 5 x 5 이상 30 x 30 이하입니다.
  • board의 각 칸에는 0 이상 100 이하인 정수가 담겨있습니다.
    • 0은 빈 칸을 나타냅니다.
    • 1 ~ 100의 각 숫자는 각기 다른 인형의 모양을 의미하며 같은 숫자는 같은 모양의 인형을 나타냅니다.
  • moves 배열의 크기는 1 이상 1,000 이하입니다.
  • moves 배열 각 원소들의 값은 1 이상이며 board 배열의 가로 크기 이하인 자연수입니다.

입출력 예

boardmovesresult

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

입출력 예에 대한 설명

입출력 예 #1

인형의 처음 상태는 문제에 주어진 예시와 같습니다. 크레인이 [1, 5, 3, 5, 1, 2, 1, 4] 번 위치에서 차례대로 인형을 집어서 바구니에 옮겨 담은 후, 상태는 아래 그림과 같으며 바구니에 담는 과정에서 터트려져 사라진 인형은 4개 입니다.

public int solution(int[][] board, int[] moves) {
        int answer = 0;

        Stack<Integer> answerStack = new Stack<>();
        for(int i = 0; i < moves.length; i++)
        {
            for(int j = 0; j < board.length; j++)
            {
                if(board[board.length - 1][moves[i] - 1] == 0)
                    break;
                if(board[j][moves[i] - 1] != 0)
                {
                    answerStack.add(board[j][moves[i] - 1]);
                    board[j][moves[i] - 1] = 0;
                    if(answerStack.size() >= 2 && answerStack.peek() == answerStack.get(answerStack.size() - 2))
                    {
                        answerStack.pop();
                        answerStack.pop();
                        answer += 2;
                    }
                    break;
                }
            }
        }
        return answer;
    }

스택을 사용하면 간단하게 풀수있다.

앱 개발 도중 앱 실행 중에 종료 후, 다시 시작할 때 튕기는 현상이 있었다. 로그인을 한 후에 재 시작했을 때 발생해서

로그인 관련 문제인가 싶어 시도 해보다가, 앱이 완전히 종료되지 않아서 발생하는게 아닌가 싶어서 시도 해 봤는데,

완전히 종료된게 아니기 때문에 발생한게 맞는 듯 싶다.

@Override
public void onBackPressed() { // DrawerLayout 이 열려있을 경우에는 DrawerLayout을 닫는다
        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        if (drawer.isDrawerOpen(GravityCompat.END)) {
            drawer.closeDrawer(GravityCompat.END);
        } else {
            if(getSupportFragmentManager().getBackStackEntryCount() == 0)
            {
                moveTaskToBack(true); // 백그라운드로 전환
                finishAndRemoveTask();
                android.os.Process.killProcess(android.os.Process.myPid());
            }else
            {
               super.onBackPressed();
            }
        }
    }

주목 할 부분은 else 문 안에 if문 쪽이다. 나 같은 경우는 프래그먼트를 사용 했기 때문에, 만약에 프래그먼트가 메인 화면의 프래그먼트 일 경우(스택에 저장된 프래그먼트가 더이상 없을 경우) 앱을 완전히 종료하고, 아닐 경우는 backPressed를 실행 하도록 했다. 앱을 완전히 종료하고 나니 재 시작 했을 때도 튕기는 현상이 없어졌다!

문제 설명

위와 같은 삼각형의 꼭대기에서 바닥까지 이어지는 경로 중, 거쳐간 숫자의 합이 가장 큰 경우를 찾아보려고 합니다. 아래 칸으로 이동할 때는 대각선 방향으로 한 칸 오른쪽 또는 왼쪽으로만 이동 가능합니다. 예를 들어 3에서는 그 아래칸의 8 또는 1로만 이동이 가능합니다.

삼각형의 정보가 담긴 배열 triangle이 매개변수로 주어질 때, 거쳐간 숫자의 최댓값을 return 하도록 solution 함수를 완성하세요.

제한사항

  • 삼각형의 높이는 1 이상 500 이하입니다.
  • 삼각형을 이루고 있는 숫자는 0 이상 9,999 이하의 정수입니다.

입출력 예

triangleresult

[[7], [3, 8], [8, 1, 0], [2, 7, 4, 4], [4, 5, 2, 6, 5]] 30

 

public int solution(int[][] triangle) {
        int answer = 0;

        for(int i = 1; i < triangle.length; i++)
        {
            for (int j = 0; j < triangle[i].length; j++)
            {
                if(j == 0)
                {
                    triangle[i][j] += triangle[i - 1][j];
                }else if ( j == triangle[i].length - 1)
                {
                    triangle[i][j] += triangle[i - 1][j - 1];
                }
                else
                {
                    triangle[i][j] += Math.max(triangle[i - 1][j - 1],triangle[i - 1][j]);
                }
            }
        }
        int max = 0;
        for(int k = 0; k < triangle[triangle.length - 1].length; k++)
        {
            if (triangle[triangle.length - 1][k] > max)
                max = triangle[triangle.length - 1][k];
        }

        answer = max;

        return answer;
    }

 

위에서 부터 배열에 값을 더해가면서 가장 아래에 있는 값 중에 가장 큰 수가 답이 된다. 처음 풀어본 레벨 3 단계 문제이다!

 

 

+ Recent posts