2022.12.01 (화요일)

yeonicerely·2022년 12월 1일
0

1. 알고리즘

A. 프로그래머스 명예의 전당(1)

출처: https://school.programmers.co.kr/learn/courses/30/lessons/138477

(1) 명예의 전당에는 가장 큰 k개의 숫자가 들어갈 수 있다
(1-1) 새로운 숫자가 들어오면 최솟값과 비교해서 크면 최솟값을 제거하고 새로운 숫자를 넣는다.
(2) 매번 최솟값을 array에 담아서 print해야 한다.


→ [1] k개의 숫자가 들어있어야 하므로 size를 확인해야 한다.
→ [2] 매번 최솟값이 무엇인지 알 수 있어야 한다.

1) 우선순위 큐 이용하기

    int[] selectKNumWithPriorityQueue(int k, int[] score){
        Queue<Integer> integerQueue = new PriorityQueue<>();
        int[] minScores = new int[score.length];
        int idx = 0;

        for (int i = 0; i < score.length; i++) {
			// [1]우선 순위 큐의 사이즈 확인하기 -> .size()를 이용
            if(integerQueue.size() < k){
                integerQueue.add(score[i]);
                int dd = integerQueue.peek();
                // [2]우선 순위 큐는 디폴트로 작은 숫자의 우선순위가 높으므로 
                // peek()를 하면 최솟값을 확인할 수 있음
//                System.out.println(integerQueue +"1");
//                System.out.printf("%d %d\n", idx, dd);
                minScores[idx] = dd;
                idx++;
            } else{
                if(integerQueue.peek() < score[i]){
                    integerQueue.poll();
                    integerQueue.add(score[i]);
                    int dd = integerQueue.peek();
                    // 제거한 후 새로 들어온 것 중에서 최솟값을 구해줘야함
                    minScores[idx] = dd;


//                    System.out.println(integerQueue + "2");
//                    System.out.printf("%d %d\n", idx, dd);
                    idx++;
                } else{
                    int dd =integerQueue.peek();
                    minScores[idx] = dd;
//                    System.out.println(integerQueue + "3");
//                    System.out.printf("%d %d\n", idx, dd);

                    idx++;
                }
            }
        }

        return minScores;

    }

2) 리스트 이용하기

최솟값 파악하는 방법?: (1) minValue라는 변수에 기록하기
(2) Collections.sort()를 이용해서 정렬한 후 최솟값을 minValue에 업데이트 하기

    int[] selectKNumWithList(int k, int[] input){
        int[] minScores = new int[input.length];
        List<Integer> integerList = new ArrayList<>();


		// [2] 최솟값이 무엇인지 파악하기: 최솟값을 변수로 기록하고 갱신하기
        int minValue = input[0];
        
        for (int i = 0; i < input.length; i++) {
        	// 리스트의 사이즈 확인하기: .size()를 이용
            if(integerList.size() < k){
                integerList.add(input[i]);
                minValue = Math.min(minValue, input[i]);

            } else{
                if(minValue < input[i]){

                    int minIdx = integerList.indexOf(minValue);
                    integerList.remove(minIdx);
                    integerList.add(input[i]);
                    
                    // [2] 최솟값: Collections.sort로 정렬한 후 갱신하기
                    Collections.sort(integerList);
                    minValue = integerList.get(0);

                }
            }

            minScores[i] = minValue;
        }

        return minScores;

    }

B. 기사단원의 무기

출처: https://school.programmers.co.kr/learn/courses/30/lessons/136798

(1) 약수의 개수만큼 power를 가진 무기를 구매할 수 있다.
(2) 만약 power가 limit를 초과하면 정해진 power의 무기를 제공한다.
(3) 무기를 만들기 위해 필요한 power의 합(약수의 개수의 합)을 구하고자 한다.


→ [1] 약수의 개수를 구한다.
→ [2] 약수의 개수가 limit를 초과하면 다른 숫자로 바꾼다.
→ [3] 1에서 number까지의 약수 개수의 합을 구한다.

  • 약수의 개수 구하기

8의 약수는 1-8, 2-4 이렇게 곱하면 자기자신(8)이 되도록 짝지어서 나타난다.
9의 약수도 1-9, 3-3 곱하면 자기자신이 되도록 나타나지만 3(루트 9)는 똑같은 숫자이므로 한번만 센다
→ N의 약수의 개수를 구할 때 루트 N 까지만 반복문을 돌린 뒤 그 수가 약수인지를 확인한 후 루트 N은 1개로, 나머지의 경우는 2개로 계산하면 된다.

