반응형

 

https://www.acmicpc.net/problem/1915

 

1915번: 가장 큰 정사각형

첫째 줄에 n, m(1 ≤ n, m ≤ 1,000)이 주어진다. 다음 n개의 줄에는 m개의 숫자로 배열이 주어진다.

www.acmicpc.net

 

처음 접근법 (실패)

DFS (재귀) 이용

DFS 호출할 때마다 cnt 증가 (cnt: 정사각형 한 변의 길이를 의미)

오른쪽, 아래쪽, 오른쪽 아래 대각선이 모두 1이면 dp[i][j] = cnt

하나라도 0이거나 범위를 벗어나면 탐색 불가 (return 0 또는 cnt-1)

 

두 번째 접근법 (https://yabmoons.tistory.com/158)

위 블로그 설명에서는 왼쪽, 위쪽, 왼쪽 위 대각선을 탐색했는데,

나는 오른쪽, 아래쪽, 오른쪽 아래 대각선을 탐색해도 될 거라고 판단

하지만, 내가 생각한 방법대로 하면 한 번 계산한 행 (또는 열)은 다음 계산에 반영 안됨.

블로그 설명대로 왼쪽, 위쪽, 왼쪽 위 대각선을 탐색한다면 한 번 계산한 행 (또는 열)도 다음 계산에 반영됨.

 

진행과정

012345678

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <algorithm>
#define rep(i,n) for(int i=1;i<=n;i++)
using namespace std;
int n, m, dp[1001][1001], ans;
void input();
void func() {
    rep(i, n) {
        rep(j, m) {
            if (dp[i][j] == 1)
                dp[i][j] = min(dp[i][j - 1], min(dp[i - 1][j - 1], dp[i - 1][j])) + 1;
            ans = max(ans, dp[i][j]);
        }
    }
}
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    input();
    func();
    cout << ans * ans;
}
void input() {
    cin >> n >> m;
    rep(i, n) {
        rep(j, m) {
            char c;
            cin >> c;
            dp[i][j]= c - '0';
        }
    }
}
cs
반응형

'백준 > DP' 카테고리의 다른 글

백준 9252 [복습필수]  (0) 2021.07.29
백준 11066  (0) 2021.07.27
백준 1937  (0) 2021.07.26
백준 1890  (0) 2021.07.23
백준 2293  (0) 2021.07.20
반응형

https://www.acmicpc.net/problem/11066

 

11066번: 파일 합치기

소설가인 김대전은 소설을 여러 장(chapter)으로 나누어 쓰는데, 각 장은 각각 다른 파일에 저장하곤 한다. 소설의 모든 장을 쓰고 나서는 각 장이 쓰여진 파일을 합쳐서 최종적으로 소설의 완성본

www.acmicpc.net

0123

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <cstring>
#include <algorithm>
#define rep(i,n) for(int i=1;i<=n;i++)
using namespace std;
int t, n, dp[501][501], sum[501];
void input();
void func() {
    int total_diff = n - 1;
 
    rep(diff, total_diff) {
        rep(i, n-diff) {
            int j = i + diff;
            for (int k = i; k < j; k++) {
                int temp = dp[i][k] + dp[k + 1][j] + sum[j]-sum[i-1];
                if (dp[i][j] == 0 || temp < dp[i][j])
                    dp[i][j] = temp;
            }
        }
    }
 
}
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cin >> t;
 
    while (t--) {
        input();
        func();
        cout << dp[1][n]<<'\n';
    }
}
 
void input() {
    memset(dp, 0sizeof(dp));
    cin >> n;
    rep(i, n) {
        cin >> sum[i];
        sum[i] += sum[i - 1];
    }
}
cs
반응형

'백준 > DP' 카테고리의 다른 글

백준 9252 [복습필수]  (0) 2021.07.29
백준 1915 [복습필수]  (0) 2021.07.28
백준 1937  (0) 2021.07.26
백준 1890  (0) 2021.07.23
백준 2293  (0) 2021.07.20
반응형

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 대시보드 - 인프런 | 강의 (inflearn.com)

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

포맷터 - Formatter

컨버터의 특별한 버전

Converter: 범용 (객체 -> 객체)

Formatter: 문자에 특화 (객체 <-> 문자)

 

1000 -> 1,000

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Slf4j
public class MyNumberFormatter implements Formatter<Number> {
 
    @Override
    public Number parse(String text, Locale locale) throws ParseException {
        log.info("text={}, locale={}", text, locale);
        return NumberFormat.getInstance(locale).parse(text);
    }
 
    @Override
        public String print(Number object, Locale locale) {
        log.info("object={}, locale={}", object, locale);
        return NumberFormat.getInstance(locale).format(object);
    }
}
cs

포맷터 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyNumberFormatterTest {
    MyNumberFormatter formatter = new MyNumberFormatter();
 
    @Test
    void parse() throws ParseException {
        Number result = formatter.parse("1,000", Locale.KOREA);
        assertThat(result).isEqualTo(1000L);
    }
 
    @Test
    void print() {
        String result = formatter.print(1000, Locale.KOREA);
        assertThat(result).isEqualTo("1,000");
    }
}
cs

 

