• AWS Korea Hero인 '송주영' 님의 처음 시작하는 Infrastructure as Code: AWS & 테라폼 인프런 강의를 참고하여, 내용 정리 및 실습을 진행해본 내용이다.
  • IaC 도구로 Hashicorp에서 만든 Terraform을 가장 많이 사용하므로, 여기서는 IaC 도구로 Terraform을 사용해보았다.
  • IaC(코드로써의 인프라)는 더이상 선택이 아니라 필수이다..!!

IaC(Infrastructure as Code)에 대한 간단한 개념

  • IaC는 인프라 구성요소들을 코드를 통해 구축하는것이다.
  • 따라서, 코드로써의 장점을 가진다. 즉 작성용이성, 재사용성, 유지보수 등의 장점을 가진다.

Terraform 구성요소

'Development Logs' 카테고리의 다른 글

Spring Boot에서의 Logging Pattern 설정  (0) 2023.08.04
Test Double에 대한 소개  (0) 2023.07.17
  • Spring Boot에서의 Logging Pattern 설정을 위해 Log와 관련된 간단한 지식을 정리한 글입니다.
  • Logging Pattern 설정과 관련된 내용입니다.

1. Logback이란?

  • Java에서 Log 기록을 위한 Open-Source framework.  SLF4J의 구현체입니다.
  • Spring Boot에서는 default로 설정되어 있어서 사용 시 별도로 라이브러리를 추가하지 않아도 됩니다.
  • spring-boot-start-web 안에 spring-boot-starter-logging에 구현체가 있습니다.
  • Logback을 이용하여 logging 하기 위해서 필요한 주요 설정 요소로는 Logger,  Appender, Encoder의 3가지가 있습니다.

  

2. Spring Boot  logback 설정파일

Spring Boot의 경우에는 아래 3가지 중 한 가지 방법을 선택합니다.

  • application.properties에 설정
  • resources/logback-spring.xml에 설정
  • resources/logback.xml에 설정

→ 해당 글에서는 appilcation.yml파일에 설정하였습니다.

 

3. Log level 순서 및 사용 방법

TRACE  < DEBUG < INFO < WARN < ERROR

  • ERROR: 요청을 처리하는 중 오류가 발생한 경우 표시
  • WARN: 처리 가능한 문제, 향후 시스템 에러의 원인이 될 수 있는 경고성 메시지를 표시
  • INFO:  상태 변경과 같은 정보성 로그를 표시
  • DEBUG: 프로그램을 디버깅하기 위한 정보를 표시
  • TRACE: 추적 레벨은 DEBUG보다 훨씬 상세한 정보를 표시

위의 순서대로 높은 레벨을 가집니다. 출력  레벨의 설정에 따라 설정 레벨 이상의 로그를 표시합니다. 예를 들어 LEVEL을 INFO로 하였을 경우 INFO, WARN, ERROR LEVEL을 표시합니다.

 

4. application.yml에서 logging.pattern 설정

4.1. logging.pattern 속성

logging.pattern 속성은 Spring Boot의 로그 출력 패턴을 구성하는 데 사용되는 여러 하위 속성들을 포함합니다. 주요하게 사용되는 하위 속성은 다음과 같습니다:

  1. logging.pattern.console: 콘솔에 출력되는 로그의 패턴을 지정합니다. 콘솔 로그 출력 형식을 설정할 때 사용됩니다.
  2. logging.pattern.file: 파일 로그의 패턴을 지정합니다. 파일 로그 출력 형식을 설정할 때 사용됩니다.
  3. logging.pattern.level: 로그 레벨 패턴을 지정합니다. 로그 레벨을 출력 형식에 포함시킬 때 사용됩니다.
  4. logging.pattern.dateformat: 로그 메시지의 날짜 형식을 지정합니다. %d 패턴을 사용하여 로그 메시지에 날짜를 포함시킬 때 사용됩니다.
  5. logging.pattern.exception: 예외 발생 시 예외 정보를 출력하는 패턴을 지정합니다. %ex 패턴을 사용하여 예외 스택 트레이스를 로그에 출력할 때 사용됩니다.
  6. logging.pattern.file.max-history: 파일 로그의 최대 보관 기간을 지정합니다. 로그 파일 보관 기간을 설정할 때 사용됩니다.
  7. logging.pattern.file.max-size: 파일 로그의 최대 크기를 지정합니다. 로그 파일 크기를 설정할 때 사용됩니다.