반복문의 범위를 N까지로 하면 O(N)이지만 루트 N까지만 반복문을 돌리면 O(sqrt(N)) 이므로 시간 복잡도와 공간복잡도에서 더 유리하다.

    int calculateNumOfDivisor(int input){
        int num = 0;

        for (int i = 1; i*i <= input; i++) {
            if(i*i == input){
                num += 1; // 제곱근인 경우 하나만
            } else if (input % i ==0) {
                num += 2; // 아닌 경우 약수가 쌍이니까 2개
            }

        }
        return num;
    

    public int solution(int number, int limit, int power) {

        int answer = 0;

        for (int i = 1; i <= number; i++) {
            int num = calculateNumOfDivisor(i);

            if(num > limit){
                answer += power;
            } else{
                answer += num;
            }
        }

        return answer;
    }

2. Spring Boot Security를 이용해서 회원가입과 로그인 기능 구현하기

3. 키워드를 통한 Java와 SpringBoot 복습

A. stream

기존에는 배열이나 컬렉션에 데이터를 저장하고 조건에 맞게 출력하기 위해서는 for문을 이용했고, 데이터를 정렬하기 위해서는 Collections.sort()나 Arrays.sort()와 같은 개별적인 클래스에 맞는 메서드를 이용해야 했다. 이러한 문제점을 해결하기 위해(하나의 방법으로 통일하기 위해) Java 8 부터 등장한 것이 stream이다.

  • .filter(조건): 조건에 해당되는 것만 걸러주는 메소드
  • .map(): collection의 원소들의 타입을 변경해주는 메소드

원래 코드

// teachers는 teacher가 들어있는 리스트
List<Teacher> likeSpringBootTeachers = new ArrayList<>();
for (int i=0; i< teachers.size(); i++){
	if(teachers.get(i).isSpringBoot()){
    	likeSpringBootTeachers.add(teachers.get(i));
    }


}

.filter()를 사용한 후

List<Teacher> likeSpringBootTeachers = teachers.stream()
		.filter(teacher -> teacher.isLikeSpringBoot() == true) // .filter(teacher::isLikeSpringBoot()) 와 동일함
		.collection(Collections.toList());

.filter()와 .map()을 사용한 후

List<String> likeSpringBootTeachers = teachers.stream()
		.filter(teacher -> teacher.isLikeSpringBoot() == true)
		.map(teacher -> teacher.getName())
		.collect(Collectors.toList());

B. @RestControllerAdvice

기존에는 Exception이 발생하면 사용자(Client)에게 어떤 Exception이 발생했는지 알려주기 위해서는 Controller로 이동시켜야 했었다.(Respository에서 Exception이 발생하면 그 Exception은 Service -> Controller -> User로 이동시켜야 했음)
하지만 @RestControllerAdvice는 모든 에러를 모아서 Client에게 전달해줄 수 있다.

1) @ExceptionHandler

ExceptionHandler를 이용하면 특정 에러만 Client에게 전달해줄 수 있다.

@RestControllerAdvice
public class ExceptionManager {

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<?> runtimeExceptionHander(RuntimeException e){

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(Response.error(e.getMessage()));

    }
}

2) CustomException

자바에서 정의한 예외 말고 비즈니스 로직에 따라서 발생할 수 있는 다른 예외(ex. 회원 가입 시 아이디 중복, 로그인 시 아이디를 조회할 수 없음, 로그인 시 비밀번호가 틀린 경우 등등)가 발생할 수 있는데 이것을 CustomException을 통해 추가해줄 수 있다.
내가 정해놓은 에러코드만 client에게 보여주고 싶으면 enum을 이용해서 정의해줄 수 있다.

(1) Custom Exception

@Getter
@AllArgsConstructor
public class HospitalReviewAppException extends RuntimeException{

	// 에러코드를 정의해놓은 Enum을 필드로 받아서 
    // RuntimeException에서 정의하지 않은 Exception도 처리할 수 있음 
    private ErrorCode errorCode;
    private String message;

    @Override
    public String toString() {

        if(message == null) return errorCode.getMessage();
        return String.format("%s %s", errorCode.getMessage(), message);
    }
}

(2) 원하는 에러코드를 Enum을 이용해서 정의해놓은 것

@AllArgsConstructor
@Getter
public enum ErrorCode {
    DUPLICATED_USER_NAME(HttpStatus.CONFLICT, "User name이 중복됩니다."),
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "internal server error가 발생했습니다."),
    USER_NOT_FOUNDED(HttpStatus.NOT_FOUND, "User를 찾지 못했습니다."),
    INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "잘못된 비밀번호 입니다.")
    ;

    private HttpStatus httpStatus;
    private String message;

}

(3) @RestControllerAdvice에서 CustomException 처리

@RestControllerAdvice
public class ExceptionManager {

