산술식에서 올바른 괄호인지 판단하기

문제

계산기 프로그램에서 소괄호(), 중괄호{}, 대괄호[] 세 종류의 괄호를 사용하며, 괄호의 포함관계는 소괄호 < 중괄호 < 대괄호 순서입니다. 이 순서는 반드시 지켜져야 합니다. 예: [ { ( ) } ]
입력된 산술식에 괄호가 옳게 구성되었는지 확인하는 solution 함수를 완성해 주세요.

  • 입력되는 산술식은 검증하지 않습니다.
  • if 구문 등 비교문은 10개 이하만 사용하시오.

( switch case문에서 case갯수, 3항 연산자 등 if로 계산할 수 있는 비교 구문)

  • 추가 제약조건은 아래와 같습니다.
    • 여는(왼쪽) 괄호는 닫는(오른쪽) 괄호 앞에 와야 하며, 항상 쌍을 이뤄야 한다.
    • 포함 관계에 따라 다수의 다른 괄호를 포함 할 수 있다. 예. [ { ( ) ( ) } { } ]
    • 동일한 레벨의 괄호는 스스로를 포함할 수 없다. 예. { { } } 불가

입출력 예

스크린샷 2019-12-15 오후 7 15 22

import java.util.Stack;

public class StackExample {


    private static int getBrace(char c){
        if(c == '{') {
            return c - 40;
        } else {
            return c;
        }
    }


    public boolean solution(String input) {

        boolean result = true;

        Stack<Character> str = new Stack<>();

        int first = 0;
        int remain = 0;

        for (char c : input.toCharArray()) {

            if ((int) c > 41 && c <= 57) continue;

            // [ = 91, { = 123 , ( = 40
            if (c == '[' || c == '{' || c == '(') {
                if (str.size() == 0) {
                    first = getBrace(c);
                } else {
                  remain = getBrace(c);

                    if (first <= remain) {
                        result = false;
                        break;
                    }
                }

                first = getBrace(c);
                str.push(c);

            } else {

                if (str.size() < 1) {
                    result = false;
                    break;
                }

                if (c == ']' && str.pop() != '[') {
                    result = false;
                    break;
                }

                if (c == '}' && str.pop() != '{') {
                    result = false;
                    break;
                }

                if (c == ')' && str.pop() != '(') {
                    result = false;
                    break;
                }

            }

        }

        if (str.size() != 0) {
            result = false;
        }

        return result;
    }


    public static void main(String[] args) {

        StackExample stack = new StackExample();

        System.out.println(stack.solution("3+[(5+1)-1]"));
        System.out.println(stack.solution("3+([5+1])"));
        System.out.println(stack.solution("3+{(5+1}"));
        System.out.println(stack.solution("3+[{(5+1)-1}+3]"));
        System.out.println(stack.solution("3+[{{5+1}-1}+3]"));
    }
}

