문제 캡처

소스 코드

import java.util.*;
import java.io.*;

public class 최소편집_15483 {
	public static void main(String[] args) throws Exception {
		BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
		String A = bf.readLine();
		String B = bf.readLine();
		int[][] dp = new int[A.length()+1][B.length()+1];

		for (int i = 0; i < dp.length; i++) {
			dp[i][0] = i;
		}
		for (int i = 0; i < dp[0].length; i++) {
			dp[0][i] = i;
		}
		
		for (int i = 1; i < A.length()+1; i++) {
			for (int j = 1; j < B.length()+1; j++) {
				if(A.charAt(i-1) == B.charAt(j-1)) {
					dp[i][j] = dp[i-1][j-1];
				}else {
					dp[i][j] = Math.min(Math.min(dp[i-1][j], dp[i][j-1]),dp[i-1][j-1])+1;					
				}
			}
		}
		
		System.out.println(dp[A.length()][B.length()]);

	}
}	

Comment…💭

  • 전형적인 DP문제,,각 자리에 알파벳을 비교하며 같으면 직전 알파벳 비교한것과 같은값으로, 아니라면 추가(dp[i][j-1]), 삭제(dp[i-1][j]), 교체(dp[i-1][j-1]) 한 값 중 가장 작은 값 선택 후 횟수 +1 하기!

문제는 다음과 같다.

나도 출튀를 참 즐겨했었지

 

숫자 N개를 입력받아, 숫자끼리 비교하며 0~9 중 같은 숫자가 쓰이는 경우가 있다면 ans++ 해주면 된다.

앗... 자리수가 0~9까지로 단 10자리?!

둘 다 있는지 비교?!

이것은...

비트마스킹.

과거의 나는 비트마스킹을 정말 싫어하였지만

이제는 그것도 다 옛말.

나는 비트의 몸을 맡기는 비트마스커 뤂시우가 되었다.

 

N개의 수를 입력받을 때, 각각의 자리수로 for문을 한 번 더 돌려, 해당하는 수의 위치의 비트를 1로 바꿔준다.

예를 들어, 비트를 바꾸기 전

int number = 0;

으로 설정해준다.

 

해당 number을 이진수로 표현하자면

number 9 8 7 6 5 4 3 2 1 0
여부 0 0 0 0 0 0 0 0 0 0

 

일 것이다.

 

만약 입력받은 수가 32라면

String temp = "32";

 

temp.length()로 for문을 돌렸을 때, 처음 temp.charAt(POINT) 은 '3'일 것이다.

그렇다면, 우리가 number에 적용해야 하는 숫자는

temp 9 8 7 6 5 4 3 2 1 0
여부 0 0 0 0 0 0 1 0 0 0

 

일 것이다.

따라서 1을 왼쪽으로 3번 shift연산 후,

 number |= temp.charAt(j) - '0';

 

number와 이를 합치면 된다!

 

2도 이와같이 진행하면, 최종적으로 number은

number 9 8 7 6 5 4 3 2 1 0
여부 0 0 0 0 0 0 1 1 0 0

1100 이렇게 될 것이다.

 

 

마찬가지로, 282도 이와같이 나타낼경우 

number 9 8 7 6 5 4 3 2 1 0
여부 0 1 0 0 0 0 0 1 0 0

100000100 이렇게 될 것이다. 마지막 2를 적용할 때, 이미 2가 1이 있지만 합집합 연산하면 그대로 1이 적용된다.

 

이 숫자들을 HashMap nums에 갯수와 함께 넣는다!

 

 