포맷터를 지원하는 컨버전 서비스

DefaultFormattingConversionService

FormattingConversionService에 기본적인 통화, 숫자 관련 몇 가지 기본 포맷터 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    @Test
    void formattingConversionService() {
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
 
        // 컨버터 등록
        conversionService.addConverter(new StringToIpPortConverter());
        conversionService.addConverter(new IpPortToStringConverter());
 
        // 포맷터 등록
        conversionService.addFormatter(new MyNumberFormatter());
 
        // 컨버터 사용
        IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class);
        assertThat(ipPort).isEqualTo(new IpPort("127.0.0.1"8080));
 
        // 포맷터 사용
        assertThat(conversionService.convert("1,000", Long.class)).isEqualTo(1000L);
        assertThat(conversionService.convert(1000String.class)).isEqualTo("1,000");
    }
cs

 

포맷터 적용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        // 컨버터 추가
        registry.addConverter(new StringToIpPortConverter());
        registry.addConverter(new IpPortToStringConverter());
 
        // 포맷터 추가
        registry.addFormatter(new MyNumberFormatter());
    }
}
 
cs

결과

  • ${number}: 10000
  • ${{number}}: 10,000
  • ${ipPort}: hello.typeconverter.type.IpPort@59cb0946
  • ${{ipPort}}: 127.0.0.1:8080

 

스프링이 제공하는 기본 포맷터

애토네이션 기반으로 원하는 형식 지정

@NumberFormat: 숫자 관련 형식 지정 포맷터

@DateTimeFormat: 날짜 관련 형식 지정 포맷터

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Controller
public class FormatterController {
    @GetMapping("/formatter/edit")
    public String formatterForm(Model model) {
        Form form = new Form();
        form.setNumber(10000);
        form.setLocalDateTime(LocalDateTime.now());
        model.addAttribute("form", form);
        return "formatter-form";
    }
 
    @PostMapping("/formatter/edit")
    public String farmatterEdit(@ModelAttribute Form form) {
        return "formatter-view";
    }
 
    @Data
    static class Form{
        @NumberFormat(pattern = "###,###")
        private Integer number;
 
        @DateTimeFormat(pattern = "yyyy-mm-dd HH:mm:ss")
        private LocalDateTime localDateTime;
    }
}
cs

 

결과

• ${form.number}: 10000

• ${{form.number}}: 10,000

• ${form.localDateTime}: 2021-01-01T00:00:00

• ${{form.localDateTime}}: 2021-01-01 00:00:00

 

정리

컨버터, 포맷터 등록 방법 다르지만, 

사용할 때는 conversionService를 통해서 일관성 있게 사용 가능

 

주의!

메시지 컨버터( HttpMessageConverter )에는 컨버전 서비스가 적용되지 않는다.

특히 객체를 JSON으로 변환할 때 메시지 컨버터를 사용하면서 이 부분을 많이 오해하는데,

HttpMessageConverter 의 역할은 HTTP 메시지 바디의 내용을 객체로 변환하거나 객체를 HTTP 메시지

바디에 입력하는 것이다. 예를 들어서 JSON을 객체로 변환하는 메시지 컨버터는 내부에서 Jackson 같은

라이브러리를 사용한다. 객체를 JSON으로 변환한다면 그 결과는 이 라이브러리에 달린 것이다. 따라서

JSON 결과로 만들어지는 숫자나 날짜 포맷을 변경하고 싶으면 해당 라이브러리가 제공하는 설정을 통해서

포맷을 지정해야 한다. 결과적으로 이것은 컨버전 서비스와 전혀 관계가 없다.

컨버전 서비스는 @RequestParam , @ModelAttribute , @PathVariable , 뷰 템플릿 등에서 사용할 수

있다.

잭슨 라이브러리 자체에서 제공하는 데이터 포맷터 사용해야 함

 

 

파일 업로드

문자, 바이너리 동시 전송

-> enctype="multipart/form-data"

 

boundary

 

업로드 파일 제한

파일 하나: spring.servlet.multipart.max-file-size=1MB 

파일 전체 합: spring.servlet.multipart.max-request-size=10MB

 

멀티파트 리졸버는 멀티파트 요청인 경우 서블릿 컨테이너가 전달하는 일반적인 HttpServletRequest 를

MultipartHttpServletRequest 로 변환해서 반환한다.

> MultipartHttpServletRequest 는 HttpServletRequest 의 자식 인터페이스이고, 멀티파트와 관련된

추가 기능을 제공한다.

 

서블릿과 파일 업로드2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Slf4j
@Controller
@RequestMapping("/servlet/v2")
public class ServletUploadControllerV2 {
    @Value("${file.dir}")
    public String fileDir;
 
    @GetMapping("/upload")
    public String newFile() {
        return "upload-form";
    }
 
