Graph 문제, AOP

calis_ws·2023년 6월 28일
0
post-custom-banner

Graph 문제 풀이

미로 탐색

public class Maze {
    private int[] dx = new int[]{-1, 1, 0, 0};
    private int[] dy = new int[]{0, 0, -1, 1};

    // 최단 거리를 돌려준다
    public int solution(int[][] maze) {
        // BFS 로직을 활용하는데,
        // 다음에 접근할 수 있는 칸을 maze의 가로 세로 크기 이내의 인접한 칸
        // 을 기준으로 판단

        // int[]를 담는 Queue, {x, y, 여태까지 이동거리}
        Queue<int[]> visitNext = new LinkedList<>();
        // 미로에서 이미 도달한 적 있는 칸임을 나타내기 위한 visited 이차원 배열
        boolean[][] visited = new boolean[6][6];
        // 좌 하단에서 시작
        visitNext.offer(new int[]{5, 0, 0});
        int answer = -1;

        // BFS 시작
        // Queue가 비어있지 않은 동안
        while (!visitNext.isEmpty()) {
            // 다음에 갈곳을 Queue에서 꺼낸다.
            int[] next = visitNext.poll();
            int nowX = next[0];
            int nowY = next[1];
            int steps = next[2];

            // 현재 칸이 3 이라면
            if (maze[nowX][nowY] == 3) {
                answer = steps;
                break;
            }
            visited[nowX][nowY] = true;

            // 4개의 방향을 확인한다.
            for (int i = 0; i < 4; i++) {
                // 다음에 도달해야 할 위치
                int nextX = nowX + dx[i];
                int nextY = nowY + dy[i];
                if (
                        // 1. 미로의 범위를 벗어나진 않는지
                        checkBounds(nextX, nextY) &&
                        // 2. 미로에서 진행 가능한 칸인지 (0 또는 3)
                        (maze[nextX][nextY] == 0 || maze[nextX][nextY] == 3) &&
                        // 3. 아직 방문한적 없는지
                        !visited[nextX][nextY]
                ) {
                    // 큐에 방문 대상으로 기록
                    visitNext.offer(new int[]{nextX, nextY, steps + 1});
                }
            }
        }
        return answer;
    }


    // 미로의 범위 내에 있는지 검사하는 메소드
    private boolean checkBounds(int x, int y) {
        return -1 < x && x < 6 && -1 < y && y < 6;
    }
    public static void main(String[] args) {
        // 2이 시작점, 3이 목적지
        int answer = new Maze().solution(new int[][]{
                {0, 0, 0, 0, 0, 0},
                {1, 0, 1, 1, 1, 0},
                {1, 0, 0, 0, 1, 0},
                {1, 0, 1, 0, 1, 0},
                {0, 0, 1, 0, 1, 0},
                {2, 1, 1, 3, 1, 1}
        });
        System.out.println(answer);
    }
}

게임 맵 최단거리

https://school.programmers.co.kr/learn/courses/30/lessons/1844

public class Programmers1844 {
    private int[] dx = new int[]{-1, 1, 0, 0};
    private int[] dy = new int[]{0, 0, -1, 1};
    private int n;
    private int m;

    // 최단 거리를 돌려준다
    public int solution(int[][] maze) {
        n = maze.length;
        m = maze[0].length;
        // BFS 로직을 활용하는데,
        // 다음에 접근할 수 있는 칸을 maze의 가로 세로 크기 이내의 인접한 칸
        // 을 기준으로 판단

        // int[]를 담는 Queue, {x, y, 여태까지 이동거리}
        Queue<int[]> visitNext = new LinkedList<>();
        // 미로에서 이미 도달한 적 있는 칸임을 나타내기 위한 visited 이차원 배열
        boolean[][] visited = new boolean[n][m];
        // 좌 상단에서 시작
        visitNext.offer(new int[]{0, 0, 1});
        int answer = -1;

        // BFS 시작
        // Queue가 비어있지 않은 동안
        while (!visitNext.isEmpty()) {
            // 다음에 갈곳을 Queue에서 꺼낸다.
            int[] next = visitNext.poll();
            int nowX = next[0];
            int nowY = next[1];
            int steps = next[2];

            // 현재 칸이 n, m 이라면
            if (nowX == n - 1 && nowY == m - 1) {
                answer = steps;
                break;
            }

            visited[nowX][nowY] = true;

            // 4개의 방향을 확인한다.
            for (int i = 0; i < 4; i++) {
                // 다음에 도달해야 할 위치
                int nextX = nowX + dx[i];
                int nextY = nowY + dy[i];
                if (
                    // 1. 미로의 범위를 벗어나진 않는지
                        checkBounds(nextX, nextY) &&
                                // 2. 미로에서 진행 가능한 칸인지
                                maze[nextX][nextY] == 1 &&
                                // 3. 아직 방문한적 없는지
                                !visited[nextX][nextY]
                ) {
                    // 큐에 방문 대상으로 기록
                    visitNext.offer(new int[]{nextX, nextY, steps + 1});
                    // 효율성 검사 통과를 위해 방문 전에 기록한다.
                    visited[nextX][nextY] = true;
                }
            }

        }
        return answer;
    }


