문제는 다음과 같다.

 

처음 보고 배낭문제 비슷하게 풀면 되는줄 알았으나..

자세히 보니 다르다! 무조건 가방보다 작거나 같은 무게 중 비싼 애들만 담으면 되는 비교적 간단한(이게 왜 골드2..?) 문제!

 

package baekjoon;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class 보석도둑_1202 {
    static class Jewel {
        int M, V;

        Jewel(int M, int V) {
            this.M = M;
            this.V = V;
        }
    }
    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(bf.readLine());
        PriorityQueue<Jewel> jewels = new PriorityQueue<>(new Comparator<Jewel>() {

            @Override
            public int compare(Jewel o1, Jewel o2) {
                return Integer.compare(o1.M, o2.M);
            }
            
        });
        int N = Integer.parseInt(st.nextToken()), K = Integer.parseInt(st.nextToken());
        for(int i=0;i<N;i++) {
            st = new StringTokenizer(bf.readLine());
            jewels.offer(new Jewel(Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken())));
        }

        PriorityQueue<Integer> bags = new PriorityQueue<>();
        for(int i=0;i<K;i++) {
            bags.offer(Integer.parseInt(bf.readLine()));
        }

        long ans = 0;
        PriorityQueue<Jewel> tempJewels = new PriorityQueue<>(new Comparator<Jewel>() {

            @Override
            public int compare(Jewel o1, Jewel o2) {
                if(o1.V == o2.V) {
                    return o1.M - o2.M;
                }
                return o2.V - o1.V;
            }
            
        });
        while(!bags.isEmpty()) {
            int bag = bags.poll();
            while(!jewels.isEmpty() && jewels.peek().M <= bag) {
                tempJewels.offer(jewels.poll());
            }
            if(tempJewels.isEmpty()) {
                continue;
            }
            ans += tempJewels.poll().V;
            System.out.println(ans);
        }
        System.out.println(ans);
    }
}

 

저음 보석을 받을 때 Jewel class를 만들어 pq안에 담아준다.

이때의 pq는 무게 M이 낮은 순으로 정렬되게!! (나는 값어치까지 비교하여 넣어주긴 했는데 이건 없어도 된다.)

 

이후 가방을 받고, 가방은 무게만 필요하니 class없이 Integer 값으로!

얘도 다른 pq에 넣어주는데, 무게가 낮은 애부터 정렬되게!! -> 정렬 조건 설정해줄 필요가 없다.

 

이후의 풀이를 단계별로 설명하자면,

1. bags에서 bag 한 개 poll 해온다.

2. bag 보다 작거나 같은 무게를 가진 jewel를 jewels에서 poll 한다.

3. poll한 jewel들을 tempJewels의 priority queue에 넣어준다.

* tempJewels는 값 V가 높은 순으로 정렬되게 Comparator로 정의해준다.(나는 무게까지 비교했는데 이거도 없어도 된다.)

4. tempJewels에서 poll 한 값을 정답으로 return할 값에 더해준다.

 

이 4가지의 단계를 bags에 남은 Integer가 없을 때까지 반복하면 된다!

그럼 정답이 뾰로롱~

곧있으면 플레 간다 야호!!

문제는 다음과 같다.

 

 

 

풀이는 비교적 간단하다.

먼저, 이중for문을 돌면서 기준 단어와 대상 단어의 접두사 길이를 구한다.이후 새로 구한 길이가 더 클 경우에만 업데이트를 한다.

 

package baekjoon;

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

public class 비슷한단어_2179 {

    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(bf.readLine());

        char[][] inputs = new char[N][];

        for(int i=0;i<N;i++) {
            inputs[i] = bf.readLine().toCharArray();
        }

        int idx1 = -1, idx2 = -1, count = -1;

        for(int i=0;i<N-1;i++) {

            for(int j=i+1;j<N;j++) {
                if(inputs[i][0] != inputs[j][0]) continue;
                int cnt = 0;
                while(cnt<inputs[i].length && cnt < inputs[j].length && inputs[i][cnt] == inputs[j][cnt]) {
                    cnt++;
                }
                if(cnt > count) {
                    count = cnt;
                    idx1 = i; idx2 = j;
                }
            }
        }

        for(int i=0;i<inputs[idx1].length;i++) {
            System.out.print(inputs[idx1][i]);
        }
        System.out.println();
        for(int i=0;i<inputs[idx2].length;i++) {
            System.out.print(inputs[idx2][i]);
        }
    }
}

 

주인장은 요즘 리팩토링에 빠졌습니다.

아침에 일어나서 자기 전까지 하루종일 리팩토링만 하고 있는 것 같아요(거짓말입니다)

제 코드 및 다른 팀원들의 코드를 내맘대로 리뷰하는 중 여러 Handler를 사용하면 코드가 더 깔끔하고 유지보수가 편해질 것 같아 상의도 없이 만들고 PR날린 그것,