괄호의 포함관계와 동일한 괄호는 서로를 포함할 수 없는 조건을 판단하기 위해서 저는 자료구조 중에서 stack을 사용해서 풀었습니다.
해당 문자열을 각각 char 타입으로 변환 후에 산술식(0 ~ 9, +, -, *, /)은 아스키 코드 표에서 42 ~ 57에 포함되기 때문에 continue를 이용하여 걸렀습니다.
그리고 {(중괄호)의 경우에는 [(대괄호) 보다 우선순위가 낮지만 아스키 코드 테이블에서는 값이 더 크기 때문에 ((소괄호)가 아스키 코드 값으로 40이기 때문에 이 값을 기준으로
우선 순위의 값을 구해서 올바른 괄호인지 판단하도록 코드를 작성하였습니다.

예산에 맞는 물건 구매

문제설명

두 가지 물품을 구매할 수 있도록 예산을 배정받았습니다, 첫 번째 물품의 가격은 a원, 두 번째 물품의 가격은 b원 입니다. 각 물품은 여러 개 구매할 수 있으며, 구매하지 않아도 괜찮습니다.
물품을 구매하는 여러 가지 방법 중, 예산을 남기지 않고 물품을 구매하는 방법의 수를 구하려 합니다. 예를 들어 예산이 23000원이고, 첫 번째 물품의 가격 a = 3000원, 두 번째 물품의 가격 b = 5000원인 경우, 예산에 딱 맞게 물건을 구매하는 방법은 다음과 같이 두 가지 방법이 있습니다.

  1. 첫 번째 물품을 1개(3000원), 두 번째 물품을 4개(20000원) 구매합니다.
  2. 첫 번째 물품을 6개(18000원), 두 번째 물품을 1개(5000원) 구매합니다.
    위 두 가지 방법 외에 예산을 남기지 않고 물품을 구매할 수 있는 방법은 없습니다.
    첫 번째 물품의 가격 a, 두 번째 물품의 가격 b, 예산 budget이 매개변수로 주어질 때, 예산을 남기지 않고 물품을 구매하는 방법의 가짓수를 return 하도록 solution 함수를 완성해주세요.

제한사항

  • a는 1 이상 10,000 이하의 자연수입니다.
  • b는 1 이상 10,000 이하의 자연수입니다.
  • budget은 1 이상 1,000,000 이하의 자연수입니다.

입출력 예 설명

스크린샷 2019-12-14 오후 3 28 37

입출력 예 #1

문제의 예시와 같습니다.

public int solution(int a, int b, int budget){

  // 3000 , 5000, 23000
  int answer = 0;


  for (int i = 0; i*a < budget; i++) {
    for (int j = 0; j*b < budget; j++) {
      if((budget-(i*a+j*b))==0)
        answer++;
     }
   }

  return answer;
}

해당 문제의 출력결과는 예산(budget)이랑 구매 품목(a,b)의 개수가 정확하게 맞아 떨어지도록 구할 수 있는 경우의 수츨 출력하는게 목적입니다.
구매 물품 a,b는 budget(예산)을 넘지 못합니다. 이를 이용해서 이중 for문을 이용해서 순차적으로 경우의 수를 따져가며 값을 구하도록 코드를 작성하였습니다.

숫자 n의 각 자리수로 분열 가능한 횟수 구하기

어떤 숫자 n을 각 자리의 숫자로 나누었을 때, 나누어떨어지면 그 숫자로 분열 가능하다고 합니다.
예를 들어 2232이라는 숫자는 2와 3 두 개의 숫자로 구성되어 있습니다. 또한, 2232는 2로도 나누어떨어지고, 3으로도 나누어 떨어집니다. 따라서 분열 가능한 횟수는 2입니다.

2232의 예에서와같이 2가 여러 번 나오더라도 분열 가능한 횟수를 셀 때는 2 한 번, 3 한 번으로 세며 중복해서 나오는 숫자 2는 고려하지 않습니다.
숫자 n이 매개변수로 주어질 때, n이 분열 가능한 횟수를 return 하도록 solution 함수를 완성하세요.
[※ 숫자는 0으로 나눌 수 없음을 유의하세요.]
제한사항

  • n은 1015 이하의 자연수입니다.

입출력 예

스크린샷 2019-12-14 오후 9 06 13

입출력 예 설명

입출력 예 #1

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

1234의 경우 다음과 같습니다.

  • 1234는 1로 나누어떨어짐
  • 1234는 2로 나누어떨어짐
  • 1234는 3으로 나누어떨어지지 않음
  • 1234는 4로 나누어떨어지지 않음
    따라서 분열 가능한 횟수는 2이므로 2를 return 합니다.
import java.util.HashSet;

public class FindDivisibleNumber {

    public int solution(long n) {

        int answer = 0;
        // 각 자리수에 해당 하는 값을 중복없이 저장하기 위한 HashSet 객체 생성
        HashSet<Integer> hashSet = new HashSet<>();
        String number = String.valueOf(n);

        getDistinctNumber(n, hashSet, number);

        hashSet.forEach(s -> System.out.println(s));

        for (int val : hashSet){
            if(n % val == 0) {
                answer++;
            }
        }

        return answer;
    }

    private void getDistinctNumber(long n, HashSet<Integer> hashSet, String number) {

        for (int i = 0; i < number.length(); i++) {
            int remain = 0;

            // 각 자리수 추출
            remain = (int) n % 10;
            hashSet.add((int) remain);
            n = n / 10;
        }
    }


    public static void main(String[] args) {

        FindDivisibleNumber divideN = new FindDivisibleNumber();
        int n = 2322;

        System.out.println(divideN.solution(n));

    }
}

각각의 자리수를 중복없이 추출하기 위해 List 타입을 사용하여 contains() 메소드를 이용하여 중복이 되지 않는 각 자리수를 추출하려 했지만,
HashSet 객체를 이용하는것이 더 적절하다고 판단하여 사용하였습니다.

체육복

문제 설명

점심시간에 도둑이 들어, 일부 학생이 체육복을 도난당했습니다. 다행히 여벌 체육복이 있는 학생이 이들에게 체육복을 빌려주려 합니다. 학생들의 번호는 체격 순으로 매겨져 있어, 바로 앞번호의 학생이나 바로 뒷번호의 학생에게만 체육복을 빌려줄 수 있습니다. 예를 들어, 4번 학생은 3번 학생이나 5번 학생에게만 체육복을 빌려줄 수 있습니다. 체육복이 없으면 수업을 들을 수 없기 때문에 체육복을 적절히 빌려 최대한 많은 학생이 체육수업을 들어야 합니다.

전체 학생의 수 n, 체육복을 도난당한 학생들의 번호가 담긴 배열 lost, 여벌의 체육복을 가져온 학생들의 번호가 담긴 배열 reserve가 매개변수로 주어질 때, 체육수업을 들을 수 있는 학생의 최댓값을 return 하도록 solution 함수를 작성해주세요.

제한사항

  • 전체 학생의 수는 2명 이상 30명 이하입니다.

  • 체육복을 도난당한 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.

  • 여벌의 체육복을 가져온 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.

  • 여벌 체육복이 있는 학생만 다른 학생에게 체육복을 빌려줄 수 있습니다.

  • 여벌 체육복을 가져온 학생이 체육복을 도난당했을 수 있습니다. 이때 이 학생은 체육복을 하나만 도난당했다고 가정하며, 남은 체육복이 하나이기에 다른 학생에게는 체육복을 빌려줄 수 없습니다.

입출력 예

스크린샷 2019-12-12 오후 5 56 26

입출력 예 설명

  • 예제 #1

    1번 학생이 2번 학생에게 체육복을 빌려주고, 3번 학생이나 5번 학생이 4번 학생에게 체육복을 빌려주면 학생 5명이 체육수업을 들을 수 있습니다.

  • 예제 #2

    3번 학생이 2번 학생이나 4번 학생에게 체육복을 빌려주면 학생 4명이 체육수업을 들을 수 있습니다.


import java.util.HashSet;

public class GymClothes {

    public int solution(int n, int[] lost, int[] reserve){

        // 현재 참석자 수
        int participant = n - lost.length;

        HashSet<Integer> hashSet = new HashSet<>();

        for (int index : reserve){
            hashSet.add(index);
        }


        for(int i =0; i< lost.length; i++){

            if(hashSet.contains(lost[i])){

                participant++;
                hashSet.remove(lost[i]);
                lost[i] = -1;
            }

        }


        for (int i = 0; i < lost.length; i++) {

            if(hashSet.contains(lost[i] - 1)){
                participant++;
                hashSet.remove(lost[i] - 1);

            }else if(hashSet.contains(lost[i] + 1)){
                participant++;
                hashSet.remove(lost[i] +1);

            }
        }

        return participant;
    }


    public static void main(String[] args) {

        GymClothes gymClothes
                = new GymClothes();


        int n = 3;
        int[] lost = {3};
        int[] reserve = {1};

        System.out.println(gymClothes.solution(n, lost, reserve));

    }
}

체육복 문제는 사실 제한사항 중에 맨 마지막에 있는 여벌 체육복을 가져온 학생이 체육복을 도난당했을 수 있습니다. 이때 이 학생은 체육복을 하나만 도난당했다고 가정하며, 남은 체육복이 하나이기에 다른 학생에게는 체육복을 빌려줄 수 없습니다.
이 부분에 대해서 고려를 해야합니다. lost[] 배열가 reserve[] 배열에서 겹치는 번호가 존재하면 해당 번호는 lost[] 배열의 다른 번호에게 체육복을 빌려줄 수 없기 때문에 컬렉션 타입의 객체의 remove() 메소드를 활용하여 reserve[] 배열의 요소 값을 가지고 있는 HashSet 객체에서 lost[] 배열의 요소 값이 포함되어있으면 중복으로 처리하여 각각의 중복되는 요소의 값들을 제거하였습니다.

K번째수

문제 설명

배열 array의 i번째 숫자부터 j번째 숫자까지 자르고 정렬했을 때, k번째에 있는 수를 구하려 합니다.

예를 들어 array가 [1, 5, 2, 6, 3, 7, 4], i = 2, j = 5, k = 3이라면

array의 2번째부터 5번째까지 자르면 [5, 2, 6, 3]입니다.
1에서 나온 배열을 정렬하면 [2, 3, 5, 6]입니다.
2에서 나온 배열의 3번째 숫자는 5입니다.

배열 array, [i, j, k]를 원소로 가진 2차원 배열 commands가 매개변수로 주어질 때, commands의 모든 원소에 대해 앞서 설명한 연산을 적용했을 때 나온 결과를 배열에 담아 return 하도록 solution 함수를 작성해주세요.

제한사항

  • array의 길이는 1 이상 100 이하입니다.
  • array의 각 원소는 1 이상 100 이하입니다.
  • commands의 길이는 1 이상 50 이하입니다.
  • commands의 각 원소는 길이가 3입니다.

입출력 예

스크린샷 2019-12-10 오후 4 29 47

입출력 예 설명

[1, 5, 2, 6, 3, 7, 4]를 2번째부터 5번째까지 자른 후 정렬합니다. [2, 3, 5, 6]의 세 번째 숫자는 5입니다.

[1, 5, 2, 6, 3, 7, 4]를 4번째부터 4번째까지 자른 후 정렬합니다. [6]의 첫 번째 숫자는 6입니다.

[1, 5, 2, 6, 3, 7, 4]를 1번째부터 7번째까지 자릅니다. [1, 2, 3, 4, 5, 6, 7]의 세 번째 숫자는 3입니다.

import java.util.Arrays;

public class FindNumberOrderBy {


    public int[] solution(int[] array, int[][] commands) {

        int[] result = new int[commands.length];

        for (int i = 0; i < commands.length; i++) {
            // to 부터 from 전까지 문자열을 발췌
            int[] temp = Arrays.copyOfRange(array, commands[i][0]-1, commands[i][1]);
            Arrays.sort(temp);
            result[i] = temp[commands[i][2]-1];
        }
        return result;
    }

    public static void main(String[] args) {

        int[] array = {1, 5, 2, 6, 3, 7, 4};
        int[][] commands = {{2, 5, 3}, {4, 4, 1}, {1, 7, 3}};

        FindNumberOrderBy findNumberOrderBy = new FindNumberOrderBy();

        int[] result = findNumberOrderBy.solution(array, commands);

        for (int value : result)
            System.out.print(value + " ");

    }
}

단순하게 이 문제는 Arrays.copyOfRange()를 알고 있다면 훨씬 간결하게 풀수 있는 문제입니다. 처음에는 array를 문자열 타입의 배열로 solution() 메소드의 아규먼트로 넘기려다가 정수형 타입의 배열에서 특정 길이만큼 자를 수 있는 util 클래스를 찾다가 이런 유용한 메소드가 있다는 것을 알게 되었습니다.

JPA Auditing이란?

Java에서 ORM 기술인 JPA를 사용하여 도메인을 관계형 데이터베이스 테이블에 매핑할 때 공통적으로 도메인들이 가지고 있는 필드나 컬럼들이 존재합니다. 대표적으로 생성일자, 수정일자, 식별자 같은 필드 및 컬럼이 있습니다.

도메인마다 공통으로 존재한다는 의미는 결국 코드가 중복된다는 말과 일맥상통합니다.
데이터베이스에서 누가, 언제하였는지 기록을 잘 남겨놓아야 합니다. 그렇기 때문에 생성일, 수정일 컬럼은 대단히 중요한 데이터 입니다.

그래서 JPA에서는 Audit이라는 기능을 제공하고 있습니다. Audit은 감시하다, 감사하다라는 뜻으로 Spring Data JPA에서 시간에 대해서 자동으로 값을 넣어주는 기능입니다. 도메인을 영속성 컨텍스트에 저장하거나 조회를 수행한 후에 update를 하는 경우 매번 시간 데이터를 입력하여 주어야 하는데, audit을 이용하면 자동으로 시간을 매핑하여 데이터베이스의 테이블에 넣어주게 됩니다.

1. Auditi 사용 예제 코드

build.grade에 의존성 추가

dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.projectlombok:lombok')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
}

기본적으로 스프링 부트에서 gradle로 의존성을 관리하게 될 경우 spring-boot-starter-data-jpa만 추가해도 Audit을 하는데는 문제가 없습니다.

참고로 자바 1.8 이상부터는 기존의 문제가 있던 Date, Calander 클래스를 사용하지 않고 LocalDate, LocalDateTime 클래스를 사용합니다. 또한 LocalDateTime 객체와 테이블 사이의 매핑이 안되던 이슈는 하이버네이트 5.2 버전부터 해결이 되었습니다.

BaseTimeEntity.java

@Getter
@MappedSuperclass 
@EntityListeners(AuditingEntityListener.class) 
public abstract class BaseTimeEntity{

    // Entity가 생성되어 저장될 때 시간이 자동 저장됩니다.
    @CreatedDate
    private LocalDateTime createdDate;

    // 조회한 Entity 값을 변경할 때 시간이 자동 저장됩니다.
    @LastModifiedDate
    private LocalDateTime modifiedDate;

}

 

 

어노테이션
설명
@MappedSuperclass JPA Entity 클래스들이 해당 추상 클래스를 상속할 경우 createDate, modifiedDate를 컬럼으로 인식
@EntityListeners(AuditingEntityListener.class 해당 클래스에 Auditing 기능을 포함
@CreatedDate Entity가 생성되어 저장될 때 시간이 자동 저장
@LastModifiedDate 조회한 Entity의 값을 변경할 때 시간이 자동 저장

클래스 상속

Posts.java

@Getter
@NoArgsConstructor
@Entity
public class Posts extends BaseTimeEntity{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 500, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    private String author;

    @Builder
    public Posts(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }

    public void update(String title, String content) {
        this.title = title;
        this.content = content;
    }
}

JPA Auditing 활성화

@EnableJpaAuditing 
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Posts 클래스가 @MappedSuperclass가 적용된 BaseTimeEntity 추상 클래스를 상속하기 때문에 JPA가 생성일자, 수정일자 컬럼을 인식하게 됩니다. 그리고 영속성 컨텍스트에 저장 후 BaseTimeEntity 클래스의 Auditing 기능으로 인해서 트랜잭션 커밋 시점에 플러시가 호출할 때 하이버네이트가 자동으로 시간 값을 채워주는것을 확인 할 수가 있습니다.

스프링 부트의 Entry 포인트인 실행 클래스에 @EnableJpaAuditing 어노테이션을 적용하여 JPA Auditing을 활성화 해야하는 것을 잊지 말아야 합니다.

테스트 수행

    @Test
    public void BaseTimeEntity_등록() throws Exception{
        //given
        LocalDateTime now = LocalDateTime.of(2019,6,4,0,0,0);

        postsRepository.save(Posts.builder()
                .title("title")
                .content("content")
                .author("author")
                .build());

        //when
        List<Posts> postsList = postsRepository.findAll();
        Posts posts = postsList.get(0);

        System.out.println(">>createdDate="+ posts.getCreatedDate() + ", modifiedDate=" + posts.getModifiedDate());

        // then
        assertThat(posts.getCreatedDate()).isAfter(now);
        assertThat(posts.getModifiedDate()).isAfter(now);
    }

간단하게 테스트 코드를 작성하여 실제로 Auditing 기능이 활성화 되어 하이버네이트가 생성일자, 수정일자 값을 자동으로 채워주는지 확인해봤습니다.

실행결과

스크린샷 2019-12-11 오전 12 37 54

참조 문헌: 스프링 부트와 AWS로 혼자 구현하는 웹서비스

'SpringFramework > JPA' 카테고리의 다른 글

객체지향 쿼리 소개(Criteria, QueryDSL, Native Query)  (0) 2019.12.19
JPA에 대한 사실과 오해  (0) 2019.11.28
값 타입  (0) 2019.11.26
영속성 전이 및 고아객체 제거  (0) 2019.11.23
지연로딩과 즉시로딩  (0) 2019.11.23

+ Recent posts