    // 미로의 범위 내에 있는지 검사하는 메소드
    private boolean checkBounds(int x, int y) {
        return -1 < x && x < n && -1 < y && y < m;
    }
    public static void main(String[] args) {
        int answer = new Programmers1844().solution(new int[][]{
                {1, 0, 1, 1, 1},
                {1, 0, 1, 0, 1},
                {1, 0, 1, 1, 1},
                {1, 1, 1, 0, 1},
                {0, 0, 0, 0, 1}
        });
        System.out.println(answer);
    }
}

타겟 넘버

https://school.programmers.co.kr/learn/courses/30/lessons/43165

public class Programmers43165 {
    public int solution(int[] numbers, int target) {
        dfs(numbers, 0, target, 0);
        return answer;
    }

    // 재귀함수 DFS를 할 예정이라, 정답을 클래스 단위에서 관리
    private int answer = 0;

    // 재귀함수 DFS
    public void dfs(
            // 내가 사용할 숫자들
            int[] numbers,
            // 내가 다음에 사용할 차례의 숫자
            // 이번 DFS 호출에서 더하거나 빼거나 할 숫자는 numbers[next]
            int next,
            // 목표 값
            int target,
            // 이번 재귀까지 합한 값
            int sum
    ) {
        // 마지막 숫자를 쓴 시점에 next는 배열의 크기와 동일해진다.
        if (next == numbers.length) {
            // target 과 일치하는지 확인
            if (sum == target) this.answer++;

        }
        else {
            // 더한 다음 다음 숫자 결정
            dfs(numbers, next + 1, target, sum + numbers[next]);
            // 뺀 다음 다음 숫자 결정
            dfs(numbers, next + 1, target, sum - numbers[next]);
        }
    }

    public static void main(String[] args) {
        int answer = new Programmers43165().solution(new int[]{1, 1, 1, 1, 1}, 3);
        System.out.println(answer);
    }

서로소 집합

서로 중복이 포함되지 않는 집합, 즉 교집합이 존재하지 않는 집합을 말한다.

특징

대표자 : 각 집합은 고유한 대표자(대표 원소)를 가지고 있는데, 이 대표자는 집합을 대표하고 집합에 속한 모든 원소들과 연결되어 있다.

Union 과 Find : 서로소 집합은 합치기(Union)와 속해 있는 집합 찾기(Find)라는 두 가지 주요 연산을 한다.

AOP (관점 지향 프로그래밍)

서로 다른 비즈니스 로직이 공통적으로 가졌으면 하는 기능을 횡단 관심 또는 횡단 관점이라고 한다. (한 메소드가 실행하는데 걸리는 시간을 구하는 기능, 메소드의 반환값을 저장하는 기능 등)
관점 지향 프로그래밍은 이러한 횡단 관심사들에 초점을 맞추는 개발 패러다임의 일종이다.

  • 핵심 관점은 비즈니스 로직을 말한다.
  • 보안, 로깅, DB는 핵심 기능은 아니지만 단순 목적을 위해 소스코드를 여러 곳에 중복 작성해야 한다.
  • 횡단 관점은 이런 관심사 분리를 통해 중복 코드 작성을 줄일 수 있게 해준다.

AOP 용어

  • Aspect : 횡단 관점에 적용할 기능을 모듈화한 단위. 즉 어떤 핵심 기능에 추가할 횡단 기능에 대한 정의

  • Join Point : 프로그램의 흐름에 관점을 진입할 수 있는 후보지. 실행 중인 프로그램에서 메소드 호출, 예외 발생 등의 시점을 지칭. 이러한 Join Point에 정의한 Aspect를 적용할 수 있다.

  • Pointcut : Join Point 중에서 우리가 몇 곳을 골라서 진입하게 되는데 그 부분을 지칭함. 어떤 특정한 Join Point를 지칭하기 위한 기준. Advice가 실제로 적용될 Join Point를 정의하는 역할

  • Advice : 특정 Join Point에서 실제로 실행될 기능. Advice는 Join Point의 전(Before), 후(After), 전후(Around) 등 다양한 시점 지칭 가능

  • 정의한 횡단 관점을 실제 프로그램에 적용시키는 과정을 Weaving이라고 한다.

프로젝트 생성

TestAspect

@Aspect가 작동된다면 성공

ResponseDto

@Data
public class ResponseDto {
    private String message;
}

UserDto

@Data
public class UserDto {
    private Long id;
    private String username;
    private String password;
    private String passwordCheck;
    private String email;
    private String phone;
}

AopController

@Slf4j
@RestController
@RequiredArgsConstructor
public class AopController {
    @PostMapping("/users")
    @LogExecutionTime
    public ResponseDto addUser(@RequestBody UserDto user) {
//        log.info(user.toString());
        log.info("addUser 호출됨");
        ResponseDto response = new ResponseDto();
        response.setMessage("addUser");
        return response;
    }