위의 하위 속성들은 logging.pattern 아래에서 로그 출력 패턴을 구성하는 데 사용됩니다. 각 하위 속성은 특정한 로그 출력 형식을 설정하기 위해 다양한 패턴을 지정할 수 있습니다. 로그 출력을 필요에 맞게 조정할 때 이러한 하위 속성들을 활용할 수 있습니다.

4.2. logging.pattern에서 자주 사용되는 요소들

  • %d: 날짜와 시간을 나타냅니다. 기본 형식은 yyyy-MM-dd HH:mm:ss.SSS입니다. 날짜와 시간 형식을 지정할 수도 있습니다.
  • %p: 로그 레벨을 나타냅니다. 주로 "TRACE", "DEBUG", "INFO", "WARN", "ERROR" 등의 값이 사용됩니다.
  • %thread: 현재 스레드 이름을 나타냅니다.
  • %t: 로깅이 발생한 스레드 이름을 나타냅니다
  • %c: 로그를 출력하는 클래스의 이름을 나타냅니다.
  • %C: 로그를 출력하는 클래스의 간결한 이름(패키지명 없이)을 나타냅니다.
  • %M: 로그를 출력하는 메서드의 이름을 나타냅니다.
  • %L: 로그를 출력하는 코드 라인 번호를 나타냅니다.
  • %m: 로그 메시지 자체를 나타냅니다.
  • %n: 개행 문자를 나타냅니다.
  • %r: 애플리케이션 시작 이후부터 로깅이 발생한 시점까지의 시간(ms)
  • %-5level: 로그 레벨, -5는 출력의 고정폭 값(5글자)
  • %logger{36}: 로그네임(className)를 나타냅니다.(36글자)
  • %msg: 로그 메시지 (=%message)
  • ${PID:-}: 프로세스 아이디
    • - 는 값을 의미하며, 값이 없는 경우, 빈 문자열로 대체됩니다.

 

5. Spring boot에서 Logging Pattern Test

5.1. 기본 Log 포멧

  • logging.pattern에 아무것도 설정하지 않았을 때를 의미합니다.
  • 날짜와시간 / 로그레벨 / 프로세스ID / 구분자 / 스레드명 / 로그네임(className)/ 로그 메시지
    • 로그네임은 너무 길면 잘릴 수 있습니다.
    • 스프링 부트의 기본적인 로그레벨은 INFO 입니다.
      • (아래 그림에서는 application-local.yml 파일에서 logging.level을 DEBUG로 설정해둔 상태임)
    • 로그 확인

Spring Boot의 기본 Log

 

5.2. Log 포맷 - 로그레벨 설정

  • 기본 Log 포맷에서 2번째 부분인 로그레벨만 포맷 설정이 가능합니다.
  • application.yml 파일 설정
    • logging.pattern.level 을 추가해봅니다.
      • %5p : 로그레벨을 5글자로 한다.
      • 그 외, 정보($로 되어있는 정보들)들을 로그레벨에 추가해보았습니다.
      •  
      • logging: pattern: level: "%5p [elders:},%X{traceId:-},%X{spanId:-}]"
  • 로그 확인
    • 로그레벨을 나타내는 2번째 부분(초록색)이 바뀐것을 확인할 수 있다.

logging.pattern.level 적용된 후 Log

 