ErrorHandler, Validator

기존 코드를 봅시다

@Service
@RequiredArgsConstructor
public class BattleServiceImpl implements BattleService {

	//생략

	@Override
	public void registBattle(BattleInviteRequest battleInviteRequest, User user) {
    
    	//생략
    
    	if (battleInviteRequest.getOppositeUserId() == user.getId()) {
			throw new IllegalArgumentException("Not valid user");
		}
		if (battleInviteRequest.getTime() <= 0) {
			throw new RuntimeException("Wrong time setting");
		}

 

아...

진짜 아... 싶죠? 분명 던지는 에러들이 서버 상 오류가아닌 비지니스로직 적 이루어질 수 있는 에러인데, 저렇게 서비스단에 2~3줄 심지어 if문으로 메서드 안에 넣으면...... 나중에 혹시라도 수정하게되거나 로직을 변경해야 되는 일이 생겼을 시 굉장히 귀찮아지겠죠?ㅎㅎ....

 

여기서 제가 생각해낸 방법은

1. 체크해야되는 조건들을 하나의 파일로 묶는다.

2. 오류 결과들을 하나의 파일에 정리한다

3. 던진다!

 

의 과정입니다.

 

먼저, 저 말도안되는 Not a valid user과 Wrong time setting을 과감히 지우고, "ErrorCode"의 Enum을 만들었습니다.

@Getter
@AllArgsConstructor
public enum ErrorCode {

    INVALID_BATTLE_TIME("최소 10분부터 최대 60분까지 설정 가능합니다."),
    INVALID_USER("유저 설정이 유효하지 않습니다."),
    INVALID_BATTLE_STARTTIME("시작 시간은 최소 60분 후부터 가능합니다."),
    INVALID_OPINION_LENGTH("선택지는 최대 16자까지 작성 가능합니다."),
    DUPLICATED_BATTLE("이미 해당시간에 예정된 배틀이 존재합니다."),
    ENDED_BATTLE("응답 기간이 종료된 배틀입니다.");

    private final String message;
}

 

Wrong time setting보다 저렇게 친절하게 message를 적어준다면 프론트분들도 처리하기 더더욱 쉽겠죠?ㅎㅎ

public class CustomException extends RuntimeException {

    private final ErrorCode errorCode;

    public CustomException(ErrorCode errorCode) {
       super(errorCode.name());
       this.errorCode = errorCode;
    }

    public ErrorCode getErrorCode() {
       return errorCode;
    }
}

 


그리고, 저 errorcode를 throw할 수 있는 custom exception을 정의해줍니다.

 

여기서 잠깐! RuntimeException을 extends하는 이유!
왜 하필 많은 오류 중 Runtime exception일까! 저는 굉장히 궁금했습니다. 사실 이름만 보면 그냥 Exception이 더 일반적으로 보이는데 쓰면 계속 throw를 하래잖아요! 그래서 패트병 대신 텀블러로 커피를 1회 마시고 GPT선생님께 여쭈어봤죠.

 

역시 우리으 친절하신 즤선생님,,

 

 

RuntimeException과 IllegalArgumentException과 같이 평소에 우리가 사용해도 try-catch 등을 쓰지 않아도 되는 Exception들은 Unchecked Exception이라고 합니다! 그에 반해, 일반 Exception은 Checked Exception이라고 try-catch나 throws를 해야된다고 하네요.

 

그래서 아무튼 Exception이 아닌 것들 중 가장 무난한 RuntimeException을 채택하였습니다.

 

그 후 원래 제가 만들어놨던 GlobalExceptionHandler 안에 제 작고 소중한 CustomException도 추가해줬어요...><

 

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
	public ResponseEntity<ApiResponseDto> handleRuntimeException(RuntimeException error) {
		ApiResponseDto res = new ApiResponseDto("fail", error.getMessage(), null);
		return new ResponseEntity<>(res, HttpStatus.BAD_REQUEST);
	}
    
    //생략

    @ExceptionHandler(CustomException.class)
    public ResponseEntity<ApiResponseDto<String>> handleCustomException(CustomException error) {
        ApiResponseDto<String> response = new ApiResponseDto<>("fail", error.getErrorCode().getMessage(), null);
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }
}

 

첫번째 메서드가 제가 직접 사용하던 RuntimeException 이고요, 밑이 제가 이제 사용하는 CustomException입니다!!

사실 기능상의 차이는 없어요~! RuntimeException도 message를 반환하고, 제 custom handler도 완전 같은 리턴타입이기에..

하지만, 귀엽잖아요.

 

다꾸, 폰꾸에 이어 에꾸(에러 꾸미기)까지... 즐겁지않나요?><

 

그리고 이제 Error을 Throw해주는데 사용될 Validator을 만들어줍니다~

@Component
@AllArgsConstructor
public class BattleValidator {