    @GetMapping("/users")
    @LogExecutionTime
    public ResponseDto getUsers() {
        ResponseDto response = new ResponseDto();
        response.setMessage("addUser");
        return response;
    }
}

LoggingAspect

@Slf4j      // log 추가
@Aspect     // 이 클래스가 관점(Aspect)임을 드러내는 어노테이션
@Component  // Bean 객체로 등록
public class LoggingAspect {
    // 컨트롤러 클래스
    // @Before: Advice, 실제로 실행될 코드를 나타냄
    // @Before.value : Pointcut Designator, 어느 JoinPoint에서 실행될 것인지
    // this : 클래스 instance 지정
//    @Before("this(com.example.aop.controller.AopController)")
    // within : 클래스 또는 패키지 지정
//    @Before("within(com.example.aop.controller.AopController)")
//    @Before("within(com.example.aop.controller..*)")
    // @annotation : 어노테이션 지정
    @After("@annotation(com.example.aop.aspect.LogArguments)")
    // JoinPoint : 이 Advice 가 실행된 JoinPoint (addUser)
    public void logParameters(JoinPoint joinPoint) {
        // Signature : JoinPoint의 정보를 담은 객체
        Signature signature = joinPoint.getSignature();
        // 메소드 이름 로그
        log.info(signature.getName());
        // 메소드 인자를 확인
        Object[] arguments = joinPoint.getArgs();
        // 인자가 없을 때
        if (arguments.length == 0) {
            log.info("no args");
        }
        for (Object argument: arguments) {
            log.info("argument: [{}]", argument);
        }
    }
    // @LogExecutionTime 어노테이션의 분은 메소드의
    // 어떤 메소드가 실행되는데 걸리는 시간을 기록하고 싶다.
    @Around("@annotation(com.example.aop.aspect.LogExecutionTime)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("start log execution time");
        long startTime = System.currentTimeMillis();
        Object proceed = joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        log.info("{} executed in: {}ms", joinPoint.getSignature(), endTime - startTime);

        return proceed;
    }
}

LogArguments

@Target(ElementType.METHOD)       // 어디에 붙는 어노테이션 인지
@Retention(RetentionPolicy.RUNTIME)    // 언제까지 유지되는 어노테이션 인지
public @interface LogArguments {
    // 내용은 없어도 된다.
}

Before

After

LogExecutionTime

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}

Around

Interceptor

사용자 요청에 의해 서버에 들어온 Request 객체를 controller의 핸들러 method로 도달하기 전에 낚아채서 개발자가 원하는 추가적인 작업을 한 후 핸들러로 보낼 수 있도록 해주는 것이다.

  • HandlerInterceptor 인터페이스 구현

  • preHandle() : '컨트롤러 실행 전'에 동작하는 메소드

  • postHandle() : '컨트롤러 실행 후, 응답이 전달되기 전(+뷰 실행 전)'에 동작하는 메소드

  • afterCompletion : '요청 처리가 마무리 된 후 (+뷰 실행 후)' 에 동작하는 메소드. 예외가 발생한다면 예외의 정보가 인자로 추가된다.

Filter

DispatcherServlet에 도달하기 전에 요청을 검사할 수 있으며, Spring 외부의 기능이므로, 예외처리 등에서 Spring Framework의 도움을 받지 못한다. (비즈니스 로직과 무관한 기능 구현에 사용)

  • Filter 인터페이스를 구현, doFilter() 메소드 1개만 구현하면 된다.

  • Bean 객체로 등록한다.

  • 구현체를 만들어 사용한다.

출처 : 멋사 5기 백엔드 위키 16팀 해고없는회고팀

인사이트 타임

홀짝 구분하기
https://school.programmers.co.kr/learn/courses/30/lessons/181944

import java.util.Scanner;

public class Solution {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        if (n % 2 == 0) System.out.print(n + " is even");
        else System.out.print(n + " is odd");
    }
}

짝수와 홀수
https://school.programmers.co.kr/learn/courses/30/lessons/12937

class Solution {
    public String solution(int num) {
        String answer = "";
        if (num % 2 == 0) answer = "Even";
        else answer = "Odd";
        return answer;
    }
}

x만큼 간격이 있는 n개의 숫자
https://school.programmers.co.kr/learn/courses/30/lessons/12954

class Solution {
    public long[] solution(int x, int n) {
        long[] answer = new long[n];
        for (int i = 0; i < n; i++) answer[i] = x * (long) i + x;
        return answer;
    }
}

easy

review

알고리즘은 너무너무 어렵다. 이하 생략

스프링은 aop에 대해 배워봤는데 역시 어렵다.
당연한 얘기지만 매일 배우는 다양한 기능 구현들을 마스터 하기엔 아무래도 꽤 오래 걸릴 것 같다. 처음엔 아무것도 모르고 의지만 불타올랐지만 하루하루 참교육을 당하고 있는 지금은 많이 꺾인 것 같다. 그저 포기하지않고 천천히 하자는 마음만이 남았다.

profile
반갑습니다람지
post-custom-banner

0개의 댓글