    @PostMapping("/upload")
    public String saveFileV1(HttpServletRequest request) throws IOException, ServletException {
        log.info("request={}", request);
 
        String itemName = request.getParameter("itemName");
        log.info("itemName={}", itemName);
 
        Collection<Part> parts = request.getParts();
        log.info("parts={}", parts);
 
        for (Part part : parts) {
            log.info("==== PART ====");
            log.info("name={}", part.getName());
            Collection<String> headerNames = part.getHeaderNames();
                for (String headerName : headerNames) {
                    log.info("header {}: {}", headerName, part.getHeader(headerName));
                }
 
            // 편의 메서드
            log.info("submittedFilename={}", part.getSubmittedFileName());
            log.info("size={}", part.getSize());
 
            // 데이터 읽기
            InputStream inputStream = part.getInputStream();
            String body = StreamUtils.copyToString(inputStream, UTF_8);
            log.info("body={}", body);
 
            // 파일 저장
            if (StringUtils.hasText(part.getSubmittedFileName())) {
                String fullPath = fileDir + part.getSubmittedFileName();
                log.info("파일 저장 fullPath={}", fullPath);
                part.write(fullPath);
            }
        }
        return "upload-form";
    }
}
cs

 

스프링과 파일 업로드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Slf4j
@Controller
@RequestMapping("/spring")
public class SpringUploadController {
    @Value("${file.dir}")
    public String fileDir;
 
    @GetMapping("/upload")
    public String newFile() {
        return "upload-form";
    }
 
    @PostMapping("/upload")
    public String saveFile(@RequestParam String itemName,
                            @RequestParam MultipartFile file,
                            HttpServletRequest request) throws IOException {
        log.info("request={}", request);
        log.info("itemName={}", itemName);
        log.info("multipartFile={}", file);
 
        if (!file.isEmpty()) {
            String fullPath = fileDir + file.getOriginalFilename();
            log.info("파일 저장 fullPath={}", fullPath);
            file.transferTo(new File(fullPath));
        }
        return "upload-form";
    }
}
 
 
cs

 

@RequestParam MultipartFile file

업로드하는 HTML Form의 name에 맞추어 @RequestParam 을 적용하면 된다. 추가로

@ModelAttribute 에서도 MultipartFile 을 동일하게 사용할 수 있다.

MultipartFile 주요 메서드

file.getOriginalFilename() : 업로드 파일 명

file.transferTo(...) : 파일 저장

반응형
반응형

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 대시보드 - 인프런 | 강의 (inflearn.com)

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

API 예외 처리 복잡한 이유

1. 예외에 따라서 각각 다른 데이터 추력

2. 같은 예외라도 컨트롤러에 따라서 응답 다를 수 있음

 

@ExceptionHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandler(IllegalArgumentException e) {
    log.error("[exceptionHandler] ex", e);
    return new ErrorResult("BAD", e.getMessage());
}
 
// 예외 타입 생략 -> 파라미터로 받은 예외를 처리
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandler(UserException e){
    log.error("[exceptionHandler] ex", e);
    ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
 
    return new ResponseEntity(errorResult, HttpStatus.BAD_REQUEST);
}
 
// 위에서 걸러지지 않은 예외는 여기서 처리
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandler(Exception e) {
    log.error("[exceptionHandler] ex", e);
    return new ErrorResult("EX""내부 오류");
}
cs

@ResponseStatus 안하면 정상 처리로 간주 (Status 200 OK)

 

@ControllerAdvice

예외 처리를 모아서 한 군데서 처리

기능과 예외처리를 분리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Slf4j
@RestControllerAdvice
public class ExControllerAdvice {
 
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
        public ErrorResult illegalExHandler(IllegalArgumentException e) {
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("BAD", e.getMessage());
    }
 
    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandler(UserException e){
        log.error("[exceptionHandler] ex", e);
        ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
 
        return new ResponseEntity(errorResult, HttpStatus.BAD_REQUEST);
    }
 
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public ErrorResult exHandler(Exception e) {
        log.error("[exceptionHandler] ex", e);
        return new ErrorResult("EX""내부 오류");
    }
}
cs

 

대상 컨트롤러 지정 방법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {
 
}
 
// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {
 
}
 
// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {
 
}
cs

 

스프링 타입 컨버터

IntegerToStringConverter

1
2
3
4
5
6
7
public class IntegerToStringConverter implements Converter<Integer, String> {
    @Override
    public String convert(Integer source) {
        log.info("convert source={}", source);
        return String.valueOf(source);
    }
}
cs

StringToIntegerConverter

1
2
3
4
5
6
7
public class StringToIntegerConverter implements Converter<String, Integer> {
    @Override
    public Integer convert(String source) {
        log.info("convert source={}", source);
        return Integer.valueOf(source);
    }
}
cs

StringToIpPortConverter

1
2
3
4
5
6
7
8
9
10
11
12
public class StringToIpPortConverter implements Converter<String, IpPort> {
    @Override
    public IpPort convert(String source) {
        log.info("convert source={}", source);
 
        // "127.0.0.1:8080"
        String[] split = source.split(":");
        String ip = split[0];
        int port = Integer.parseInt(split[1]);
        return new IpPort(ip, port);
    }
}
cs