    static Date now = new Date();
    static Calendar calendar = Calendar.getInstance();
    private final BattleRepository battleRepository;

    public void validateOppositeUser(BattleInviteRequest battleInviteRequest, User user) {
       if (battleInviteRequest.getOppositeUserId() == user.getId()) {
          throw new CustomException(ErrorCode.INVALID_USER);
       }
    }

    public void validateTime(int minutes) {
       if (minutes < 10) {
          throw new CustomException(ErrorCode.INVALID_BATTLE_TIME);
       }
    }

 

아까랑 똑같은 코드에 똑같은 결과를 반환하는데, 이렇게 설정하는것이 훨씬 간지작살나지 않나요?

이제 서비스단에 이 Validator 내 메서드를 적용하면...!!

 

@Transactional
@Override
public void registBattle(BattleInviteRequest battleInviteRequest, User user) {

	//생략...

    battleValidator.validateOppositeUser(battleInviteRequest, user);
    battleValidator.validateTime(battleInviteRequest.getTime());
    battleValidator.validateStartTime(battleInviteRequest.getStartDate());
    battleValidator.checkOtherBattles(user, battleInviteRequest.getStartDate(), endDate);
}

 

TADA!!!!!

나만의 귀여운 코꾸(코드꾸미기...), 완성!!!

아까 말도안되는 if문과 비교해보면,, 훨씬 귀엽고 깜찍해졌죠?

 

이렇게 작성한 코드는 후에 다른사람이 보았을 때도 이해하기 쉽고, 유지보수도 용이해집니다~

 

사실 저는 1년전 이맘때쯤, 굉장한 홍머병에 걸렸었는데요,

그때는 플젝때도 남들이 내 코드를 이해하기 어렵고, 건드리지 못하는 것이 뭔가 고수같고 개짱짱맨 코드인 줄 알았답니다...

하하 코드는 무조건 간결!!해야하고 모듈화가 잘 되어있어야합니다~

 

이거는 알림 기능에서도 적용할 수 있는데요!

어떠한 알림을 보낼 지 프론트님님님들과 상의 후

public enum NotificationType {
    BATTLE_REQUEST(0, "%s님이 배틀을 신청했어요! 지금 바로 확인해보세요."),
    LIVE_NOTICE(1, "[%s] 라이브 시작 5분 전입니다!"),
    BATTLE_ACCEPT(2, "[%s] 배틀이 승낙되어 대기중입니다."),
    BATTLE_DECLINE(3, "[%s] 배틀이 거절되었습니다."),
    BATTLE_UNSATISFIED(4, "[%s] 배틀이 인원수를 충족하지 못해 모닥불로 이동하였습니다."),
    BATTLE_SATISFIED(5, "[%s] 배틀이 인원수를 충족하여 불구경 대기중입니다.");
    //todo 인원 수 미달 -> 모닥불 (참여자, 개최자)
    //todo 인원 수 충족 -> 라이브 개최 (참여자, 개최자)

    private final int code;
    private final String messageTemplate;

    NotificationType(int code, String messageTemplate) {
       this.code = code;
       this.messageTemplate = messageTemplate;
    }

    public int getCode() {
       return code;
    }

    public String getMessageTemplate() {
       return messageTemplate;
    }
}

 

이렇게 enum으로 정리해놓으며, 추가/수정하면 편하답니다~

 

그럼 여기서 퀴즈! 알림 메서드들의 문구들을 어떻게하면 더 그럴싸하게 작성할 수 있을까요?(제발도와주세요 저거 넘 구린거같아)

 

그럼 담에 또봐요 안뇽~~~

바바룽

 

'개발일지' 카테고리의 다른 글

[개발일지01] Pagination이란 무엇인가?  (0) 2024.07.28

문제는 이러하다.

 

 

기존의 알파벳과 다르게 a,b,k,d,e,g,h,i,l,m,n,ng,o,p,r,s,t,u,w,y 순으로만 사용되고,

여기서 눈 여겨 볼 점은

 

1. n과 o 사이의 ng

2. k의 위치

3. ng를 기준으로 앞 c, f, j 뒤 q, v, x, z 부재

 

정도인 것 같다.

풀이의 흐름은 다음과 같다.

 

1. c는 존재하지 않으며 그 위치에 k가 있으므로 입력을 받을 시 c -> k로 치환.

 

2. ng를 처리하기위해 ng 다음 알파벳인 o, p, r, ... , y를 char기준으로 +1씩 늘려줌.

예를 들면 ..., m, n, o(==ng), p(==o), q(==o), s(==r), t(==s), u(==t), ...

 

package baekjoon;

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

public class 민식어_1599 {
    static class Word {
        String before, after;
        Word(String before) {
            this.before = before;
        }
    }
    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(bf.readLine());
        ArrayList<Word> arr = new ArrayList<>();