5.3. Log 포맷 - 전체 포맷 로그 설정

  • 기본 Log 포맷의 전체 부분을 설정할 수 있습니다.
  • application.yml 파일 설정
    • logging.pattern.console 을 추가해봅니다.
    • 기본 로그 포맷에서 필요한 부분들을 수정합니다.
      • 날짜와시간 / 로그레벨 / 프로세스ID / 구분자 / 스레드명 / 로그네임(className)/ 로그 메시지
      • 날짜와시간 : %d{yyyy-MM-dd HH:mm:ss.SSS}
      • 로그레벨 : [%5p] %clr([elders},%X{traceId:-},%X{spanId:-}])
      • 프로세스ID : [${PID:-}]
      • 구분자 : --- , >>>
      • 스레드명 : [%15thread]
      • 로그네임(className) : %clr([%logger{36}])
      • 로그 메시지 : %msg %n
    • logging:
        pattern:
          console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%5p] %clr([elders:},%X{traceId:-},%X{spanId:-}]) [${PID:-}] --- [%15thread] %clr([%logger{36}]) >>> %msg %n"
  • 로그 확인
    • %clr 한 부분들은 spring boot에서 제공하는 색으로 console에 찍히는 log가 수정된다.

logging.pattern.console 적용 후 Log

 

5.4. Log 포맷 - level, console 동시 설정

  • application.yml 파일 설정시, logging.pattern.level과 logging.pattern.console을 같이 설정하게 되면, logging.pattern.level의 설정 값이 무시됨
  • application.yml 파일 설정
    • logging:
        pattern:
          level: "%5p [elders:},%X{traceId:-},%X{spanId:-}]"
          console: "%msg %n"
  • 로그 확인
    • logging.pattern.console에서 설정한 %msg(로그메시지)만 찍히고 있는걸 확인할 수 있다.

logging.pattern.level, logging.level.console이 적용된 후 Log

 

Reference

0. 들어가면서


Kent BeckXP(Extreme Programming) Explained 저서에는 이런 문구가 있습니다.

개발의 기본 흐름은 일단 실패하는 테스트를 작성하고, 그 다음으로 그 테스트를 통과하도록 만드는 것이다.

해결하고 싶은 스토리들을 목록으로 만들고, 그 스토리들을 표현하는 테스트들을 작성하고, 그런 다음 그 테스트들 통과하도록 만든다. 여러분이 작성해야 할 필요가 있다고 생각하는 테스트들을 목록으로 만들고, 테스트를 하나 작성하고, 그 테스트를 통과하도록 만들고, 다른 테스트를 작성하고, 두 테스트 모두 통과하도록 만들고 하면서 목록이 비워질 때까지 일한다.

이번에 VmWare Tanzu Labs분들에게 TDD로 진행하는 법을 배우면서, 테스트 주도 개발(TDD, Test Driven Development)는 실천을 통해서 얻을 수 있는 느낌을 많이 받았다. TDD를 어느 상황에 어떻게 한다 라고 정의 내리긴 힘들고 각 상황별, 자기가 테스트하고 싶은 대상에 따라, TDD를 진행하는 방식이 달라질수 있다는 느낌을 많이 받았다.

그렇지만, 개발자는 TDD를 실천하기 위한 몇가지 도구들이 필요하고, 그 중 Test Double의 개념 설명과 그 것을 구현하는 도구인 Mockito에서 지원하는 Mock, Spy에 대해 간략하게 소개하고자 한다.

 

 

1. Test Double


테스트 더블(Test Double)이라는 단어는 영화 산업에서 위험한 장면을 촬영할 때 배우를 대체할 대역인 스턴트 더블(stunt double)에서 유래했습니다. 테스트를 진행할 때 실제 클래스를 사용하는 것이 아니라 이와 동일한 형태를 가진 테스트 더블을 사용합니다.

 

1.1. Why Using Test Double