그 후, HashMap을 돌면서 같은 nums에 대해 1개 이상일 경우 ans를 업데이트 해준다.

        for (Map.Entry<Integer, Integer> entry:nums.entrySet()) {
            int num = entry.getKey();
            int cnt = entry.getValue();
            
            ans += (long)cnt*(cnt-1)/2; //cnt 중 순서 없이 2개 뽑기

 

이 num을 다른 HashMap내 num 들과 비교해아하는데, 중복 방지를 위해 현재의 num보다 큰 애들과 비교해준다.

비교하는 수를 num = 32, num2 = 282라고 가정해보자.

 

두 수는 "2" 라는 공통된 수를 갖고있다. 이는 비트마스킹 된 값으로 쉽게 찾을 수 있는데

num 9 8 7 6 5 4 3 2 1 0
여부 0 0 0 0 0 0 1 1 0 0

이것은 32의 nums 값이고,

num2 9 8 7 6 5 4 3 2 1 0
여부 0 1 0 0 0 0 0 1 0 0

이것은 282의 nums 값이다.

이 둘을 교집합연산 (&) 하면

num&num2
9
8 7 6 5 4 3 2 1 0
여부 0 0 0 0 0 0 0 1 0 0

공통으로 갖는 2만 1이되고, 나머지는 0이 됨을 알 수 있다.

 

따라서 num&num2 연산을 했을 시 0이 아니라면, 공통된 수를 갖고 있는 것이다!

이때는 num이 가지고있는 갯수 중 하나와, num2가 가지고 있는 갯수 중 하나를 뽑아야되기 때문에 nums.get(num) * nums.get(num2) 로 ans를 업데이트한다.

            for (int num2 : nums.keySet()) {
                if(num >= num2) continue; //num보다 작은 경우 continue처리
                if((num & num2) != 0) {
                    ans += (long) cnt*nums.get(num2);
                }
            }

 

이 과정을 모두 끝낸다면

 

맞으면 된다!

 

 

 

전체 코드

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

public class 수업시간에교수님몰래교실을나간상근이_2825 {
    public static void main(String[] args) throws NumberFormatException, IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(bf.readLine());
        Map<Integer, Integer> nums = new HashMap<>();

        for(int i = 0; i < N; i++) {
            String temp = bf.readLine();
            int number = 0;
            for(int j = 0; j < temp.length(); j++) {
                number |= 1 << (temp.charAt(j) - '0');
            }
            nums.put(number, nums.getOrDefault(number, 0) + 1);
        }

        long ans = 0;
        for (Map.Entry<Integer, Integer> entry:nums.entrySet()) {
            int num = entry.getKey();
            int cnt = entry.getValue();
            
            ans += (long)cnt*(cnt-1)/2;
            
            for (int num2 : nums.keySet()) {
                if(num >= num2) continue;
                if((num & num2) != 0) {
                    ans += (long) cnt*nums.get(num2);
                }
            }
        }
        System.out.println(ans);
    }
}

 

 

 

 

 

 

'Algorithm' 카테고리의 다른 글

[boj] 사냥꾼_8983JAVA  (0) 2024.09.22
[boj] 최소 편집_15483JAVA  (0) 2024.09.16
[boj] RGB거리 2_17404 JAVA  (0) 2024.09.01
[boj] 보석 도둑_1202 JAVA  (0) 2024.08.25
[boj] 비슷한 단어_2179 JAVA  (0) 2024.08.18

문제는 다음과 같다핑

 

문제에 대하여 간략히 설명을 해보자면,

 

1. 각 집은 빨강, 초록, 파랑 중 하나로 칠해야 한다.

2. 옆집과 같은 색으로 칠할 수 없다.

3. 첫 번째 집과 마지막 집도 서로 다른 색이어야 한다.

 

의 조건을 만족하는, 색 최소비용을 더해서 구하면 된다.

 

import java.io.*;
import java.util.StringTokenizer;


public class RGB거리2_17404 {

    public static void main(String[] args) throws IOException {
        BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(bf.readLine());
        int[][] arr = new int[n + 1][3], dp = new int[n + 1][3];
        StringTokenizer st = null;

        for(int i = 1 ; i <= n; i++){
            st = new StringTokenizer(bf.readLine());
            for(int j = 0 ; j < 3; j++){
                arr[i][j] = Integer.parseInt(st.nextToken());
            }
        }

        int ans = 10000001;
        for(int k = 0; k < 3; k++) {
            for(int i = 0 ; i < 3; i++) {
                if(i == k) {
                    dp[1][i] = arr[1][i];
                }
                else {
                    dp[1][i] = ans;
                }
            }

            for (int i = 2; i <= n; i++) {
                dp[i][0] = Math.min(dp[i - 1][1], dp[i - 1][2]) + arr[i][0];
                dp[i][1] = Math.min(dp[i - 1][0], dp[i - 1][2]) + arr[i][1];
                dp[i][2] = Math.min(dp[i - 1][0], dp[i - 1][1]) + arr[i][2];
            }
            
            for(int i = 0 ; i < 3; i++) {
                if(i != k) {
                    ans = Math.min(ans, dp[n][i]);
                }
            }
        }
        System.out.println(ans);
    }
}

 

전형적인 dp 문제!

먼저 arr 배열로 색 별 가격을 쭉 받은다음, dp 배열로 집을 빨강, 초록, 파랑으로 칠하는 세 가지 경우를 각각 고려한다.

그 후 dp 배열을 만들어 dp[i][j]를 i번째 집을 색깔 j로 칠할 때의 최소 비용으로 둔다.

dp[i][j] = min(dp[i-1][k]) + arr[i][j]

 

마지막 집은 첫 번째 집과 같은 색이 될 수 없으므로, 첫 번째 집의 색깔과 다른 색깔 중 최소 비용을 선택한다.

                if(i != k) {
                    ans = Math.min(ans, dp[n][i]);
                }

 

 

문제는 다음과 같다.

 

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

자세히 보니 다르다! 무조건 가방보다 작거나 같은 무게 중 비싼 애들만 담으면 되는 비교적 간단한(이게 왜 골드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으로 정리해놓으며, 추가/수정하면 편하답니다~

 

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

 

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

바바룽

 

문제는 이러하다.

 

 

기존의 알파벳과 다르게 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