IpPortToStringConverter

1
2
3
4
5
6
7
public class IpPortToStringConverter implements Converter<IpPort, String> {
    @Override
    public String convert(IpPort source) {
        log.info("convert source={}", source);
        return source.getIp() + ":" + String.valueOf(source.getPort());
    }
}
cs

타입 컨버터를 하나하나 직접 사용하면, 개발자가 직접 컨버팅 하는 것과 큰 차이가 없음

 

컨버전 서비스 - ConversionService

개별 컨버터를 모아두고 그것들을 묶어서 편리하게 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    @Test
    void conversionService() {
        // 등록
        DefaultConversionService conversionService = new DefaultConversionService();
        conversionService.addConverter(new StringToIntegerConverter());
        conversionService.addConverter(new IntegerToStringConverter());
        conversionService.addConverter(new StringToIpPortConverter());
        conversionService.addConverter(new IpPortToStringConverter());
 
        // 사용
        assertThat(conversionService.convert("10", Integer.class)).isEqualTo(10);
        assertThat(conversionService.convert(10String.class)).isEqualTo("10");
 
        IpPort ipPort = conversionService.convert("127.0.0.1:8080", IpPort.class);
        assertThat(ipPort).isEqualTo(new IpPort("127.0.0.1"8080));
 
        String ipPortString = conversionService.convert(new IpPort("127.0.0.1"8080), String.class);
        assertThat(ipPortString).isEqualTo("127.0.0.1:8080");
    }
cs

 

 

인터페이스 분리 원칙 - ISP(Interface Segregation Principal)

인터페이스 분리 원칙은 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.

DefaultConversionService 는 다음 두 인터페이스를 구현했다.

ConversionService : 컨버터 사용에 초점

ConverterRegistry : 컨버터 등록에 초점

이렇게 인터페이스를 분리하면 컨버터를 사용하는 클라이언트와 컨버터를 등록하고 관리하는 클라이언트의

관심사를 명확하게 분리할 수 있다. 특히 컨버터를 사용하는 클라이언트는 ConversionService 만

의존하면 되므로, 컨버터를 어떻게 등록하고 관리하는지는 전혀 몰라도 된다. 결과적으로 컨버터를

사용하는 클라이언트는 꼭 필요한 메서드만 알게된다. 이렇게 인터페이스를 분리하는 것을 ISP 라 한다.

ISP 참고: https://ko.wikipedia.org/wiki/

%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4_%EB%B6%84%EB%

A6%AC_%EC%9B%90%EC%B9%99

 

스프링에 Converter 적용하기

1
2
3
4
5
6
7
8
9
10
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToIpPortConverter());
        registry.addConverter(new IpPortToStringConverter());
        registry.addConverter(new StringToIntegerConverter());
        registry.addConverter(new IntegerToStringConverter());
    }
}
cs

 

스프링 기본 컨버터 제공

사용자 지정 컨버터의 우선순위가 더 높음

 

localhost:8080/ip-port?ipPort=127.0.0.1:8080

1
2
3
4
5
6
@GetMapping("/ip-port")
public String ipPort(@RequestParam IpPort ipPort) {
    System.out.println("ipPort.getIp() = " + ipPort.getIp());
    System.out.println("ipPort.getPort() = " + ipPort.getPort());
    return "ok";
}
cs

 

뷰 템플릿에 컨버터 적용하기

스프링: 문자를 객체로

뷰 템플릿: 객체를 문자로  

 

${...}: 컨버터 적용 안함

${{...}}: 컨버터 적용

1
2
3
4
5
6
@GetMapping("/converter-view")
public String converterView(Model model) {
    model.addAttribute("number"10000);
    model.addAttribute("ipPort"new IpPort("127.0.0.1"8080));
    return "converter-view";
}
cs

결과

• ${number}: 10000

• ${{number}}: 10000

• ${ipPort}: hello.typeconverter.type.IpPort@59cb0946

• ${{ipPort}}: 127.0.0.1:8080

  -> IpPort 객체를 String으로 변환한 뒤 출력

 

th:object="${form}"에서 

th:field="*{ipPort}"

  -> 컨버터 자동 적용

th:value="*{ipPort}"

  -> 컨버터 적용 안됨

 

예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@GetMapping("/converter/edit")
public String converterForm(Model model){
    IpPort ipPort = new IpPort("127.0.0.1"8080);
    Form form = new Form(ipPort);
    model.addAttribute("form", form);
    return "converter-form";
}
 
@PostMapping("/converter/edit")
public String converterEdit(@ModelAttribute Form form, Model model) {
    IpPort ipPort = form.getIpPort();
    model.addAttribute("ipPort", ipPort);
    return "converter-view";
}
 
@Data
static class Form{
    private IpPort ipPort;
 
    public Form(IpPort ipPort) {
        this.ipPort = ipPort;
    }
}
cs
반응형
반응형

https://www.acmicpc.net/problem/1937

 

1937번: 욕심쟁이 판다