단위 테스트(unit test)는 시스템이 커질수록 쉽지 않아지기 마련입니다. 테스트하고 싶은 메소드 내부에 다른 컴포넌트(component)에 의존한 기능들로 인해 결합도(coupling)가 높을수도 있고, 제어하기 어려운 네트워크나 데이터베이스를 사용하는 기능들이 존재할 수 있습니다. 이런 여러 가지 제약 사항들 때문에 어려운 테스트를 빠르고 쉽게 진행하기 위해 테스트 더블을 사용합니다.

시스템 컴포넌트 단위 테스트

https://www.crocus.co.kr/1555

 

1.2. When Using Test Double

테스트 더블은 다음과 같은 시기에 사용합니다.

  • 예측 불가능한 요소를 통제하여 테스트하기를 원하는 경우
  • 느린 테스트를 보다 빠르게 진행하기를 원하는 경우
  • 통합 환경 구축의 어려움이 발생하는 경우
  • 실제 클래스를 사용하기 어렵고 불편한 경우

 

 

2. Test Double 소개


  • 테스트를 도와주는 개념적인 요소들이다.
  • Test Double에는 5가지 Type이 있다.

TestDouble의 5가지 Type

 

2.1. Test Double Type별 소개

  • 여기서 sut(System Under Test)가 의미하는 것은 테스트하고자 하는 대상을 말한다.
  • 여기서 예제 코드들은 Mockito의 Mock을 사용하여 대부분 작성하였다.

Mock

Mock

  • sut의 메소드가 실행될때, 일어나는 sut의 어떤 행위를 검증하고자 할때, 사용한다.
    • 예를들면, sut는 BarController이고, BarController 메소드를 실행하면, BarAdaptor.getLineCount(request)이란 행위를 한다고 하자
    • 이때, 아래와 같이 검증할수 있고, 이것을 Mock이라고 한다.
verify(BarAdaptor).getLineCount(request);

 

Spy

Spy

  • 직접 customizing한 mock이라고 할 수 있다.
    • 테스트에 사용되는 객체, 메소드의 사용 여부 및 정상 호출여부를 주로 알려주기 위해 사용되며, 테스트에 사용되는 객체를 implements 또는 extends 받아서, @Override 해서 직접 메소드를 customizing 해서 테스트할 수 있다.
  • customizing
public class BarClientSpy implements BarClient {
    private final List<Integer> callSequences = new ArrayList<>();

    public List<Integer> getCallSequences() {
        return this.callSequences;
    }

    @Override
    public BarResDTO bar(BarReqDTO request) {
        callSequences.add(1);
        return stubResponse();
    }
    
    private BarResDTO stubResponse() {
        return BarResDTO.builder
                .cd("sampleCd")
                .msg("sampleMsg")
                .build();
    }
}

 

Stub

Stub

  • sut의 메소드가 실행될 때, 일어나는 어떤 행위를 정의(준비해둔 결과를 반환)해줄 때 사용한다.
    • 어떤걸 리턴해줘라(행위)
      • .thenAnswer(추가 제어; return 반환값)
        • 어떤 행위(when)을 했을때, 필연적으로 제어하는 로직이 필요한 경우, thenAnswer를 이용할 수 있다.
when(barAdaptor.getLineCount(request))
                .thenAnswer((invocation) -> {
                    ResponseHeadersContext.setResponseHeader(
                            Map.of("transaction-id", List.of("12345"))
                    );
                    return BarResDTO.builder()
                                    .cd("sampleCd")
                                    .msg("sampleMsg")
                                    .build()
                                );
                });
    • .thenReturn(반환값)
when(barClient.bar(a, b))
                .thenReturn(
                        BarResDTO.builder()
                        		.cd("sampleCd")
                                .msg("sampleMsg")
                                .build()
                );

 

Fake

Fake

  • 테스트를 위해 실제처럼 동작해주는 객체들이다.
  • 주로 외부시스템인 경우 많이 사용하며, 비즈니스 로직들을 추가해서 만들어도 fake가 될 수 있다.
    • ex) wiremock, embededRedis
    • WireMock.stubFor()
