서론

Front를 Vue.js, Back을 Spring Boot로 만든 토이 프로젝트에서 CORS로 인해 통신이 되지 않는 오류가 발생하여 문제를 해결한 방법을 작성 해둔다.


문제 상황

화면에서 체크박스를 클릭하면 서버로 requert를 보내고 서버에서 그에 맞는 response를 주는 방식에 간단한 프로젝트 인데 클릭 시 아래 이미지 처럼 ‘Network Error’라는 alert를 발생시키고 통신이 되지 않는 문제가 발생 하였고, 개발자 도구로 콘솔을 확인해보니 아래 이미지 처럼 ‘CORS Preflight Did Not Succeed’과

교차 출처 요청 차단: 동일 출처 정책으로 인해 http://localhost:9312/frame에 있는 원격 자원을 차단하였습니다. (원인: CORS 사전 점검 응답 실패).

라는 메시지가 출력된 것을 확인했다.


해결 방법

여러 가지 방법이 있겠지만 필자는 WebMvcConfigurer를 상속받아 설정을 추가해주는 방식으로 처리했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:8080")
.allowCredentials(true);
}
}

  1. 위 코드 처럼 WebMvcConfigurer 를 상속받는 WebConfig class를 작성한다.
  2. 그 중 addCorsMappings() 메서드를 override 한다.
  3. CorsRegistry 클래스의 addMapping() 메서드를 통해 CORS 요청 처리를 활성화할 URL를 지정하는데 이 때 “/**” 같은 Ant 스타일 패턴이나 정확한 경로(ex /admin)를 지정하는 것도 가능하다.
  4. 그 후 allowedOrigins() 메서드에서 CORS 요청을 허용 할 URL를 지정한다.
  5. 참고로 allowCredentials 설정을 true로 줬는데 이렇게 Access-Control-Allow-Credentials를 true로 할 경우 allowedOrigins()에서 “*”로 해서 모든 요청에 대해 CORS를 허용 할 수 없다.

이 방법 외엔 @Crossorigin을 이용해 개별 클래스 혹은 메서드에 CORS 요청 인증을 응답하도록 설정 하는 것도 가능하다.

정상 동작한 모습


여담

이전에 개발 할 때는 설정하지 않아도 잘 됐던거 같은데 오랜만에 프로젝트를 클론받아 실행하니 동작하지 않아 당황했다… 왜 그럴까?


참고 사이트

댓글 공유

서론

집에서 서버에 접속할 일이 생겨서 평소처럼 ssh로 접속을 하려고 했는데..
접속이 되지 않아 당황했는데 미래의 나와, 다른 사람들은 이러지 않길 바라며 글을 남긴다.


접속 방법

우선 .pem 파일이 존재하는 경로에 폴더명이 한글인 폴더가 존재하면 안된다.

  1. 설정의 앱 및 기능에서 선택적 기능을 선택
  2. 기능 추가를 선택하여 ssh를 검색해서 OpenSSH 클라이언트를 설치
  3. SSH 이용 가능

여담

나의 경우엔 .pem 파일이 한글 폴더가 있는 경로에 존재해서 인식하지 못했던 문제였지만
혹시 모르니 SSH 클라이언트 설치방법도 작성해놓는다.

댓글 공유

문제

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


코드

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
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

StringBuilder sb = new StringBuilder();
int i = 0;

String input = "";
while (!"0 0 0".equals(input = br.readLine())) {
int[] lpv = convertStringArrayToIntegerArray(input.split(" "));

int l = lpv[0];
int p = lpv[1];
int v = lpv[2];

sb.append("Case ").append(++i).append(": ").append((v/p) * l + ((v%p) > l ? l : (v%p))).append("\n");
}

System.out.println(sb);
}

private static int[] convertStringArrayToIntegerArray(String[] args) {
int[] array = new int[args.length];
int i = 0;
for (String str : args) {
array[i++] = Integer.parseInt(str);
}

return array;
}

흐름

  1. V일 중에 연속되는 P일 동안 L일 만큼 휴가를 사용 할 수 있으므로
  2. V에서 P를 나눈 값에서 L일을 곱하면 사용 가능한 휴가 일수가 구해지고
  3. L이 V일에서 P일을 나눈 값보다 작을 땐 L일 만큼 휴가를 더 갈 수 있고
  4. 큰 경우엔 V % P 일 만큼 갈 수 있으므로 구한 값을 더하면
  5. 총 휴가 일수를 구할 수 있다.

결과

댓글 공유

서론

사실, 제목이 결론이다. @Requestbody Annotation을 사용하려면 반드시 해당 DTO에는 기본 생성자가 명시적으로 존재하여야 한다. 뭔가 설정을 잘못했는지는 모르겠지만 intellij 에서 stacktrace가 출력되지 않아 이 사실을 알 수 가 없어서 엄청나게 삽질을 했기에 내용을 정리한다…


본론

상황

controller 테스트에서 모든 조건을 맞췄는데도 500 에러가 발생하여 테스트를 통과하지 못하는 상황이었는데 @Requestbody annotation을 DTO가 아닌 HashMap으로 변경하니 테스트가 정상적으로 통과되어 JSON이 제대로 DTO로 매핑되지 않아서 발생하는 문제라고 판단했다.

에러 발생

No suitable constructor found for type [simple type, class 클래스명]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)

직역하면 기본 생성자가 존재하지 않아서 에러가 발생했다는 내용이다.

해결 방법

빌더패턴을 사용하던 클래스에 @Noargsconstructor annotation을 사용해서 기본 생성자를 생성하게 하였다.

결론

@Requestbody와 DTO를 매핑되게 해야 할 경우 많은 조건이 필요하다.
이전 포스트에 작성했던 boolean의 변수명이라던가, 지금과 같은 기본 생성자라 라던지… 이게 모두 @Requestbody가 jackson 라이브러리를 이용해 매핑하기 때문이다.


참고 사이트

댓글 공유

서론

AWS 프리티어는 이미 다른 서비스가 이용하고 있고 Java를 지원해주는 클라우드 서비스가 없었는데 heroku를 발견하여 사용방법을 정리 해둔다.

참고로 데이터베이스는 한글을 지원하지 않는다.


본론

  1. heroku 회원 가입
  1. App 생성

위에 보이는 create a new app 버튼을 클릭하고

App name을 지정해야 하는데 이미 heroku에 존재하는 service의 name은 생성 할 수 없고,
여기서 지정한 App name으로 호스팅 될 URL이 생성된다.

  1. heroku CLI 설치

생성이 완료되면 위 화면처럼 Deploy tab으로 이동되는데 설명되어 있는 것 처럼 먼저 Heroku CLI를 자신의 OS 버전에 맞게 설치한다.

  1. Procfile 생성

Heroku는 실행 할 때 마다 port를 자동으로 지정해주는데 port를 고정시키기 위해 우선 application.properties에 port를 바인딩 해준다.

application.properties

1
server.port=${port:8080}

그 후 Procfile을 Project 루트 디렉토리에 확장자 없이 생성하고 아래와 같이 작성한다.

1
web: java -Dserver.port=$PORT $JAVA_OPTS -jar [실행될 jar파일 경로]

Procfile의 경로

  1. 배포

이후엔 heroku의 가이드를 그대로 따라하면 된다.

모든 가이드를 정상적으로 잘 따라하면 위 처럼 접속할 수 있는 URL이 출력되고 해당 URL로 접속하면

  1. 확인

https://backjoonframeautomaticgenerat.herokuapp.com/

정상적으로 실행되어 서비스가 실행되는 것을 확인 할 수 있다.


결론

데이터베이스를 사용하지 않는 서비스나, 한글이 입력되지 않는 서비스의 경우 무료로 이용 할 수 있는 좋은 클라우드 서비스 인 것 같다.

참고로 30분간 접속이 없으면 휴면 모드로 전환되어 최초 접속이 다소 느릴 수 있으나 무료 서비스인 만큼 그정도는 감안해주자.

이 글에 소개한 CLI를 이용한 방법 말고 Github와 연결해서 branch에 push하면 자동으로 배포되게 할 수 도 있는 것 같으니 찾아보고 적용하면 이 방법 보다 더 편할 것이다.


참고 사이트

댓글 공유

서론

연관관계에 있는 객체를 가져와서 set 하는 메서드를 테스트 하는 도중 아래와 같은 에러가 발생하였다.


본론

해당 에러

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
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.jjlee.wedding.payment.domain.Cost.costOptions, no session or session was closed

at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:383)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:375)
at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:122)
at org.hibernate.collection.PersistentBag.size(PersistentBag.java:248)
at com.xxxxxx.ImplTest.가져와서_셋해야하는지_테스트(CostServiceImplTest.java:89)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

이 때 디버깅을 해보면 연결된 entitiy에서

1
Unable to evaluate the expression Method threw 'org.hibernate.LazyInitializationException' exception.

라는 에러가 발생해 있는데 이는 결국 select 한 entitiy가 영속성 컨텍스트 내에 존재하지 않기 때문에 발생한 애러이다.
위 에러에서 말하는 세션이 바로 영속성 컨텍스트를 말하는 것이고, 이는 한 트랜잭션안에 해당 entitiy가 존재하지 않다는 것과 같은 말이다.

해결

이를 해결하기 위해선 현재 지연로딩으로 되어있는 연관관계를 즉시로딩으로 변경하여 한번에 가져오던가,
혹은 Test 메서드에 @Transactional 어노테이션을 줘서 트랜잭션 내에 존재하도록 해주면 테스트가 정상적으로 통과되게 된다.

결과


참고 사이트

댓글 공유

서론

현재 작업 중인 프로젝트에서 테스트 코드를 작성해 테스트할 일이 있었는데 프로젝트의 환경은 spring 4.3에 Junit 4.8이었다.

이에 원래 사용하던 junit5로 넘어갈까 하였으나 junit5를 사용하려면 설정을 spring boot으로 해야 한다는 글들이 있어 같은 테스트 환경을 만들기 위해 junit만 4.12 버전으로 업그레이드한 후 테스트를 진행하였는데 spring 프로젝트지만 config 설정들을 boot 처럼 java 파일로 관리하는 형태여서 java 파일과 properties 파일을 동시에 잡아 줄 필요가 있었는데
@Contextconfiguration(classes = {블라블라…}, locations = {블라블라…})로 잡으니 에러가 발생하여 해결한 방법을 작성해놓는다.


본론

코드

1
2
3
4
5
6
7
@ContextConfiguration(classes = {
DatabaseConfig.class,
SecurityConfig.class,
SocialConfig.class,
EnumConfig.class,
WebMvcConfig.class
}, locations = "classpath:properties/test.properties")

서론에 적은 것 처럼 classes와 locations를 둘 다 설정하였더니 아래와 같이 에러가 발생하였다.

에러

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
java.lang.IllegalArgumentException: Cannot process locations AND classes for context configuration [ContextConfigurationAttributes@64c87930 declaringClass = 'com.xxxx.xxxImplTest', classes = '{class com.xxxx.config.DatabaseConfig, class com.xxx.config.SecurityConfig, class com.xxx.config.SocialConfig, class com.xxx.config.EnumConfig, class com.xxx.config.WebMvcConfig}', locations = '{classpath:properties/test.properties}', inheritLocations = true, initializers = '{}', inheritInitializers = true, name = [null], contextLoaderClass = 'org.springframework.test.context.ContextLoader']: configure one or the other, but not both.

at org.springframework.util.Assert.isTrue(Assert.java:68)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.processContextConfiguration(AbstractDelegatingSmartContextLoader.java:154)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:371)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:305)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildTestContext(AbstractTestContextBootstrapper.java:112)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:120)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:105)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTestContextManager(SpringJUnit4ClassRunner.java:152)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.<init>(SpringJUnit4ClassRunner.java:143)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:104)
at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:86)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:37)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
at org.junit.internal.requests.ClassRequest.createRunner(ClassRequest.java:28)
at org.junit.internal.requests.MemoizingRequest.getRunner(MemoizingRequest.java:19)
at org.junit.internal.requests.FilterRequest.getRunner(FilterRequest.java:36)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:49)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

해결

@Testpropertysource(classpath:””)로 test properties를 잡아주고
@Contextconfiguration(classes = { XXXConfig.class}) 로 Java config 파일들을 잡아준다.

1
2
3
4
5
6
7
8
@ContextConfiguration(classes = {
DatabaseConfig.class,
SecurityConfig.class,
SocialConfig.class,
EnumConfig.class,
WebMvcConfig.class
})
@Testpropertysource("classpath:properties/test.properties")

댓글 공유

서론

많은 블로그 서비스 중 필자는 Git, Github를 활용해서 사용하는 hexo로 github 블로그를 선택했지만, 다른 블로그 서비스들과는 달리 설치형 블로그의 특성상 설치된 PC가 아닌 경우엔 블로그에 글을 작성하는 일이 쉽지 않고, 블로그가 설치된 PC가 고장이라도 나는 날에는 고스라니 블로그를 날려버리는 상황이 닥치는 경우도 왕왕있다. (예로 필자는 블로그를 설치한 HDD가 고장나 블로그를 새로 작성했다.) 이런 경우를 방지하고, 블로그 작성의 확장성을 위해 github을 이용해 Hexo 블로그를 back up 하는 방법을 알아본다.


방법

post와 테마를 저장 할 저장소 생성
  1. 우선 위 사진처럼 github에서 테마와 post를 저장 할 repository를 생성한다.
    이 때 테마를 저장 할 저장소의 이름은 테마와 같게 한다.
설치한 테마의 저장소 변경
  1. 테마를 저장하기 위해 연결된 원격 저장소의 위치를 확인하고 설치한 테마 폴더 안에서 git 원격 저장소 주소를 변경한다.
  2. 변경 후 테마용 저장소에 테마를 push한다.
push 된 테마 확인
  1. 위 처럼 정상적으로 저장되어 있는지 확인하고 themes 폴더안에서 테마를 삭제한다.
  2. git의 submodule 기능을 이용해 저장한 테마를 submodule로 추가하는데 이 때 디렉토리의 위치는 themes 폴더 내로 이동해서 submodule 기능을 실행한다.
1
git submodule add 테마 저장소
  1. post를 저장 할 저장소 주소를 추가한다.(cotent)
1
git remote add content post 저장소
post 저장 확인
  1. post 저장용 저장소에 내용을 push하고 정상적으로 저장되었는지 확인한다.
  2. 이 후엔 post 작성 후 post만 content 저장소로 push해주면 된다.

결과

이제 우리는 불미스러운 사고로 블로그가 설치된 HDD가 고장나거나(ㅠㅠ), 블로그가 설치 안된 다른 PC or 노트북에서도 백업된 theme와 post들을 다운받아 블로그를 작성 할 수 있게 되었다.

여담이지만, 과거로 돌아간다면 나는 velog를 사용 할 듯 싶다.


참고 사이트

댓글 공유

서론

토이프로젝트 중 @Reqeust 어노테이션을 적용한 DTO에서 boolean 데이터를 제대로 전달 받지 못하는 문제가 발생하여 이를 정리한다.


문제 발생

vue.js에서 넘어온 데이터를 @RequestBody 어노테이션을 활용해 DTO 객체로 전달 받으려 하였는데 boolean 타입의 데이터가 정상적으로 넘어오지 않는 문제가 발생하였다.

문제가 발생한 Test Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void dto_boolean_test() throws Exception {
//given
RequestDTO dto = RequestDTO.builder()
.isTestCase(true)
.isNQuantity(true)
.isSpaceIncludeNumber(true)
.build();

String test = objectMapper.writeValueAsString(dto);

System.out.println(test);
final ResultActions action = mockMvc.perform(post("/frame")
.contentType(MediaType.APPLICATION_JSON)
.content(test))
.andDo(print());

//then
MvcResult result = action
.andExpect(status().isOk())
.andExpect(content().json(test))
.andReturn();
}

에러 결과


원인

You can annotate any field with @Getter and/or @Setter, to let lombok generate the default getter/setter automatically.
A default getter simply returns the field, and is named getFoo if the field is called foo (or isFoo if the field’s type is boolean).

위 설명처럼 lombok에서 제공하는 @Getter 혹은 @Setter 어노테이션을 사용 할 경우 자동으로 getter/setter 메서드를 생성해주는데
이 때 boolean 타입의 변수에 붙는 prefix는 get이 아닌 is이므로 @RequestBody에서 찾을 수 없어 바인딩 되지 않아 발생하는 문제였다.

실패한 코드

이 처럼 boolean 변수에 is prefix를 붙여놓은 상태에서 @Getter 어노테이션을 사용하니, 내부적으로 isIsTestCase() 같은 이상한 네이밍의 메서드가 생성되서
@RequestBody에서 바인딩에 사용하는 Jackson 라이브러리의 ObjectMapper에서 필드를 찾을 수 없어서 바인딩 되지 않아 DTO에 정상적으로 값이 입력되지 않았던 것이다.

By default Jackson maps the fields of a JSON object to fields in a Java object by matching the names of the JSON field to the getter and setter methods in the Java object. Jackson removes the “get” and “set” part of the names of the getter and setter methods, and converts the first character of the remaining name to lowercase.


해결

  1. boolean 변수명에서 is prefix를 제거한다.
  2. default로 false로 되어 있는 lombok.getter.noIsPrefix=true 설정을 추가한다.
    이 설정을 추가하면 boolean 변수도 get prefix를 사용한다.

필자는 boolean 변수에서 is prefix를 제거하는 방식으로 처리했다.
다른 타입은 자료형에 따라 prefix를 붙이지 않는 상황에서 boolean 변수에만 붙이는 것이 옳지 않다고 생각했기 때문에 1번을 선택했다.

수정된 코드

결과


참고 사이트

댓글 공유

문제

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


코드

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
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
br.readLine();
Integer[] array = convertStringArrayToIntegerArray(br.readLine().split(" "));
List<Integer> list = Arrays.asList(array.clone());

Collections.sort(list);

Map<Integer, Integer> map = new HashMap<>();
int index = 0;
for (int i : list) {
if (!map.containsKey(i)) {
map.put(i, index++);
}
}

StringBuilder sb = new StringBuilder();
for (int i : array) {
sb.append(map.get(i)).append(" ");
}

System.out.println(sb.toString());
}

private static Integer[] convertStringArrayToIntegerArray(String[] args) {
Integer[] array = new Integer[args.length];
int i = 0;
for (String str : args) {
array[i++] = Integer.parseInt(str);
}

return array;
}

흐름

  1. 입력받은 좌표를 저장한 리스트로 수정하면 출력 순서를 맞출 수 없으니 우선 좌표 list를 복사한다.
  2. 복사한 list를 정렬하고 순서대로 반복하면서 좌표값을 key로 index를 저장한다.
    1. {-10 : 0}, {-9, 1}… {4,4}
  3. 입력받은 좌표를 순서대로 반복하면서 map에 key로 index를 가져온다.

좌표 압축의 이유

좌표 {2 4 -10 4 -9} 압축 전
좌표 {2 4 -10 4 -9} 압축 후
  • 문제의 예제인 좌표 {2 4 -10 4 -9}을 위와 같은 알고리즘으로 압축하면 {2 3 0 3 1}이 되는데 위 그림 처럼 압축된 점들도 같은 동일선상 안에 놓이게 된다.
  • 이렇게 범위가 매우 넓은 좌표의 경우에 좌표를 인덱싱해서 처리 할 경우 손쉽게 처리 할 수 있게 된다.

결과


참고 사이트

댓글 공유

Junggu Ji

author.bio


author.job