n × n의 크기의 대나무 숲이 있다. 욕심쟁이 판다는 어떤 지역에서 대나무를 먹기 시작한다. 그리고 그 곳의 대나무를 다 먹어 치우면 상, 하, 좌, 우 중 한 곳으로 이동을 한다. 그리고 또 그곳에

www.acmicpc.net

dp[i][j]

i, j에서 갈 수 있는 최대 개수

 

DFS 핵심

현재 칸에서 다음 칸으로 이동: 1번

다음 칸에서 임의의 칸으로 이동: 최대 x번

∴ 현재 칸 -> 다음 칸 -> 임의의 칸: 최대 x + 1번

 

다음 칸의 계산이 이미 끝났을 경우 (visited = true)

  -> 다음 칸의 값(dp[nx][ny])을 가져와서 계산

 

다음 칸의 계산이 안되어 있을 경우 (visited = false)

  -> dfs(nx, ny) 수행 / return 값: 다음 칸의 값

1
2
3
4
5
6
7
// 계산이 끝났을 경우
if (visited[nx][ny])
    dp[x][y] = max(dp[x][y], dp[nx][ny] + 1);
 
// 계산을 해야하는 경우
else 
    dp[x][y] = max(dp[x][y], dfs(nx, ny) + 1);
cs

 

진행과정

012345678910

소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <algorithm>
#define rep(i,n) for(int i=0;i<n;i++)
using namespace std;
int dx[] = { -1010 };
int dy[] = { 010-1 };
int ans, n, arr[501][501], dp[501][501];
bool visited[501][501];
// dp[i][j]: i, j에서 갈 수 있는 최대 개수
int dfs(int x, int y) {
    visited[x][y] = 1;
    dp[x][y]++;
    rep(i, 4) {
        int nx = x + dx[i];
        int ny = y + dy[i];
 
        // 이동 불가
        if (nx < 0 || ny >= n || ny < 0 || ny >= n || arr[x][y] >= arr[nx][ny]) continue;
 
        // 계산이 끝났을 경우
        if (visited[nx][ny])
            dp[x][y] = max(dp[x][y], dp[nx][ny] + 1);
 
        // 계산을 해야하는 경우
        else 
            dp[x][y] = max(dp[x][y], dfs(nx, ny) + 1);
    }
    return dp[x][y];
}
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cin >> n;
    rep(i, n) {
        rep(j, n) 
            cin >> arr[i][j];
    }
 
    rep(i, n) {
        rep(j, n) {
            if (!visited[i][j]) 
                dfs(i, j);
        }
    }
 
    rep(i, n) {
        rep(j, n)
            ans = max(ans, dp[i][j]);
    }
    cout << ans;
}
cs
반응형

'백준 > DP' 카테고리의 다른 글

백준 1915 [복습필수]  (0) 2021.07.28
백준 11066  (0) 2021.07.27
백준 1890  (0) 2021.07.23
백준 2293  (0) 2021.07.20
백준 1103  (0) 2021.07.08
반응형

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 대시보드 - 인프런 | 강의 (inflearn.com)

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

API 예외 처리

HTML 페이지: 오류 페이지만 있으면 됨

API: 각 오류 상황에 맞는 응답 스펙을 정하고 JSON으로 전송

(웹 브라우저가 아니면 HTML을 직접 받아서 할 수 있는 것 거의 없음)

 

에러 직접 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> errorPage500Api(HttpServletRequest request, HttpServletResponse response){
    log.info("API errorPage 500");
    // 에러 받아옴
    Exception ex = (Exception) request.getAttribute(ERROR_EXCEPTION);
 
    // 응답 객체 생성
    Map<String, Object> result = new HashMap<>();
    result.put("status", request.getAttribute(ERROR_STATUS_CODE));
    result.put("message", ex.getMessage());
 
    // 응답 코드
    Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
    return new ResponseEntity<>(result, HttpStatus.valueOf(statusCode));
}
cs