stubFor(
        WireMock.post("/bar")
                .withRequestBody(
                        containing(encodeUrl)
                )
                .willReturn(
                        aResponse()
                                .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
                                .withBody(response)
                )
);

 

Dummy

Dummy

  • 특정 검증하고자 하는 메소드에 어떤값이 들어가도 상관 없는 값이다.
    • 들어가지 않으면 컴파일러가 되지 않는 경우 dummy를 넣어준다.
    • any()
sut.call(a, any())

 

 

3. Mockito 소개


  • 테스트를 도와주는 도구의 종류이다.
  • Mockito에서 지원하는 테스트 도구에는 대표적으로 2가지가 있다.

Mockito에서 지원하는 테스트 도구

 

Mock

  • 여기서 지원하는 mock이란 위에서 말하는 개념적인 mock과는 다른 테스트를 도와주는 도구라고 생각해야한다.
  • Mockito.mock : 테스트 더블 (아직 어떻게 쓸지 모르기 때문에 테스트 더블 상태이다)
    • 그리고 어떻게 사용할지에 대해 아래와 같이 구분되게 되는것이다.
    • Mockito.mock().thenReturn → Stub
    • verify() -> Mock
  • @MockBean
    • @SpringBootTest, @WebMvcTest 등에서 사용한다.(통합테스트에서 쓰는것임)
    • mock() 이랑 역할이 똑같다.
    • Spring이 bean 으로 등록된 애들을 사용하므로, 의존성 관계를 생각해서 mockbean으로 등록해줘야하는 경우가 생길 수 있다.

 

Spy

  • Spy는 when then 안해주면 null이된다.
  • when then 안해주면 구현되어있는데로 동작한다.

 

Q&A


  • @SpringBootTest 또는 그냥 unit test 를 하는 기준?
    • spring의 힘이 필요한 경우..ㅎㅎ
    • 가능한 unit test로 진행하는게 좋다
    • 보통 외부랑 접점이 있는 경우, 많이 사용하는 것 같기도 함
    • 최대한 의존성을 간단히 가져가야된다.(추상화 Layer를 둘 수 있도록 한다)
  • 테스트 코드를 짜다보면 의존성을 주입을 많이 가지게 되는 경우가 생기게 된다.
    • 이때, 최대한 의존성을 간단히 가져갈 수 있도록 해야하며, 추상화 Layer를 두어 해결할 수 있다.
    • ex) authservice - kakao 예시
기존 추상화 Layer를 두어서 역할 구분

 

 

Reference

1장. 들어가기

1. 이 책은 자바 언어와 그 기반 라이브러리, 즉 java.lang, java.util, java.io, java.util.concurrent, java.util.function 같은 하위 패키지를 효과적으로 사용하게끔 구성했음(때때로 다른 패키지도 다룸)

2. 많은 디자인 패턴과 관용구를 설명해줌

3. 이 책의 규칙 대부분은 아주 핵심적인 기본 원칙 몇 개에서 파생됨
바로 명료성단순성임.
- 컴포넌트는 사용자를 놀라게하는 동작을 해서는 절대는 안됨.
- 코드는 복사되는게 아니라 재사용되어야함.
- 컴포넌트 사이의 의존성은 최소로 유지해야함.

4. 기술 용어는 대부분 자바 8용 언어 명세를 따르며, 주요사항은 아래와 같음
 - 자바가 지원하는 타입은 인터페이스, 클래스, 배열, 기본타입 까지 총 4가지임.
 - 애너테이션(annotation)은 인터페이스의 일종
 - 열거 타입(enum)은 클래스의 일종
 - 인터페이스, 클래스, 배열은 참조타입(reference type)이라고 함
 - 즉, 인터페이스, 클래스, 배열은 객체(object)이고 기본타입은 그렇지 않음
 - 클래스의 멤버로는 필드, 매서드, 멤버 클래스, 멤버 인터페이스가 있음
 - 메서드 시그니처는 메서드의 이름과 입력 매개변수(parameter)의 타입들로 이뤄짐(반환값의 타입은 시그니처에 포함되지 않음
)