    @ExceptionHandler(HospitalReviewAppException.class)
    public ResponseEntity<?> hospitalReviewAppExceptionHandler(HospitalReviewAppException e){
        return ResponseEntity.status(e.getErrorCode().getHttpStatus())
                .body(Response.error(e.getErrorCode().getMessage()));
    }
}

C. @Bean

new를 이용해서 선언을 해주면 선언해주는 만큼 heap 영역에 저장된다는 문제점이 있다. 따라서 SpringBoot에서는 @Bean을 이용해서 빈으로 등록해주면 application context에 올라가서 new를 여러 번 해주지 않아도 사용할 수 있도록 해준다.

1) @Configuration

특정 클래스의 메소드를 @Bean을 이용해 수동으로 bean으로 등록하려면 싱글톤을 보장하기 위해서 @Configuration 어노테이션을 활용해서 그 클래스에서 bean을 등록해준다고 명시를 해주어야 한다.

@Component를 이용해서 자동으로 빈을 등록해도 되지만 이렇게 하면 해당 클래스의 객체가 1개만 생성되도록 제어가 되어 메소드를 한 번만 호출할 수 있다. 하지만 @Bean으로 직접 등록을 하면 해당 메소드를 여러번 호출할 수 있다. 이 때 여러 개의 빈이 생성될 수 있는데 @Configuration 어노테이션을 붙여주면 여러번 호출을 해도 항상 동일한 객체를 반환하여 싱글톤을 보장해줄 수 있다.

// 출처: https://mangkyu.tistory.com/234
@Configuration
public class MyBeanConfiguration { 

    @Bean 
    public MangKyuResource mangKyuResource() {
        return new MangKyuResource(); 
    } 
    
    @Bean 
    public MyFirstBean myFirstBean() { 
        return new MyFirstBean(mangKyuResource()); 
    } 
    
    @Bean 
    public MySecondBean mySecondBean() { 
        return new MySecondBean(mangKyuResource()); 
    } 
}

2) @Component, @ Service, @Controller, @Repository

@Component는 @Service와 @Configuration, @Repository, @Configuration의 메타어노테이션(meta-annotation, 다른 annotation에서도 사용되는 annotation, 부모 클래스와 비슷하다고 볼 수 있다.)으로 @Bean을 지정해준다.

(1) @Service
비즈니스 로직이나 respository layer 호출하는 함수에 사용된다.

@Service
public class UserService {


    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User join(UserJoinRequest userJoinRequest) {
        userRepository.findByUserName(userJoinRequest.getName())
                .ifPresent(user -> {throw new HospitalException(ErrorCode.DUPLICATED_USER_NAME, "사용자 " + userJoinRequest.getName() + "이 존재합니다.");
                });


        User savedUser = userRepository.save(userJoinRequest.toEntity());

        return savedUser;
        
    }
}

(2) @Repository
DB나 다른 파일과 같은 외부와의 입출력을 처리한다.

@Repository("userDAO")
public class HospitalDao {

    public void add(String[] inputs) throws ClassNotFoundException, SQLException {

        Map<String, String> env = System.getenv();
        String dbHost = env.get("DB_HOST");
        String dbUser = env.get("DB_USER");
        String dbPassword = env.get("DB_PASSWORD");

        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection(dbHost,dbUser,dbPassword);
        PreparedStatement ps = conn.prepareStatement("INSERT INTO seoul_hospital(id, category, emergency,name) VALUES(?, ?, ?,?)");

        ps.setString (1, inputs[0]);
        ps.setString(2, inputs[1]);
        ps.setString(3,inputs[2]);
        ps.setString(4,inputs[3]);

        ps.executeUpdate();
        ps.close();
        conn.close();

    }
...
}

(4) @Controller
Web MVC 코드에 사용되는 어노테이션으로 웹과의 요청과 응답을 처리한다.@RequestMapping 어노테이션을 해당 어노테이션 밑에서만 사용할 수 있다.

@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Slf4j
public class UserController {

    private final UserService userService;

    @PostMapping("/join")
    public Response<UserJoinResponse> join(@RequestBody UserJoinRequest userJoinRequest){
        log.info(userJoinRequest.getUserName(), userJoinRequest.getEmail());
        UserDto userDto = userService.join(userJoinRequest);
        return Response.success(new UserJoinResponse(userDto.getUserName(), userDto.getEmailAddress()));

    }

    @PostMapping("/login")
    public Response<UserLoginResponse> login(@RequestBody UserLoginRequest userLoginRequest){
        String token = userService.login(userLoginRequest.getUserName(), userLoginRequest.getPassword());
        return Response.success(new UserLoginResponse(token));
    }

}

출처
1. https://sanghye.tistory.com/39
2. https://baek-kim-dev.site/64
3. https://mangkyu.tistory.com/75
4. https://mangkyu.tistory.com/234

0개의 댓글