        for(int i=0;i<N;i++) {
            String input = bf.readLine();
            Word word = new Word(input);
            word.after = "";

            for(int j=0;j<input.length();j++) {
                if(input.charAt(j)=='g' && j != 0 && input.charAt(j-1)=='n') continue;
                if(input.charAt(j) == 'k') {
                    word.after += "c"; continue;
                } else if(input.charAt(j) >= 'n') {
                    if(input.charAt(j)=='n' && j < input.length()-1 && input.charAt(j+1)=='g') {
                        word.after += "o"; continue;
                    } else if(input.charAt(j)=='n') {
                        word.after += "n"; continue;
                    } else {
                        word.after += Character.toString((char)(input.charAt(j) + 1));
                    }
                } else {
                    word.after += Character.toString(input.charAt(j));
                }
            }
            arr.add(word);
        }

        Collections.sort(arr, new Comparator<Word>() {

            @Override
            public int compare(Word o1, Word o2) {
                return o1.after.compareTo(o2.after);
            }
            
        });
        for(Word w : arr) {
            System.out.println(w.before);
        }
    }
}

 

원래는 그냥 char배열로 받고 출력 전 원래의 단어로 돌릴까 싶었지만, 아무래도 새로운 class로 변경 전 알파벳과 변경 후 알파벳을 각각 저장하는 게 날 것 같았다.

 

아무튼, 성공!

인스타그램이나 유튜브와 같은 매체에서 검색 시,

G O O O O ... L E 와 같이 클릭하여 페이지를 넘기는 형식과 다르게

끝도 없이 스크롤이 생기며 컨텐츠들이 로드되는데요!

 

이런 무한스크롤을 구현하기 위해서 필요한 것이 Pagination 입니다!!

사실 무한스크롤 뿐만 아니라 그냥 값들을 다 받아올 때 Pagination을 쓰는 것을 추천드립니다.

데이터가 몇백,, 몇천을 넘어 몇백만개,, 이거 다 한번에 넘길 거 아니잖아요?

 

모든 코드는 Java, Spring(boot) 개발 환경을 기준으로 작성합니다!!

 

먼저, 프론트로부터 페이지 값을 넘겨받습니다.

가령, 유저가 처음으로 데이터들을 요청을 했을 시 프론트가 백으로 요청해야되는 page 값은 1이되겠죵!(default 값)

무한스크롤로 구현할 시 현재 2페이지 마지막 부분을 유저가 보고있다면, page = 3으로 요청을 해야겠죵

 

여기서 두 가지로 방법이 나뉩니다.

 

1. 처음부터 pageable 개체로 받기

@PostMapping("/get-list-by-pageable")
public ResponseEntity<?> getListByPageable(Pageable pageable, @RequestBody Object otherMethods) {
	Page<?> page = pageServiceImpl.getListByPageable(pageable, otherMethods);
    // 이하생략...
}

 

2. int page, int sort, int size 등 param값으로 넘겨받기

@PostMapping("/get-list-by-params")
public ResponseEntity<?> getListByParams(@RequestParam("page") int page, @RequestParam("size") int size, @RequestParam("sort") int sort, @RequsetBody Object otherMethods) {
	Page<?> page = pageServiceImpl.getListByParams(page, size, sort, otherMethods);
    // 이하 생략...
}

 

사실 뭐가됐듬 상관 없습니다 하지만 전 2번이 더 좋더라고용 처리하기가 쉬워서

 

그 다음, 서비스 단에서 Pageable 객체를 생성합니당

public Page<?> getListByParams(int page, int size, int sort, Object otherMethods) {
	//pageable 객체를 만들어주기~
	Pageable pageable = PageRequestOf(page, size, sort);
    List<?> list = pageMapper.getList(otherMethods);
    
    return new PageImpl<>(list, pageable, list.size());
}

 

만약 본인이 Controller단에서부터 Pageable로 받는다!! 하면 Pageable 객체를 만들어 줄 필요가 없겠죠~~

List를 가져온 다음, 만들거나 받아온 pageable 객체와 함께 Page를 만들어서 return하면 된답니다!

 

Swagger로 돌려보면!

    "pageable": {
        "sort": {
            "empty": true,
            "sorted": false,
            "unsorted": true
        },
        "offset": 0,
        "pageNumber": 0,
        "pageSize": 5,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalElements": 99,
    "totalPages": 20,
    "size": 5,
    "number": 0,
    "sort": {
        "empty": true,
        "sorted": false,
        "unsorted": true
    },
    "first": true,
    "numberOfElements": 99,
    "empty": false

 

이런식으로 list와 함께 현재 페이지 정보가 같이 return되는 것을 알 수 있어요~

참 쉽죠!

 

+ Recent posts