* 참고
메소드 시그니처(method signature)
메소드 오버로딩의 핵심은 바로 메소드 시그니처(method signature)에 있습니다.
메소드 시그니처란 메소드의 선언부에 명시되는 매개변수의 리스트를 가리킵니다.
만약 두 메소드가 매개변수의 개수와 타입, 그 순서까지 모두 같다면, 이 두 메소드의 시그니처는 같다고 할 수 있습니다.

www.acmicpc.net/problem/1181

문제

해결 방안

1. HashSet을 이용하여 입력을 받아 중복된 경우를 제거해준다.

2. ArrayList에 담아서 해당 ArrayList를 Collections.sort()를 이용하여 정렬해준다.
이 때, new Comparator를 사용하여 길이가 짧은 것과 길이가 같으면 사전순으로 정렬하는 코드를 구현해준다.

코드

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

public class Main {

	public static void main(String[] args) throws Exception{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String str = br.readLine();
		int n = Integer.parseInt(str);
		
		HashSet<String> set = new HashSet<>();
		
		for(int i=0; i<n; i++) {
			set.add(br.readLine());
		}
		
		List<String> words = new ArrayList<>(set);
		
		Collections.sort(words, new Comparator<String>() {
			@Override
			public int compare(String s1, String s2) {
				if(s1.length() < s2.length())
					return -1;
				else if(s1.length() > s2.length())
					return 1;
				else
					return s1.compareTo(s2);
			}
		});
		
		for(String word : words) {
			System.out.println(word);
		}
	}

}

www.acmicpc.net/problem/7568

문제

해결 방안

전체 경우의 수를 비교해보면 되는 간단한 문제이다. 완전탐색을 이용하여 peoples i번째의 키와 몸무게를 다른 j번째 키와 몸무게를 비교하면 된다.

코드

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

// 덩치
public class Main {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String str = br.readLine();
		int n = Integer.parseInt(str);
		
		int[][] peoples = new int[n][2];
		
		for(int i=0; i<n; i++) {
			str = br.readLine();
			peoples[i][0] = Integer.parseInt(str.split(" ")[0]);
			peoples[i][1] = Integer.parseInt(str.split(" ")[1]);
		}
		// 입력끝 ====
		
		int x1, y1;
		int x2, y2;
		int k;
		for(int i=0; i<n; i++) {
			x1 = peoples[i][0];
			y1 = peoples[i][1];
			k = 1;
			
			for(int j=0; j<n; j++) {
				if(i!=j) {
					x2 = peoples[j][0];
					y2 = peoples[j][1];
					
					if(x1<x2 && y1<y2) {
						k++;
					}
				}
			}
			System.out.print(k + " ");
		}
		
	}

}

www.acmicpc.net/problem/2667

문제

해결 방안

total이 단지 수가 되고 지도를 탐색하면서 1을 발견하면 total++을 해준다.
그리고 DFS탐색으로 통해 해당 단지에 속한 집의 갯수를 cnt에 저장하여 dfs()를 빠져나오면 ArrayList에 해당 cnt를 넣어준다.

그리고 ArrayList에 저장된 각 단지의 집의 갯수들을 정렬하여 출력해주면 된다.

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;

public class Main {
	
	static int n;// 지도 크기
	static int[][] maps;
	static boolean[][] visited;
	
	static int total=0; // 총 단지수
	static int cnt;
	static List<Integer> cnts = new ArrayList<>();// 각 단지에 속하는 집의 수
	
	// 북 동 남 서
	static int[] dy = {-1, 0, 1, 0}; // 세로
	static int[] dx = {0, 1, 0, -1}; // 가로

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		String str = br.readLine();
		n = Integer.parseInt(str);
		