Header의 Accept가 */*이면 그냥 HTML

Header의 Accept가 application/json이면 JSON

 

 

스프링 부트 기본 오류 처리

WebServerCustomizer가 없어도 Header의 Accept가 application/json이면 스프링 부트에서 json 형식으로 전송

(기본 경로: /error)

{

    "timestamp": "2021-07-23T07:22:22.285+00:00",

    "status": 500,

    "error": "Internal Server Error",

    "path": "/api/members/ex"

}

 

HTML 화면 처리: BasicErrorController

API 오류 처리: @ExceptionHandler

 

HandlerExceptionResolver

컨트롤러(핸들러) 밖으로 던져진 예외를 해결 및 동작 방식 변경

 

적용 전: preHandle -> 컨트롤러 -> afterCompletion -> WAS

적용 후: preHandle -> 컨트롤러 -> ExceptionResolver -> afterCompletion -> WAS

※ ExceptionResolver로 예외를 해결해도 postHandle()은 호출되지 않음

 

MyHandlerExceptionResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Slf4j
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    try {
        if(ex instanceof IllegalArgumentException){
            log.info("IllegalArgumenException resolver to 400");
            // IllegalArgumentException을 받으면 400에러를 전송
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
 
            // return을 정상적으로 해서 예외를 삼킴
            return new ModelAndView();
        }
    } catch (IOException e) {
        log.error("resolver ex", e);
    }
 
    return null;
    }
}
cs

 

MyHandlerExceptionResolver 핸들러 등록

1
2
3
4
5
6
7
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.add(new MyHandlerExceptionResolver());
    }
}
cs

ExceptionResolver가 ModelAndView 반환 

: try, catch 하듯이 Exception을 처리해서 정상 흐름처럼 변경 (Exception을 Resolve)

 

반환 값에 따른 동작 방식

① 빈 ModelAndView:  뷰를 렌더링 하지 않고, 정상 흐름으로 서블릿 리턴

② ModelAndView 지정: ModelAndView 에 View , Model 등의 정보를 지정해서 반환하면 뷰를 렌더링

③ null: 다음 ExceptionResolver 를 찾아서 실행. 처리할 수 있는 ExceptionResolver 가 없으면 예외 처리가 안되고, 기존에 발생한 예외를 서블릿 밖으로 던짐

 

UserExceptionHandlerResolver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Slf4j
public class UserExceptionHandlerResolver implements HandlerExceptionResolver {
    private final ObjectMapper objectMapper = new ObjectMapper();
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try{
            if (ex instanceof UserException) {
                log.info("UserException resolver to 400");
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                // 헤더에 따라서 json, html 선택
                String acceptHeader = request.getHeader("accept");
                if (acceptHeader.equals("application/json")) {
                Map<String, Object> errorResult = new HashMap<>();
                errorResult.put("ex", ex.getClass());
                errorResult.put("message", ex.getMessage());
                // 객체를 String으로 변경
                String result = objectMapper.writeValueAsString(errorResult);
                response.setContentType("application/json");
                response.setCharacterEncoding("utf-8");
                response.getWriter().write(result);
                return new ModelAndView();
            }
            else {
                return new ModelAndView("error/500");
            }
        } catch(IOException e){
            log.error("resolver ex", e);
        }
        return null;
    }
}
cs

UserExceptionHandlerResolver 추가

1
2
3
4
5
6
7
8
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.add(new MyHandlerExceptionResolver());
        resolvers.add(new UserExceptionHandlerResolver());
    }
}
cs

 

예외를 여기서 마무리하기

예외 발생: 컨트롤러 -> WAS -> 오류 페이지 찾음 -> 다시 /error 호출

ExceptionResolver 활용: 컨트롤러에서 예외 발생해도 ExceptionResolver에서 예외 처리. 서블릿 컨테이너까지 예외가 전달되지 않고, 스프링 MVC에서 예외 처리 끝남. (정상 처리)

 

스프링이 제공하는 ExceptionResolver

스프링 부트 제공

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

 

① ResponseStatusExceptionResolver

예외에 따라서 HTTP 상태 코드 지정

  1. @ResponseStatus가 달려있는 예외
  2. ResponseStatusException 예외

예외 발생 컨트롤러

1
2
3
4
@GetMapping("/api/response-status-ex1")
public String responseStatusEx1() {
    throw new BadRequestException();
}
cs

 

여기서 처리 (상태 코드 변경 가능)

1
2
3
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청 오류")
public class BadRequestException extends RuntimeException {
}
cs

 

메시지 기능

reason을 MessageSource에서 찾는 기능

1
2
3
4
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "error.bad")
public class BadRequestException extends RuntimeException {
}
 
cs

messages.properties

error.bad=잘못된 요청 오류입니다.

 

@ResponseStatus: 는 라이브러리의 예외 코드와 같이 개발자가 직접 변경할 수 없는 예외에는 적용할 수 없다.

애노테이션을 사용하기 때문에 동적으로 변경하는 것이 어려움.

 

=> ResponseStatusException으로 처리

1
2
3
4
@GetMapping("/api/response-status-ex2")
public String responseStatusEx2() {
    throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.bad"new IllegalArgumentException());
}
cs

 

② DefaultHandlerExceptionResolver

스프링 내부에서 발생하는 스프링 예외를 처리

ex. 파라미터 바인딩 시점에 타입이 맞지 않음 -> TypeMismatchException

 

정리

1. ExceptionHandlerExceptionResolver 다음 시간에

2. ResponseStatusExceptionResolver HTTP 응답 코드 변경

3. DefaultHandlerExceptionResolver 스프링 내부 예외 처리

 

 

반응형
반응형

 

https://www.acmicpc.net/problem/1005

 

1005번: ACM Craft

첫째 줄에는 테스트케이스의 개수 T가 주어진다. 각 테스트 케이스는 다음과 같이 주어진다. 첫째 줄에 건물의 개수 N 과 건물간의 건설순서규칙의 총 개수 K이 주어진다. (건물의 번호는 1번부

www.acmicpc.net

0123456

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>    
#include <vector>
#include <queue>
#include <algorithm>
#include <cstring>
#define rep(i,n) for(int i=1;i<=n;i++)
using namespace std;
vector<vector<int> > v;
queue<int> q;
int t, n, k, target;
int build_time[1001];
int in_degree[1001];
int dp[1001];
int before[1001];
void func();
void init();
void input();
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cin >> t;
    while (t--) {
        input();
        func();
        cout << dp[target] << '\n';
    }
}
 
void func() {
    rep(i, n) {
        if (in_degree[i] == 0
            q.push(i);
        dp[i] = build_time[i];
    }
 
    while (!q.empty()) {
        int now = q.front(); q.pop();
        for (int next : v[now]) {
            in_degree[next]--;
            before[next] = max(before[next], dp[now]);
            if (in_degree[next] == 0) {
                q.push(next);
                dp[next] += before[next];
                if (next == target) return;
            }
        }
    }
}
 
void init() {
    v.clear();
    v.resize(n + 1);
    while (!q.empty()) q.pop();
    memset(build_time, 0sizeof(build_time));
    memset(in_degree, 0sizeof(in_degree));
    memset(dp, 0sizeof(dp));
    memset(before, 0sizeof(before));
}
 
void input() {
    cin >> n >> k;
    init();
    rep(i, n)
        cin >> build_time[i];
    rep(i, k) {
        int a, b;
        cin >> a >> b;
        v[a].push_back(b);
        in_degree[b]++;
    }
    cin >> target;
}
cs
반응형

'백준 > 트리DP' 카테고리의 다른 글

백준 2213  (0) 2021.08.02
백준 2533 [복습필수]  (0) 2021.08.01
백준 15681  (0) 2021.07.30
반응형

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 대시보드 - 인프런 | 강의 (inflearn.com)

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

서블릿 예외 처리 방법

① Exception

   WAS <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러 (예외 발생)

② response.sendError(HTTP 상태 코드, 오류 메시지): 상태 코드와 오류 메시지 설정 가능

   WAS (sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러 (response.sendError())

 

기본 에러 페이지 없으면 상태코드와 함께 메시지 출력

① ServletExController: 예외를 발생 시키는 컨트롤러

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@Controller
public class ServletExController {
    @GetMapping("/error-ex")
    public void errorEx() {
        throw new RuntimeException("예외 발생!");
    }
    @GetMapping("/error-404")
    public void error404(HttpServletResponse response) throws IOException {
        response.sendError(404"404 오류!");
    }
    @GetMapping("/error-500")
    public void error500(HttpServletResponse response) throws IOException {
        response.sendError(500);
    }
}
cs

② WebServerCustomizer: 예외 발생 시, 어떤 URL로 요청을 보낼 지 설정

1
2
3
4
5
6
7
8
9
10
11
@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
    @Override
    public void customize(ConfigurableWebServerFactory factory) {
        ErrorPage errorPage404 = new ErrorPage(NOT_FOUND, "/error-page/404");
        ErrorPage errorPage500 = new ErrorPage(INTERNAL_SERVER_ERROR, "/error-page/500");
        ErrorPage errorPageEx = new ErrorPage(RuntimeException.class"/error-page/500");
 
        factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
    }
}
cs

③ ErrorPageController: 예외 처리 화면을 띄워주는 컨트롤러

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@Controller
public class ErrorPageController {
 
    @RequestMapping("/error-page/404")
    public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
        log.info("errorPage 404");
        return "error-page/404";
    }
 
    @RequestMapping("/error-page/500")
    public String errorPage500(HttpServletRequest request, HttpServletResponse response) {
        log.info("errorPage 500");
        return "error-page/500";
    }
}
cs

 

오류 페이지 작동 원리

서블릿은

   ① Exception이 발생해서 서블릿 밖으로 전달되거나

   ② response.sendError()가 호출되었을 때

설정된 오류 페이지를 찾음

 

WAS는 해당 예외를 처리하는 오류 페이지 정보를 확인

new ErrorPage(RuntimeException.class, "/error-page/500");

 

WAS는 오류 페이지 출력을 위해 /error-page/500으로 요청

 

예외 발생과 오류 페이지 요청 흐름

1. 예외가 발생해서 WAS까지 전파됨

WAS <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러 (예외 발생)

 

2. WAS는 오류 페이지 경로를 찾아서 내부에서 오류 페이지 호출. 이때 오류 페이지 경로로 필터, 서블릿, 인터셉터, 컨트롤러가 모두 다시 호출됨

WAS '/error-page/500' 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러 (/error-page/500) -> View

 

※ 웹 브라우저 (클라이언트)는 서버 내부에서 이런 일이 일어나는지 전혀 모름

 

오류 정보를 request의 attribute에 추가해서 넘겨주면 오류 페이지에서 전달된 오류 정보 사용 가능

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//RequestDispatcher 상수로 정의되어 있음
public static final String ERROR_EXCEPTION = "javax.servlet.error.exception";
public static final String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type";
public static final String ERROR_MESSAGE = "javax.servlet.error.message";
public static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri";
public static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name";
public static final String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
 
private void printErrorInfo(HttpServletRequest request) {
    log.info("ERROR_EXCEPTION: {}", request.getAttribute(ERROR_EXCEPTION));
    log.info("ERROR_EXCEPTION_TYPE: {}", request.getAttribute(ERROR_EXCEPTION_TYPE));
    log.info("ERROR_MESSAGE: {}", request.getAttribute(ERROR_MESSAGE));
    log.info("ERROR_REQUEST_URI: {}", request.getAttribute(ERROR_REQUEST_URI));
    log.info("ERROR_SERVLET_NAME: {}", request.getAttribute(ERROR_SERVLET_NAME));
    log.info("ERROR_STATUS_CODE: {}", request.getAttribute(ERROR_STATUS_CODE));
    log.info("dispatchType: {}", request.getDispatcherType());
}
cs

 

결과

ERROR_EXCEPTION_TYPE: class java.lang.RuntimeException
ERROR_MESSAGE: Request processing failed; nested exception is java.lang.RuntimeException: 예외 발생!
ERROR_REQUEST_URI: /error-ex
ERROR_SERVLET_NAME: dispatcherServlet
ERROR_STATUS_CODE: 500
dispatchType: ERROR

 

서블릿 예외 처리 - 필터

서버 내부에서 오류 페이지를 호출한다고 해서 해당 필터나 인터셉터가 한 번 더 호출되는 것은 매우 비효율적임

클라이언트로 부터 발생한 정상 요청인지, 오류 페이지를 출력하기 위한 내부 요청인지 구분해야 함

 

DispatcherType

① REQUEST : 클라이언트 요청

② ERROR : 오류 요청

③ FORWARD : MVC에서 배웠던 서블릿에서 다른 서블릿이나 JSP를 호출할 때

RequestDispatcher.forward(request, response);

④ INCLUDE : 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때

RequestDispatcher.include(request, response);

⑤ ASYNC : 서블릿 비동기 호출

 

필터 등록: DispatcherTypes에 따라서 Filter 호출 여부 결정됨

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean logFilter() {
    FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
    filterRegistrationBean.setFilter(new LogFilter());
    filterRegistrationBean.setOrder(1);
    filterRegistrationBean.addUrlPatterns("/*");
    filterRegistrationBean.setDispatcherTypes(DispatcherType.ERROR, DispatcherType.REQUEST);
    return filterRegistrationBean;
    }
}
cs

 

서블릿 예외 처리 - 인터셉터

인터셉터는 dispatcherType 지정 불가
excludePathPatterns
에서 에러 페이지를 제외시킴

1
2
3
4
5
6
7
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LogInterceptor())
            .order(1)
            .addPathPatterns("/**")
            .excludePathPatterns("/css/**""*.ico""/error""/error-page/**");
}
cs

 

필터: DispatchType 으로 중복 호출 제거

        dispatchType=REQUEST

인터셉터: 경로 정보로 중복 호출 제거

        excludePathPatterns("/error-page/**")

 

순서

1. WAS(/error-ex, dispatchType=REQUEST) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러

2. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)

3. WAS 오류 페이지 확인

4. WAS(/error-page/500, dispatchType=ERROR) -> 필터(x) -> 서블릿 -> 인터셉터(x) -> 컨트롤러(/error-page/500) -> View

 

 

스프링 부트에서 제공하는 오류 페이지

기본 오류 페이지 경로: /error

스프링 부트가 자동으로 BasicErrorController 등록 (기본적인 로직 모두 개발되어 있음)

개발자는 오류 페이지 화면만 등록

 

뷰 선택 우선순위

BasicErrorController 의 처리 순서

① 뷰 템플릿

resources/templates/error/500.html

resources/templates/error/5xx.html

 

② 정적 리소스( static , public )

resources/static/error/400.html

resources/static/error/404.html

resources/static/error/4xx.html

 

③ 적용 대상이 없을 때 뷰 이름( error )

resources/templates/error.html

 

  • 해당 경로 위치에 HTTP 상태 코드 이름의 뷰 파일을 넣어두면 됨.
  • 뷰 템플릿 > 리소스 / 구체적인 것 (404, 500) > 덜 구체적인 것 (5xx)
  • 5xx, 4xx 라고 하면 500대, 400대 오류를 처리해줌

 

BasicErrorController 컨트롤러는 다음 정보를 model에 담아서 뷰에 전달.

뷰 템플릿은 이 값을 활용해서 출력 가능

 

* timestamp: Fri Feb 05 00:00:00 KST 2021

* status: 400

* error: Bad Request

* exception: org.springframework.validation.BindException

* trace: 예외 trace

* message: Validation failed for object='data'. Error count: 1

* errors: Errors(BindingResult)

* path: 클라이언트 요청 경로 (`/hello`)

 

 

오류 정보를 고객에게 노출하는 것 별로 안좋음

BasicErrorController에서 오류 정보 model에 포함 여부 선택 가능 (application.properties)

 

기능 확장

ErrorController 또는 BasicErrorController 상속 받아서 구현 / 기능 추가

반응형

+ Recent posts