		maps = new int[n][n];
		visited = new boolean[n][n];
		
		for(int i=0; i<n; i++) {
			str = br.readLine();
			for(int j=0; j<n; j++) {
				maps[i][j]=Integer.parseInt(str.split("")[j]);
			}
		}
		// === 입력 끝=======
		
		// 탐색 + 단지 수 정해주기
		for(int i=0; i<n; i++) {
			for(int j=0; j<n; j++) {
				cnt = 0;
				if(maps[i][j]==1 && !visited[i][j]) {
					total++;
					cnt++;
					dfs(i, j);
					cnts.add(cnt);
				}
			}
		}
		
		// 출력
		System.out.println(total);
		Collections.sort(cnts);
		for(int i=0; i<cnts.size(); i++) {
			System.out.println(cnts.get(i));
		}
		
	}
	
	static void dfs(int cy, int cx) {
		
		visited[cy][cx] = true;
		
		int ny, nx;
		for(int i=0; i<4; i++) {
			ny = cy + dy[i];
			nx = cx + dx[i];
			
			if(ny>=0 && ny<n && nx>=0 && nx<n) {
				if(!visited[ny][nx] && maps[ny][nx]==1) {
					cnt++;
					dfs(ny, nx);
				}
			}
		}
		
	}

}

www.acmicpc.net/problem/1012

문제

해결 방안

배추가 있는 공간들을 DFS 탐색을 통해 분리된 갯수를 찾는 문제이다.

maps에 배추가 있는 값들을 추가해주고 maps를 탐색하면서 1(배추가 있는 곳)일 때 result(지렁이)++해주고 DFS를 해서 visited를 true로 만들어줘서 다음에 1일때 이미 방문했으면 넘어갈 수 있게 해주었다.

 

코드

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

//유기농 배추
public class Main {
	
	static int m; // 가로길이(1~50)
	static int n; // 세로길이(1~50)
	static int k; // 배추가 심어져있는 위치의 갯수(1~2500)
	static int[][] maps; // 배추 지도
	static boolean[][] visited;
	static int result; // 최소 벌레 수
	
	// 북 동 남 서
	static int[] dx = {0, 1, 0, -1}; // 가로
	static int[] dy = {-1, 0, 1, 0}; // 세로

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String str = br.readLine();
		int t = Integer.parseInt(str);
		
		for(int i=0; i<t; i++) {
			str = br.readLine();
			m = Integer.parseInt(str.split(" ")[0]);
			n = Integer.parseInt(str.split(" ")[1]);
			k = Integer.parseInt(str.split(" ")[2]);
			
			maps = new int[n][m];
			visited = new boolean[n][m];
			result = 0;
			
			// 배추 심어져있는 갯수
			int x; // 가로
			int y; // 세로
			for(int j=0; j<k; j++) {
				str = br.readLine();
				x = Integer.parseInt(str.split(" ")[0]);
				y = Integer.parseInt(str.split(" ")[1]);
				// 지도에 넣어주기
				maps[y][x] = 1;
			}
			
			// 지렁이 둘 구간 탐색
			for(int a=0; a<n; a++) {
				for(int b=0; b<m; b++) {
					//System.out.print(maps[a][b]);
					if(maps[a][b]==1 && !visited[a][b]) {
						result++;
						visited[a][b] = true;
						dfs(a, b);
					}
				}
			}
			//결과값 출력
			System.out.println(result);
		}
	}
	
	static void dfs(int y, int x) {
		int nx, ny;
		
		for(int i=0; i<4; i++) {
			ny = y + dy[i];
			nx = x + dx[i];
			
			// 범위 체크
			if(ny>=0 && nx>=0 && ny<n && nx<m) {
				// 배추가 있고 방문 안한 곳
				if(maps[ny][nx]==1 && !visited[ny][nx]) {
					visited[ny][nx] = true;
					dfs(ny, nx);
				}				
			}
		}
	}

}

+ Recent posts