[{"content":"도입 초심을 잃고 나태해진 자신을 반성하며 블로그를 다시 써보려고 합니다..\n예전에 썼던 글들을 보면서, 그래도 열심히 정리하고 노력했던게 뿌듯하기도 했고, 무엇보다 그동안 계속 속썩이던 구글 검색엔진이 드디어 내 블로그를 크롤링해서 미약하나마 트래픽이 발생했고, 다시 블로깅을 할 의지가 조금 생겼습니다!\n그동안의 행보 싸피를 수료하고, 운좋게도 바로 취업이 되어 8월부터 입사를 하게 되었습니다.\n입사 후에는 그래도 첫 회사라고 나름 의욕도 있었고, 열심히 다니다보니 어느새 블로그 생각은 점점 머릿속에서 사라지고 있었습니다..\n회사까지 출퇴근 거리도 꽤나 멀었고(왕복 4시간\u0026hellip;) 회사에 적응하면서 나름 보탬이 되고자 열심히 배우다보니 조금 현실에 안주했던 것 같습니다.\n평화롭게 회사를 다니던 어느날, 어이없게도 갑작스럽게 회사에서 월급 디폴트 선언1을 하면서 일상이 흔들리기 시작했습니다.\n월급과 희망이 없는 나날들 사실 첫번째 월급날도 입금이 조금 지연되었지만, 일시적이겠거니 했었습니다. 하지만 갑자기 월급이 끊기고, 식권도 끊기고, 기타 수많은 복지들이 사라지는걸 보며 생각보다 보통 일이 아니라는 것을 알게 되었습니다.\n월급이 밀린다는 소식이 전사에 퍼지자, 수많은 개발자분들이 퇴사를 하셨고 대부분의 서비스는 개발이 잠정 중단되었습니다. 사내에서 쓰는 다양한 프레임워크들이 많았는데, 이러한 프레임워크를 유지보수하는 분들도 연락이 잘 안되는 경우도 많았습니다.\n상황이 이러하니 마음잡고 개발을 하는 사람들도 없었고, 이직은 해야겠는데 바깥 시장도 쉽지는 않으니 다들 혼란 속에서 이런저런 유언비어들이 계속 퍼져나갔습니다.\n당연히 신입으로서 뭔가를 배우거나 해볼 수 있는 것도 없었고, 빨리 정리하고 퇴사를 하는게 맞겠다는 생각에 한달 정도 버티던 회사를 결국 퇴사했습니다.\n다시 취준생으로 10월 25일, 이제 3개월도 안된, 중고신입이라고 부르기도 애매한 경력을 가지고 다시 취업시장에 뛰어들게 되었습니다.\n겨우 3개월만에 돌아온 취준 시장은 입사 전보다도 훨씬 무겁고 경직된 느낌이었습니다.\n취준생분들은 공감하시겠지만 이미 레드오션이 된 개발바닥에서 신입은 최소 백준 골드 이상의 문제를 30분 이내에 풀 수 있는 실력과, 현업에 가까운 환경에서 다양한 프로젝트를 하면서 대규모 트래픽까지 경험해야 하는 극한의 능력자들을 가리키는 말이 되었습니다.\n하지만 이런 능력치를 가진 신입이더라도 문제는 뽑는 회사가 없다는 것입니다. 여기저기 플랫폼에는 대부분 3년차 이상의 경력직들만을 모집하고 있었고, 신입을 뽑는 회사는 극히 드물었습니다.\n저도 조금만 일찍 준비했더라면 하반기 공채를 노려볼 수 있었겠지만, 그마저도 거의 끝나나고 있었습니다.\n무엇보다 가장 힘들었던 것은 코딩테스트였습니다. 전역하고부터 싸피 1학기때까지 열심히 풀었던 실력은 다 어디로 갔는지, 쉽게 풀었던 것 같은 문제들도 다시 풀려니 많은 부담이 되었습니다.\n그래서 10월 한달은 코딩테스트만 주구장창 풀었고2, 실력은 조금씩 좋아졌지만 문제는 채용하는 회사가 이미 거의 끝났다는 것입니다.\n앞으로의 계획 위에서는 현재 상황을 조금 비관적으로 얘기한 감이 있지만, 그래도 퇴사 이후 어느정도 안정된 마음이 생기자 다시금 개발이 하고 싶어졌습니다.\n다행히 사이드 프로젝트를 진행중인게 몇 개 있어서 개발의 끈을 놓지는 않고 있습니다. 지금 진행중인 채용 프로세스가 한군데 남았는데 어떻게 될지는 모르겠지만, 내년도에도 개발자로서 계속 도전해볼 생각입니다.\n앞으로의 블로그 계획 뭔가 블로그 계획을 쓰려고 시작한 포스팅이었는데, 신세 한탄만 한 것 같네요\u0026hellip;;\n시간적 여유가 생긴 만큼, 적어도 주 2회는 포스팅 할 수 있도록 블로그도 꾸준히 써 볼 생각입니다.\n일단 사이드 프로젝트를 하면서 접한 스프링 시큐리티 관련 기술들을 패키지로 하나의 포스팅을 해볼 생각입니다. 코딩테스트 문제들도 주 1회 정도씩 괜찮은 문제들을 선별해서 올려보겠습니다. 이후에는 이전 회사에서 프레임워크처럼 만들었던 짭 스프링(?)이 있었는데 해당 프레임워크를 다시 개발해보고, 실제 스프링의 구조와 비교해보면서 조금 깊이있는 주제도 다뤄보고 싶습니다. 그리고 바쁘다는 핑계로 못읽었던 개발 서적들을 요새 열심히 읽고 있는데, 읽으면서 독후감도 계속 써보겠습니다. 올해는 이정도 하면 거의 마무리되지 않을까 싶네요..\n결론 현실에 안주하지 말고 성실히 살자! 기업명은 밝히지 않겠으나, 나름 직원도 많고 신입 초봉도 높은 티어에 속하는 기업이었는데 많이 충격이었습니다\u0026hellip;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n10월 초부터 회사 시스템이 거의 붕괴(?)가 되어 출근해서 딱히 업무가 없었고, 코딩테스트를 계속 풀 수 있었습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/restart/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003cp\u003e초심을 잃고 나태해진 자신을 반성하며 블로그를 다시 써보려고 합니다..\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e예전에 썼던 글들을 보면서, 그래도 열심히 정리하고 노력했던게 뿌듯하기도 했고, 무엇보다 그동안 계속 속썩이던 구글 검색엔진이 드디어 내 블로그를 크롤링해서 미약하나마 트래픽이 발생했고, 다시 블로깅을 할 의지가 조금 생겼습니다!\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"그동안의-행보\"\u003e그동안의 행보\u003c/h2\u003e\n\u003cp\u003e싸피를 수료하고, 운좋게도 바로 취업이 되어 8월부터 입사를 하게 되었습니다.\u003c/p\u003e\n\u003cp\u003e입사 후에는 그래도 첫 회사라고 나름 의욕도 있었고, 열심히 다니다보니 어느새 블로그 생각은 점점 머릿속에서 사라지고 있었습니다..\u003c/p\u003e\n\u003cp\u003e회사까지 출퇴근 거리도 꽤나 멀었고(왕복 4시간\u0026hellip;) 회사에 적응하면서 나름 보탬이 되고자 열심히 배우다보니 조금 현실에 안주했던 것 같습니다.\u003c/p\u003e","title":"개발 블로그를 다시 시작하며..."},{"content":"도입 미루고 미루던 블로그를 이제야 만들었다. 사실 내가 블로그를 쓰게 될 것이라고는 전혀 생각지도 못했지만, 이왕 시작한거 열심히 써보려고 한다.\n첫 협업과 위키페이지 작성 블로그를 쓰게 된 가장 큰 계기는 첫 프로젝트에서 처음으로 협업을 하면서 느꼈던 생각들이었다.\n같은 팀원들끼리 공유해야 할 것도 많았고, 팀장으로서 소통의 창구가 필요한 것도 느끼고 있었기에 처음에는 짧은 지식으로나마 직접 위키페이지를 만들었다.\n(Vue.js로 로컬에서 라이브 서버를 띄워놓음\u0026hellip;)\n처음에는 나름 잘 만들었다고 뿌듯해 했지만,\n로컬에서만 작동하는 부분(같은 네트워크 대역을 쓸때만 접속이 가능) 수정사항이나 새로운 요구사항이 발생할 때마다 변경이 힘듦 팀원들도 사실 많이 이용하지는 않았는지 별로 피드백이 없었음 위의 이유(핑계)들로 프로젝트가 바빠지면서 점차 소홀히 관리하게 되었다.\n첫 실패와 다짐 EC2 서버에 별도로 포트를 잡아 해당 페이지를 올려버릴까도 생각했었지만, 변경내역 관리나 빌드 등에 공수가 너무 들 것 같아 포기했었다.\n하지만 프로젝트가 진행되면서 전역적인 설정이나 참고할만한 내용들을 올리는 창구는 통일되지 않았고, 비슷한 문제를 여러 번 설명하는 과정에서 확실히 시행착오들을 한군데에 정리해 둘 필요성이 있겠다는 생각이 들었다.\n또한, 나중에 어떤 기술을 잘 모르는 사람들에게 내가 쓴 블로그의 글과 함께 설명한다면, 훨씬 이해를 도울 수 있겠다는 것도 이유 중 하나였다.\n당시의 생각들과 실패를 반면교사 삼아, 이번에 마음먹고 며칠 고생해서 깃블로그를 만들었다. 아직 부족한 부분이 많지만 개선해나가는 과정에서 많이 배울 수 있을 것 같아 기대도 된다. 그리고 이렇게 내 지식을 정리하면서 느끼는 것은, 정리하거나 누군가에게 설명해보지 않고서는 내가 어떤 걸 알고 어떤 걸 정말 모르는지 판단하기 어렵다는 것이다.\n앞으로의 계획 기술 블로그 우선, 개발자로 진로를 굳힌 만큼 내 기술과 지식, 시행착오들을 잘 정리해두려고 한다. 나도 물론 참고하겠지만, 다른 사람들에게도 조금이나마 도움이 되기를 바라는 마음이다. 오픈소스의 첫 발걸음을 뗀 것 같아 설레는 마음도 있다.\nLanguages JAVA\n자바에 대해 기초를 배우기는 했지만, java8 이후부터 변경된 사항과 좀더 깊은 부분들은 아직 많이 부족하다는 것을 코드를 짜면서도 많이 느낀다. 가급적 많은 사람들에게 도움이 될 수 있도록 시행착오나, 정말 필요한 내용들에 대해서 정리해보려고 한다.\nOthers\n패러다임은 바뀌고 프로그래밍 언어는 계속 출시된다. 언젠가 자바와 스프링도 도태될 것이고, 더욱 멋지고 빠른 언어들이 계속 등장할 것이다. 다양한 언어가 있겠지만, 당장 알아보고 싶은건 Kotlin과 Elixir이다. 지금까지 배운 언어들도 정말 훌륭하고 멋진 기능들을 제공했지만, 똑똑한 개발자들이 만든 다른 언어들이 얼마나 대단한 기능을 제공할지 벌써 기대가 된다.\nSpring\n이 블로그 이름에서도 알 수 있듯이, 스프링은 나에게 많은 영감과 배움의 기회를 주었다. 스프링을 좀 더 깊이 알면 알수록, 객체지향에도 한발짝 다가가는 기분이 든다.\n스프링의 내부적인 동작들도 궁금하고, 특히 요즘은 스프링이 제공하는 어노테이션의 한계\n(ex. bean은 최대 얼마나 등록 가능한지, 이떄 어플리케이션에 부하가 어느정도 되는지)\n도 궁금해져서 언젠가 연구하고 정리해보고 싶다.\nDatabase\n아직 데이터베이스에 대한 지식은 정말 부족하다. JPA가 정말 너무 많이 도와주다보니 오히려 DB에 대해 너무 모르게 된 것이 아닌가 하는 생각도 든다.\n어차피 백엔드 개발자라면 결국 데이터와의 싸움을 하게 될 텐데, 나중에 후회하기 전에 미리 공부해두고 싶다.\nAlgorithm\n결국 컴퓨터와 대화를 잘하려면 그들의 생각을 배울 필요가 있다. 아직 부족하지만, 조금씩 알고리즘 문제도 풀면서 논리적인 사고를 하는 습관을 계속 익혀야겠다. 가능할지는 모르겠지만, 객체지향적으로 인터페이스를 설계하고 클래스를 구현하는 방식으로 알고리즘 문제를 푸는 시도도 해보고 싶다.\nDesign Pattern\n구조와 패턴을 잘 아는것은 그만큼 코드를 보는 눈을 넓혀주는 것 같다. 디자인 패턴에 대해 공부하고, 특히 실제로 어디에서 쓰이고 있는지 정리해보고 싶다.\nArchitecture\n코드를 짜면서 항상 잘 짜고 있는지 의구심이 들었다. 좋은 코드와 좋은 설계가 무엇인지 공부하고 실제로 코드에 적용하면서 배우는 시간을 가져볼 예정이다.\nCommunications\n군에 있을때부터 지독하게 얽혀있던 통신, 항상 어렵고 항상 모르겠지만 이제는 뭔가 익숙하다못해 친밀한 느낌마저도 든다. 나름 내 인생에 많은 부분을 차지했던 분야인만큼 잘 정리해두면 많은 사람들에게 도움이 될 수 있을 것 같다.\nInfrastructure\n인프라는 모든 서비스들이 제대로 동작하는 토대가 된다. 그만큼 중요한 분야이기 때문에 소홀히 할 수 없고, 트렌드도 자주 바뀌는만큼 계속 관심갖고 지켜볼 필요가 있을 것 같다.\nSecurity\n보안도 군에서부터 많이 따라다녔던 녀석이지만, 바쁘고 귀찮다는 핑계로 많이 소홀히 한적도 있었다. 배우면 배울수록 절대 소홀히 할 수 없는 부분이라고 생각해서 계속 공부해 나가려고 한다.\nEtc\n더 많은 분야들이 있겠지만, 우선 위의 분야들 위주로 정리해나가고자 한다. 하지만 스스로도 너무 여기저기 기웃거리는 타입임을 잘 아는지라 언제 새로운 태그가 생길런지는 모르겠다.\n취미 블로그 Diary or Plan\n매일 일기를 쓰는건 힘들겠지만, 주에 1번 혹은 달에 한번이라도 일기를 적어보려고 한다. 내 생각을 정리하는데 글쓰기 만큼 좋은것도 없는 것 같다. 그리고 앞으로의 계획이나 생각 등도 한곳에 정리해두고 싶다.\nCube\n요새 왜인지는 모르겠는데 어려서 했던 큐브가 재미있어서 조금씩 정리해보려고 한다. 너무 많은 시간을 뺏는 취미는 아니니까 시간날때 조금씩 하는 정도는 괜찮겠지..\n결론 프로젝트를 하면서 블로그의 필요성을 진득하게 느껴서 다양한 분야의 기술을 한곳에 정리하는 개발 블로그를 운영해보려 한다.\n🔥 시작은 미미하지만 많은 사람들이 믿고 찾아볼 수 있는 위키가 될 수 있도록 꾸준히 노력하자.\n","permalink":"https://leaf-nam.github.io/posts/helloworld/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003cp\u003e미루고 미루던 블로그를 이제야 만들었다. 사실 내가 블로그를 쓰게 될 것이라고는 전혀 생각지도 못했지만, 이왕 시작한거 열심히 써보려고 한다.\u003c/p\u003e\n\u003ch2 id=\"첫-협업과-위키페이지-작성\"\u003e첫 협업과 위키페이지 작성\u003c/h2\u003e\n\u003cp\u003e블로그를 쓰게 된 가장 큰 계기는 첫 프로젝트에서 처음으로 협업을 하면서 느꼈던 생각들이었다.\u003c/p\u003e\n\u003cp\u003e같은 팀원들끼리 공유해야 할 것도 많았고, 팀장으로서 소통의 창구가 필요한 것도 느끼고 있었기에 처음에는 짧은 지식으로나마 직접 위키페이지를 만들었다.\u003c/p\u003e\n\u003cp\u003e(\u003cdel\u003eVue.js로 로컬에서 라이브 서버를 띄워놓음\u0026hellip;\u003c/del\u003e)\u003c/p\u003e\n\u003cp\u003e처음에는 나름 잘 만들었다고 뿌듯해 했지만,\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e로컬에서만 작동하는 부분(같은 네트워크 대역을 쓸때만 접속이 가능)\u003c/li\u003e\n\u003cli\u003e수정사항이나 새로운 요구사항이 발생할 때마다 변경이 힘듦\u003c/li\u003e\n\u003cli\u003e팀원들도 사실 많이 이용하지는 않았는지 별로 피드백이 없었음\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e위의 이유(\u003cdel\u003e핑계\u003c/del\u003e)들로 프로젝트가 바빠지면서 점차 소홀히 관리하게 되었다.\u003c/p\u003e","title":"블로그를 쓰게 된 계기와 앞으로의 방향성"},{"content":"도입 처음 개발 블로그를 만들려고 했을때 찾아보니, 국내에서 많이 사용하는 개발 블로그에는 3가지 종류가 있었습니다.\nvelog tistory github 각각의 장단점을 살펴보고, 제가 깃허브 블로그를 선택한 이유에 대해서 설명하려고 합니다.\nvelog velopert(김민준) 님께서 개발하신 개발 블로그입니다. 플랫폼이 개발 블로그에 특화되어 있고, UI도 깔끔해서 블로깅을 할때 1순위로 고민되는 옵션입니다.\n장점 간편한 사용법과 블로깅\n사용법이 간편해서 블로깅에만 편하게 집중할 수 있습니다. 처음 블로깅을 시작하거나, 머리아픈 것을 싫어한다면 가장 추천되는 블로그인 이유입니다. 불필요한 옵션 및 세팅 최소화\n1번과도 연결되는 부분인데, 별다른 옵션이나 기능이 없어서 다른 고민을 할 필요가 없습니다. 처음 글을 작성하면 별다른 세팅 없이도 조회수나 댓글 등을 쉽게 확인 가능합니다. 다양한 개발자들의 생태계 구축\n훌륭한 개발자분들께서 이미 좋은 글들을 많이 남겨주고 계시고, 커뮤니티처럼 본인만의 의견이나 짤, 드립들도 심심치 않게 올라옵니다. 개발 커뮤니티답게 업계의 좋은 정보들도 많이 얻을 수 있고, 다른 분들의 글을 보며 좋은 자극을 받을 수 있습니다. 단점 적은 옵션과 선택폭, 획일화된 디자인\n사실 장점 1, 2번에서 필연적으로 발생하는 단점입니다. 세팅이 간편하고 접근성이 쉬운 만큼, 개발자 본인이 선택할 수 있는 옵션은 많지 않습니다. 본인만의 블로그로 커스터마이징 하고 싶다면 한계가 있는 부분은 사실입니다. 또한, UI가 기본적으로 예쁘기는 하지만 본인만의 스타일이나 방향성 등을 보여주기는 힘듭니다. 게시글 기능에만 초점이 맞춰져 있기도 하고, 세팅할 수 있는 요소가 거의 없어 velog의 스타일로 획일화되는 것 같습니다. 광고 불가능\n기본적으로 velopert님이 운영하시는 블로그이다 보니 광고를 통해 수익을 얻는 것이 불가능합니다. 광고수익을 기대하는 분들이 velog를 주저하는 이유 중 하나일 것 같습니다. 통계 기능\n본인이 작성한 게시물에 대한 조회수나 좋아요 등에 대한 개수정도만 확인 가능하여 구체적인 통계 시스템을 구축하는 것은 힘들어 보입니다. 개인 블로그에 대해 좀더 철저히 관리하고 싶으면 망설여지는 부분입니다. tistory kakao에서 운영하는 블로그입니다. 2006년이라는 오랜 세월동안 국내의 메이저 블로그로 자리잡았고, 다양한 분야의 블로그와 본인만의 커스터마이징이 장점입니다.\n장점 자유로운 스킨 편집\n개발자라면 기본적인 마크업 언어는 많이 접해보셨을 것입니다. 마크업 언어를 통한 스킨편집을 지원하기 때문에 본인만의 스타일로 블로그를 꾸밀 수 있습니다. 특히 블로깅을 잘 하시는 분들을 보면 티스토리 블로그인지 깃허브 블로그인지 헷갈릴 정도로 예쁘게 커스터마이징한 블로그를 심심치 않게 볼 수 있습니다. 소통 및 통계 기능\n기본적으로 댓글이나 구독 등 다른 사용자와의 소통을 위한 기능이 잘 되어 있습니다. 또한, 방문자에 대한 통계와 유입 경로 등을 상세히 분석할 수 있어 본인의 블로그를 확장하는데 많은 도움을 얻을 수 있습니다. 구글 애드센스 등 수익모델과의 연동\n구글 애드센스나 카카오 애드핏 등 광고 서비스와 쉽게 연동이 가능합니다. 광고를 통해 수익을 얻으시려는 분들은 많이 고민하게 되는 옵션 중 하나일 것 같습니다. 실제로 찾아보니 네이버 블로그보다 광고 연동이 잘 되어 티스토리를 사용한다는 분들도 많이 접할 수 있었습니다. 단점 지나친 광고노출\n공부를 하기 위해 다른 티스토리 블로그에 들어가보면, 점점 광고가 많아지는 것 같은 느낌을 받습니다. 특히 어떤 블로그는 다른 탭이나 어플리케이션으로 이동했다가 돌아오면 전체화면을 가려버리는 악성 모달창(?)이 뜨기도 합니다. 이러한 광고 기능으로 인해 순수하게 정보 공유 목적으로 블로그를 운영하거나 정보를 얻으려는 사람들은 불편함을 느낄 것 같습니다. 블로그 관리 의존성\n블로그 관리가 티스토리 고객센터에서 이루어지다 보니 결국 블로그의 생존성도 티스토리 고객센터에 의존하게 됩니다. 특정 게시글에 비정상적인 트래픽 용량이 발생하거나 신고를 많이 받은 블로그는 차단당한다는 글도 많이 보았습니다. 실제로 고객센터에서도 일일히 관리하기가 힘들다보니 일단 제재를 받기 시작하면 소명 등의 절차가 까다롭고 본인의 포스팅을 지키기가 어렵다고 합니다. 많은 사용자에 따른 보안 및 기타 이슈\n개인적인 파일이나 정보 등을 함부로 블로그에 올리면 안되겠지만, 본인의 개인정보가 많은 사람들이 사용하고 어떤 정책으로 관리될지 모를 티스토리 서버의 db로 전송된다는 부분은 찝찝합니다. (사실 깃허브나 여타 블로그도 서버에 파일이 올라가는건 똑같긴 하지만, 티스토리는 좀더 많은 사람이 사용하다 보니 불안한 감이 있습니다.) 그리고 국내에서 많은 트래픽이 발생하는 메이저 포털인 만큼, 여러 사람들에게 포스팅이 노출될 것이고 필연적으로 불필요한 충돌이나 문제(도배나 악성댓글 등)가 발생할 수도 있을 것 같습니다. github 장점 극강의 자유도, 확장의 용이성\n모든 기능을 커스터마이징 할 수 있습니다. 애초에 html을 만들어서 올려야 하기 때문에, html 형식을 벗어나지만 않는다면 모든 뷰를 수정할 수 있습니다. 특히 css를 잘 다룬다면, 본인만의 개성있는 포트폴리오 페이지를 제공하기에는 안성맞춤입니다. 또한, 기능을 확장하려면 본인이 해당 기능을 직접 구현하거나, 외부 라이브러리 의존을 통해 구현할 수 있습니다. 최초에는 기능이 없었지만, 다양한 기능을 직접 추가하거나 삭제할 수 있습니다. 레포지토리와의 연동\n정적 페이지 빌드를 위해 기본적으로 깃허브에 커밋을 해야 합니다. 커밋 기록 (잔디) 도 남길 수 있고, 무엇보다 깃허브가 제공하는 모든 버전관리, 이슈 트래킹 등을 그대로 사용할 수 있다는 점이 매력적입니다. 경험\n사실 개발자로서 본인만의 페이지를 운영하는 경험은 향후 많은 도움이 될 것입니다. 직접 페이지를 빌드하거나 수정하고, 다른 유저의 피드백을 받고, 어떻게 하면 나의 페이지를 노출할 수 있을지 고민하는 일련의 과정이 개발자의 현업과도 일맥상통하는 부분이 있습니다. 단점 초기 진입장벽\n모든 기능을 구현할 수 있다는 것은 반대로 말하면 모든 기능을 알아야 한다는 것입니다. 마크업 언어에 대한 이해는 기본이고, 각종 기능의 내부적인 기능을 알아야 구현이 가능하여 초기 세팅이나 공부하는 과정에서 많은 시간이 걸립니다. 오버헤드\n단지 블로그 글을 작성하고 싶을 뿐인데, 수행하는 과정이 정말 많습니다. 로컬에서 글을 작성하고, (템플릿 프레임워크를 쓴다면)로컬 서버에서 확인 후 빌드하고, 깃에 커밋까지 해야 등록이 됩니다. 또한, 깃이 이를 연동하는데 최소 1분정도는 소요되어 직접 결과를 확인하는데에도 많은 시간이 걸립니다. 초기 유입과 확장\n다른 플랫폼에 비해 새로운 도메인에서 시작하다보니, 초기에 다른 사람들과 소통하기가 쉽지 않습니다. 당장 이 글을 쓰는 저도 제 글을 얼마나 읽어주실지 잘 모르겠습니다. 어느 정도 트래픽이 발생하기 위해서는 꾸준한 관리와 노력이 필요할 것 같습니다. 개발 블로그 선택 저는 위의 장단점을 분석하면서 결국 깃허브 블로그를 운영하기로 마음먹었습니다.\n어차피 개발공부를 해야 했고, 나중에 현업이나 프로젝트에서 마주칠 문제라면 차라리 혼자 공부하면서 미리 부딪혀 보는게 낫겠다는 생각이었습니다.\n그리고 아직은 많은 사람들이 사용하는 블로그에 글을 작성하기에는 제 비루한 실력이 노출되는 부분도 걱정이 되었습니다.\n마지막으로, 백엔드 개발자로서 수정에는 닫혀있고 확장에는 열려있는 구조 1 를 선택하지 않을 수 없었습니다. (무엇보다 간지가 납니다.)\n결론 velog, tistory, github 블로그를 비교해본 결과, 저는 깃허브 블로그를 사용하기로 마음먹었습니다. 다음 포스팅에서는 여러 SSG(Static Site Generator) 도구 중 hugo를 도입하게 된 배경과 간략한 소개, 그리고 설치방법에 대해 알아보겠습니다.\nReferences OCP(Open Close Principal; 개방 폐쇄의 원칙)\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/gitblog/1/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003cp\u003e처음 개발 블로그를 만들려고 했을때 찾아보니, 국내에서 많이 사용하는 개발 블로그에는 3가지 종류가 있었습니다.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003evelog\u003c/li\u003e\n\u003cli\u003etistory\u003c/li\u003e\n\u003cli\u003egithub\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e각각의 장단점을 살펴보고, 제가 깃허브 블로그를 선택한 이유에 대해서 설명하려고 합니다.\u003c/p\u003e\n\u003ch2 id=\"velog\"\u003evelog\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"velog image\" loading=\"lazy\" src=\"/posts/gitblog/1/velog.png#center\"\u003e\u003c/p\u003e\n\u003cp\u003evelopert(김민준) 님께서 개발하신 개발 블로그입니다. 플랫폼이 개발 블로그에 특화되어 있고, UI도 깔끔해서 블로깅을 할때 1순위로 고민되는 옵션입니다.\u003c/p\u003e\n\u003ch3 id=\"장점\"\u003e장점\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e간편한 사용법과 블로깅\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e사용법이 간편해서 블로깅에만 편하게 집중할 수 있습니다.\u003c/li\u003e\n\u003cli\u003e처음 블로깅을 시작하거나, 머리아픈 것을 싫어한다면 가장 추천되는 블로그인 이유입니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003e불필요한 옵션 및 세팅 최소화\u003c/strong\u003e\u003c/p\u003e","title":"개발 블로그의 종류와 선택"},{"content":"개요 Zynq 보드와 같은 SoC 기반 시스템에서 사용자 애플리케이션(App) 이 FPGA 디바이스를 제어하기 위해서는 커널(Kernel) 과 디바이스(Device) 간의 메모리 접근 메커니즘을 이해하는 것이 중요합니다.\n이번 글에서는 디바이스 트리(Device Tree) 와 비트스트림(Bitstream) 을 기반으로 커널과 유저 영역에서 디바이스를 읽고 쓰는 전체 구조를 살펴보겠습니다.\n전체 그림 전체 아키텍처는 다음과 같습니다.\n위와 같이 device, kernel, user 가 상호작용하여 프로그램을 수행합니다.\n징크보드로 전체 구조를 살펴보면 위와 같습니다.\n유저(app) 사용자가 기대하는 동작은 다음과 같습니다.\nopen() : 장치 드라이버를 연다.\nread() : 장치 정보를 조회한다. ex. led가 켜져있는지 확인\nwrite() : 장치에 쓰기 작업을 한다. ex. led 켜기\nioctl() : 장치에 기타 작업을 한다. ex. led 색 바꾸기 등\n위 함수들의 이름은 앞으로 계속 나오기때문에 기억해두시면 좋습니다.\n커널 이번에는 커널 입장에서 해야 할 일들을 확인해 보겠습니다.\nprobe() : 드라이버 주소를 받아온다. read() : 사용자가 읽으면 디바이스 주소를 읽어서 전달한다. write(), ioctl() : 사용자가 요청하면 디바이스 주소에 요청값을 쓴다. 디바이스 디바이스는 다음의 한가지 일만 하면 됩니다.\n비트스트림(.bit), 디바이스 트리(.dts) : 자신의 메모리 주소 제공 주소체계 각 요소가 주소를 어떻게 관리하는지 좀만 더 들어가보겠습니다.\n유저 \u0026amp; 커널 위 그림과 같이 징크보드(4GB RAM)의 메모리는 유저영역과 커널영역으로 나눠서 사용합니다.\n유저영역 주소 : 0x00000000 ~ 0xBFFFFFFF 커널영역 주소 : 0xC0000000 ~ 0xFFFFFFFF 디바이스 위와 같이 디바이스 트리는 PS와 PL영역으로 구분됩니다.\n커널영역 주소 : PS(고정) + PL(유동) 쉽게 PS는 칩과 연결된 영역, PL은 FPGA등으로 프로그래밍 가능한 영역이라고 생각할 수 있습니다.\n메모리 접근 메커니즘 그럼 서로 다른 주소체계를 사용하는 커널과 디바이스가 어떻게 메모리에 접근하는지 살펴보겠습니다.\n아래는 디바이스 - 커널 - 유저 순으로 실제 장비가 등록되고, 호출되는 순서를 시간 순으로 나열한 것입니다.\n1. 비트스트림 생성 시점 FPGA 등으로 설계한 PL영역의 하드웨어 비트스트림을 추출합니다.\nPS영역은 이미 고정된 주소이기 때문에 변경되지 않습니다.\n2. 커널 빌드 시점 비트스트림의 각 주소에 맞는 디바이스 트리를 작성합니다. 커널을 빌드할때 부팅파일(BOOT.BIN)에 비트스트림을 포함시키고, 작성한 디바이스 트리(system.dts)를 전달합니다.\n3. 부팅(오버레이) 시점 디바이스 트리에 적혀있는 설정 정보를 보고 각 장치와 모듈을 매핑할 맵을 커널 내부에 등록합니다.\n4. 연결(probe) 시점 커널은 platform_get_resource 함수를 호출해서 제어에 필요한 디바이스의 주소를 확인하고 ioremap등의 함수로 가상메모리와 연결하여 사용할 준비를 마칩니다.\n5. 열기(open) 시점 유저가 파일을 열면(open) 커널이 등록한(연결된) 디바이스 주소를 파일 구조체에 연결합니다.\n5. 읽기(read) 시점 유저가 시스템 콜(read)을 호출하면 커널이 디바이스 주소에서 정보를 가져옵니다.\n6. 쓰기(write) 시점 유저가 시스템 콜(write)을 호출하면 커널이 디바이스 주소에 정보를 씁니다.\n7. 제거(remove) 시점 장치 주소 및 기타 사용하던 메모리 자원을 제거합니다.\n세부구현 위와 같은 메커니즘이 커널 내부에서 어떻게 동작할 수 있는지, 이를 가능하게 하는 핵심 기법들을 살펴보겠습니다.\n시스템 콜 오버라이드 장치 드라이버에서 커널이 기본적으로 사용하는 시스템 콜은 아래와 같습니다.\nprobe open read write ioctl remove 다양한 장치 드라이버에서 해당 시스템 콜을 사용하기 위해, 커널은 해당 API를 추상화시켜 놓았습니다.\n따라서 아래와 같이 디바이스 드라이버의 파일 포인터에 해당 함수들을 오버라이딩하면 커널 코드로 작성된 시스템 콜이 기본 시스템 콜 대신 실행됩니다.\n// 디바이스 드라이버에 정의된 오버라이드 가능한 함수들 static struct platform_driver my_driver = { .probe = my_probe, .remove = my_remove }; // 파일 포인터에 정의된 오버라이드 가능한 함수들 static const struct file_operations my_fops = { .open = my_open, .read = my_read, .write = my_write, .ioctl = my_ioctl }; 디바이스 트리 탐색 위와 같이 각 장치의 영역에 대응하는 디바이스 트리를 작성해서 주소를 트리(Tree) 자료구조로 표현할 수 있습니다.\n각 장치는 디바이스 트리에 compatible이라는 속성을 사용해서 각 디바이스의 이름을 커널에 전달합니다. 커널은 해당 이름과 주소를 별도의 맵으로 저장해둡니다.\n(platform_device 구조체) : (platform_driver 구조체)\n위와 같이 공통 인터페이스인 platform_driver를 구현해서 커스텀 함수(prove, remove)를 실행할 수 있습니다.\n이후 platform_device_register()함수를 통해 구현한 드라이버를 커널에 등록 및 사용합니다.\n장치 드라이버 역참조 장치 드라이버는 platform_driver 구조체의 probe함수에서만 가져올 수 있기 때문에, 이후의 시스템 콜(read, write)에서는 참조가 불가능합니다.\n이를 가져오기 위해 전역변수에 저장할 수도 있지만, 이는 드라이버 전역 노출과 함께 불필요한 함수에서 접근할 수 있는 문제가 있기 때문에 커스텀 디바이스를 만들고 이를 드라이버 파일 포인터에 넣는 기법을 사용할 수 있습니다.\nstruct my_device { struct cdev cdev; // ⭐ 핵심: 내부에 cdev 포함 int value; }; 위와 같이 먼저 커스텀 디바이스 내부에 cdev를 포함시킵니다. cdev는 다음과 같이 inode 안에 저장되어 있기 때문에 open 시스템 콜 사용시 바로 접근이 가능합니다.\nstruct file { struct inode { struct cdev *i_cdev; // ⭐ 역참조를 위해 저장할 구조체(open 할때 이미 생성됨) }; // 생략 const struct file_operations *f_op; // 시스템 콜 오버라이드 void *private_data; // ⭐ 드라이버와 연결할 영역 // 생략 }; container_of() 함수를 사용하면 특정 구조체의 위치(주소)를 역으로 계산해서 가져올 수 있기 때문에, cdev를 구조체에 포함시키면 역으로 디바이스 드라이버의 주소를 알아낼 수 있습니다.\n// open 함수에서 private data와 my device 연결 static int my_open(struct inode *inode, struct file *file) { struct my_device *dev; /* ⭐ container_of로 cdev → my_device 찾기 */ dev = container_of(inode-\u0026gt;i_cdev, struct my_device, cdev); file-\u0026gt;private_data = dev; printk(\u0026#34;open: dev=%p\\n\u0026#34;, dev); return 0; } 위와 같이 저장하면 이제 아래와 같이 read, write에서 디바이스 드라이버를 사용할 수 있습니다.\nstatic ssize_t my_read(struct file *file, char __user *buf, size_t len, loff_t *off) { // 저장한 디바이스 꺼내기 struct my_device *dev = file-\u0026gt;private_data; printk(\u0026#34;read: value=%d\\n\u0026#34;, dev-\u0026gt;value); return 0; } static ssize_t my_write(struct file *file, const char __user *buf, size_t len, loff_t *off) { struct my_device *dev = file-\u0026gt;private_data; // 저장한 디바이스에 쓰기 dev-\u0026gt;value = 1234; printk(\u0026#34;write: value=%d\\n\u0026#34;, dev-\u0026gt;value); return len; } 결론 지금까지 커널이 어떻게 디바이스를 인식해서 상호작용하는지 전체 구조를 알아보았습니다. 각 디바이스에 맞는 드라이버를 플랫폼 독립적으로 구현할 수 있도록 추상화한 부분이 아주 흥미로웠습니다.\n커널을 공부하면서 C로도 객체지향이 가능하고, 실제로 다양한 패턴이 적용되는걸 보면서 결국 모든 언어는 추상화와 설계가 가장 중요함을 다시금 느꼈습니다.\nReferences URL 게시일자 방문일자 작성자 The Linux Kernel — Linux and the Devicetree 공식 문서 N/A 2026-03-26 kernel The Linux Kernel — Xilinx FPGA 공식 문서 N/A 2026-03-26 kernel The Linux Kernel — Devicetree FPGA bindings 공식 문서 N/A 2026-03-26 kernel Das U-Boot — Xilinx Device Tree bindings (U-Boot 공식 문서) N/A 2026-03-26 u-boot Device Tree Linux 14 Jul 2023 2026-03-26 scaler PS/PL Interfaces N/A 2026-03-26 pynq ","permalink":"https://leaf-nam.github.io/posts/rtos/embedded-linux-device-tree-understand/","summary":"\u003ch2 id=\"개요\"\u003e개요\u003c/h2\u003e\n\u003cp\u003eZynq 보드와 같은 SoC 기반 시스템에서 \u003cstrong\u003e사용자 애플리케이션(App)\u003c/strong\u003e 이 \u003cstrong\u003eFPGA 디바이스\u003c/strong\u003e를 제어하기 위해서는 \u003cstrong\u003e커널(Kernel)\u003c/strong\u003e 과 \u003cstrong\u003e디바이스(Device)\u003c/strong\u003e 간의 메모리 접근 메커니즘을 이해하는 것이 중요합니다.\u003c/p\u003e\n\u003cp\u003e이번 글에서는 \u003cstrong\u003e디바이스 트리(Device Tree)\u003c/strong\u003e 와 \u003cstrong\u003e비트스트림(Bitstream)\u003c/strong\u003e 을 기반으로 커널과 유저 영역에서 디바이스를 읽고 쓰는 전체 구조를 살펴보겠습니다.\u003c/p\u003e\n\u003ch2 id=\"전체-그림\"\u003e전체 그림\u003c/h2\u003e\n\u003cp\u003e전체 아키텍처는 다음과 같습니다.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"device, kernel, user\" loading=\"lazy\" src=\"/posts/rtos/embedded-linux-device-tree-understand/image.png\"\u003e\u003c/p\u003e\n\u003cp\u003e위와 같이 \u003cstrong\u003edevice\u003c/strong\u003e, \u003cstrong\u003ekernel\u003c/strong\u003e, \u003cstrong\u003euser\u003c/strong\u003e 가 상호작용하여 프로그램을 수행합니다.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"device, kernel, user on jynq\" loading=\"lazy\" src=\"/posts/rtos/embedded-linux-device-tree-understand/image-1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e징크보드로 전체 구조를 살펴보면 위와 같습니다.\u003c/p\u003e","title":"임베디드 리눅스에서 커널 시스템 콜과 디바이스 트리 동작원리 이해하기"},{"content":"상황 Windows11 CubeIDE STM32F429 + FreeRTOS 포팅 context switch(이하 문맥전환) 수업 시간에 각 태스크에서 led를 켜고, delay 시 led를 끄도록 위 코드와 같이 작성함 디버깅 시 첫번째, 두번째 문맥 전환에서는 정상 동작 세번째 문맥 전환 발생 시 Bus Fault 발생 소스 코드 void xPortPendSVHandler( void ) { /* This is a naked function. */ ledoff(); __asm volatile ( \u0026#34; mrs r0, psp \\n\u0026#34; \u0026#34; isb \\n\u0026#34; \u0026#34; \\n\u0026#34; \u0026#34; ldr r3, pxCurrentTCBConst \\n\u0026#34; /* Get the location of the current TCB. */ \u0026#34; ldr r2, [r3] \\n\u0026#34; \u0026#34; \\n\u0026#34; \u0026#34; tst r14, #0x10 \\n\u0026#34; /* Is the task using the FPU context? If so, push high vfp registers. */ \u0026#34; it eq \\n\u0026#34; \u0026#34; vstmdbeq r0!, {s16-s31} \\n\u0026#34; \u0026#34; \\n\u0026#34; \u0026#34; stmdb r0!, {r4-r11, r14} \\n\u0026#34; /* Save the core registers. */ \u0026#34; str r0, [r2] \\n\u0026#34; /* Save the new top of stack into the first member of the TCB. */ \u0026#34; \\n\u0026#34; \u0026#34; stmdb sp!, {r0, r3} \\n\u0026#34; \u0026#34; mov r0, %0 \\n\u0026#34; \u0026#34; msr basepri, r0 \\n\u0026#34; \u0026#34; dsb \\n\u0026#34; \u0026#34; isb \\n\u0026#34; \u0026#34; bl vTaskSwitchContext \\n\u0026#34; \u0026#34; mov r0, #0 \\n\u0026#34; \u0026#34; msr basepri, r0 \\n\u0026#34; \u0026#34; ldmia sp!, {r0, r3} \\n\u0026#34; \u0026#34; \\n\u0026#34; \u0026#34; ldr r1, [r3] \\n\u0026#34; /* The first item in pxCurrentTCB is the task top of stack. */ \u0026#34; ldr r0, [r1] \\n\u0026#34; \u0026#34; \\n\u0026#34; \u0026#34; ldmia r0!, {r4-r11, r14} \\n\u0026#34; /* Pop the core registers. */ \u0026#34; \\n\u0026#34; \u0026#34; tst r14, #0x10 \\n\u0026#34; /* Is the task using the FPU context? If so, pop the high vfp registers too. */ \u0026#34; it eq \\n\u0026#34; \u0026#34; vldmiaeq r0!, {s16-s31} \\n\u0026#34; \u0026#34; \\n\u0026#34; \u0026#34; msr psp, r0 \\n\u0026#34; \u0026#34; isb \\n\u0026#34; \u0026#34; \\n\u0026#34; #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata workaround. */ #if WORKAROUND_PMU_CM001 == 1 \u0026#34; push { r14 } \\n\u0026#34; \u0026#34; pop { pc } \\n\u0026#34; #endif #endif \u0026#34; \\n\u0026#34; \u0026#34; bx r14 \\n\u0026#34; \u0026#34; \\n\u0026#34; \u0026#34; .align 4 \\n\u0026#34; \u0026#34;pxCurrentTCBConst: .word pxCurrentTCB \\n\u0026#34; ::\u0026#34;i\u0026#34;(configMAX_SYSCALL_INTERRUPT_PRIORITY) ); } /*-----------------------------------------------------------*/ 원인(요약) xPortPendSVHandler()는 naked 함수로 선언되어 있음\n해당 함수 상단에서 ledoff()라는 일반 C 함수 호출\n이 과정에서 LR(r14) 값이 오염\n오염된 LR 값이 TCB에 저장되고, 이후 복귀 과정에서 사용되면서 Fault 발생\n상세분석 다양한 원인이 함께 연관되어 발생한 문제이므로, 각각의 상황을 상세히 분석해 보겠습니다.\nSTM32의 ISR(인터럽트 서비스 루틴) 인터럽트 진입 시, Cortex-M 하드웨어는 다음을 자동으로 수행 LR에 EXC_RETURN 값 저장\n스택에 스택 프레임 저장\n스택 프레임 구성 // 1 R0 ~ R3 // 2 R12 // 3 LR // 4 PC // 5 xPSR 인터럽트 복귀 시 LR의 EXC_RETURN 값과 스택 프레임에 저장된 PC 값 확인\n복귀 모드 및 복귀 주소를 결정하여 복귀 수행\n위 과정은 하드웨어에 의해 자동으로 처리됩니다.\ncontext switching(문맥 전환) FreeRTOS의 context switch는 위 하드웨어 스택 프레임과 별도로 태스크의 실행 상태를 저장해야 합니다.\n이를 위해 다음 레지스터를 소프트웨어적으로 저장합니다.\nR4 ~ R11 R14 (LR) 이는 태스크가 중단된 시점의 상태를 유지하기 위함입니다.\n문맥 전환 후, 새로운 TCB + 기존 레지스터 값 활용해서 새로운 태스크로 복귀합니다. naked 함수 assembly코드를 직접 사용하기 위해 프로토타입에 다음과 같이 선언합니다. void xPortPendSVHandler( void ) __attribute__ (( naked )); naked 함수의 특징 컴파일러가 함수 프롤로그 / 에필로그를 생성하지 않음 스택 프레임 자동 저장/복원이 이루어지지 않음 레지스터 보존에 대한 ABI 보장이 없음 PendSV(문맥 전환용 인터럽트) 핸들러는 직접 레지스터를 제어해야 하므로 naked 함수로 구현됩니다.\nassembly 분석 분석에 불필요한 값은 생략했습니다. 사실 영문 주석으로 잘 달려있긴 합니다.\n/* 문맥 전환 전 현재 레지스터의 상태를 메모리와 기존 TCB에 저장 */ \u0026#34; stmdb r0!, {r4-r11, r14} \\n\u0026#34; /* Save the core registers. */ \u0026#34; str r0, [r2] \\n\u0026#34; /* Save the new top of stack into the first member of the TCB. */ /* 문맥 전환(가장 우선순위 높은 Task 가져옴) */ \u0026#34; bl vTaskSwitchContext \\n\u0026#34; /* 문맥 전환 후 새로운 TCB를 가져옴 */ \u0026#34; ldr r1, [r3] \\n\u0026#34; /* The first item in pxCurrentTCB is the task top of stack. */ /* 새로운 TCB의 주소로 복귀 */ \u0026#34; bx r14 \\n\u0026#34; 문제 상황 재정리 원래 상황으로 돌아가서, 위 코드 맨위에 다른 함수를 호출한 경우를 보겠습니다. void xPortPendSVHandler( void ) { // 함수가 처음 호출되면 LR에 EXC_RETURN(0xFFFFFFF5) 저장 // 다른 함수 호출된 후 복귀되면서 LR에 ledoff()의 복귀 주소가 저장(오염) ledoff(); /* 문맥 전환 전 현재 레지스터의 상태를 메모리와 기존 TCB에 저장 =\u0026gt; r14에 EXC_RETURN이 아닌 주소값 저장 */ \u0026#34; stmdb r0!, {r4-r11, r14} \\n\u0026#34; \u0026#34; str r0, [r2] \\n\u0026#34; /* 문맥 전환(가장 우선순위 높은 Task 가져옴) */ \u0026#34; bl vTaskSwitchContext \\n\u0026#34; /* 문맥 전환 후 새로운 TCB를 가져옴 =\u0026gt; 첫번째 호출시에는 EXC_RETURN 정상적으로 가져옴, 두번째 호출부터 복귀주소(오염된 값) 가져옴 */ \u0026#34; ldr r1, [r3] \\n\u0026#34; /* 새로운 TCB의 주소로 복귀 */ \u0026#34; bx r14 \\n\u0026#34; } 첫 번째 문맥 전환\n오염된 태스크로 아직 복귀하지 않으므로 정상 동작 두 번째 문맥 전환 시 오염된 LR 값을 가진 태스크로 복귀 시도\nEXC_RETURN 규칙에 맞지 않는 값 사용 Bus Fault 발생 결론 ISR + naked + context switch라는 특수한 상황 조건이 겹쳐서 발생한 오류였습니다. | STM32의 문맥 전환을 전반적으로 이해할 수 있는 좋은 예제인 것 같습니다. naked가 선언된 곳에는 절대 C 함수를 호출하지 말자!\n","permalink":"https://leaf-nam.github.io/posts/rtos/rtos-context-switch-bus-fault/","summary":"\u003ch2 id=\"상황\"\u003e상황\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eWindows11 CubeIDE\u003c/li\u003e\n\u003cli\u003eSTM32F429 + FreeRTOS 포팅\u003c/li\u003e\n\u003cli\u003econtext switch(이하 문맥전환) 수업 시간에 각 태스크에서 led를 켜고, delay 시 led를 끄도록 위 코드와 같이 작성함\u003c/li\u003e\n\u003cli\u003e디버깅 시 \u003ccode\u003e첫번째, 두번째 문맥 전환\u003c/code\u003e에서는 정상 동작\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e세번째 문맥 전환 발생 시\u003c/code\u003e Bus Fault 발생\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg alt=\"situation1\" loading=\"lazy\" src=\"/posts/rtos/rtos-context-switch-bus-fault/situation1.png#center\"\u003e\u003c/p\u003e\n\u003ch3 id=\"소스-코드\"\u003e소스 코드\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e \u003cspan class=\"nf\"\u003exPortPendSVHandler\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e \u003cspan class=\"kt\"\u003evoid\u003c/span\u003e \u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"cm\"\u003e/* This is a naked function. */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"nf\"\u003eledoff\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"kr\"\u003e__asm\u003c/span\u003e \u003cspan class=\"k\"\u003evolatile\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e(\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   mrs r0, psp                     \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   isb                           \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;                              \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   ldr   r3, pxCurrentTCBConst         \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"cm\"\u003e/* Get the location of the current TCB. */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   ldr   r2, [r3]                  \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;                              \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   tst r14, #0x10                  \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"cm\"\u003e/* Is the task using the FPU context?  If so, push high vfp registers. */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   it eq                        \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   vstmdbeq r0!, {s16-s31}            \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;                              \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   stmdb r0!, {r4-r11, r14}         \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"cm\"\u003e/* Save the core registers. */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   str r0, [r2]                  \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"cm\"\u003e/* Save the new top of stack into the first member of the TCB. */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;                              \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   stmdb sp!, {r0, r3}               \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   mov r0, %0                      \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   msr basepri, r0                  \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   dsb                           \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   isb                           \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   bl vTaskSwitchContext            \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   mov r0, #0                     \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   msr basepri, r0                  \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   ldmia sp!, {r0, r3}               \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;                              \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   ldr r1, [r3]                  \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"cm\"\u003e/* The first item in pxCurrentTCB is the task top of stack. */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   ldr r0, [r1]                  \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;                              \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   ldmia r0!, {r4-r11, r14}         \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"cm\"\u003e/* Pop the core registers. */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;                              \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   tst r14, #0x10                  \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"cm\"\u003e/* Is the task using the FPU context?  If so, pop the high vfp registers too. */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   it eq                        \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   vldmiaeq r0!, {s16-s31}            \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;                              \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   msr psp, r0                     \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   isb                           \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;                              \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"cp\"\u003e#ifdef WORKAROUND_PMU_CM001 \u003c/span\u003e\u003cspan class=\"cm\"\u003e/* XMC4000 specific errata workaround. */\u003c/span\u003e\u003cspan class=\"cp\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"cp\"\u003e#if WORKAROUND_PMU_CM001 == 1\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;         push { r14 }            \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;         pop { pc }               \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"cp\"\u003e#endif\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"cp\"\u003e#endif\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;                              \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   bx r14                        \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;                              \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;   .align 4                     \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"s\"\u003e\u0026#34;pxCurrentTCBConst: .word pxCurrentTCB   \u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;i\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003econfigMAX_SYSCALL_INTERRUPT_PRIORITY\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e   \u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e/*-----------------------------------------------------------*/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"원인요약\"\u003e원인(요약)\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003exPortPendSVHandler()는 \u003ccode\u003enaked 함수\u003c/code\u003e로 선언되어 있음\u003c/p\u003e","title":"Context Switch 중 Bus Fault 발생 원인 분석(STM32F429 + FreeRTOS)"},{"content":"출처 에어컨 접근 문제 분석 에어컨을 켜서 온도를 설정하거나, 끄면서 차량의 온도를 조절하는데 최소의 비용이 들 수 있도록 해야 합니다.\n탑승객이 존재할때만 온도가 유지되면 됩니다.\n시간복잡도 분석 온도의 범위는 -10 \u0026lt;= temperature \u0026lt;= 40로, 전체 범위를 확인하는데 많은 시간복잡도가 필요하지 않습니다. 전체 탑승객의 정보(시간)인 onboard의 길이는 2 \u0026lt;= onboard.length \u0026lt;= 1,000으로, 전체 시간을 탐색하는데 O(N^3)의 알고리즘까지 사용이 가능합니다. 다만 온도 범위를 모두 탐색하는 것까지 고려하면 O(N^3)이하의 알고리즘이 필요합니다.\n대부분의 완전탐색 알고리즘을 통해 문제를 해결할 수 있음을 알 수 있습니다. 공간복잡도 분석 시간복잡도와 마찬가지로, 공간복잡도는 고려하지 않아도 될 정도로 주어진 변수의 범위가 작은 편입니다. 풀이 DP 완전탐색을 구현하기 위해 DP를 사용했습니다. DFS로도 가지치기만 잘하면서 백트래킹하면 시간복잡도 내에 가능할 것으로 보이지만, DP가 구현이 더 쉽고 직관적이기 때문에 DP로 풀이했습니다.\nDP배열을 다음과 같이 정의합니다. DP[i][j] : 해당 시간[i], 온도[j]에서 사용한 소비전력의 최솟값 저장\n최초 DP는 무한대로 초기화한 후, 외부온도를 0(소비전력 없음)으로 초기화합니다. 이제 전체 시간을 탐색하면서 이전 온도로부터 다음 온도를 몇 도로 설정할지 확인해서 DP를 최신화합니다. 승객이 타고있지 않을때는 괜찮지만, 승객이 타면 주어진 온도 범위에 포함된 값들만 남겨야 합니다. 주의사항 에어컨을 켜지 않는 경우도 있음을 주의합니다. 탑승객이 탑승하지 않았을 때는 주어진 온도 범위에 포함되지 않아도 됩니다. 코드 import java.util.*; class Solution { final int inf = Integer.MAX_VALUE; public int solution(int temperature, int t1, int t2, int a, int b, int[] onboard) { int len = onboard.length; // DP[i][j] : 해당 시간[i], 온도[j]에서 사용한 소비전력의 최솟값 저장 int[][] dp = new int[len][51]; // DP 초기화 Arrays.stream(dp).forEach(d -\u0026gt; Arrays.fill(d, inf)); dp[0][temperature + 10] = 0; // 전체 시간 확인(i : 시간) for (int i = 1; i \u0026lt; len; i++) { // 탑승객 탑승여부 boolean boarding = onboard[i] == 1; // 전체 온도 탐색(j : 이전 시간의 온도) for (int j = -10; j \u0026lt;= 40; j++) { if (dp[i - 1][j + 10] == inf) continue; /* 에어컨 켜기 */ // 에어컨 온도 설정(k : 다음 설정온도) for (int k = -10; k \u0026lt;= 40; k++) { // 설정온도가 이전 온도보다 큰지 확인해서 다음온도 결정 int temp = j == k? j : j \u0026gt; k? j - 1 : j + 1; if (boarding \u0026amp;\u0026amp; (temp \u0026lt; t1 || temp \u0026gt; t2)) continue; // 다음 온도가 이전 온도와 같은지 확인해서 비용 결정 int cost = temp == j? b : a; // DP에 비용 최솟값 저장 dp[i][temp + 10] = Math.min(dp[i - 1][j + 10] + cost, dp[i][temp + 10]); } /* 에어컨 끄기 */ // 에어컨 껐을 때의 온도 변화 결정 int temp = j == temperature? j : j \u0026gt; temperature? j - 1 : j + 1; if (boarding \u0026amp;\u0026amp; (temp \u0026lt; t1 || temp \u0026gt; t2)) continue; // DP에 비용 최솟값 저장 dp[i][temp + 10] = Math.min(dp[i - 1][j + 10], dp[i][temp + 10]); } } // 마지막 DP의 온도 돌면서 비용 최솟값 선택 int answer = inf; for (int i = -10; i \u0026lt;= 40; i++) { answer = Math.min(dp[len - 1][i + 10], answer); } return answer; } } 결과 리뷰 DP를 잘 활용하면 풀 수 있는 문제였습니다.\nDP는 처음 어떤 값을 저장할지 명확히 정의하는게 중요한 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 ","permalink":"https://leaf-nam.github.io/cote/programmers_214289/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/214289\"\u003e에어컨\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"문제-분석\"\u003e문제 분석\u003c/h3\u003e\n\u003cp\u003e에어컨을 켜서 온도를 설정하거나, 끄면서 차량의 온도를 조절하는데 최소의 비용이 들 수 있도록 해야 합니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e탑승객이 존재할때만 온도가 유지되면 됩니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"시간복잡도-분석\"\u003e시간복잡도 분석\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e온도의 범위는 \u003ccode\u003e-10 \u0026lt;= temperature \u0026lt;= 40\u003c/code\u003e로, 전체 범위를 확인하는데 많은 시간복잡도가 필요하지 않습니다.\u003c/li\u003e\n\u003cli\u003e전체 탑승객의 정보(시간)인 \u003ccode\u003eonboard\u003c/code\u003e의 길이는 \u003ccode\u003e2 \u0026lt;= onboard.length \u0026lt;= 1,000\u003c/code\u003e으로, 전체 시간을 탐색하는데 \u003ccode\u003eO(N^3)\u003c/code\u003e의 알고리즘까지 사용이 가능합니다.\n\u003cblockquote\u003e\n\u003cp\u003e다만 온도 범위를 모두 탐색하는 것까지 고려하면 O(N^3)이하의 알고리즘이 필요합니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003cli\u003e대부분의 완전탐색 알고리즘을 통해 문제를 해결할 수 있음을 알 수 있습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"공간복잡도-분석\"\u003e공간복잡도 분석\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e시간복잡도와 마찬가지로, 공간복잡도는 고려하지 않아도 될 정도로 주어진 변수의 범위가 작은 편입니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"풀이\"\u003e풀이\u003c/h2\u003e\n\u003ch3 id=\"dp\"\u003eDP\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e완전탐색을 구현하기 위해 DP를 사용했습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003eDFS\u003c/code\u003e로도 가지치기만 잘하면서 백트래킹하면 시간복잡도 내에 가능할 것으로 보이지만, \u003ccode\u003eDP\u003c/code\u003e가 구현이 더 쉽고 직관적이기 때문에 \u003ccode\u003eDP\u003c/code\u003e로 풀이했습니다.\u003c/p\u003e","title":"[Java]Programmers 에어컨"},{"content":"출처 프로그래머스 봉인된 주문 접근 문제 분석 알파벳 순으로 정렬된 주문서에서 주어진 숫자(n)에 해당하는 주문서를 찾는 문제입니다. 이 때, 특정 주문(bans)이 삭제되어 있는 상태입니다. 시간복잡도 분석 주어진 숫자는 n = 10^15 인 long 타입의 숫자입니다. 따라서 해당 숫자에서 글자를 가져오는 알고리즘은 O(N)보다 작아야 합니다.\n주어진 주문인 bans는 길이가 300,000이하입니다. 또한, 각 주문의 길이는 최대 11입니다. 모든 주문의 알파벳을 비교하기 위해서는 300,000 x 300,000(주문 간 비교) x 11(주문 길이) = 9x10^12 x 11 이므로 시간복잡도가 초과될 가능성이 있습니다. 따라서 전체 주문을 최대 O(NlogN)까지 확인할 수 있습니다.\n공간복잡도 분석 문제에서 삭제된 주문이 주어지기 때문에 이를 별도로 저장할 필요는 없습니다. 풀이 26진법 주문서의 순서는 다음과 같습니다. \u0026#34;a\u0026#34;→\u0026#34;b\u0026#34;→\u0026#34;c\u0026#34;→\u0026#34;d\u0026#34;→\u0026#34;e\u0026#34;→\u0026#34;f\u0026#34;→...→\u0026#34;z\u0026#34; →\u0026#34;aa\u0026#34;→\u0026#34;ab\u0026#34;→...→\u0026#34;az\u0026#34;→\u0026#34;ba\u0026#34;→...→\u0026#34;by\u0026#34;→\u0026#34;bz\u0026#34;→\u0026#34;ca\u0026#34;→...→\u0026#34;zz\u0026#34; →\u0026#34;aaa\u0026#34;→\u0026#34;aab\u0026#34;→...→\u0026#34;aaz\u0026#34;→\u0026#34;aba\u0026#34;→...→\u0026#34;azz\u0026#34;→\u0026#34;baa\u0026#34;→...→\u0026#34;zzz\u0026#34; →\u0026#34;aaaa\u0026#34;→...→\u0026#34;aazz\u0026#34;→\u0026#34;abaa\u0026#34;→...→\u0026#34;czzz\u0026#34;→\u0026#34;daaa\u0026#34;→...→\u0026#34;zzzz\u0026#34; →\u0026#34;aaaaa\u0026#34;→... 위와 같은 형태는 26진법과 동일합니다. 주어진 숫자를 뒤에서부터 26으로 나누면서 확인하면 문자(주문)로 변경할 수 있습니다.\n봉인된 주문배열 정렬 문제에서 주어지는 봉인된 주문의 배열인 bans는 정렬 후 확인해야 합니다. 작은 값부터 확인하는 이유는 앞에서 제거한 주문이 이후의 결과에 영향을 줄 수 있기 때문입니다.\n문제의 1번 예제를 통해 정렬해야 하는 이유를 확인해보겠습니다. 위와 같이 ae라는 주문이 먼저 나오게 되면, 문제에서 주어진 n = 30보다 크기 때문에 해당 주문은 무시해야 합니다. 그러나, 위와 같이 앞에서 d, e, aa라는 주문을 제외한 뒤 ae라는 주문이 나오면 문제에서 주어진 n = 30이 뒤로 밀려나면서 해당 주문을 포함시켜야 합니다. 따라서, 주어진 주문을 앞에서부터 확인하기 위해 정렬이 필요함을 알 수 있습니다.\n비교 함수 구현 비교함수를 구현하면 정렬할 때와, 현재 주문과 삭제된 주문의 크기를 비교할 때 용이합니다. 코드 import java.util.*; class Solution { public String solution(long n, String[] bans) { // 정렬을 통해 삭제된 주문 순서대로 확인 Arrays.sort(bans, (o1, o2) -\u0026gt; compare(o1, o2)); // 삭제된 주문이 현재 주문보다 크다면 주문 숫자 1 증가(삭제 처리) for (String ban : bans) { if (compare(ban, getSpell(n)) \u0026lt;= 0) n++; } // 최종 주문 반환 return getSpell(n); } // 주문 간의 크기 비교 int compare(String s1, String s2) { // 두 주문의 길이가 다르다면, 길이가 긴 주문이 더 큼 if (s1.length() != s2.length()) return Integer.compare(s1.length(), s2.length()); // 두 주문의 길이가 같다면, 앞에서부터 각 주문의 문자 비교 for (int i = 0; i \u0026lt; s1.length(); i++) { if (s1.charAt(i) \u0026lt; s2.charAt(i)) return -1; else if (s1.charAt(i) \u0026gt; s2.charAt(i)) return 1; } // 모든 비교가 끝났다면 동일한 주문으로 판단 return 0; } // 숫자 -\u0026gt; 주문 변환(진법 변환) String getSpell(long n) { StringBuilder sb = new StringBuilder(); // 해당 숫자 맨뒤에서부터 26으로 나눈 값 문자로 치환 while(n \u0026gt; 0){ sb.append((char)(\u0026#39;a\u0026#39; + (n - 1) % 26)); n = (n - 1) / 26; } // 뒤에서부터 확인했으므로 뒤집기 return sb.reverse().toString(); } } 결과 리뷰 처음부터 진법문제로 생각했다면 훨씬 쉽게 풀었을텐데, 단순 구현이라고 생각해서 복잡한 코드1를 작성하느라 시간이 많이 걸렸습니다. 문제의 본질을 파악하는 습관을 들여야겠습니다.\nReferences URL 게시일자 방문일자 작성자 최초에 작성한 숫자 -\u0026gt; 문자열 변환 코드입니다. 진법 문제로 생각하지 않고, 각 자리수를 일일이 분리해서 문자열을 찾았습니다.\nString getSpell(long n) { int exp = 0; long start = -1, end = 0; while (end \u0026lt; n) { start += (long)Math.pow(26, exp); end += (long)Math.pow(26, exp + 1); exp++; } StringBuilder sb = new StringBuilder(); while (exp \u0026gt; 0) { long ns = start; int cnt = 0; while (true) { ns += (long)Math.pow(26, exp - 1); if (ns \u0026gt;= n) break; start = ns; cnt++; } sb.append((char)(cnt + \u0026#39;a\u0026#39;)); exp--; } return sb.toString(); } \u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/cote/programmers_389481/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/389481\"\u003e프로그래머스 봉인된 주문\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"문제-분석\"\u003e문제 분석\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e알파벳 순으로 정렬된 주문서에서 주어진 숫자(\u003ccode\u003en\u003c/code\u003e)에 해당하는 주문서를 찾는 문제입니다.\u003c/li\u003e\n\u003cli\u003e이 때, 특정 주문(\u003ccode\u003ebans\u003c/code\u003e)이 삭제되어 있는 상태입니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"시간복잡도-분석\"\u003e시간복잡도 분석\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e주어진 숫자는 \u003ccode\u003en = 10^15\u003c/code\u003e 인 long 타입의 숫자입니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e따라서 해당 숫자에서 글자를 가져오는 알고리즘은 \u003ccode\u003eO(N)\u003c/code\u003e보다 작아야 합니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cul\u003e\n\u003cli\u003e주어진 주문인 \u003ccode\u003ebans\u003c/code\u003e는 길이가 \u003ccode\u003e300,000\u003c/code\u003e이하입니다. 또한, 각 주문의 길이는 최대 \u003ccode\u003e11\u003c/code\u003e입니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e모든 주문의 알파벳을 비교하기 위해서는  \u003c!-- raw HTML omitted --\u003e\n\u003ccode\u003e300,000 x 300,000(주문 간 비교) x 11(주문 길이)\u003c/code\u003e \u003c!-- raw HTML omitted --\u003e\n\u003ccode\u003e= 9x10^12 x 11\u003c/code\u003e \u003c!-- raw HTML omitted --\u003e\n이므로 시간복잡도가 초과될 가능성이 있습니다. \u003c!-- raw HTML omitted --\u003e\n따라서 전체 주문을 최대 \u003ccode\u003eO(NlogN)\u003c/code\u003e까지 확인할 수 있습니다.\u003c/p\u003e","title":"[Java]Programmers 봉인된 주문"},{"content":"문제 상황 절대 경로에서 NGinX를 재부팅하면 정상 동작했지만, 심볼릭 링크를 통해 기존 설정 서버 내부 NGinX의 설정(Config)에 특정 Server를 추가하는 작업을 수행중이었습니다. 다음과 같이 같이 NGinX의 Server 별로 sites-available파일을 생성 후, sites-enabled에 심볼릭 링크를 생성 후 연결하였습니다. # NGinX 파일서버 블록 생성 vi sites-available/file-server # 파일서버 블록 내용 작성 # 심볼릭 링크 연결 ln -s sites-enabled ./sites-available 이후 다음과 같이 nginx.conf 파일에 링크 파일(sites-enabled)을 추가했습니다. http { # ..(생략).. include sites-enabled/*; } 문제 해결 다음 스택오버플로우 게시글을 보고 문제 상황을 유추해볼 수 있었습니다. 심볼릭 링크 이해 References URL 게시일자 방문일자 작성자 Stack Overflow 2013.08.06. 2025.01.10 iamyojimbo GeeksForGeeks 2020.10.09. 2025.01.10. GeeksForGeeks ","permalink":"https://leaf-nam.github.io/tips/250311/","summary":"\u003ch2 id=\"문제-상황\"\u003e문제 상황\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e절대 경로에서 \u003ccode\u003eNGinX\u003c/code\u003e를 재부팅하면 정상 동작했지만, 심볼릭 링크를 통해\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"기존-설정\"\u003e기존 설정\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e서버 내부 \u003ccode\u003eNGinX\u003c/code\u003e의 설정(Config)에 특정 Server를 추가하는 작업을 수행중이었습니다.\u003c/li\u003e\n\u003cli\u003e다음과 같이 같이 \u003ccode\u003eNGinX\u003c/code\u003e의 Server 별로 \u003ccode\u003esites-available\u003c/code\u003e파일을 생성 후, \u003ccode\u003esites-enabled\u003c/code\u003e에 심볼릭 링크를 생성 후 연결하였습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# NGinX 파일서버 블록 생성 \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003evi sites-available/file-server\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 파일서버 블록 내용 작성\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# 심볼릭 링크 연결\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eln -s sites-enabled ./sites-available\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e이후 다음과 같이 \u003ccode\u003enginx.conf\u003c/code\u003e 파일에 링크 파일(\u003ccode\u003esites-enabled\u003c/code\u003e)을 추가했습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ehttp \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"c1\"\u003e# ..(생략)..\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  include sites-enabled/*\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"문제-해결\"\u003e문제 해결\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://stackoverflow.com/questions/18089525/nginx-sites-enabled-sites-available-cannot-create-soft-link-between-config-fil\"\u003e다음 스택오버플로우 게시글\u003c/a\u003e을 보고 문제 상황을 유추해볼 수 있었습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"심볼릭-링크-이해\"\u003e심볼릭 링크 이해\u003c/h2\u003e\n\u003ch2 id=\"references\"\u003eReferences\u003c/h2\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth style=\"text-align: left\"\u003eURL\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e게시일자\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e방문일자\u003c/th\u003e\n          \u003cth style=\"text-align: left\"\u003e작성자\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003ca href=\"https://stackoverflow.com/questions/18089525/nginx-sites-enabled-sites-available-cannot-create-soft-link-between-config-fil\"\u003eStack Overflow\u003c/a\u003e\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e2013.08.06.\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e2025.01.10\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003eiamyojimbo\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd style=\"text-align: left\"\u003e\u003ca href=\"https://www.geeksforgeeks.org/difference-between-relative-and-absolute-symlinks/\"\u003eGeeksForGeeks\u003c/a\u003e\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e2020.10.09.\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003e2025.01.10.\u003c/td\u003e\n          \u003ctd style=\"text-align: left\"\u003eGeeksForGeeks\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e","title":"NGinX 심볼릭 링크 트러블슈팅"},{"content":"출처 Programmers 불량 사용자(2019 카카오 개발자 겨울 인턴십) 접근 문제 분석 주어진 응모자 아이디에서 불량 사용자가 될 수 있는 모든 경우를 탐색하는 완전탐색(Brute Force) 문제입니다.\n시간복잡도 분석 주어진 사용자 아이디와 조합의 개수가 n \u0026lt;= 8 이므로, 모든 경우를 선택하는 시간복잡도는 불량 사용자 조합의 크기인 8! = 40,302 가 됩니다.\n또한, 모든 사용자와 조합을 각각 1회씩 비교할 경우 시간복잡도는 8 x 8 = 64입니다.\n따라서 완전탐색을 구현하기만 하면 시간복잡도는 충분함을 알 수 있습니다.\n풀이 완전탐색(DFS) 가장 빠르게 완전탐색을 구현할 수 있는 DFS를 활용하겠습니다.\n다음과 같이 1번 사용자 아이디부터 순차적으로 불량 사용자 아이디와 비교하면서, 해당 사용자를 조합에 추가합니다.\n조합 중복 방지(Set) 문제에서 나열된 순서와 관계없이 아이디 목록의 내용이 동일하다면 같은 것으로 처리하라는 조건이 있으므로, 아이디 목록의 중복을 방지해야 합니다.\n아이디 목록을 정렬한 뒤, Set 자료구조를 사용하여 저장하면 중복을 방지할 수 있습니다.\n코드 import java.util.*; class Solution { // 중복 방지를 위한 조합(Set) Set\u0026lt;String\u0026gt; answers = new HashSet\u0026lt;\u0026gt;(); public int solution(String[] user_id, String[] banned_id) { // 추후 리스트 복사를 위해 변경 List\u0026lt;String\u0026gt; bannedId = Arrays.asList(banned_id); // 아이디 목록 정렬(나열 순서 변경 방지) Arrays.sort(user_id); // 완전탐색 dfs(user_id, bannedId, new ArrayList\u0026lt;\u0026gt;(), 0); return answers.size(); } void dfs(String[] userId, List\u0026lt;String\u0026gt; bannedId, List\u0026lt;String\u0026gt; find, int now) { // 전체 불량 사용자 확인 시 정답에 추가 if (bannedId.isEmpty()) { // 정렬된 상태이기 때문에 리스트의 toString() 그대로 키로 사용 answers.add(find.toString()); return; } // 탐색 종료조건 if (now == userId.length) return; String cur = userId[now]; // 현재 탐색중인 사용자가 불량 사용자 리스트에 포함되는지 확인 for (int i = 0; i \u0026lt; bannedId.size(); i++) { // 같은 사용자인지 확인 if (eq(cur, bannedId.get(i))) { // 탐색된 사용자 정답 리스트에 추가 find.add(cur); // 배열 깊은복사 후 탐색된 불량 사용자 리스트 제거 List\u0026lt;String\u0026gt; temp = new ArrayList\u0026lt;\u0026gt;(bannedId); temp.remove(i); // DFS 수행 dfs(userId, temp, find, now + 1); // 복귀 후 백트래킹을 위해 탐색된 사용자 제거 find.remove(find.size() - 1); } } // 현재 탐색중인 사용자 패스(완전탐색) dfs(userId, bannedId, find, now + 1); } // 같은 사용자인지 확인 /* 다음 정규표현식으로도 가능 cur.matches(bannedId.get(i).replace(\u0026#34;*\u0026#34;, \u0026#34;.\u0026#34;)) */ boolean eq(String a, String b) { if (a.length() != b.length()) return false; for (int i = 0; i \u0026lt; a.length(); i++) { if (b.charAt(i) == \u0026#39;*\u0026#39;) continue; if (a.charAt(i) != b.charAt(i)) return false; } return true; } } 결과 리뷰 완전탐색을 할 수 있는지와 두 문자열을 비교할 수 있는지 묻는 백트래킹 문제였습니다.\n완전탐색 로직을 잘못 작성해서 시간초과가 발생했는데1, 재귀적으로 호출되는 함수가 불필요한 동작을 하고 있지는 않은지 잘 판단해야겠습니다.\nReferences URL 게시일자 방문일자 작성자 다음은 완전탐색 과정에서 시간초과가 발생한 코드입니다.\nvoid dfs(String[] userId, List\u0026lt;String\u0026gt; bannedId, List\u0026lt;String\u0026gt; find, int now) { if (bannedId.isEmpty()) { answers.add(find.toString()); return; } if (now == userId.length) return; String cur = userId[now]; for (int i = 0; i \u0026lt; bannedId.size(); i++) { if (eq(cur, bannedId.get(i))) { find.add(cur); List\u0026lt;String\u0026gt; temp = new ArrayList\u0026lt;\u0026gt;(bannedId); temp.remove(i); dfs(userId, temp, find, now + 1); find.remove(find.size() - 1); } // 시간초과 발생(For문 내부에서 중복 재귀호출 발생) else dfs(userId, bannedId, find, now + 1); } } \u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/cote/programmers_64064/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/64064\"\u003eProgrammers 불량 사용자(2019 카카오 개발자 겨울 인턴십)\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"문제-분석\"\u003e문제 분석\u003c/h3\u003e\n\u003cp\u003e주어진 응모자 아이디에서 불량 사용자가 될 수 있는 모든 경우를 탐색하는 \u003ccode\u003e완전탐색(Brute Force)\u003c/code\u003e 문제입니다.\u003c/p\u003e\n\u003ch3 id=\"시간복잡도-분석\"\u003e시간복잡도 분석\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e주어진 사용자 아이디와 조합의 개수가 \u003ccode\u003en \u0026lt;= 8\u003c/code\u003e 이므로, 모든 경우를 선택하는 시간복잡도는 불량 사용자 조합의 크기인 \u003ccode\u003e8! = 40,302\u003c/code\u003e 가 됩니다.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e또한, 모든 사용자와 조합을 각각 1회씩 비교할 경우 시간복잡도는 \u003ccode\u003e8 x 8 = 64\u003c/code\u003e입니다.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e따라서 완전탐색을 구현하기만 하면 시간복잡도는 충분함을 알 수 있습니다.\u003c/p\u003e","title":"[Java]Programmers 불량 사용자(2019 카카오 개발자 겨울 인턴십)"},{"content":"출처 프로그래머스 GPS(2017 카카오코드 본선) 접근 문제 분석 오류가 발생한 택시의 경로를 최소한으로 수정하여 정확한 이동 경로를 구하는 문제입니다.\n우선, 택시의 이동을 표현하기 위한 그래프 구조가 필요합니다. 문제에서 최소한으로 이동하는 경로를 찾기 위해서는 갈 수 있는 모든 경로에 대해 완전탐색을 수행해야 합니다. 시간복잡도 분석 완전탐색 전체 경우의 수를 BFS 또는 DFS를 활용해서 제한시간 내 목적지까지 갈 수 있는 모든 경로를 찾아서 주어진 경로와의 차이를 비교하는 방법이 있습니다.\n이러한 경우, 한 거점에서 다음 거점으로 이동하는 경우의 수는 정점과 연결된 간선(m)만큼 곱해지게 됩니다. 정점(n = 200)과 간선(m = 10,000)이 균일하게 연결된 그래프로 가정하면 한 정점에 연결된 간선의 수가 50개이기 때문에, 전체 경우의 수는 정점 1개에 50개씩 증가하여 O(N) = 50 ^ 200이 됩니다.\n따라서, 단순한 완전탐색으로는 전체 경우의 수를 모두 확인할 수 없음을 알 수 있습니다. 완전탐색을 최적화하는 기법이 필요함을 알 수 있습니다.\n풀이 DP 문제의 요구사항은 도착 지점까지 수정한 오류의 개수의 최솟값을 구하는 것입니다.\n즉, 위의 완전탐색에서 모든 경로를 탐색하는 것은 불필요하며, 각 거점에서 수정한 오류의 최솟값을 알 수 있다면 다음 거점에서의 최솟값을 구할 수 있습니다. 이전 값을 다음 탐색에 활용하는 DP 알고리즘을 적용할 수 있습니다.\n문제의 예시를 통해 살펴보겠습니다.\nt = 2일 때 2번 거점의 오류수정 횟수의 최솟값을 구해보겠습니다.\n문제에서 주어진 택시 경로 상, t = 2일 때 택시는 원래 2번 거점에 있어야 합니다. 따라서 2번 거점은 1번 거점에서 오류수정 없이 이동이 가능합니다. 문제에서 주어진 경로에 있는 정점은 이전 위치의 값을 그대로 사용할 수 있습니다.\n이번에는 t = 2일 때 3번 거점의 오류수정 횟수의 최솟값을 구해보겠습니다.\n문제에서 주어진 택시 경로 상, t = 2일 때 택시는 원래 2번 거점에 있어야 합니다. 따라서 3번 거점은 1번 거점에서 1회 오류수정을 해야 이동할 수 있습니다. 문제에서 주어진 경로가 아닌 정점은 이전 위치의 값에서 오류수정이 1회 필요합니다.\n이번에는 t = 2일 때 1번 거점의 오류수정 횟수의 최솟값을 구해보겠습니다.\n문제에서 주어진 택시 경로 상, t = 2일 때 택시는 원래 2번 거점에 있어야 합니다. 따라서 1번 거점은 1번 거점에서 1회 오류수정을 해야 이동할 수 있습니다. 택시가 이전 위치에 그대로 있는 경우도 있음을 유의합니다.\n규칙성을 좀 더 명확히 찾기 위해 t = 3인 상황을 확인해보겠습니다.\nt = 3일 때 1번 거점\nt = 3일 때 택시는 원래 3번 거점에 있어야 합니다.(오류수정 1회 필요) 1번 거점에서 이동 시 : t = 2일 때 1번 거점의 값인 1 + 오류수정 1회 =\u0026gt; 2 2번 거점에서 이동 시 : t = 2일 때 2번 거점의 값인 0 + 오류수정 1회 =\u0026gt; 1 3번 거점에서 이동 시 : t = 2일 때 3번 거점의 값인 1 + 오류수정 1회 =\u0026gt; 2 문제에서 최솟값만 필요하기 때문에, 2번 거점에서 1번 거점으로 이동하는 1만 저장합니다.\n위와 같은 방식으로 특정 시간에 특정 거점의 오류수정 횟수의 최솟값을 DP에 저장하여 완전탐색을 최적화할 수 있습니다.\n오류수정으로 도달할 수 없는 경우 -1을 반환해야 함을 주의합니다.\n코드 import java.util.*; class Solution { public int solution(int n, int m, int[][] edge_list, int k, int[] gps_log) { // 그래프(간선 리스트) 생성 List\u0026lt;Integer\u0026gt;[] adjList = new List[n + 1]; for (int i = 0; i \u0026lt; m; i++) { int from = edge_list[i][0], to = edge_list[i][1]; if (adjList[from] == null) adjList[from] = new ArrayList\u0026lt;\u0026gt;(List.of(from)); if (adjList[to] == null) adjList[to] = new ArrayList\u0026lt;\u0026gt;(List.of(to)); adjList[from].add(to); adjList[to].add(from); } // DP 생성 및 초기화 : i : 시간, j : 거점, DP[i][j] -\u0026gt; \u0026#39;i\u0026#39;시간에 \u0026#39;j\u0026#39;거점에 도착할 수 있는 최소 오류수정 횟수 int[][] dp = new int[k][n + 1]; Arrays.stream(dp).forEach(d -\u0026gt; Arrays.fill(d, Integer.MAX_VALUE)); dp[0][gps_log[0]] = 0; // DP 완전탐색 for (int i = 1; i \u0026lt; k; i++) { for (int j = 1; j \u0026lt;= n; j++) { // 이전 거점이 도달할 수 없는 위치이면 통과 if (dp[i - 1][j] == Integer.MAX_VALUE) continue; // 간선리스트 탐색 for (int next : adjList[j]) { // 택시 경로에 위치한 경우 : 이전 경로 값의 최솟값 사용 if (next == gps_log[i]) dp[i][next] = Math.min(dp[i][next], dp[i - 1][j]); // 택시 경로에 위치하지 않은 경우 : 이전 경로 값의 최솟값 + 1 사용 else dp[i][next] = Math.min(dp[i][next], dp[i - 1][j] + 1); } } } // 마지막 거점의 최소 오류수정 횟수 반환 int answer = dp[k - 1][gps_log[k - 1]]; // 도달할 수 없으면 -1 반환 return answer == Integer.MAX_VALUE ? -1 : answer; } } 결과 소요시간 : 1시간 초과 + 풀이 검색 리뷰 최단경로(BFS)로 풀어보다가 문제의 테스트 케이스가 1개밖에 없어서 헤매다가 결국 공식 풀이를 일부 참고했습니다.\nDP 문제는 다양한 유형을 접해보는 수밖에 없는 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 https://tech.kakao.com/posts/342 2017.09.13. 2025.02.18. bryan.j ","permalink":"https://leaf-nam.github.io/cote/programmers_1837/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/1837\"\u003e프로그래머스 GPS(2017 카카오코드 본선)\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"문제-분석\"\u003e문제 분석\u003c/h3\u003e\n\u003cp\u003e오류가 발생한 택시의 경로를 최소한으로 수정하여 정확한 이동 경로를 구하는 문제입니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e우선, 택시의 이동을 표현하기 위한 그래프 구조가 필요합니다.\u003c/li\u003e\n\u003cli\u003e문제에서 최소한으로 이동하는 경로를 찾기 위해서는 갈 수 있는 모든 경로에 대해 완전탐색을 수행해야 합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"시간복잡도-분석\"\u003e시간복잡도 분석\u003c/h3\u003e\n\u003ch4 id=\"완전탐색\"\u003e완전탐색\u003c/h4\u003e\n\u003cp\u003e전체 경우의 수를 \u003ccode\u003eBFS\u003c/code\u003e 또는 \u003ccode\u003eDFS\u003c/code\u003e를 활용해서 \u003cstrong\u003e제한시간 내 목적지까지 갈 수 있는 모든 경로를 찾아서\u003c/strong\u003e 주어진 경로와의 차이를 비교하는 방법이 있습니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e이러한 경우, 한 거점에서 다음 거점으로 이동하는 경우의 수는 \u003ccode\u003e정점과 연결된 간선(m)\u003c/code\u003e만큼 곱해지게 됩니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e정점(\u003ccode\u003en = 200\u003c/code\u003e)과 간선(\u003ccode\u003em = 10,000\u003c/code\u003e)이 균일하게 연결된 그래프로 가정하면 한 정점에 연결된 간선의 수가 \u003ccode\u003e50개\u003c/code\u003e이기 때문에, \u003c!-- raw HTML omitted --\u003e\n전체 경우의 수는 정점 1개에 \u003ccode\u003e50개\u003c/code\u003e씩 증가하여 \u003ccode\u003eO(N) = 50 ^ 200\u003c/code\u003e이 됩니다.\u003c/p\u003e","title":"[Java]Programmers GPS(2017 카카오코드 본선)"},{"content":"출처 Programmers 자물쇠와 열쇠(2020 KAKAO BLIND RECRUITMENT) 접근 문제 분석 주어진 자물쇠와 열쇠를 이동시키고 돌려서 맞출 수 있는지 확인하는 문제입니다.\n열쇠 혹은 자물쇠를 돌리는 로직과, 이동시키는 로직을 구현하면 됩니다. 문제에서 주어진 열쇠보다 자물솨의 크기가 작기 때문에, 자물쇠를 이동시키고 돌리는 식으로 구현하는 것이 더 효율적입니다.\n모든 칸에 대해서 자물쇠를 돌리면서 맞추고, 이동하는 식으로 완전탐색을 진행합니다. 시간복잡도 분석 자물쇠와 열쇠의 크기는 M \u0026lt;= N \u0026lt;= 20이므로, 전체 칸의 개수는 M^2 \u0026lt;= N^2 \u0026lt;= 400입니다.\n시간복잡도는 O(N^4)이상도 충분하므로, 문제 풀이에 큰 영향을 주지 않습니다.\n공간복잡도 분석 자물쇠와 열쇠의 크기가 20이하이기 때문에, 공간복잡도도 크게 신경쓰지 않아도 됩니다.\n풀이 자물쇠 이동 주어진 시간복잡도 및 공간복잡도가 충분하기 때문에, 열쇠를 자물쇠 크기만큼 확장시킨 뒤 이동하면서 완전탐색을 진행합니다.\npublic boolean solution(int[][] key, int[][] lock) { int N = key.length, M = lock.length; // 자물쇠 만큼 상하좌우로 확장된 키 int[][] keyExtend = new int[N + 2 * M][N + 2 * M]; // 키 내부 채우기 for (int i = M; i \u0026lt; N + M; i++) { for (int j = M; j \u0026lt; N + M; j++) { keyExtend[i][j] = key[i - M][j - M]; } } } 자물쇠 돌리기 주어진 자물쇠를 90도씩 돌리면서 탐색하면 회전된 4개의 자물쇠를 얻을 수 있습니다.\n실제 구현에서 역방향 원소는 자물쇠의 크기 M에서부터 반대로 탐색하는 식으로 구현하였습니다.\n// 회전된 자물쇠를 반환하는 메서드 int[][] changeDir(int d, int M, int[][] lock) { // 정방향은 그대로 반환 if (d == 0) return lock; // 회전된 자물쇠 저장 int[][] ret = new int[M][M]; for (int i = 0; i \u0026lt; M; i++) { for (int j = 0; j \u0026lt; M; j++) { // 90도 회전 -\u0026gt; i, j 교환, i는 역순 탐색 if (d == 1) ret[i][j] = lock[j][M - i - 1]; // 180도 회전 -\u0026gt; i, j 역순 탐색 else if (d == 2) ret[i][j] = lock[M - i - 1][M - j - 1]; // 270도 회전 -\u0026gt; i, j교환, j는 역순 탐색 else ret[i][j] = lock[M - j - 1][i]; } } return ret; } 자물쇠와 열쇠 비교 자물쇠와 열쇠가 같은 값인지 확인하여, 하나라도 같은 값이 있다면 열쇠와 자물쇠가 맞물리지 않으므로 false를 반환하도록 하였습니다.\n이 때, 열쇠가 자물쇠보다 크기 때문에, 시작점(r, c)을 이동하면서 비교하였습니다.\n// r, c : 시작점 boolean match(int r, int c, int M, int[][] key, int[][] lock) { for (int i = r; i \u0026lt; r + M; i++) { for (int j = c; j \u0026lt; c + M; j++) { // 하나라도 같은 값이 있으면 열쇠-자물쇠 일치하지 않음 if (key[i][j] == lock[i - r][j - c]) return false; } } // 모두 다른 값이면 열쇠-자물쇠 일치 return true; } 코드 import java.util.*; class Solution { public boolean solution(int[][] key, int[][] lock) { int N = key.length, M = lock.length; // 크기 확장(N -\u0026gt; N + 2M) int[][] keyExtend = new int[N + 2 * M][N + 2 * M]; for (int i = M; i \u0026lt; N + M; i++) { for (int j = M; j \u0026lt; N + M; j++) { keyExtend[i][j] = key[i - M][j - M]; } } // 자물쇠 4방향 돌리기 int[][][] locks = new int[4][M][M]; for (int i = 0; i \u0026lt; 4; i++) { locks[i] = changeDir(i, M, lock); } // 4방향 자물쇠 정답 확인 for (int i = 0; i \u0026lt; N + M; i++) { for (int j = 0; j \u0026lt; N + M; j++) { for (int d = 0; d \u0026lt; 4; d++) { if (match(i, j, M, keyExtend, locks[d])) return true; } } } return false; } // 90도씩 회전된 자물쇠 반환 int[][] changeDir(int d, int M, int[][] lock) { if (d == 0) return lock; int[][] ret = new int[M][M]; for (int i = 0; i \u0026lt; M; i++) { for (int j = 0; j \u0026lt; M; j++) { if (d == 1) ret[i][j] = lock[j][M - i - 1]; else if (d == 2) ret[i][j] = lock[M - i - 1][M - j - 1]; else ret[i][j] = lock[M - j - 1][i]; } } return ret; } // 열쇠와 자물쇠 맞물리는지 확인 boolean match(int r, int c, int M, int[][] key, int[][] lock) { for (int i = r; i \u0026lt; r + M; i++) { for (int j = c; j \u0026lt; c + M; j++) { if (key[i][j] == lock[i - r][j - c]) return false; } } return true; } } 결과 소요 시간 : 28분 리뷰 주어진 자물쇠와 열쇠의 크기가 크지 않아서 어렵지 않게 구현할 수 있었습니다. N, M의 크기가 커서 시간초과가 발생했더라면 최적화하는데 많이 애먹었을 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 ","permalink":"https://leaf-nam.github.io/cote/programmers_60059/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/60059\"\u003eProgrammers 자물쇠와 열쇠(2020 KAKAO BLIND RECRUITMENT)\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"문제-분석\"\u003e문제 분석\u003c/h3\u003e\n\u003cp\u003e주어진 자물쇠와 열쇠를 이동시키고 돌려서 맞출 수 있는지 확인하는 문제입니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e열쇠 혹은 자물쇠를 돌리는 로직과, 이동시키는 로직을 구현하면 됩니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e문제에서 주어진 열쇠보다 자물솨의 크기가 작기 때문에, 자물쇠를 이동시키고 돌리는 식으로 구현하는 것이 더 효율적입니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cul\u003e\n\u003cli\u003e모든 칸에 대해서 자물쇠를 돌리면서 맞추고, 이동하는 식으로 완전탐색을 진행합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"시간복잡도-분석\"\u003e시간복잡도 분석\u003c/h3\u003e\n\u003cp\u003e자물쇠와 열쇠의 크기는 \u003ccode\u003eM \u0026lt;= N \u0026lt;= 20\u003c/code\u003e이므로, 전체 칸의 개수는 \u003ccode\u003eM^2 \u0026lt;= N^2 \u0026lt;= 400\u003c/code\u003e입니다.\u003c/p\u003e","title":"[Java]Programmers 자물쇠와 열쇠(2020 KAKAO BLIND RECRUITMENT)"},{"content":"출처 프로그래머스 110 옮기기\n접근 문제 분석 주어진 이진 문자열에서 110이라는 문자열을 이동시켜야 하는 문제입니다. 문자열에서 110을 제거한 뒤, 새로운 위치에 얼마나 빨리 삽입할 수 있는지가 포인트입니다. 주어진 문자열의 길이는 1,000,000으로, O(N)혹은 O(NlogN) 이내의 시간복잡도를 구현해야 합니다. 퓰이 우선 문제에서 주어진대로 110을 옮겨야 하기 때문에, 우선 110을 모두 제거하고 그 개수를 저장해야 합니다.\n110 제거 그러나 문자열에서 110을 제거하는 과정이 만만치 않습니다.\n문자열에서 단순히 110이라는 글자를 찾아 삭제하면 끝일 것 같지만, 다음과 같은 경우에는 한번에 끝나지 않습니다. 다음과 같이 문자열이 주어졌다고 가정해보겠습니다. 문자열에서 110을 제거하면, 다시 110이라는 문자열이 나타납니다. 따라서, 문자열에서 110이 나타나지 않을 때까지 110을 제거해야 합니다. 또한, 단순히 제거만 하는게 아니라 제거한 횟수를 별도로 저장해야 합니다.\n따라서, 문자열을 앞에서부터 탐색하다가, 110을 발견하면 해당 문자열을 저장한 뒤 새로운 문자열을 다시 탐색합니다.\n스택을 사용할 수도 있지만, 여기서는 가변 객체1인 StringBuilder를 활용해서 문자열을 제거하겠습니다.\n다음과 같이 문자열이 주어졌다고 가정하겠습니다. 문자열에서 110이 나올떄까지 탐색을 합니다. 해당 문자열을 제거한 뒤, 4칸 앞으로 이동해서 다시 탐색을 수행합니다.\n4칸 전에서 시작하는 이유는, 해당 위치부터 다시 110이 나올 가능성이 생기기 떄문입니다.\n새로운 문자열에서 다시 110이 나올떄까지 탐색합니다.\n동일하게 해당 문자열을 제거한 뒤 4칸 앞으로 이동해서 다시 탐색을 수행합니다.\n그러나 시작점보다 앞으로 갈 수는 없기 때문에 0으로 이동합니다. 이후 탐색을 마칠 때까지 110이 발견되지 않습니다. Greedy 이렇게 주어진 문자열에서 110을 제거했다면, 가장 정렬이 빠른 순서로 만들기 위해 새로운 위치에 삽입해야 합니다.\n정렬했을 때 가장 최적이 되는 위치는 탐욕 알고리즘(Greedy)으로 구할 수 있습니다.\n110이라는 숫자는 맨 뒤에 0이 붙기 때문에 가장 빠른 정렬을 만들기 위해 1보다는 앞에 나와야 하지만, 0보다는 뒤에 나와야 합니다.\n따라서, 뒤에서부터 첫번째로 0이 나오는 지점 바로 뒤에 삽입했을 때가 가장 정렬이 빠른 순서가 됩니다.\n코드 import java.util.*; class Solution { public String[] solution(String[] s) { String[] answer = new String[s.length]; for (int i = 0; i \u0026lt; s.length; i++) { // 가변 문자열 생성 StringBuilder sb1 = new StringBuilder(s[i]); // 110 임시 저장공간 생성 StringBuilder sb2 = new StringBuilder(); // 앞에서부터 110 나올때까지 탐색 for (int j = 0; j \u0026lt; sb1.length(); j++) { if (j \u0026gt;= 2 \u0026amp;\u0026amp; sb1.charAt(j) == \u0026#39;0\u0026#39; \u0026amp;\u0026amp; sb1.charAt(j - 1) == \u0026#39;1\u0026#39; \u0026amp;\u0026amp; sb1.charAt(j - 2) == \u0026#39;1\u0026#39;) { // 가변 문자열 제거 sb1.deleteCharAt(j); sb1.deleteCharAt(j - 1); sb1.deleteCharAt(j - 2); // 앞에서부터 4칸 혹은 0으로 이동 j = Math.max(-1, j - 5); // 임시공간에 저장 sb2.append(\u0026#34;110\u0026#34;); } } // 110을 제거한 새로운 문자열 생성 String now = sb1.toString(); // 뒤에서부터 0 위치 확인 int idx = now.lastIndexOf(\u0026#39;0\u0026#39;); // 0 위치 바로 뒤에 저장된 110 문자열 추가 answer[i] = now.substring(0, idx + 1) + sb2.toString() + now.substring(idx + 1, now.length()); } return answer; } } 결과 소요시간 : 2시간 초과 리뷰 문제에서 주어진 시간복잡도가 넉넉하지 않아서 불변객체인 String을 매번 생성하니 시간초과가 계속 발생했습니다.2\n다른 언어는 모르겠지만, Java의 경우 언어의 특성을 잘 이해해야 풀 수 있다는 점에서 좋은 연습문제인 것 같습니다.\n이런 문제를 접할 때마다 언어의 패러다임과 그에 대한 이해도도 매우 중요함을 느낍니다.\nReferences URL 게시일자 방문일자 작성자 Java의 String은 불변객체이므로, 값이 변경될 때마다 매번 새로운 객체를 생성하는 과정에서 메모리가 많이 소모되지만, StringBuilder는 가변객체이므로 값이 변경되어도 새로운 객체가 생성되지 않아 값의 변경이 많을 경우 효율적입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n다음은 시간초과가 발생한 코드입니다.\nimport java.util.*; class Solution { public String[] solution(String[] s) { String[] answer = new String[s.length]; for (int i = 0; i \u0026lt; s.length; i++) { String now = s[i]; StringBuilder sb = new StringBuilder(); for (int j = 0; j \u0026lt; now.length(); j++) { if (now.charAt(j) == \u0026#39;0\u0026#39; \u0026amp;\u0026amp; j \u0026gt;= 2 \u0026amp;\u0026amp; now.substring(j - 2, j + 1).equals(\u0026#34;110\u0026#34;)) { // 불변객체인 String 계속 생성 now = now.substring(0, j - 2) + now.substring(j + 1, now.length()); j = Math.max(-1, j - 5); sb.append(\u0026#34;110\u0026#34;); } } int idx = now.lastIndexOf(\u0026#39;0\u0026#39;); answer[i] = now.substring(0, idx + 1) + sb.toString() + now.substring(idx + 1, now.length()); } return answer; } } 제출 결과 \u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/cote/programmers_77886/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/77886\"\u003e프로그래머스 110 옮기기\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"문제-분석\"\u003e문제 분석\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e주어진 이진 문자열에서 \u003ccode\u003e110\u003c/code\u003e이라는 문자열을 이동시켜야 하는 문제입니다.\u003c/li\u003e\n\u003cli\u003e문자열에서 \u003ccode\u003e110\u003c/code\u003e을 제거한 뒤, 새로운 위치에 얼마나 빨리 삽입할 수 있는지가 포인트입니다.\u003c/li\u003e\n\u003cli\u003e주어진 문자열의 길이는 \u003ccode\u003e1,000,000\u003c/code\u003e으로, O(N)혹은 O(NlogN) 이내의 시간복잡도를 구현해야 합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"퓰이\"\u003e퓰이\u003c/h2\u003e\n\u003cp\u003e우선 문제에서 주어진대로 \u003ccode\u003e110\u003c/code\u003e을 옮겨야 하기 때문에, 우선 \u003ccode\u003e110\u003c/code\u003e을 모두 제거하고 그 개수를 저장해야 합니다.\u003c/p\u003e\n\u003ch4 id=\"110-제거\"\u003e110 제거\u003c/h4\u003e\n\u003cp\u003e그러나 문자열에서 \u003ccode\u003e110\u003c/code\u003e을 제거하는 과정이 만만치 않습니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e문자열에서 단순히 \u003ccode\u003e110\u003c/code\u003e이라는 글자를 찾아 삭제하면 끝일 것 같지만, 다음과 같은 경우에는 한번에 끝나지 않습니다.\n\u003cul\u003e\n\u003cli\u003e다음과 같이 문자열이 주어졌다고 가정해보겠습니다.\n\u003cimg alt=\"img.png\" loading=\"lazy\" src=\"/cote/programmers_77886/img.png\"\u003e\u003c/li\u003e\n\u003cli\u003e문자열에서 \u003ccode\u003e110\u003c/code\u003e을 제거하면, 다시 \u003ccode\u003e110\u003c/code\u003e이라는 문자열이 나타납니다.\n\u003cimg alt=\"img_1.png\" loading=\"lazy\" src=\"/cote/programmers_77886/img_1.png\"\u003e\u003c/li\u003e\n\u003cli\u003e따라서, \u003ccode\u003e문자열에서 110이 나타나지 않을 때까지 110을 제거\u003c/code\u003e해야 합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e또한, 단순히 제거만 하는게 아니라 제거한 횟수를 별도로 저장해야 합니다.\u003c/p\u003e","title":"[Java]Programmers 110 옮기기"},{"content":"출처 Programmers 표 병합(2023 KAKAO BLIND RECRUITMENT) 접근 문제 분석 표 편집 프로그램의 기능을 구현해야 합니다. UPDATE : 셀 1개의 값 바꾸기, 모든 셀의 값 찾아 바꾸기 MERGE : 셀 합치기(그룹화) UNMERGE : 셀 분리하기(그룹화 해제) PRINT : 셀 1개의 값 출력하기 표의 크기가 50 x 50이고, 명령의 길이가 1000 이하이므로, 시간복잡도는 충분한 편입니다. UPDATE는 비교적 쉽게 구현할 수 있지만, 문제의 이름처럼 표 병합을 얼마나 정확하고 빠르게 구현하는지가 중요한 문제입니다.\nMap 활용 그룹화(병합) 구현 병합을 구현하는 다양한 방식이 있지만, 저는 HashMap을 사용해서 매번 그룹을 생성하는 방식을 사용했습니다.\n병합이 일어날 때마다 그룹(Group)을 새로 생성합니다.\n생성된 그룹은 고유의 KEY를 공유하며, 같은 KEY를 가진 표는 동일한 값을 참조하도록 합니다.\n이를 위해 HashMap에 그룹을 KEY별로 구분해서 저장합니다.\n위와 같은 표가 있다고 가정하면, 다음과 같이 그룹화를 수행합니다.\n그룹화 시마다 새로운 그룹 KEY를 생성해서 Map에 저장합니다. 생성된 그룹 KEY로 표를 업데이트합니다. 만약, 그룹과 그룹 간의 업데이트가 발생하면, 다음과 같이 그룹화를 수행합니다.\n새로운 그룹 KEY를 생성해서 Map에 저장합니다. 기존 그룹들을 모두 새로운 그룹 KEY로 업데이트합니다. 이 때, 새로 생성되는 그룹의 값을 기존 그룹의 값 : (2, 1)value으로 업데이트 하는 점과 연관된 그룹을 한번에 업데이트하는 점에 주목합니다.\n풀이 import java.util.*; class Solution { List\u0026lt;String\u0026gt; answer; String[][] cell; Map\u0026lt;String, String\u0026gt; groups; int GROUP_ID; public String[] solution(String[] commands) { // 초기화 answer = new ArrayList\u0026lt;\u0026gt;(); cell = new String[51][51]; Arrays.stream(cell).forEach(c -\u0026gt; Arrays.fill(c, \u0026#34;EMPTY\u0026#34;)); groups = new HashMap\u0026lt;\u0026gt;(); // 명령 수행 for (String command : commands) { String[] splited = command.split(\u0026#34; \u0026#34;); switch(splited[0]) { case \u0026#34;UPDATE\u0026#34;: if (splited.length == 4) update(splited[1], splited[2], splited[3]); else update(splited[1], splited[2]); break; case \u0026#34;MERGE\u0026#34;: merge(splited[1], splited[2], splited[3], splited[4]); break; case \u0026#34;UNMERGE\u0026#34;: unmerge(splited[1], splited[2]); break; case \u0026#34;PRINT\u0026#34;: print(splited[1], splited[2]); break; } } return answer.toArray(new String[answer.size()]); } // 해당 셀 업데이트 void update(String R, String C, String value) { int r = Integer.parseInt(R); int c = Integer.parseInt(C); // 그룹일 때는 그룹 값 업데이트 if (isGroup(r, c)) groups.put(cell[r][c], value); else cell[r][c] = value; } // 전체 셀 업데이트 void update(String value1, String value2) { for (int r = 1; r \u0026lt;= 50; r++) { for (int c = 1; c \u0026lt;= 50; c++) { // 그룹의 값을 변경할 때는 그룹 값 업데이트 if (isGroup(r, c) \u0026amp;\u0026amp; getValue(r, c).equals(value1)) groups.put(cell[r][c], value2); else if (cell[r][c].equals(value1)) cell[r][c] = value2; } } } // 병합 void merge(String R1, String C1, String R2, String C2) { int r1 = Integer.parseInt(R1); int c1 = Integer.parseInt(C1); int r2 = Integer.parseInt(R2); int c2 = Integer.parseInt(C2); if (r1 == r2 \u0026amp;\u0026amp; c1 == c2) return; // 새로운 그룹 생성 String newGroupId = \u0026#34;$\u0026#34; + GROUP_ID++; // 새로운 그룹의 값 : (r1, c1)이 비었을 때는 (r2, c2) 값 사용 if (getValue(r1, c1).equals(\u0026#34;EMPTY\u0026#34;) \u0026amp;\u0026amp; !getValue(r2, c2).equals(\u0026#34;EMPTY\u0026#34;)) groups.put(newGroupId, getValue(r2, c2)); else groups.put(newGroupId, getValue(r1, c1)); // 그룹일 때는 전체 그룹 키 업데이트 if (isGroup(r1, c1)) update(cell[r1][c1], newGroupId); else cell[r1][c1] = newGroupId; if (isGroup(r2, c2)) update(cell[r2][c2], newGroupId); else cell[r2][c2] = newGroupId; } // 병합 해제 void unmerge(String R, String C) { int r = Integer.parseInt(R); int c = Integer.parseInt(C); // 그룹 전체 값 비운 뒤 해당 셀의 값만 변경 if (isGroup(r, c)) { String temp = getValue(r, c); update(cell[r][c], \u0026#34;EMPTY\u0026#34;); cell[r][c] = temp; } } // 출력 void print(String R, String C) { int r = Integer.parseInt(R); int c = Integer.parseInt(C); answer.add(getValue(r, c)); } // 값 가져오기 : 그룹일 때는 그룹의 값 사용 String getValue(int r, int c) { if (isGroup(r, c)) return groups.get(cell[r][c]); else return cell[r][c]; } // 그룹인지 확인 : 식별자($) boolean isGroup(int r, int c) { if (cell[r][c].contains(\u0026#34;$\u0026#34;)) return true; return false; } } 결과 소요시간 : 2시간 초과 리뷰 다양한 방식으로 접근이 가능한 문제였습니다. 그룹을 구현하기 위해 많은 고민을 했는데, Union-Find를 사용하는 것이 훨씬 구현도 편하고 메모리도 덜 사용하는 방식인 것 같습니다.\n다음번에 다시 풀떄는 Union-Find를 사용해 봐야겠습니다.\nReferences URL 게시일자 방문일자 작성자 ","permalink":"https://leaf-nam.github.io/cote/programmers_150366/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/150366\"\u003eProgrammers 표 병합(2023 KAKAO BLIND RECRUITMENT)\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"문제-분석\"\u003e문제 분석\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e표 편집 프로그램의 기능을 구현해야 합니다.\n\u003cul\u003e\n\u003cli\u003eUPDATE : 셀 1개의 값 바꾸기, 모든 셀의 값 찾아 바꾸기\u003c/li\u003e\n\u003cli\u003eMERGE : 셀 합치기(그룹화)\u003c/li\u003e\n\u003cli\u003eUNMERGE : 셀 분리하기(그룹화 해제)\u003c/li\u003e\n\u003cli\u003ePRINT : 셀 1개의 값 출력하기\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e표의 크기가 \u003ccode\u003e50 x 50\u003c/code\u003e이고, 명령의 길이가 \u003ccode\u003e1000 이하\u003c/code\u003e이므로, 시간복잡도는 충분한 편입니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003eUPDATE\u003c/code\u003e는 비교적 쉽게 구현할 수 있지만, 문제의 이름처럼 \u003ccode\u003e표 병합\u003c/code\u003e을 얼마나 정확하고 빠르게 구현하는지가 중요한 문제입니다.\u003c/p\u003e","title":"[Java]Programmers 표 병합(2023 KAKAO BLIND RECRUITMENT)"},{"content":"출처 Programmers 등산코스 정하기(2022 KAKAO TECH INTERNSHIP) 접근 문제 분석 산의 봉우리까지 이동하는 과정을 시뮬레이션 하는 문제입니다. Intensity는 이동 과정에서 가장 긴시간을 뜻합니다. 문제에서는 정상에 도착한 후, 다시 출입구로 돌아와야 한다고 했지만 올라갔던 길을 그대로 내려올 수 있기 때문에, 정상까지만 경로를 추적하면 됩니다. 따라서 각 출발지(Gate)에서 도착지(Summit)까지의 Intensity가 최소가 되도록 완전탐색을 수행해야 합니다. 조건 분석 문제에서 주어진 정점(Vertex)은 n = 50,000 이고 간선(Edge)은 paths = 200,000입니다. 탐색의 시작점이 최대 n이기 때문에, 이미 방문한 정점을 필요시에만 재방문하도록 최적화하면 시간복잡도 내 문제를 해결할 수 있습니다. 전체 간선을 1회만 탐색할 경우 시간복잡도는 O(N) = E + V = 250,000가 됩니다.1\n이 때, 정점의 개수에 비해 간선이 적으므로, 인접 리스트를 활용하는 것이 효과적입니다.2 간선의 개수가 적은 그래프에서 행렬(n x n matrix)을 사용하게 되면, 불필요한 탐색 및 메모리 초과가 발생할 수 있습니다.\nBFS 한 정점에서 다른 정점으로 이동 시, 깊이를 따라 이동하면(DFS) 불필요한 탐색이 발생하기 때문에 시간초과가 발생할 수 있습니다.\n아래와 같은 깊은 그래프가 있다고 가정하겠습니다. DFS는 산꼭대기에 도착할때까지 깊이가 계속 깊어지므로, 다음과 같이 깊은 그래프에서는 모든 경로를 탐색하면서 시간복잡도가 계속 증가하게 됩니다. 그러나 BFS에서는 최단 경로로만 이동하기 때문에 불필요한 탐색을 최적화할 수 있습니다. 방문처리 최적화 단순히 방문여부로 방문체크를 한다면, 이후에 더 작은 Intensity로 방문하는 경우를 탐색할 수 없기 때문에, 방문처리 배열은 DP 형태로 Intensity 최솟값을 저장해야 합니다.\n문제에서 주어진 시작점이 여러개이기 때문에, 새로운 시작점에서 서로 다른 BFS를 수행하게 됩니다.\n그러나, 이전 등산코스에서 방문했던 지점이라면, 새로운 BFS에서 다시 방문할 필요가 없기 때문에 이를 최적화할 수 있습니다.\n위와 같이 1번 시작점에서 Intensity = 3으로 방문했던 지점이라면, 3번 시작점에서는 어떻게 해도 Intensity를 줄일 수 없기 때문에 해당 정점은 더이상 방문할 필요가 없습니다. 더 나아가서, 탐색 과정에서 해당 정점을 이미 더 작은 가중치로 다시 방문할 필요가 없기 때문에 최적화가 가능합니다.\n최소 가중치만 방문하는 점에서 다익스트라 알고리즘이 됩니다.\n도착지 최적화 도착지를 Set에 삽입하면, 해당 지점이 도착지인지 O(1)로 판단할 수 있기 때문에 도착여부를 빠르게 파악할 수 있습니다. 이 때, 문제에서 산봉우리 번호가 더 낮은 등산코스를 선택해야 하기 때문에, 더 빠른 도착지점이 앞으로 올 수 있도록 TreeSet으로 구현체를 선택합니다. TreeSet은 내부 원소를 정렬된 형태로 유지합니다.\n풀이 import java.util.*; class Solution { // 정답 초기화 int[] answer = {0, 10_000_001}; public int[] solution(int n, int[][] paths, int[] gates, int[] summits) { // 인접리스트 정의 및 생성 List\u0026lt;int[]\u0026gt;[] adjList = new List[n + 1]; for (int i = 0; i \u0026lt; paths.length; i++) { int start = paths[i][0], end = paths[i][1], time = paths[i][2]; if (adjList[start] == null) adjList[start] = new ArrayList\u0026lt;\u0026gt;(); if (adjList[end] == null) adjList[end] = new ArrayList\u0026lt;\u0026gt;(); adjList[start].add(new int[] {end, time}); adjList[end].add(new int[] {start, time}); } // DP 형태의 방문배열 int[] isVisited = new int[n + 1]; Arrays.fill(isVisited, 10_000_001); // 도착지 Set Set\u0026lt;Integer\u0026gt; summitSet = new TreeSet\u0026lt;\u0026gt;(); for (int summit : summits) summitSet.add(summit); // 각 시작지점에서 다익스트라 수행 for (int start : gates){ dijkstra(n, start, adjList, isVisited, summitSet); } return answer; } void dijkstra(int n, int start, List\u0026lt;int[]\u0026gt;[] adjList, int[] isVisited, Set\u0026lt;Integer\u0026gt; summitSet) { // Dijkstra를 위한 PriorityQueue PriorityQueue\u0026lt;int[]\u0026gt; q = new PriorityQueue\u0026lt;\u0026gt;((o1, o2) -\u0026gt; Integer.compare(o1[1], o2[1])); isVisited[start] = 0; q.offer(new int[] {start, 0}); while (!q.isEmpty()) { int[] now = q.poll(); // 탐색 과정에서 더 작은 가중치가 발생한 경우 탐색 종료 if (now[1] \u0026gt; isVisited[now[0]]) continue; // 도착지점일 경우 탐색 종료 if (summitSet.contains(now[0])) continue; for (int[] adjs : adjList[now[0]]) { // Intensity : 현재 경로에서 가장 긴 시간 int intensity = Math.max(adjs[1], now[1]); // 방문체크 : Intensity가 더 작을때만 재방문 if (intensity \u0026gt;= isVisited[adjs[0]]) continue; isVisited[adjs[0]] = intensity; // 새로운 지점에서 다시 BFS q.offer(new int[] {adjs[0], intensity}); } } // 산봉우리의 최솟값 찾기 int minSummit = 0, minIntensity = 10_000_001; // TreeSet이므로 번호가 낮은 산봉우리부터 확인 for (int summit : summitSet) { // 번호가 큰 산봉우리는 Intensity 최솟값이 작을때만 갱신 if (isVisited[summit] \u0026lt; minIntensity) { minIntensity = isVisited[summit]; minSummit = summit; } } // 정답 최신화 if ((answer[1] \u0026gt; minIntensity) || // Intensity가 같으면 번호 확인 (answer[1] == minIntensity \u0026amp;\u0026amp; answer[0] \u0026gt; minSummit)) { answer = new int[] {minSummit, minIntensity}; } } } 결과 소요 시간 : 1시간 30분 리뷰 문제를 정확히 이해하지 않고 구현하려다 보니 구현이 오래 걸렸습니다. 코스에서 가중치가 가장 작은 값만 확인하면 되므로 일반적인 Dijkstra와는 다른 로직임을 나중에야 알게 되었습니다. 문제를 정확히 이해하고 구현하는 습관을 길러야겠습니다.\nReferences URL 게시일자 방문일자 작성자 희소 그래프 2023.04.01. 2025.01.22. Wikipedia 물론 문제에서 각 정점을 1번씩만 방문하지는 않겠지만, 이미 방문한 정점을 특정 조건에서만 방문하도록 최적화하면 유사한 시간복잡도 내에서 탐색이 가능합니다.\n다익스트라를 사용하면 시작점 1개의 탐색횟수는 O(N) = ElogV = paths * log(n) = 3,122,000가 되지만, 다음 시작점의 탐색에서는 방문 배열을 통해 필요한 정점만 재방문하므로, 점점 방문 횟수가 줄어서 결국 O(N) = ElogV + a가 됩니다.\n\u0026#160;\u0026#x21a9;\u0026#xfe0e; 정점에 비해 간선이 적은 그래프를 희소 그래프라고 합니다.\n해당 문제에서 최대 간선의 숫자는 n * (n - 1) / 2 = 1,249,975,000인 반면, 해당 그래프의 간선의 숫자는 200,000이므로 정점에 비해 간선이 적다고 할 수 있습니다.\n\u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/cote/programmers_118669/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/118669\"\u003eProgrammers 등산코스 정하기(2022 KAKAO TECH INTERNSHIP)\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"문제-분석\"\u003e문제 분석\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e산의 봉우리까지 이동하는 과정을 시뮬레이션 하는 문제입니다.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eIntensity\u003c/code\u003e는 이동 과정에서 가장 긴시간을 뜻합니다.\u003c/li\u003e\n\u003cli\u003e문제에서는 정상에 도착한 후, 다시 출입구로 돌아와야 한다고 했지만 \u003cstrong\u003e올라갔던 길을 그대로 내려올 수 있기 때문에\u003c/strong\u003e, 정상까지만 경로를 추적하면 됩니다.\u003c/li\u003e\n\u003cli\u003e따라서 각 출발지(Gate)에서 도착지(Summit)까지의 \u003ccode\u003eIntensity\u003c/code\u003e가 최소가 되도록 완전탐색을 수행해야 합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"조건-분석\"\u003e조건 분석\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e문제에서 주어진 정점(Vertex)은 \u003ccode\u003en = 50,000\u003c/code\u003e 이고 간선(Edge)은 \u003ccode\u003epaths = 200,000\u003c/code\u003e입니다.\u003c/li\u003e\n\u003cli\u003e탐색의 시작점이 최대 \u003ccode\u003en\u003c/code\u003e이기 때문에, \u003cstrong\u003e이미 방문한 정점을 필요시에만 재방문하도록 최적화\u003c/strong\u003e하면 시간복잡도 내 문제를 해결할 수 있습니다.\n\u003cblockquote\u003e\n\u003cp\u003e전체 간선을 1회만 탐색할 경우 시간복잡도는 \u003ccode\u003eO(N) = E + V = 250,000\u003c/code\u003e가 됩니다.\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e\u003c/p\u003e","title":"[Java]Programmers 등산코스 정하기(2022 KAKAO TECH INTERNSHIP)"},{"content":"출처 SWEA 1249 보급로 접근 시간복잡도 계산 N \u0026lt;= 100, 지도의 최대 크기가 10000이므로, 방문 체크만 잘 해주면 완전탐색을 하는데 큰 문제가 없는 조건입니다. BFS BFS(너비우선 탐색)를 통해 최단경로를 구할 수 있습니다.\n이 때, 미로찾기 알고리즘처럼 목적지에 도착하면 끝나는 것이 아니라, 가중치가 가장 낮은 경로로 이동해야 합니다.\n따라서, 이미 목적지에 도착했더라도 더 빠른 경로가 있기 때문에 BFS를 종료하면 안됩니다.\n위 그림에서 우 -\u0026gt; 하 순으로 탐색을 진행할 경우, 전체 비용이 6인 경로가 먼저 탐색되지만 비용이 가장 작은 경로는 아닙니다. 이를 위해, 각 지점마다 도달할 수 있는 최소 가중치를 저장해두고, 해당 가중치보다 작은 값만 재방문이 가능하도록 하여 방문 횟수를 줄일 수 있습니다.\n코드로 표현하면 다음과 같습니다. static int bfs(int N, int[][] map) { Deque\u0026lt;int[]\u0026gt; q = new ArrayDeque\u0026lt;\u0026gt;(); // visited : 해당 지점에 도달할 수 있는 최소 가중치 int[][] visited = new int[N][N]; // 최소 가중치 초기화 Arrays.stream(visited).forEach(v -\u0026gt; Arrays.fill(v, Integer.MAX_VALUE)); q.offer(new int[] {0, 0, map[0][0]}); visited[0][0] = map[0][0]; while (!q.isEmpty()) { int[] cur = q.poll(); for (int i = 0; i \u0026lt; 4; i++) { int nr = cur[0] + dr[i]; int nc = cur[1] + dc[i]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N) continue; // 다음 방문지점이 현재 값보다 클 때만 방문 int now = cur[2] + map[nr][nc]; if (now \u0026gt;= visited[nr][nc]) continue; visited[nr][nc] = now; q.offer(new int[] {nr, nc, now}); } } return visited[N - 1][N - 1]; } Dijkstra 현재 문제는 시간복잡도 상 BFS로도 충분히 해결이 가능하지만, 가중치가 있는 최단경로를 구할 때는 Dijkstra를 사용하면 더욱 빠르게 풀 수 있습니다. Dijkstra는 일종의 Greedy알고리즘 기법을 활용하여 다음 탐색지점을 결정하기 때문에 최단 경로로만 이동하여 탐색 횟수를 줄입니다.\n위 알고리즘에서 Queue를 PriorityQueue로 변경 후, 현재 지점이 최소인지 확인 후 탐색을 시작하면 다익스트라 알고리즘이 적용됩니다. 코드로 표현하면 다음과 같습니다. static int dijkstra(int N, int[][] map) { // 우선순위 큐 사용 | 기존 : Deque\u0026lt;int[]\u0026gt; q = new ArrayDeque\u0026lt;\u0026gt;(); PriorityQueue\u0026lt;int[]\u0026gt; q = new PriorityQueue\u0026lt;\u0026gt;((o1, o2) -\u0026gt; Integer.compare(o1[2], o2[2])); int[][] visited = new int[N][N]; Arrays.stream(visited).forEach(v -\u0026gt; Arrays.fill(v, Integer.MAX_VALUE)); q.offer(new int[] {0, 0, map[0][0]}); visited[0][0] = map[0][0]; while (!q.isEmpty()) { int[] cur = q.poll(); // 탐색 최적화(탐색 과정에서 현재보다 더 빠른 경로가 나타날 수 있음) if (visited[cur[0]][cur[1]] \u0026lt; cur[2]) continue; for (int i = 0; i \u0026lt; 4; i++) { int nr = cur[0] + dr[i]; int nc = cur[1] + dc[i]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N) continue; int now = cur[2] + map[nr][nc]; if (now \u0026gt;= visited[nr][nc]) continue; visited[nr][nc] = now; q.offer(new int[] {nr, nc, now}); } } return visited[N - 1][N - 1]; } 풀이 import java.io.*; import java.util.*; class Solution { public static void main(String args[]) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int T = Integer.parseInt(br.readLine()); for(int test_case = 1; test_case \u0026lt;= T; test_case++) { int N = Integer.parseInt(br.readLine()); int[][] map = new int[N][N]; for (int i = 0; i \u0026lt; N; i++) { char[] s = br.readLine().toCharArray(); for (int j = 0; j \u0026lt; N; j++) map[i][j] = s[j] - \u0026#39;0\u0026#39;; } //System.out.println(\u0026#34;#\u0026#34; + test_case + \u0026#34; \u0026#34; + bfs(N, map)); System.out.println(\u0026#34;#\u0026#34; + test_case + \u0026#34; \u0026#34; + dijkstra(N, map)); } } static int[] dr = {0, 0, 1, -1}; static int[] dc = {1, -1, 0, 0}; // BFS static int bfs(int N, int[][] map) { // 너비 우선으로 탐색하는 큐 Deque\u0026lt;int[]\u0026gt; q = new ArrayDeque\u0026lt;\u0026gt;(); int[][] visited = new int[N][N]; Arrays.stream(visited).forEach(v -\u0026gt; Arrays.fill(v, Integer.MAX_VALUE)); q.offer(new int[] {0, 0, map[0][0]}); visited[0][0] = map[0][0]; while (!q.isEmpty()) { int[] cur = q.poll(); if (visited[cur[0]][cur[1]] \u0026lt; cur[2]) continue; // 4방향 탐색 for (int i = 0; i \u0026lt; 4; i++) { int nr = cur[0] + dr[i]; int nc = cur[1] + dc[i]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N) continue; int now = cur[2] + map[nr][nc]; if (now \u0026gt;= visited[nr][nc]) continue; visited[nr][nc] = now; q.offer(new int[] {nr, nc, now}); } } // 목적지까지의 최소경로 출력 return visited[N - 1][N - 1]; } // 다익스트라 static int dijkstra(int N, int[][] map) { // 우선순위 큐 사용 PriorityQueue\u0026lt;int[]\u0026gt; q = new PriorityQueue\u0026lt;\u0026gt;((o1, o2) -\u0026gt; Integer.compare(o1[2], o2[2])); // 방문 배열 초기화 int[][] visited = new int[N][N]; Arrays.stream(visited).forEach(v -\u0026gt; Arrays.fill(v, Integer.MAX_VALUE)); q.offer(new int[] {0, 0, map[0][0]}); visited[0][0] = map[0][0]; while (!q.isEmpty()) { int[] cur = q.poll(); // 탐색 최적화 if (visited[cur[0]][cur[1]] \u0026lt; cur[2]) continue; for (int i = 0; i \u0026lt; 4; i++) { int nr = cur[0] + dr[i]; int nc = cur[1] + dc[i]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N) continue; int now = cur[2] + map[nr][nc]; if (now \u0026gt;= visited[nr][nc]) continue; visited[nr][nc] = now; q.offer(new int[] {nr, nc, now}); } } return visited[N - 1][N - 1]; } } 결과 소요시간 : 28:33 BFS 시 실행 시간 : 0.53029s 다익스트라 적용 시 : 0.21911s 리뷰 뭔가 출제자는 다익스트라를 의도하고 낸 것 같은데 BFS만으로도 풀려서 생각보다 금방 풀 수 있었습니다.\n그래도 시간복잡도가 빡빡한 문제를 대비해야 하기 때문에 최적화를 항상 고민해야겠습니다.\nReferences URL 게시일자 방문일자 작성자 ","permalink":"https://leaf-nam.github.io/cote/swea_1249/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://swexpertacademy.com/main/solvingProblem/solvingProblem.do\"\u003eSWEA 1249 보급로\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"시간복잡도-계산\"\u003e시간복잡도 계산\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eN \u0026lt;= 100\u003c/code\u003e, 지도의 최대 크기가 \u003ccode\u003e10000\u003c/code\u003e이므로, 방문 체크만 잘 해주면 완전탐색을 하는데 큰 문제가 없는 조건입니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"bfs\"\u003eBFS\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003eBFS(너비우선 탐색)\u003c/code\u003e를 통해 최단경로를 구할 수 있습니다.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e이 때, 미로찾기 알고리즘처럼 목적지에 도착하면 끝나는 것이 아니라, 가중치가 가장 낮은 경로로 이동해야 합니다.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e따라서, 이미 목적지에 도착했더라도 더 빠른 경로가 있기 때문에 \u003ccode\u003eBFS\u003c/code\u003e를 종료하면 안됩니다.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"img.png\" loading=\"lazy\" src=\"/cote/swea_1249/img.png\"\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e위 그림에서 \u003ccode\u003e우 -\u0026gt; 하\u003c/code\u003e 순으로 탐색을 진행할 경우, \u003ccode\u003e전체 비용이 6\u003c/code\u003e인 경로가 먼저 탐색되지만 비용이 가장 작은 경로는 아닙니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e이를 위해, 각 지점마다 도달할 수 있는 최소 가중치를 저장해두고, 해당 가중치보다 작은 값만 재방문이 가능하도록 하여 방문 횟수를 줄일 수 있습니다.\u003c/p\u003e","title":"[Java]SWEA 1249 보급로"},{"content":"출처 2024 KAKAO WINTER INTERNSHIP 산 모양 타일링 접근 시간복잡도 구하기 n \u0026lt;= 100,000 이므로 전체 타일의 개수는 최대 사다리꼴의 윗변의 모든 자리에 삼각형을 넣을 수 있으므로 2n + 1 + n \u0026lt;= 300,001개 입니다.\n이러한 삼각형의 배치를 완전탐색으로 구하는 것은 불가능하기 때문에, DP를 통한 최적화가 필요합니다.\n규칙성 찾기 예제의 타일을 1칸씩 세면서 경우의 수를 세면 규칙성을 확인할 수 있습니다.\n다음과 같이 (1 ~ 9) 순으로 예제타일의 규칙성을 구해보겠습니다.\n첫번째 지점에서 타일을 만들 수 있는 경우의 수는 다음과 같이 1가지밖에 없습니다. 두번째 지점에서 타일을 만드는 경우는 다음과 같이 3가지가 있습니다. 아직 규칙성이 명확하지 않으니 좀더 진행하겠습니다.\n세번째 지점은 산 모양이 아닙니다. 이 때 타일을 만드는 경우는 다음과 같습니다. 위와 같이 산 모양이 아닐 경우, 2번째 타일과 1번째 타일로 현재 타일을 만들 수 있습니다.\n전체 경우는\n2번 타일 경우의 수 + 1번 타일 경우의 수 = 3 + 1 = 4 입니다.\n네 번째 지점은 산 모양입니다. 이 때 타일을 만드는 경우는 다음과 같습니다. 위와 같이 해당 지점이 산 모양일 경우, 이전 타일로 2가지, 전전 타일로 1가지를 합쳐 현재 타일을 만들 수 있습니다. 전체 경우는 3번 타일 경우의 수 X 2 + 2번 타일 경우의 수 = 4 * 2 + 3 = 11 입니다.\n이를 일반화하면 다음과 같습니다.\n// 산 모양일 경우 dp[i] = (dp[i - 1] * 2 + dp[i - 2]); // 산 모양이 아닐 경우 dp[i] = (dp[i - 1] + dp[i - 2]) 실제 풀이에서는 아랫변의 삼각형 개수가 n개가 아닌 2n + 1개 임에 주의합니다.\n풀이 class Solution { int MOD = 10007; public int solution(int n, int[] tops) { int t = 2 * n + 1; int[] dp = new int[t + 1]; dp[0] = 1; dp[1] = 1; for (int i = 2; i \u0026lt;= t; i++) { if (i % 2 == 0 \u0026amp;\u0026amp; tops[(i - 1) / 2] == 1) dp[i] = (dp[i - 1] * 2 + dp[i - 2]) % MOD; else dp[i] = (dp[i - 1] + dp[i - 2]) % MOD; } return dp[t]; } } 결과 걸린 시간 : 2시간 초과 리뷰 타일 모양에 너무 집중하다 보니 규칙을 구하는데 오랜 시간이 걸렸습니다.\nDP문제는 가장 작은 단위부터 꼼꼼히 따져보는 습관을 가져야 할 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 ","permalink":"https://leaf-nam.github.io/cote/programmers_258705/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/258705\"\u003e2024 KAKAO WINTER INTERNSHIP 산 모양 타일링\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"시간복잡도-구하기\"\u003e시간복잡도 구하기\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003ccode\u003en \u0026lt;= 100,000\u003c/code\u003e 이므로 전체 타일의 개수는 최대 사다리꼴의 윗변의 모든 자리에 삼각형을 넣을 수 있으므로 \u003ccode\u003e2n + 1 + n \u0026lt;= 300,001\u003c/code\u003e개 입니다.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e이러한 삼각형의 배치를 완전탐색으로 구하는 것은 불가능하기 때문에, DP를 통한 최적화가 필요합니다.\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"규칙성-찾기\"\u003e규칙성 찾기\u003c/h3\u003e\n\u003cp\u003e예제의 타일을 1칸씩 세면서 경우의 수를 세면 규칙성을 확인할 수 있습니다.\u003c/p\u003e\n\u003cp\u003e다음과 같이 (1 ~ 9) 순으로 예제타일의 규칙성을 구해보겠습니다.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"img_2.png\" loading=\"lazy\" src=\"/cote/programmers_258705/img_2.png\"\u003e\u003c/p\u003e","title":"[Java]Programmers 산 모양 타일링(2024 KAKAO WINTER INTERNSHIP)"},{"content":"출처 https://www.acmicpc.net/problem/17470 접근 시간복잡도 분석 문제에서 주어진 배열의 크기는 최대 100 x 100이고, 전체 연산의 개수는 2,000,000개 이므로, 일반적인 회전을 구현할 경우 시간 초과가 발생합니다.\nO(N) = 100 x 100 x 2,000,000 = 2 x 10^13입니다.\n배열 연산 압축하기(X) 해당 접근은 잘못된 풀이입니다. 올바른 풀이는 아래를 참고하시기 바랍니다.\n연산을 구현하고 배열을 돌리다 보면, 뭔가 최적화할 수 있는 것 같은 느낌이 듭니다.\n상하(1)-좌우(2) 반전\n2번 하면 원래 배열로 복귀합니다. 오른쪽(3) 왼쪽(4) 회전\n4번 하거나 오른쪽 -\u0026gt; 왼쪽, 왼쪽 -\u0026gt; 오른쪽순으로 회전하면 원래 배열로 복귀합니다. 내부 오른쪽(5) 왼쪽(6) 회전\n4번 하거나 오른쪽 -\u0026gt; 왼쪽, 왼쪽 -\u0026gt; 오른쪽순으로 회전하면 원래 배열로 복귀합니다. 이러한 경우를 고려해서 연산 횟수를 줄일 수 있을 것 같아, 다음과 같이 압축을 시도할 수 있습니다.\n최초 연산\nchar[] calc = {1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2 ,3, 4, 1, 2, 3, 4}; 압축 : 3과 4가 만나면 원래 배열로 복귀하므로 삭제\nchar[] calc = {1, 2, 1, 2, 1, 2, 1, 2 ,1, 2}; 압축 : 1과 2가 짝수개이면 원래 배열로 복귀하므로 삭제\nchar[] calc = {1, 2}; 그러나 다음과 같이 회전 도중에 뒤집기가 발생하면 기존 배열과 다른 모양이 되기 때문에 이러한 압축은 불가능합니다.\nchar[] calc = {1, 3, 1}; // 뒤집기 -\u0026gt; 회전 -\u0026gt; 뒤집기 char[] calc = {1, 1, 3}; // 뒤집기 -\u0026gt; 뒤집기 -\u0026gt; 회전 작은 배열로 압축하기 문제에서 시간 초과가 발생하는 주된 원인은 배열의 크기가 너무 크기 때문입니다.\n배열이 2 x 2사이즈라고 가정하면, 전체 연산의 횟수가 2,000,000이기 때문에 전체 연산을 수행하는 시간복잡도는\nO(N) = 4 x 2,000,000 = 8 x 10^7 이 됩니다.\n따라서, 다음과 같이 배열의 크기를 작게 줄여서(압축해서) 연산을 마친 뒤 원본 배열을 읽는 식으로 연산을 최적화할 수 있습니다.\n배열 내부 복원하기 배열을 압축해서 연산을 하다 보니, 실제 배열로 복원하면서 뒤집기와 회전 연산에 차이가 발생합니다.\n다음과 같이 3가지 경우를 유념해야 합니다.\n뒤집기를 하면 기존 배열의 원소를 반대로 복원해야 합니다.\n회전을 하면 복원 시 기존 배열을 90도 회전해야 합니다.\n회전된 상태에서는 뒤집기의 상하가 반대로 적용됩니다.\n압축된 배열을 올바르게 복원하기 위해 변수를 연산과정에서 저장해두고, 복원 시 활용합니다.\n뒤집기 상하, 뒤집기 좌우 와 회전 횟수 총 3가지 변수를 저장합니다.\n풀이 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.*; public class Main { static int N, M, R; static char[] calc; // 압축된 배열 static int[][] temp = {{0, 1}, {2, 3}}; // 복원 시 필요한 변수 static boolean flipX, flipY; static int turn; public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer stringTokenizer = new StringTokenizer(br.readLine()); N = Integer.parseInt(stringTokenizer.nextToken()); M = Integer.parseInt(stringTokenizer.nextToken()); R = Integer.parseInt(stringTokenizer.nextToken()); int[][] arr = new int[N][M]; for (int i = 0; i \u0026lt; N; i++) { stringTokenizer = new StringTokenizer(br.readLine()); for (int j = 0; j \u0026lt; M; j++) arr[i][j] = Integer.parseInt(stringTokenizer.nextToken()); } calc = br.readLine().replace(\u0026#34; \u0026#34;, \u0026#34;\u0026#34;).toCharArray(); calculate(calc); print(arr); } // 출력 시 압축된 배열을 복원 private static void print(int[][] arr) { // 원본 배열의 각 사분면 저장 int[][][] divide = new int[4][N / 2][M / 2]; for (int i = 0; i \u0026lt; N; i++) { for (int j = 0; j \u0026lt; M; j++) { if (i \u0026lt; N / 2 \u0026amp;\u0026amp; j \u0026lt; M / 2) divide[0][i][j] = arr[i][j]; if (i \u0026lt; N / 2 \u0026amp;\u0026amp; j \u0026gt;= M / 2) divide[1][i][j - M / 2] = arr[i][j]; if (i \u0026gt;= N / 2 \u0026amp;\u0026amp; j \u0026lt; M / 2) divide[2][i - N / 2][j] = arr[i][j]; if (i \u0026gt;= N / 2 \u0026amp;\u0026amp; j \u0026gt;= M / 2) divide[3][i - N / 2][j - M / 2] = arr[i][j]; } } // 복원 시 저장된 변수 사용 for (int i = 0; i \u0026lt; 4; i++) { if (flipX) divide[i] = flipX(divide[i]); if (flipY) divide[i] = flipY(divide[i]); if (turn != 0) for (int j = 0; j \u0026lt; turn; j++) divide[i] = turnR(divide[i]); } // 회전이 발생하면 원본 배열의 좌우 크기도 변경 if (turn % 2 == 1) { int temp = N; N = M; M = temp; } // 각 사분면 복원하며 읽기 StringBuilder sb = new StringBuilder(); for (int i = 0; i \u0026lt; N; i++) { for (int j = 0; j \u0026lt; M; j++) { // 1사분면의 현재 값 읽어서 복원(temp[0][0]) if (i \u0026lt; N / 2 \u0026amp;\u0026amp; j \u0026lt; M / 2) sb.append(divide[temp[0][0]][i][j]); // 나머지 사분면도 동일하게 진행 if (i \u0026lt; N / 2 \u0026amp;\u0026amp; j \u0026gt;= M / 2) sb.append(divide[temp[0][1]][i][j - M / 2]); if (i \u0026gt;= N / 2 \u0026amp;\u0026amp; j \u0026lt; M / 2) sb.append(divide[temp[1][0]][i - N / 2][j]); if (i \u0026gt;= N / 2 \u0026amp;\u0026amp; j \u0026gt;= M / 2) sb.append(divide[temp[1][1]][i - N / 2][j - M / 2]); if (j != M - 1) sb.append(\u0026#34; \u0026#34;); } if (i != N - 1) sb.append(\u0026#34;\\n\u0026#34;); } System.out.print(sb); } // 연산 수행하면서 복원을 위한 변수 저장 static void calculate(char[] calc) { for (char r : calc) { switch (r) { // 뒤집기 시 회전된 상태(turn % 2 == 0) 에서는 반대로 뒤집기 case \u0026#39;1\u0026#39;: { temp = flipY(temp); if (turn % 2 == 0) flipY = !flipY; else flipX = !flipX; break; } case \u0026#39;2\u0026#39;: { temp = flipX(temp); if (turn % 2 == 0) flipX = !flipX; else flipY = !flipY; break; } // 회전 시 4바퀴 이상 돌면 원복해야 하므로 모듈러 연산 case \u0026#39;3\u0026#39;: temp = turnR(temp); turn++; turn %= 4; break; case \u0026#39;4\u0026#39;: temp = turnL(temp); turn--; turn += 4; turn %= 4; break; // 내부 이동은 별도 복원값 저장 불필요 case \u0026#39;5\u0026#39;: temp = rotateR(temp); break; case \u0026#39;6\u0026#39;: temp = rotateL(temp); break; } } } // Y방향(세로) 뒤집기 static int[][] flipY(int[][] arr) { int n = arr.length; int m = arr[0].length; int[][] temp = new int[n][m]; for (int i = 0; i \u0026lt; n; i++) { for (int j = 0; j \u0026lt; m; j++) { temp[n - i - 1][j] = arr[i][j]; } } return temp; } // X방향(가로) 뒤집기 static int[][] flipX(int[][] arr) { int n = arr.length; int m = arr[0].length; int[][] temp = new int[n][m]; for (int i = 0; i \u0026lt; n; i++) { for (int j = 0; j \u0026lt; m; j++) { temp[i][m - j - 1] = arr[i][j]; } } return temp; } // 오른쪽으로 돌리기 static int[][] turnR(int[][] arr) { int n = arr.length; int m = arr[0].length; int[][] temp = new int[m][n]; for (int i = 0; i \u0026lt; n; i++) { for (int j = 0; j \u0026lt; m; j++) { temp[j][i] = arr[n - i - 1][j]; } } return temp; } // 왼쪽으로 돌리기(압축된 배열에만 사용) static int[][] turnL(int[][] arr) { int[][] temp = new int[2][2]; for (int i = 0; i \u0026lt; 2; i++) { for (int j = 0; j \u0026lt; 2; j++) { temp[j][i] = arr[i][1 - j]; } } return temp; } // 내부 오른쪽으로 돌리기(압축된 배열에만 사용) static int[][] rotateR(int[][] arr) { int[][] temp = new int[2][2]; for (int i = 0; i \u0026lt; 2; i++) { for (int j = 0; j \u0026lt; 2; j++) { temp[0][0] = arr[1][0]; temp[1][0] = arr[1][1]; temp[1][1] = arr[0][1]; temp[0][1] = arr[0][0]; } } return temp; } // 내부 왼으로 돌리기(압축된 배열에만 사용) static int[][] rotateL(int[][] arr) { int[][] temp = new int[2][2]; for (int i = 0; i \u0026lt; 2; i++) { for (int j = 0; j \u0026lt; 2; j++) { temp[0][0] = arr[0][1]; temp[1][0] = arr[0][0]; temp[1][1] = arr[1][0]; temp[0][1] = arr[1][1]; } } return temp; } } 결과 소요시간 : 2시간 초과 리뷰 처음에는 배열 연산을 압축하는 것으로 충분히 시간복잡도 내에서 풀이가 가능하다고 생각해서 계속 시도했지만, 차분히 생각해보니 최악의 경우 시간복잡도를 초과할 수밖에 없겠다는 생각이 들었습니다.\n이후 질문 게시판에서 다른 분의 댓글 설명을 보고 감을 잡아서 풀 수 있었습니다.\n항상 최악의 시간복잡도를 고려하는 것을 연습해야겠습니다.\nReferences URL 게시일자 방문일자 작성자 백준 게시판 2020 2024.12.31. posko17 ","permalink":"https://leaf-nam.github.io/cote/bj_17470/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.acmicpc.net/problem/17470\"\u003ehttps://www.acmicpc.net/problem/17470\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"시간복잡도-분석\"\u003e시간복잡도 분석\u003c/h3\u003e\n\u003cp\u003e문제에서 주어진 배열의 크기는 최대 \u003ccode\u003e100 x 100\u003c/code\u003e이고, 전체 연산의 개수는 \u003ccode\u003e2,000,000\u003c/code\u003e개 이므로, 일반적인 회전을 구현할 경우 시간 초과가 발생합니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003eO(N) = 100 x 100 x 2,000,000 = 2 x 10^13\u003c/code\u003e입니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"배열-연산-압축하기x\"\u003e배열 연산 압축하기(X)\u003c/h3\u003e\n\u003cblockquote\u003e\n\u003cp\u003e해당 접근은 잘못된 풀이입니다. 올바른 풀이는 \u003ca href=\"#%EC%9E%91%EC%9D%80-%EB%B0%B0%EC%97%B4%EB%A1%9C-%EC%95%95%EC%B6%95%ED%95%98%EA%B8%B0\"\u003e아래\u003c/a\u003e를 참고하시기 바랍니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e연산을 구현하고 배열을 돌리다 보면, 뭔가 최적화할 수 있는 것 같은 느낌이 듭니다.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e상하(1)-좌우(2) 반전\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003e2번\u003c/code\u003e 하면 원래 배열로 복귀합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e오른쪽(3) 왼쪽(4) 회전\u003c/p\u003e","title":"[Java]백준 17470 배열 돌리기 5"},{"content":"출처 프로그래머스 멀쩡한 사각형 접근 규칙을 알고 나면 구하기 쉽지만, 모르면 생각보다 까다로운 문제입니다.\n규칙 구하기 주어진 예시 (w = 8, h = 12)에 대해 규칙을 확인해보겠습니다.\n문제의 예시를 자세히 보면 작은 사각형이 반복되는 것을 알 수 있습니다. w = 8과 h = 12의 최대공약수인 4개의 사각형이 생성됩니다.\n즉, 최대공약수로 해당 길이를 나누었을 때 생성된 작은 사각형의 잘린 개수를 구하면 됩니다.\n최대공약수로 나눠진 사각형(nw x nh = 2 x 3)의 잘린 개수는 다음과 같이 구할 수 있습니다.\n잘리지 않은 사각형을 자른 선 기준으로 구분합니다.(위 : 노랑, 아래 : 하양)\n잘리지 않은 사각형끼리 직사각형 형태로 모읍니다.\n잘리지 않은 사각형의 크기는 기존 사각형보다 1씩 작은 크기가 됩니다.\n남은 사각형 : (nw - 1) x (nh - 1) = 1 x 2\n다른 형태의 사각형을 여러 개 그려보면서 잘라보면 해당 규칙을 쉽게 발견할 수 있습니다.1\n따라서 전체 잘린 사각형의 크기는 다음과 같이 구할 수 있습니다.\n잘린 사각형 = 나눠진 사각형 - 잘리지 않은 사각형\n= (nw x nh) - (nw - 1) * (nh - 1)\n= (nw * nh) - (nw * nh - nw - nh + 1)\n= (nw + nh - 1)\n유클리드 호제법 유클리드 호제법은 최대공약수를 빠르게 구할 수 있는 알고리즘입니다.\n정수 a, b에 대하여 두 수의 최대공약수를 (a, b)라고 표현하면 아래 식이 성립합니다.\n(a, b) = (b, a % b)\n증명은 위 위키백과 링크에 자세히 정리되어 있으니 필요하시면 참고하시기 바랍니다!\n위 문제의 예시에서 w = 8, h = 12이므로, 두 수의 최대공약수를 다음과 같이 구할 수 있습니다.\n(8, 12) = (8, 12 % 8) = (8, 4)\n(8, 4) = (8 % 4, 4) = (0, 4)\n∴ (8, 12) = 4\n위 과정을 일반화하여 코드로 나타내면 다음과 같이 재귀적으로 표현할 수 있습니다.\nint getGcd(int w, int h) { // 피제수가 0이면 나머지 숫자가 최대공약수 if (h == 0) return w; // 유클리드 호제법으로 재귀호출 return getGcd(h, w % h); } 나머지 연산 과정에서 최대 log(N) 씩 피제수가 감소하므로, 시간복잡도는 O(log N)가 됩니다.\n풀이 소요시간 : 1:08:00 class Solution { public long solution(int w, int h) { int d = getGcd(w, h); int nw = w / d, nh = h / d; return (long)w * h - (nw + nh - 1) * d; } int getGcd(int w, int h) { if (h == 0) return w; return getGcd(h, w % h); } } 결과 소요 시간 : 01:08:53 리뷰 규칙을 구하는데 너무 오래 걸렸고, 유클리드 알고리즘도 오랜만에 쓰려다 보니 생각이 안나서 위키백과를 참고했습니다.\n유클리드 알고리즘을 모르면 시간복잡도 내 풀 수 없는 문제들도 있으니, 만날 때마다 한번씩 사용해보는게 중요한 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 유클리드 호제법 2024.10.22 2024.12.23. Wikipedia 3 x 4, 3 x 5, 4 x 5인 사각형 중 잘리지 않은 크기는 다음과 같습니다. \u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/cote/programmers_62048/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/62048\"\u003e프로그래머스 멀쩡한 사각형\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cp\u003e규칙을 알고 나면 구하기 쉽지만, 모르면 생각보다 까다로운 문제입니다.\u003c/p\u003e\n\u003ch3 id=\"규칙-구하기\"\u003e규칙 구하기\u003c/h3\u003e\n\u003cp\u003e주어진 예시 (\u003ccode\u003ew = 8, h = 12\u003c/code\u003e)에 대해 규칙을 확인해보겠습니다.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"예시\" loading=\"lazy\" src=\"/cote/programmers_62048/example.png\"\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e문제의 예시를 자세히 보면 작은 사각형이 반복되는 것을 알 수 있습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg alt=\"예시2\" loading=\"lazy\" src=\"/cote/programmers_62048/example2.png\"\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003ew = 8\u003c/code\u003e과 \u003ccode\u003eh = 12\u003c/code\u003e의 최대공약수인 \u003ccode\u003e4\u003c/code\u003e개의 사각형이 생성됩니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e즉, 최대공약수로 해당 길이를 나누었을 때 생성된 작은 사각형의 잘린 개수를 구하면 됩니다.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e최대공약수로 나눠진 사각형(\u003ccode\u003enw x nh = 2 x 3\u003c/code\u003e)의 잘린 개수는 다음과 같이 구할 수 있습니다.\u003c/p\u003e","title":"[Java]Programmers 멀쩡한 사각형"},{"content":"출처 메두사와 전사들 접근 복잡한 문제인만큼 구현하면서 순차적으로 처리해야 할 과정이 많은데, 이에 필요한 알고리즘을 정리해보면 다음과 같습니다.\n메두사의 이동경로 구현 BFS + 경로 역추적 시선 생성 배열 탐색 구현 병사 이동 및 공격 배열 탐색 구현 알고리즘 자체는 어렵지 않으나, 배열 탐색을 구현하는 과정이 복잡합니다.\n메두사 이동경로 구현(BFS) 메두사가 이동하는 과정에 장애물이 있기 때문에, BFS를 통해 미로를 탐색하는 최단경로를 구해야 합니다.\n또한, 이동경로를 다시 돌면서 병사들과의 상호작용을 확인하기 위해 이러한 경로를 별도 배열에 저장해야 합니다.\n이는 다음과 같이 BFS시에 이전 노드 정보를 함께 저장하여 LinkedList 형태로 구현할 수 있습니다.\n이를 코드로 다음과 같이 구현할 수 있습니다.\n/* 생략 */ // BFS + 이전노드 탐색용 static class Node { int r; int c; Node before; public Node(int r, int c, Node node) { this.r = r; this.c = c; this.before = node; } } // BFS로 메두사 이동 최단경로 구하기 + 리스트로 경로 반환 static List\u0026lt;int[]\u0026gt; bfs() { Queue\u0026lt;Node\u0026gt; q = new ArrayDeque\u0026lt;\u0026gt;(); boolean[][] visited = new boolean[N][N]; q.offer(new Node(s[0], s[1], null)); visited[s[0]][s[1]] = true; while(!q.isEmpty()) { Node now = q.poll(); // 도착하면 배열 생성한 뒤, 역순으로 배열에 추가 후 반환 if (now.r == e[0] \u0026amp;\u0026amp; now.c == e[1]) { List\u0026lt;int[]\u0026gt; ret = new ArrayList\u0026lt;\u0026gt;(); while (now != null) { ret.add(new int[]{now.r, now.c}); now = now.before; } return ret; } // 상하좌우로 이동 for (int i = 0; i \u0026lt; 4; i++) { int nr = now.r + dr[i]; int nc = now.c + dc[i]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N || visited[nr][nc] || town[nr][nc] == 1) continue; visited[nr][nc] = true; q.offer(new Node(nr, nc, now)); } } // 도착하지 못하면 null 반환 return null; } /* 생략 */ 시선 생성 구현(배열 탐색) 기본 시선 생성하기\n메두사의 시선은 상하좌우로 움직일 수 있습니다.\n해당 시선에서 대각선 방향으로 모든 시선이 채워지게 됩니다.\n다음과 같이 각 시선 방향을 따라가면서 양쪽 대각선으로 뻗어나가는 식으로 구현합니다.\n시선을 처리하는 로직만 코드로 확인해보면 다음과 같습니다.\n// 시선 생성 static void see(int[] now) { /* 생략 */ // 4방향 시선 이동 for (int d = 0; d \u0026lt; 4; d++) { int[][] temp = new int[N][N]; for (int i = 0; i \u0026lt; N; i++) Arrays.fill(temp[i], 0); // 4방향 시선 확인 int cur = getSight(now, temp, d); /* 생략 */ } } // 상하좌우 -\u0026gt; 대각선 확인을 위한 배열 static int[][] dir = {{7, 1}, {3, 5}, {5, 7}, {1, 3}}; // 8방향 미리 지정 static int[] dr3 = {-1, -1, 0, 1, 1, 1, 0, -1}; static int[] dc3 = {0, 1, 1, 1, 0, -1, -1, -1}; // 시선 배열 채우기 static int getSight(int[] now, int[][] sight, int d) { int r = now[0], c = now[1], cnt = 0; boolean hide = false; // 최초 위치에서 대각선 확인(대각선 확인할때는 위에서 정의한 dir 배열 활용) cnt += seeDiagonal(now, sight, dir[d], d); while (true) { int nr = r + dr[d], nc = c + dc[d]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N) break; // 1칸씩 시선 방향으로 이동하면서 대각선 확인 cnt += seeDiagonal(new int[] {nr, nc}, sight, dir[d], d); // 처음으로 병사 등장하기 이전에는 1로 채우기(시선 표시) if (!hide) sight[nr][nc] = 1; /* 생략 */ r = nr; c = nc; } return cnt; } // 대각선 확인하기 static int seeDiagonal(int[] now, int[][] sight, int[] ds, int d) { int cnt = 0; // 주어진 방향으로 뻗어나가기 for (int dd : ds) { int r = now[0], c = now[1]; while (true) { int nr = r + dr3[dd], nc = c + dc3[dd]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N) break; /* 생략 */ // 해당 위치 1로 채우기(시선 표시) sight[nr][nc] = 1; r = nr; c = nc; } } return cnt; } 병사 돌로 만들기\n이렇게 시선을 채우는 중간에 병사가 있다면 해당 위치를 돌로 만듭니다.\n또한, 메두사의 시선과 같은 방향으로는 이후 메두사의 시선이 들어오지 못하도록 처리합니다.\n코드 구현 과정에서는 메두사의 시선이 어떤 방향에서 들어왔는지 별도 파라미터로 전달이 필요합니다.\n마지막으로 각 방향에서 돌로 만든 병사의 최댓값을 확인해서 최대인 방향으로 시선을 처리합니다.\n그림으로 표현하면 다음과 같습니다.\n전체 과정을 코드로 구현해보면 다음과 같습니다. // 시선 생성 static void see(int[] now) { // 좌표별 병사 숫자를 빠르게 세기 위해 좌표별 병사 숫자 미리 표기 for (int i = 0; i \u0026lt; N; i++) Arrays.fill(town[i], 0); for (int[] warrior : warriors) town[warrior[0]][warrior[1]]++; int total = 0; // 시선에서 보이는 병사 최댓값 저장 for (int d = 0; d \u0026lt; 4; d++) { int[][] temp = new int[N][N]; for (int i = 0; i \u0026lt; N; i++) Arrays.fill(temp[i], 0); // 현재 방향에서 보이는 총 병사 숫자 확인 int cur = getSight(now, temp, d); // 현재 방향이 최대일때만 갱신 if (total \u0026lt; cur) { sight = temp; total = cur; } } } static int[][] dir = {{7, 1}, {3, 5}, {5, 7}, {1, 3}}; static int[] dr3 = {-1, -1, 0, 1, 1, 1, 0, -1}; static int[] dc3 = {0, 1, 1, 1, 0, -1, -1, -1}; // 상하좌우 배열과 대각선 배열의 방향을 맞추기 위함 static int[] changeDir = {0, 4, 6, 2}; // 시선 배열 채우기 + 각 방향에서 볼 수 있는 병사 반환 static int getSight(int[] now, int[][] sight, int d) { int r = now[0], c = now[1], cnt = 0; boolean hide = false; // 최초 위치에서 대각선 확인 cnt += seeDiagonal(now, sight, dir[d], d); // 시선 방향으로 뻗어나가기 while (true) { int nr = r + dr[d], nc = c + dc[d]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N) break; cnt += seeDiagonal(new int[] {nr, nc}, sight, dir[d], d); // 처음으로 병사 등장하기 이전에만 1로 채우기(이후에는 돌이 가리므로 채우지 않음) if (!hide) sight[nr][nc] = 1; // 처음으로 병사 등장하는 시점 확인 if (!hide \u0026amp;\u0026amp; town[nr][nc] != 0) { // 해당 위치에서 돌로 만들기 // 원래 상하좌우는 0-\u0026gt;1-\u0026gt;2-\u0026gt;3 이고, 8방향 탐색에서는 0-\u0026gt;4-\u0026gt;6-\u0026gt;2 방향전환 필요 makeStone(new int[] {nr, nc}, sight, new int[] {changeDir[d]}); // 이후는 가려져서 1을 채우지 않음 hide = true; // 병사 숫자 세기 cnt += town[nr][nc]; } r = nr; c = nc; } return cnt; } static int seeDiagonal(int[] now, int[][] sight, int[] ds, int d) { int cnt = 0; for (int dd : ds) { int r = now[0], c = now[1]; while (true) { int nr = r + dr3[dd], nc = c + dc3[dd]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N) break; // 이미 가려진 위치가 있으면(2이면) 탐색 종료 if (sight[nr][nc] == 2) break; // 병사 있으면 해당위치에서 돌로 만들고 탐색 종료 if (town[nr][nc] != 0) { cnt += town[nr][nc]; // 현재 시선방향(상하좌우) 변환 + 현재 대각선 이동방향 파라미터 전달 makeStone(new int[]{nr, nc}, sight, new int[] {changeDir[d], dd}); r = nr; c = nc; break; } sight[nr][nc] = 1; r = nr; c = nc; } } return cnt; } // 돌로 만들기 static void makeStone(int[] now, int[][] sight, int[] ds) { // 해당 위치 -2로 변경 sight[now[0]][now[1]] = -2; // 주어진 방향으로 뻗어나가면서 가리기 for (int d : ds) { int r = now[0], c = now[1]; while (true) { int nr = r + dr3[d], nc = c + dc3[d]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N) break; // 해당 위치 2로 변경(가림 표시) sight[nr][nc] = 2; r = nr; c = nc; } } } 메두사의 시선을 처리하는게 개인적으로 가장 복잡했던 것 같습니다.\n병사 이동 및 공격 구현(배열 탐색) 병사들은 총 2번 이동하며, 2회의 움직임이 다르기 때문에 별도로 구현해야 합니다.\n매 회마다 병사가 이동한 횟수, 돌이 된 횟수, 메두사를 공격한 횟수를 세어 출력합니다.\n메두사를 공격했을 때에는 병사를 삭제하는 것에 주의합니다.\n비교적 단순한 구현이어서 코드가 필요하신 분들은 아래 풀이에서 확인하시면 됩니다.\n풀이 import java.util.*; import java.io.*; public class Main { static int N, M; static int[] s, e, answer; static int[][] town, sight; static List\u0026lt;int[]\u0026gt; warriors; static int[] dr = {-1, 1, 0, 0}, dr2 = {0, 0, -1, 1}; static int[] dc = {0, 0, -1, 1}, dc2 = {-1, 1, 0, 0}; public static void main(String[] args) throws IOException { // 입력 받기 init(); // 메두사 이동경로 생성 List\u0026lt;int[]\u0026gt; route = bfs(); // 도달 불가능 예외처리 if (route == null) { System.out.println(-1); System.exit(0); } StringBuilder sb = new StringBuilder(); // 메두사 이동경로대로 진행 for (int i = route.size() - 2; i \u0026gt;= 1; i--) { int[] r = route.get(i); answer = new int[3]; // 해당 위치 병사 삭제 killWarriors(r); // 시선 생성 see(r); // 병사 이동 moveWarriors(r); sb.append(answer[0] + \u0026#34; \u0026#34; + answer[1] + \u0026#34; \u0026#34; + answer[2] + \u0026#34;\\n\u0026#34;); } System.out.print(sb); System.out.println(0); } // 시선 생성 static void see(int[] now) { // town에 병사 위치 찍어놓기 for (int i = 0; i \u0026lt; N; i++) Arrays.fill(town[i], 0); for (int[] warrior : warriors) town[warrior[0]][warrior[1]]++; // 4방향 시선 이동 int total = 0; // 시선에서 보이는 병사 최댓값 저장 for (int d = 0; d \u0026lt; 4; d++) { int[][] temp = new int[N][N]; for (int i = 0; i \u0026lt; N; i++) Arrays.fill(temp[i], 0); // 현재 방향에서 보이는 총 병사 숫자 확인 int cur = getSight(now, temp, d); // 현재 방향이 최대일때만 갱신 if (total \u0026lt; cur) { sight = temp; total = cur; } } } // 상하좌우 -\u0026gt; 대각선 확인 및 변환을 위한 배열 static int[][] dir = {{7, 1}, {3, 5}, {5, 7}, {1, 3}}; static int[] changeDir = {0, 4, 6, 2}; // 8방향 미리 지정 static int[] dr3 = {-1, -1, 0, 1, 1, 1, 0, -1}; static int[] dc3 = {0, 1, 1, 1, 0, -1, -1, -1}; // 시선 배열 채우기 + 각 방향에서 볼 수 있는 병사 반환 static int getSight(int[] now, int[][] sight, int d) { int r = now[0], c = now[1], cnt = 0; boolean hide = false; // 최초 위치에서 대각선 확인 cnt += seeDiagonal(now, sight, dir[d], d); while (true) { int nr = r + dr[d], nc = c + dc[d]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N) break; // 1칸씩 시선 방향으로 이동하면서 대각선 확인 cnt += seeDiagonal(new int[] {nr, nc}, sight, dir[d], d); // 처음으로 병사 등장하기 이전에는 1로 채우기(시선 표시) if (!hide) sight[nr][nc] = 1; // 처음으로 병사 등장하는 시점 확인 if (!hide \u0026amp;\u0026amp; town[nr][nc] != 0) { // 해당 위치에서 돌로 만들기 makeStone(new int[] {nr, nc}, sight, new int[] {changeDir[d]}); hide = true; // 병사 숫자 세기 cnt += town[nr][nc]; } r = nr; c = nc; } return cnt; } // 대각선 확인하기 static int seeDiagonal(int[] now, int[][] sight, int[] ds, int d) { int cnt = 0; // 주어진 방향으로 뻗어나가면서 병사 있는지 확인 for (int dd : ds) { int r = now[0], c = now[1]; while (true) { int nr = r + dr3[dd], nc = c + dc3[dd]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N) break; // 이미 가려진 위치가 있으면(2이면) 탐색 종료 if (sight[nr][nc] == 2) break; // 병사 있으면 해당위치에서 돌로 만들고 탐색 종료 if (town[nr][nc] != 0) { cnt += town[nr][nc]; makeStone(new int[]{nr, nc}, sight, new int[] {changeDir[d], dd}); r = nr; c = nc; break; } // 해당 위치 1로 채우기(시선 표시) sight[nr][nc] = 1; r = nr; c = nc; } } return cnt; } // 돌로 만들기 static void makeStone(int[] now, int[][] sight, int[] ds) { // 해당 위치 -2로 변경 sight[now[0]][now[1]] = -2; // 주어진 방향으로 뻗어나가면서 가리기 for (int d : ds) { int r = now[0], c = now[1]; while (true) { int nr = r + dr3[d], nc = c + dc3[d]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N) break; // 해당 위치 2로 변경(가림 표시) sight[nr][nc] = 2; r = nr; c = nc; } } } // 병사 이동하기 static void moveWarriors(int[] now) { loop: // 중간에 병사를 제거하므로 뒤에서부터 확인 for (int i = warriors.size() - 1; i \u0026gt;= 0; i--) { int[] warrior = warriors.get(i); // 해당 위치가 이미 돌이면 돌이 된 병사 숫자에 추가 if (sight[warrior[0]][warrior[1]] == -2) { answer[1]++; continue; } // 상하좌우로 메두사 방향으로 이동 for (int j = 0; j \u0026lt; 4; j++) { int nr = warrior[0] + dr[j]; int nc = warrior[1] + dc[j]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N || sight[nr][nc] == 1 || sight[nr][nc] == -2) continue; // 메두사와 가까워지는 방향으로만 이동 if (Math.abs(now[0] - warrior[0]) + Math.abs(now[1] - warrior[1]) \u0026lt;= Math.abs(now[0] - nr) + Math.abs(now[1] - nc)) continue; warrior[0] = nr; warrior[1] = nc; // 이동거리에 추가 answer[0]++; break; } // 해당위치에 메두사 있으면 병사 제거 후 공격 횟수 추가 if (warrior[0] == now[0] \u0026amp;\u0026amp; warrior[1] == now[1]) { warriors.remove(i); answer[2]++; continue loop; } // 좌우상하로 메두사 방향으로 이동(방향만 다르고 로직 동일) for (int j = 0; j \u0026lt; 4; j++) { int nr = warrior[0] + dr2[j]; int nc = warrior[1] + dc2[j]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N || sight[nr][nc] == 1 || sight[nr][nc] == -2) continue; if (Math.abs(now[0] - warrior[0]) + Math.abs(now[1] - warrior[1]) \u0026lt;= Math.abs(now[0] - nr) + Math.abs(now[1] - nc)) continue; warrior[0] = nr; warrior[1] = nc; answer[0]++; break; } // 해당위치에 메두사 있으면 병사 제거 후 공격 횟수 추가 if (warrior[0] == now[0] \u0026amp;\u0026amp; warrior[1] == now[1]) { warriors.remove(i); answer[2]++; } } } // 메두사 이동 후 해당위치 병사 삭제 static void killWarriors(int[] now) { for (int i = warriors.size() - 1; i \u0026gt;= 0; i--) { if (warriors.get(i)[0] == now[0] \u0026amp;\u0026amp; warriors.get(i)[1] == now[1]) { warriors.remove(i); } } } // BFS + 이전노드 탐색용 static class Node { int r; int c; Node before; public Node(int r, int c, Node node) { this.r = r; this.c = c; this.before = node; } } // BFS로 메두사 이동 최단경로 구하기 static List\u0026lt;int[]\u0026gt; bfs() { Queue\u0026lt;Node\u0026gt; q = new ArrayDeque\u0026lt;\u0026gt;(); boolean[][] visited = new boolean[N][N]; q.offer(new Node(s[0], s[1], null)); visited[s[0]][s[1]] = true; while(!q.isEmpty()) { Node now = q.poll(); // 도착하면 역순으로 이동 배열에 추가 후 반환 if (now.r == e[0] \u0026amp;\u0026amp; now.c == e[1]) { List\u0026lt;int[]\u0026gt; ret = new ArrayList\u0026lt;\u0026gt;(); while (now != null) { ret.add(new int[]{now.r, now.c}); now = now.before; } return ret; } // 상하좌우로 이동 for (int i = 0; i \u0026lt; 4; i++) { int nr = now.r + dr[i]; int nc = now.c + dc[i]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N || visited[nr][nc] || town[nr][nc] == 1) continue; visited[nr][nc] = true; q.offer(new Node(nr, nc, now)); } } // 도착하지 못하면 null 반환 return null; } // 입력 받기 static void init() throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); N = Integer.parseInt(st.nextToken()); M = Integer.parseInt(st.nextToken()); st = new StringTokenizer(br.readLine()); s = new int[2]; e = new int[2]; s[0] = Integer.parseInt(st.nextToken()); s[1] = Integer.parseInt(st.nextToken()); e[0] = Integer.parseInt(st.nextToken()); e[1] = Integer.parseInt(st.nextToken()); st = new StringTokenizer(br.readLine()); warriors = new ArrayList\u0026lt;\u0026gt;(); for (int i = 0; i \u0026lt; M; i++) { warriors.add(new int[] {Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken())}); } town = new int[N][N]; for (int i = 0; i \u0026lt; N; i++) { st = new StringTokenizer(br.readLine()); for (int j = 0; j \u0026lt; N; j++) town[i][j] = Integer.parseInt(st.nextToken()); } } } 결과 소요시간 : 반나절 + a 리뷰 오랜만에 벽을 느껴본 구현문제였습니다.\n그래도 포기하지 않고 풀어내서 최소한의 자존심은 지킨 것 같아 다행입니다..\n처음에는 시선 처리를 BFS로 구현했는데, 51%에서 시간초과가 발생했습니다.1\n매 시선처리마다 전체 지도를 모두 돌아보기 때문에 시간초과가 발생한 것 같습니다. 메두사가 이동할 수 있는 전체 경로의 길이는 최대 N^2입니다. 4방향 시선을 저장하기 위해 각 칸을 초기화하면 4 X N^2 = N^2입니다. 4방향을 전체 탐색하면 결국 모든 칸을 탐색하므로 N^2입니다. 따라서, 전체 시간복잡도는 O(N^6) = 15,625,000,000입니다. 정답 풀이에서는 돌이 된 병사가 시선이 들어오지 못하게 직선으로 가려주어 전체 지도를 탐색하지 않도록 최적화했습니다. References URL 게시일자 방문일자 작성자 실패한 시선처리 코드는 다음과 같습니다.\n메두사 위치부터 BFS로 시선들을 채우다가, 병사를 만나면 메두사와 멀어지는 방향으로 가려진 시선을 채우도록 구현했습니다.\n// 시선 처리 static void see(int[] now) { int total = 0; for (int d = 0; d \u0026lt; 4; d++) { int[][] temp = new int[N][N]; int cur = getSight(now, temp, false, getDir(d)); if (total \u0026lt; cur) { sight = temp; total = cur; } } } // 방향 + 대각선 방향으로 BFS static int[] getDir(int d) { switch (d) { case 0 : return new int[]{7, 0, 1}; case 1 : return new int[]{3, 4, 5}; case 2 : return new int[]{5, 6, 7}; case 3 : return new int[]{1, 2, 3}; } throw new RuntimeException(); } // 메두사와 병사가 멀어지는 방향으로만 BFS static int[] changeDir(int[] medusa, int[] warrior, int[] ds) { if (medusa[0] == warrior[0]) { if (medusa[1] \u0026gt; warrior[1]) return new int[] {6}; else return new int[] {2}; } if (medusa[1] == warrior[1]) { if (medusa[0] \u0026gt; warrior[0]) return new int[] {0}; else return new int[] {4}; } int[] ret = {-1, -1, -1}; int idx = 0; for (int d : ds) { int nr = warrior[0] + dr3[d]; int nc = warrior[1] + dc3[d]; if (Math.abs(nr - medusa[0]) \u0026lt; Math.abs(warrior[0] - medusa[0])) continue; if (Math.abs(nc - medusa[1]) \u0026lt; Math.abs(warrior[1] - medusa[1])) continue; ret[idx++] = d; } return ret; } // 병사일때는 2로 채우고, 메두사일때는 1로 채움 static int getSight(int[] now, int[][] sight, boolean hide, int[] ds) { Queue\u0026lt;int[]\u0026gt; q = new ArrayDeque(); q.offer(now); int cnt = 0; if (hide) sight[now[0]][now[1]] = 0; loop2: while (!q.isEmpty()) { int[] nn = q.poll(); if (sight[nn[0]][nn[1]] == -1 || sight[nn[0]][nn[1]] == -2) continue; sight[nn[0]][nn[1]] = hide? -1 : 1; // 메두사일 때 병사를 만나면 해당위치에서 병사로 BFS 시작 if (!hide) { for (int i = 0; i \u0026lt; warriors.size(); i++) { if (warriors.get(i)[0] == nn[0] \u0026amp;\u0026amp; warriors.get(i)[1] == nn[1]) { sight[nn[0]][nn[1]] = -2; cnt++; } } if (sight[nn[0]][nn[1]] == -2) { getSight(nn, sight, true, changeDir(now, nn, ds)); continue; } } for (int d : ds) { if (d == -1) continue; int nr = nn[0] + dr3[d]; int nc = nn[1] + dc3[d]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N || sight[nr][nc] != 0) continue; q.offer(new int[] {nr, nc}); } } sight[now[0]][now[1]] = hide? -2 : 2; return cnt; } \u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/cote/codetree_medusa_and_warriors/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.codetree.ai/training-field/frequent-problems/problems/medusa-and-warriors/description?page=1\u0026amp;pageSize=10\"\u003e메두사와 전사들\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cp\u003e복잡한 문제인만큼 구현하면서 순차적으로 처리해야 할 과정이 많은데, 이에 필요한 알고리즘을 정리해보면 다음과 같습니다.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e메두사의 이동경로 구현\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eBFS + 경로 역추적\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e시선 생성\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e배열 탐색 구현\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e병사 이동 및 공격\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e배열 탐색 구현\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cblockquote\u003e\n\u003cp\u003e알고리즘 자체는 어렵지 않으나, 배열 탐색을 구현하는 과정이 복잡합니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"메두사-이동경로-구현bfs\"\u003e메두사 이동경로 구현(BFS)\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e메두사가 이동하는 과정에 장애물이 있기 때문에, \u003ccode\u003eBFS\u003c/code\u003e를 통해 \u003cstrong\u003e미로를 탐색하는 최단경로\u003c/strong\u003e를 구해야 합니다.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e또한, 이동경로를 다시 돌면서 병사들과의 상호작용을 확인하기 위해 이러한 경로를 별도 배열에 저장해야 합니다.\u003c/p\u003e","title":"[Java]코드트리 메두사와 전사들 문제 해설"},{"content":"출처 수식 최대화(2020 KAKAO Internship) 접근 완전탐색(DFS) 각 연산자의 우선순위를 변경하면서 전체 탐색을 하고, 변경된 우선순위마다 수식을 계산해서 최댓값을 갱신합니다.\n연산자 순서에 따라 결과가 달라지므로, 순열(Permutation)에 해당합니다. 다양한 구현방법이 있지만, 개인적으로 다양한 조건을 만족하는 순열(조합)을 빠르게 구현할 수 있는 DFS를 선호합니다.\n3가지 연산자만 있으므로, 모든 경우의 수를 구해도 6가지이기 때문에 시간복잡도는 충분합니다. 후위 표기 변환(InFix to PostFix) 보통 사람이 계산을 할 때는 A + B * C의 형태로 수식을 써서 계산을 합니다. 이 때, A와 B 사이에 연산자가 있는 형태를 중위 표기(InFix)라고 합니다.\n그러나, 컴퓨터는 이러한 계산을 A B C * +의 형태로 변환해서 숫자 이후에 연산자가 나오는 형태로 변경한 뒤, 스택에 옮겨 계산하게 됩니다. 이러한 표기법을 후위 표기(PostFix)라고 합니다.\n후위 표기법은 연산의 우선순위에 따라 쉽게 수식을 표현하고, 계산할 수 있다는 장점이 있습니다.\n이번 풀이에서도 이러한 후위 표기법을 구현하여 문제를 해결하겠습니다.\n중위 표기 -\u0026gt; 후위 표기의 변환과정 (Infix -\u0026gt; Postfix)을 자세히 살펴보면 다음과 같습니다.\n결과를 담을 큐와 연산자를 임시 저장할 스택을 정의합니다. 중위 표기에서 숫자가 나오면 바로 큐에 삽입합니다. 중위 표기에서 연산자가 나오면 다음 절차를 수행합니다. 현재 저장된 스택에 현재 연산자보다 우선순위가 큰 연산자가 나올때까지 스택을 비웁니다. 현재 연산자를 스택에 저장합니다. 모든 중위 표기 원소를 확인한 후, 스택이 비어있지 않다면 해당 연산자들을 순차적으로 큐에 삽입합니다. 위 과정을 그림과 함께 순서대로 보겠습니다.\n아래는 문제에서 주어진 1번 예제에서 정답인 (우선순위 : X -\u0026gt; + -\u0026gt; -)의 답을 구하는 과정입니다.\n숫자(100, 200)들은 큐에 바로 담고, 첫번째 연산자인 -는 스택이 비었으므로 바로 담습니다. 두번째 연산자인 X는 스택에 담고, 해당 연산자보다 우선순위가 큰 연산자는 없으므로, 스택에서 연산자를 제거하지 않습니다. 숫자 300은 바로 큐에 담고, 세번째 연산자인 -를 스택에 담는 과정에서 해당 연산자보다 우선순위가 큰 연산자인 X를 큐에 담습니다. 나머지 숫자 (500, 20)를 큐에 담고, 네번째 연산자인 +를 담는 과정에서 스택의 첫번째 연산자(-)가 해당 연산자보다 우선운위가 작으므로 스텍에서 연산자를 제거하지 않습니다. 중위 표기의 모든 원소를 확인했는데, 스택이 아직 비어있지 않으므로 스택의 모든 연산자를 큐에 추가합니다. 후위 표기 계산 후위 표기를 계산할 때는 숫자들을 임시 저장할 스택을 생성하고, 완료된 숫자들을 저장하면서 계산합니다.\n후위 표기법의 계산 과정을 자세히 살펴보면 다음과 같습니다.\n숫자를 임시 저장할 스택을 정의합니다. 숫자가 나오면 스택에 담습니다. 연산자가 나오면 스택에서 숫자를 꺼내 계산한 뒤, 다시 스택에 넣습니다. 위 과정을 그림과 함께 순서대로 보겠습니다.\n위에서 변환한 큐를 계산하는 과정입니다.\n연산자가 나올때까지 모든 숫자를 스택에 담습니다. 연산자가 나오면 스택에서 2개를 꺼내 계산합니다. 다시 연산자가 나올때까지 모든 숫자를 스택에 담습니다. 연산자마다 스택에서 2개씩 숫자를 꺼내 계산합니다. 문제에서는 계산 결과의 절댓값을 구해야 하므로, 정답인 60420을 얻을 수 있습니다.\n풀이 import java.util.*; class Solution { long answer = 0; List\u0026lt;String\u0026gt; exps = new ArrayList\u0026lt;\u0026gt;(); public long solution(String expression) { parse(expression); dfs(new char[3], 0, new boolean[4]); return answer; } // 주어진 문자열에서 숫자와 연산자 분리 void parse(String e) { StringBuilder sb = new StringBuilder(); for (char c : e.toCharArray()) { if (c - \u0026#39;0\u0026#39; \u0026lt; 0) { exps.add(sb.toString()); exps.add(new String(new char[]{c})); sb = new StringBuilder(); } else { sb.append(c); } } exps.add(sb.toString()); } // 연산자 순열 구하기(3! = 6) char[] exp = {\u0026#39;+\u0026#39;, \u0026#39;-\u0026#39;, \u0026#39;*\u0026#39;}; void dfs(char[] priority, int now, boolean[] isAdded) { if (now == 3) { answer = Math.max(answer, calculate(priority)); return; } for (char c : exp) { if (isAdded[c - \u0026#39;*\u0026#39;]) continue; isAdded[c - \u0026#39;*\u0026#39;] = true; priority[now] = c; dfs(priority, now + 1, isAdded); isAdded[c - \u0026#39;*\u0026#39;] = false; } } // 숫자 계산 long calculate(char[] priority) { Deque\u0026lt;String\u0026gt; q = infixToPostfix(priority); // 숫자들을 임시 저장할 스택 Stack\u0026lt;Long\u0026gt; temp2 = new Stack\u0026lt;\u0026gt;(); while (!q.isEmpty()) { String e = q.poll(); // 연산자일 경우 스택에서 2개를 꺼내서 계산 후 결과값 스택으로 이동 if (e.equals(\u0026#34;*\u0026#34;) || e.equals(\u0026#34;+\u0026#34;) || e.equals(\u0026#34;-\u0026#34;)) { long i2 = temp2.pop(); long i1 = temp2.pop(); temp2.push(calc(e, i1, i2)); } else { // 숫자는 바로 스택으로 이동 temp2.push(Long.parseLong(e)); } } return Math.abs(temp2.pop()); } // 중위 표기 -\u0026gt; 후위표기 변환 Deque\u0026lt;String\u0026gt; infixToPostfix(char[] priority) { Deque\u0026lt;String\u0026gt; q = new ArrayDeque\u0026lt;\u0026gt;(); Stack\u0026lt;String\u0026gt; temp = new Stack\u0026lt;\u0026gt;(); for (String e : exps) { if (e.equals(\u0026#34;*\u0026#34;) || e.equals(\u0026#34;+\u0026#34;) || e.equals(\u0026#34;-\u0026#34;)) { // 현재 숫자보다 우선순위가 크거나 같은 연산자들을 모두 스택에서 큐로 이동 while (!temp.isEmpty() \u0026amp;\u0026amp; getPriority(priority, temp.peek()) \u0026gt;= getPriority(priority, e)) { q.offer(temp.pop()); } // 현재 연산자 스택에 보관 temp.push(e); } else { // 숫자는 바로 큐에 넣기 q.offer(e); } } // 연산자가 스택에 남아있다면 큐로 이동 while (!temp.isEmpty()) q.offer(temp.pop()); return q; } // 문자열인 계산식과 숫자들을 받아서 계산 long calc(String c, long i1, long i2) { switch (c.charAt(0)) { case \u0026#39;+\u0026#39;: { return i1 + i2; } case \u0026#39;-\u0026#39;: { return i1 - i2; } case \u0026#39;*\u0026#39;: { return i1 * i2; } } throw new RuntimeException(); } // 우선순위 배열로부터 우선순위 가져옴 int getPriority(char[] priority, String now) { for (int i = 0; i \u0026lt; 3; i++) { if (priority[i] == now.charAt(0)) return i; } return -1; } } 결과 풀이시간 : 2시간 초과 리뷰 연산 순서를 정렬하는 로직을 구현하면서 컴퓨터의 연산 과정을 이해하는 데에도 도움이 되는 좋은 문제인 것 같습니다.\n후위 표기로 변환 후 계산하는 방법을 전에도 배웠는데, 오랜만에 접하니 생각보다 구현이 오래 걸렸습니다. 머릿속 로직을 구현하는 연습을 좀 더 해야할 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 중위 표기(InFix) 2024.10.01. 2024.12.18. Wikipedia 후위 표기(PostFix) 2024.06.04. 2024.12.18. Wikipedia ","permalink":"https://leaf-nam.github.io/cote/programmers_67257/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/67257\"\u003e수식 최대화(2020 KAKAO Internship)\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"완전탐색dfs\"\u003e완전탐색(DFS)\u003c/h3\u003e\n\u003cp\u003e각 연산자의 우선순위를 변경하면서 전체 탐색을 하고, 변경된 우선순위마다 수식을 계산해서 최댓값을 갱신합니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e연산자 순서에 따라 결과가 달라지므로, 순열(Permutation)에 해당합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e다양한 구현방법이 있지만, 개인적으로 다양한 조건을 만족하는 순열(조합)을 빠르게 구현할 수 있는 \u003ccode\u003eDFS\u003c/code\u003e를 선호합니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cul\u003e\n\u003cli\u003e3가지 연산자만 있으므로, 모든 경우의 수를 구해도 \u003ccode\u003e6가지\u003c/code\u003e이기 때문에 시간복잡도는 충분합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"후위-표기-변환infix-to-postfix\"\u003e후위 표기 변환(InFix to PostFix)\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e보통 사람이 계산을 할 때는 \u003ccode\u003eA + B * C\u003c/code\u003e의 형태로 수식을 써서 계산을 합니다. 이 때, A와 B 사이에 연산자가 있는 형태를 \u003ca href=\"https://ko.wikipedia.org/wiki/%EC%A4%91%EC%9C%84_%ED%91%9C%EA%B8%B0%EB%B2%95\"\u003e\u003cstrong\u003e중위 표기(InFix)\u003c/strong\u003e\u003c/a\u003e라고 합니다.\u003c/p\u003e","title":"[Java]Programmers 수식 최대화(2020 KAKAO Internship)"},{"content":"출처 백준 1005 ACM Craft 접근 완전탐색(BFS) 특정 건물이 나올때까지 각 건물들을 탐색하는 과정을 BFS로 구현할 수 있습니다.\n건물 사이의 관계(간선)의 개수가 100,000이므로, 각 간선을 한번씩만 방문한다면 시간복잡도 내 풀이가 가능합니다.\n위상 정렬 각 건물은 해당 건물이 지어지기 전에 특정 건물을 지어야 하는 선행조건이 있습니다.\n이를 구현하기 위해 위상정렬을 활용할 수 있습니다.\n위상정렬은 다음과 같이 해당 건물을 짓기 전에 선행되어야 하는 건물이 모두 지어졌는지(선행 건물의 개수가 0인지) 확인하는 과정을 통해 구현할 수 있습니다.\n각 노드를 초기화하면서 선행차수를 확인합니다. BFS 과정해서 해당 노드를 탐색 완료한 경우, 다음 노드의 선행차수를 1씩 줄입니다. 선행차수가 0인 노드만 탐색합니다. 누적합 건물은 시간 순으로 지어져야 하므로, 해당 건물이 지어지는 순서대로 탐색해야 합니다.\n이는 해당 건물이 지어지는데 걸리는 시간으로 정렬되는 우선순위 큐에 해당 건물을 짓는데까지 걸린 시간(누적합)을 넣음으로써 구현할 수 있습니다.\n풀이 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Comparator; import java.util.PriorityQueue; import java.util.StringTokenizer; public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int T = Integer.parseInt(br.readLine()); for (int i = 0; i \u0026lt; T; i++) { StringTokenizer st = new StringTokenizer(br.readLine()); int N = Integer.parseInt(st.nextToken()); int K = Integer.parseInt(st.nextToken()); int[] D = new int[N + 1]; st = new StringTokenizer(br.readLine()); for (int j = 1; j \u0026lt;= N; j++) { D[j] = Integer.parseInt(st.nextToken()); } // 간선의 수가 많으므로 인접행렬로 구현 int[][] adjm = new int[N + 1][N + 1]; int[] indegree = new int[N + 1]; for (int j = 0; j \u0026lt; K; j++) { st = new StringTokenizer(br.readLine()); int from = Integer.parseInt(st.nextToken()); int to = Integer.parseInt(st.nextToken()); // 해당 인접행렬 1로 초기화 adjm[from][to] = 1; // 선행차수 저장 indegree[to]++; } int target = Integer.parseInt(br.readLine()); solve(N, D, adjm, target, indegree); } } private static void solve(int n, int[] d, int[][] adjm, int target, int[] indegree) { // 누적합 작은순 정렬 PriorityQueue\u0026lt;int[]\u0026gt; q = new PriorityQueue\u0026lt;\u0026gt;(Comparator.comparingInt(o -\u0026gt; o[1])); boolean[] visited = new boolean[n + 1]; // 선행차수가 0인 건물부터 BFS for (int i = 1; i \u0026lt;= n; i++) { if (indegree[i] == 0) q.offer(new int[]{i, d[i]}); } while (!q.isEmpty()) { // 누적합 가장 작은 건물 순으로 확인 int[] now = q.poll(); // Target 찾으면 출력 후 종료 if (now[0] == target) { System.out.println(now[1]); return; } visited[now[0]] = true; for (int i = 1; i \u0026lt;= n; i++) { // 나머지 선행차수 1씩 감소 if (visited[i] || adjm[now[0]][i] == 0 || --indegree[i] != 0) continue; // 누적합 저장 q.offer(new int[]{i, now[1] + d[i]}); } } } } 결과 걸린 시간 : 1:15:00 리뷰 처음에는 지어야 하는 건물을 역순으로 BFS 탐색을 통해 구현할 수 있다고 생각해서 1시간 정도 고민했는데, 이러한 방식으로는 동시에 생산을 시작할 수 있는 경우의 수를 계산할 수 없음을 해당 글을 통해 알게 되었습니다.\n문제를 구현하기 전에 해당 방식으로 완전탐색이 가능한지 제대로 따져보고 구현을 시작하는 습관을 들여야겠습니다.\nReferences URL 게시일자 방문일자 작성자 백준 게시판 글 2024.1. 2024.12.13. wnsrl1228 위상정렬 2022.4.3. 2024.12.13. Wikipedia ","permalink":"https://leaf-nam.github.io/cote/bj_1005/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.acmicpc.net/problem/1005\"\u003e백준 1005 ACM Craft\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"완전탐색bfs\"\u003e완전탐색(BFS)\u003c/h3\u003e\n\u003cp\u003e특정 건물이 나올때까지 각 건물들을 탐색하는 과정을 \u003ccode\u003eBFS\u003c/code\u003e로 구현할 수 있습니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e건물 사이의 관계(간선)의 개수가 \u003ccode\u003e100,000\u003c/code\u003e이므로, 각 간선을 한번씩만 방문한다면 시간복잡도 내 풀이가 가능합니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"위상-정렬\"\u003e위상 정렬\u003c/h3\u003e\n\u003cp\u003e각 건물은 해당 건물이 지어지기 전에 특정 건물을 지어야 하는 선행조건이 있습니다.\u003c/p\u003e\n\u003cp\u003e이를 구현하기 위해 \u003ca href=\"https://ko.wikipedia.org/wiki/%EC%9C%84%EC%83%81%EC%A0%95%EB%A0%AC\"\u003e\u003cstrong\u003e위상정렬\u003c/strong\u003e\u003c/a\u003e을 활용할 수 있습니다.\u003c/p\u003e\n\u003cp\u003e위상정렬은 다음과 같이 해당 건물을 짓기 전에 선행되어야 하는 건물이 모두 지어졌는지(\u003ccode\u003e선행 건물의 개수가 0인지\u003c/code\u003e) 확인하는 과정을 통해 구현할 수 있습니다.\u003c/p\u003e","title":"[Java]백준 1005 ACM Craft"},{"content":"도입 지난 포스팅 [Java]Spring Security WebMVC 기본 구조 [Java]Spring Security 예외처리, 캐싱, 로깅 [Java]Spring Security 인증(Authentication)과 인가(Authorization) [Java]Spring Security(With TDD) 기본 인증 및 인가 구현하기 [Java]Spring Security(With TDD) JWT 직접 구현하기 저번 시간에는 JWT의 인증 필터를 직접 구현해서 인증 로직을 완성했습니다.\n이번 시간에는 Spring에서 제공하는 oauth2-resource-server 라이브러리를 통해 동일한 인증 로직을 구현해보겠습니다.\n설계 요구사항 분석 요구 사항은 지난 포스트와 동일합니다.\n로그인 실패 시 jwt를 발행하지 않는다. 정상 로그인 시 jwt를 발행한다.(Happy Case) 잘못된 jwt로 인증할 수 없다. 권한이 부족한 jwt에 인가할 수 없다. 정상적인 jwt로 특정 권한의 api를 사용할 수 있다.(Happy Case) 통합 테스트 통합 테스트도 지난 시간에 사용한 것을 그대로 사용하겠습니다.\n다만, 기존 테스트와 URL 기반으로 요청을 분리하기 위해 다음과 같이 일부 테스트 코드를 변경하였습니다. 테스트에 사용할 SecurityConfig를 JwtSecurityConfig에서 JwtSecurityConfigV2로 변경합니다. 요청 URL을 /jwt/**에서 /jwt/v2/**로 변경합니다. package com.springsecurity.jwt.integration; /* 생략 */ @ExtendWith(SpringExtension.class) // SecurityConfig 변경 @ContextConfiguration(classes = JwtSecurityConfigV2.class) @WebAppConfiguration @Import(IntegrationTestConfig.class) public class JWTIntegrationV2Test { MockMvc mockMvc; JwtUtil jwtUtil; @Autowired WebApplicationContext context; @BeforeEach void init() { jwtUtil = new JwtUtil(); mockMvc = MockMvcBuilders.webAppContextSetup(context) .apply(springSecurity()).build(); } @Test @DisplayName(\u0026#34;1. 로그인 실패 시 jwt를 발행하지 않는다.\u0026#34;) void testLoginFailure() throws Exception { // given : 유저와 관리자 아이디, 잘못된 패스워드 String userId = \u0026#34;user\u0026#34;, adminId = \u0026#34;admin\u0026#34;; String password = \u0026#34;badPassword\u0026#34;; // when : 사용자 토큰 발급 시도 mockMvc.perform(post(\u0026#34;/jwt/v2/token\u0026#34;) .param(\u0026#34;username\u0026#34;, userId) .param(\u0026#34;password\u0026#34;, password)) // then : 401(Unauthenticated) 오류 .andExpect(status().is(401)); // when : 관리자 토큰 발급 시도 mockMvc.perform(post(\u0026#34;/jwt/v2/token\u0026#34;) .param(\u0026#34;username\u0026#34;, adminId) .param(\u0026#34;password\u0026#34;, password)) // then : 401(Unauthenticated) 오류 .andExpect(status().is(401)); } @Test @DisplayName(\u0026#34;2. 정상 로그인 시 jwt를 발행한다.(Happy Case)\u0026#34;) void testLoginSuccess() throws Exception { // given : 유저와 관리자 아이디, 패스워드 String userId = \u0026#34;user\u0026#34;, adminId = \u0026#34;admin\u0026#34;; String userPass = \u0026#34;user1234\u0026#34;, adminPass = \u0026#34;admin1234\u0026#34;; // when : 토큰 발급 String userToken = mockMvc.perform(post(\u0026#34;/jwt/v2/token\u0026#34;) .param(\u0026#34;username\u0026#34;, userId).param(\u0026#34;password\u0026#34;, userPass)) .andExpect(status().is(200)) .andReturn().getResponse().getContentAsString(); String adminToken = mockMvc.perform(post(\u0026#34;/jwt/v2/token\u0026#34;) .param(\u0026#34;username\u0026#34;, adminId).param(\u0026#34;password\u0026#34;, adminPass)) .andExpect(status().is(200)) .andReturn().getResponse().getContentAsString(); // then : 정상 토큰여부 확인(JwtUtil) jwtUtil.validate(userToken); jwtUtil.validate(adminToken); } @Test @DisplayName(\u0026#34;3. 잘못된 jwt로 인증할 수 없다.\u0026#34;) void testAuthentication() throws Exception { // given : 잘못된 jwt 토큰 String badToken = \u0026#34;bearer asdf1234\u0026#34;; // when : User API 접근 mockMvc.perform(get(\u0026#34;/jwt/v2/user/resources\u0026#34;) .header(HttpHeaders.AUTHORIZATION, badToken)) // then : 401 오류 .andExpect(status().is(401)); } @Test @DisplayName(\u0026#34;4. 권한이 부족한 jwt에 인가할 수 없다.\u0026#34;) void testAuthorization() throws Exception { // given : USER 권한 String role = \u0026#34;ROLE_USER\u0026#34;; // when : Admin API 접근 mockMvc.perform(get(\u0026#34;/jwt/v2/admin/resources\u0026#34;) .with(jwt().authorities(new SimpleGrantedAuthority(role)))) // then : 403(Forbidden) 오류 .andExpect(status().is(403)); } @Test @DisplayName(\u0026#34;5. 정상적인 jwt로 특정 권한의 api를 사용할 수 있다.`(Happy Case)`\u0026#34;) void testHappyCase() throws Exception { // given : ADMIN 권한 String role = \u0026#34;ROLE_ADMIN\u0026#34;; // 권한 없이 PUBLIC API 접근 mockMvc.perform(get(\u0026#34;/jwt/v2/public/resources\u0026#34;)) .andExpect(status().is(200)); // Admin 권한으로 USER API 접근 mockMvc.perform(get(\u0026#34;/jwt/v2/user/resources\u0026#34;) .with(jwt().authorities(new SimpleGrantedAuthority(role)))) .andExpect(status().is(200)); // Admin 권한으로 Admin API 접근 mockMvc.perform(get(\u0026#34;/jwt/v2/admin/resources\u0026#34;) .with(jwt().authorities(new SimpleGrantedAuthority(role)))) .andExpect(status().is(200)); } } 위와 같이 Spring Security 설정을 변경 후, 컴파일 오류를 해결하기 위해 다음과 같이 임시로 JwtSecurityConfigV2를 생성합니다. package com.springsecurity.jwt.config; public class JwtSecurityConfigV2 { } 이제 테스트를 실행하면 다음과 같이 모든 테스트가 실패합니다. 실패 메시지를 확인해보면, springSecurityFilterChain cannot be null. 이라는 메시지를 확인할 수 있습니다. MockMVC를 초기화하는 과정에서 SpringSecurity를 실행할 때 필수적인 bean인 springSecurityFilterChain이 Spring에 등록되어 있지 않기 때문입니다. 아직 해당 설정파일에 아무것도 작성하지 않았기 때문에 당연한 결과입니다.\n구현 라이브러리 가져오기 우선, oauth2-resource-server1를 사용하기 위해 다음과 같이 build.gradle에 의존성을 주입합니다.\n// build.gradle dependencies { implementation \u0026#39;org.springframework.boot:spring-boot-starter-security\u0026#39; implementation \u0026#39;org.springframework.boot:spring-boot-starter-web\u0026#39; // 의존성 추가 implementation \u0026#39;org.springframework.boot:spring-boot-starter-oauth2-resource-server\u0026#39; /* 생략 */ } JwtSecurityConfigV2 구현 이후, 기존에 통합테스트를 실행하기 위해 클래스만 생성해 두었던 JwtSecurityConfigV2에 다음과 같이 설정합니다.\nSecurityMatcher URL 설정 : JwtSecurityConfig와 URL기반으로 설정을 분리하기 위함입니다. CSRF 토큰 해제 : 해당 옵션을 해제하지 않고 JWT 로그인 시 CSRF 토큰이 없으면 오류가 발생할 수 있습니다. JWT는 stateless한 인증 방식이기 때문에 CSRF토큰을 구현하기가 제한됩니다. Session Management 해제 : JWT는 Session 방식을 사용하지 않기 때문에 이를 해제해주어야 세션 정보를 서버에 별도로 저장하지 않습니다. 권한별 인가 로직 작성 : API별 필요한 권한을 명시합니다. CustomAuthenticationEntryPoint 등록 : 저번에 생성한 해당 클래스를 ExceptionHandler로 등록해야 인증 실패 시 302오류가 발생하지 않습니다. 지난 시간에 생성한 JwtSecurityConfig설정과 대부분 동일하지만, 요청 URL을 다르게 설정하기 때문에 두 필터가 모두 SecurityFilterChain에 등록되어 각각 적용됩니다.\npackage com.springsecurity.jwt.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class JwtSecurityConfigV2 { @Bean public SecurityFilterChain jwtFilterChainV2(HttpSecurity http, CustomAuthenticationEntryPoint customAuthenticationEntryPoint) throws Exception { return http .securityMatcher(\u0026#34;/jwt/v2/**\u0026#34;) .csrf(AbstractHttpConfigurer::disable) .sessionManagement(session -\u0026gt; session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorize -\u0026gt; authorize .requestMatchers(\u0026#34;/jwt/v2/public/**\u0026#34;).permitAll() .requestMatchers(\u0026#34;/jwt/v2/user/**\u0026#34;).hasRole(\u0026#34;USER\u0026#34;) .requestMatchers(\u0026#34;/jwt/v2/admin/**\u0026#34;).hasRole(\u0026#34;ADMIN\u0026#34;) .requestMatchers(\u0026#34;/jwt/v2/token\u0026#34;).permitAll() .anyRequest().authenticated()) .exceptionHandling(handler -\u0026gt; handler .authenticationEntryPoint(customAuthenticationEntryPoint)) .build(); } } 이제 테스트를 수행하면 다음과 같이 3번 테스트만 성공합니다.\n해당 API의 Controller를 구현하지 않았기 때문에 404오류가 발생합니다.\nAPI 구현 지난 시간과 마찬가지로, 다음과 같이 JwtApiControllerV2에 모든 API 를 생성하겠습니다.\npackage com.springsecurity.jwt.api; import com.springsecurity.jwt.Role; import com.springsecurity.jwt.utility.JwtUtil; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @RequestMapping(\u0026#34;/jwt/v2\u0026#34;) public class JwtApiControllerV2 { private final JwtUtil jwtUtil; @GetMapping(\u0026#34;/admin/resources\u0026#34;) public String getAdminResources() { return \u0026#34;ADMIN 자원 획득\u0026#34;; } @GetMapping(\u0026#34;/user/resources\u0026#34;) public String getUserResources() { return \u0026#34;USER 자원 획득\u0026#34;; } @GetMapping(\u0026#34;/public/resources\u0026#34;) public String getPublicResources() { return \u0026#34;PUBLIC 자원 획득\u0026#34;; } @PostMapping(\u0026#34;/token\u0026#34;) public ResponseEntity\u0026lt;String\u0026gt; getToken(@RequestParam String username, @RequestParam String password) { if (username.equals(\u0026#34;user\u0026#34;) \u0026amp;\u0026amp; password.equals(\u0026#34;user1234\u0026#34;)) return new ResponseEntity\u0026lt;\u0026gt;(jwtUtil.issue(\u0026#34;user\u0026#34;, Role.USER.name()), HttpStatus.OK); if (username.equals(\u0026#34;admin\u0026#34;) \u0026amp;\u0026amp; password.equals(\u0026#34;admin1234\u0026#34;)) return new ResponseEntity\u0026lt;\u0026gt;(jwtUtil.issue(\u0026#34;admin\u0026#34;, Role.ADMIN.name()), HttpStatus.OK); return new ResponseEntity\u0026lt;\u0026gt;(HttpStatus.UNAUTHORIZED); } } 해당 Controller를 통합 테스트에 사용하기 위해 다음과 같이 IntegrationTestConfig를 수정합니다.\n해당 클래스는 이전 포스트에서 통합 테스트 시 필요한 설정과 의존관계를 한번에 불러오기 위해 별도로 분리한 설정파일입니다.\npackage com.springsecurity.jwt.config; /* 생략 */ @Configuration //JwtApiControllerV2 추가 @Import({JwtApiControllerV2.class, JwtApiController.class, JwtUtil.class, JwtAuthenticationFilter.class, CustomAuthenticationEntryPoint.class}) public class IntegrationTestConfig { @Bean(name = \u0026#34;mvcHandlerMappingIntrospector\u0026#34;) public HandlerMappingIntrospector mvcHandlerMappingIntrospector() { return new HandlerMappingIntrospector(); } @Bean(autowireCandidate = false) public static RoleHierarchy testRoleHierarchy() { return RoleHierarchyImpl.withDefaultRolePrefix() .role(\u0026#34;ADMIN\u0026#34;).implies(\u0026#34;USER\u0026#34;) .build(); } } 이제, 다음과 같이 jwt 발행 관련 로직이 모두 성공합니다.\n나머지 테스트에서는 아직 JWT를 활용한 인증 로직이 적용되지 않았기 때문에 401 오류가 발생합니다.\nDecoder, Converter 구현 토큰 인증을 도입하기 위해서는 Decoder와 Converter가 필요합니다.\nDecoder : Authorization 헤더에 있는 Bearer + 'accessToken'형태의 토큰을 복호화하여 유효성 검증과 함께 내부의 권한을 체크합니다. Converter : 복호화된 토큰에서 권한을 찾아서 grantedAuthorities에 등록합니다. 다음과 같이 JwtSecurityConfigV2 내부에 이를 구현하겠습니다.\npackage com.springsecurity.jwt.config; /* 생략 */ @Configuration @EnableWebSecurity public class JwtSecurityConfigV2 { @Bean public SecurityFilterChain jwtFilterChainV2(HttpSecurity http, CustomAuthenticationEntryPoint customAuthenticationEntryPoint) throws Exception { return http .securityMatcher(\u0026#34;/jwt/v2/**\u0026#34;) .csrf(AbstractHttpConfigurer::disable) .sessionManagement(session -\u0026gt; session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // OAuth2ResourceServer 사용하도록 FilterChain 등록 .oauth2ResourceServer(oauth2 -\u0026gt; oauth2.jwt(Customizer.withDefaults())) .authorizeHttpRequests(authorize -\u0026gt; authorize .requestMatchers(\u0026#34;/jwt/v2/public/**\u0026#34;).permitAll() .requestMatchers(\u0026#34;/jwt/v2/user/**\u0026#34;).hasRole(\u0026#34;USER\u0026#34;) .requestMatchers(\u0026#34;/jwt/v2/admin/**\u0026#34;).hasRole(\u0026#34;ADMIN\u0026#34;) .requestMatchers(\u0026#34;/jwt/v2/token\u0026#34;).permitAll() .anyRequest().authenticated()) .exceptionHandling(handler -\u0026gt; handler .authenticationEntryPoint(customAuthenticationEntryPoint)) .build(); } // Decoder 등록 @Bean JwtDecoder jwtDecoder(JwtUtil jwtUtil) { // 기존 JwtUtil.class에서 사용한 SecretKey 활용 return NimbusJwtDecoder.withSecretKey(jwtUtil.secretKey()) // 기존에 발행된 토큰을 파싱해보면 헤더에 {alg: HS384} 라고 명시됨 .macAlgorithm(MacAlgorithm.HS384).build(); } // Converter 등록 @Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); // 권한을 가져올 claim명 설정 grantedAuthoritiesConverter.setAuthoritiesClaimName(\u0026#34;role\u0026#34;); // 권한 앞에 Prefix 설정 grantedAuthoritiesConverter.setAuthorityPrefix(\u0026#34;ROLE_\u0026#34;); JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); return jwtAuthenticationConverter; } } 이제 테스트를 실행해보면, 다음과 같이 모든 테스트가 성공합니다.\n커버리지 분석 결과, 다음과 같이 모든 코드를 테스트하고 있습니다.\n결론 이번 시간에는 oauth2-resource-server 라이브러리를 사용하여 JWT인증을 직접 구현해 보았습니다.\n해당 프로젝트의 전체 코드는 다음 깃허브 링크에서 확인하실 수 있습니다.\n해당 라이브러리에서도 내부적으로는 지난 시간에 구현한 JWT 인증 필터와 유사한 로직이 진행됩니다. 그러나 개인적으로는 필터를 직접 구현하는 것보다 많은 사용자들에게서 이미 검증된 라이브러리를 사용하는 것이 더욱 안전해 보이고, 예기치 못한 오류를 방지할 수 있을 것 같습니다.\n다음 포스팅 References URL 게시일자 방문일자 작성자 OAuth 2.0 Resource Server - 2024.12.17. Spring 해당 라이브러리의 내부적인 구조와 자세한 사용법은 다음 링크를 참고하시기 바랍니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/spring_security/6/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003ch3 id=\"지난-포스팅\"\u003e지난 포스팅\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/1\"\u003e[Java]Spring Security WebMVC 기본 구조\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/2\"\u003e[Java]Spring Security 예외처리, 캐싱, 로깅\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/3\"\u003e[Java]Spring Security 인증(Authentication)과 인가(Authorization)\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/4\"\u003e[Java]Spring Security(With TDD) 기본 인증 및 인가 구현하기\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/5\"\u003e[Java]Spring Security(With TDD) JWT 직접 구현하기\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e저번 시간에는 \u003ccode\u003eJWT\u003c/code\u003e의 \u003cstrong\u003e인증 필터를 직접 구현\u003c/strong\u003e해서 인증 로직을 완성했습니다.\u003c/p\u003e\n\u003cp\u003e이번 시간에는 \u003ccode\u003eSpring\u003c/code\u003e에서 제공하는 \u003ccode\u003eoauth2-resource-server\u003c/code\u003e 라이브러리를 통해 동일한 인증 로직을 구현해보겠습니다.\u003c/p\u003e\n\u003ch2 id=\"설계\"\u003e설계\u003c/h2\u003e\n\u003ch3 id=\"요구사항-분석\"\u003e요구사항 분석\u003c/h3\u003e\n\u003cp\u003e요구 사항은 \u003ca href=\"https://leaf-nam.github.io/posts/spring_security/5/#%EC%9A%94%EA%B5%AC%EC%82%AC%ED%95%AD-%EB%B6%84%EC%84%9D\"\u003e지난 포스트\u003c/a\u003e와 동일합니다.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e로그인 실패 시 jwt를 발행하지 않는다.\u003c/li\u003e\n\u003cli\u003e정상 로그인 시 jwt를 발행한다.\u003ccode\u003e(Happy Case)\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e잘못된 jwt로 인증할 수 없다.\u003c/li\u003e\n\u003cli\u003e권한이 부족한 jwt에 인가할 수 없다.\u003c/li\u003e\n\u003cli\u003e정상적인 jwt로 특정 권한의 api를 사용할 수 있다.\u003ccode\u003e(Happy Case)\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch3 id=\"통합-테스트\"\u003e통합 테스트\u003c/h3\u003e\n\u003cp\u003e통합 테스트도 \u003ca href=\"https://leaf-nam.github.io/posts/spring_security/5/#%ED%86%B5%ED%95%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%91%EC%84%B1\"\u003e지난 시간\u003c/a\u003e에 사용한 것을 그대로 사용하겠습니다.\u003c/p\u003e","title":"[Java]Spring Security(With TDD) JWT 라이브러리 활용해서 간편하게 구현하기"},{"content":"출처 백준 9202 Boggle\n접근 Backtracking 특정 단어를 한 글자씩 찾아들어가면서 단어 사전에 존재하는지 확인하는 방식은 전형적인 DFS의 형태입니다.\n주어진 단어의 개수가 w \u0026lt; 300,000 이지만 탐색해야 하는 전체 Boggle 보드의 크기가 4X4이고, 이러한 Boggle의 개수가 b \u0026lt; 30이기 때문에 DFS 자체는 많은 시간복잡도가 필요하지 않습니다. DFS 탐색 과정에서 다음 글자를 추가했을 때, 단어 사전에 포함된 단어를 만들 수 있는지를 빠르게 확인하여 불가능한 경우를 가지치기(pruning)한다면, 시간복잡도를 더욱 줄일 수 있습니다. 이러한 기법을 Backtracking이라고 합니다.\nTrie 단어 사전에 현재 단어가 포함되어 있는지를 확인하는 기법에는 여러 가지가 있지만, 저는 Trie1 자료구조를 통해 단어를 저장하고, 이렇게 저장된 노드를 활용해서 다음 노드로 탐색할지 여부를 결정하였습니다.\n문제에서 주어진 예시를 Trie 형태로 저장하면 다음과 같습니다.\nRoot 노드로부터 각 알파벳을 하나씩 갖는 자식 노드들을 순차적으로 저장합니다. 마지막 노드 위치에 단어가 있다는 것을 표시하기 위해 isFinished라는 플래그 값을 별도로 저장합니다. A -\u0026gt; C -\u0026gt; M 순서로 탐색하는 중, 현재 노드인 M 노드에 isFinished값이 있으므로 ACM이라는 단어가 사전에 있음을 확인할 수 있습니다.\n만약, A노드에서 C가 아닌 다른 자식을 찾으려고 하면, 단어 사전에 존재하지 않는다는 것을 O(1)의 시간복잡도로 확인하는 것이 가능합니다. 풀이 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.*; public class Main { // 루트 노드 별도로 저장 static Node root; // Trie 노드 static class Node { char c; Node[] children; boolean isFinished; // 생성자 Node(char c) { this.c = c; this.children = new Node[26]; this.isFinished = false; } // 자식이 없다면 추가 후 자식 참조값 리턴 Node add(char c) { Node temp = this.children[c - \u0026#39;A\u0026#39;]; if (temp != null) return temp; Node child = new Node(c); this.children[c - \u0026#39;A\u0026#39;] = child; return child; } // 자식이 있는지 여부 확인 boolean hasChild() { if (children == null) return false; for (Node child : children) if (child != null) return true; return false; } } public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int N = Integer.parseInt(br.readLine()); // 루트노드 생성 root = new Node(\u0026#39;r\u0026#39;); for (int i = 0; i \u0026lt; N; i++) { char[] word = br.readLine().toCharArray(); Node temp = root; // 단어 순서대로 따라가면서 자식 노드 추가 for (char c : word) { temp = temp.add(c); } // 마지막 알파벳에 플래그 표시 temp.isFinished = true; } br.readLine(); int b = Integer.parseInt(br.readLine()); // DFS 결과 저장할 HashSet Set\u0026lt;String\u0026gt; result = new HashSet\u0026lt;\u0026gt;(); for (int i = 0; i \u0026lt; b; i++) { // 새로운 boggle 입력 char[][] boggle = new char[4][4]; for (int j = 0; j \u0026lt; 4; j++) { boggle[j] = br.readLine().toCharArray(); } for (int r = 0; r \u0026lt; 4; r++) { for (int c = 0; c \u0026lt; 4; c++) { // 첫번째 자식부터 탐색 Node temp = root.children[boggle[r][c] - \u0026#39;A\u0026#39;]; if (temp == null) continue; // 첫번째 자식 현재위치에 넣고 탐색 시작 StringBuilder sb = new StringBuilder(); sb.append(boggle[r][c]); dfs(boggle, result, r, c, temp, sb, new boolean[4][4]); } } printAnswer(result); result.clear(); if (i != b - 1) br.readLine(); } } // DFS + Backtracking static int[] dr = {0, 0, 1, -1, 1, 1, -1, -1}; static int[] dc = {1, -1, 0, 0, 1, -1, -1, 1}; static void dfs(char[][] boggle, Set\u0026lt;String\u0026gt; result, int r, int c, Node temp, StringBuilder sb, boolean[][] isVisited) { isVisited[r][c] = true; // 단어가 있는 위치에 도달하면 해당 단어 추가 if (temp.isFinished) { result.add(sb.toString()); // 현재 위치 아래에 자식이 없다면 가지치기 if (!temp.hasChild()) return; } // 8방향 탐색 for (int i = 0; i \u0026lt; 8; i++) { int nr = r + dr[i]; int nc = c + dc[i]; if (nr \u0026lt; 0 || nr \u0026gt;= 4 || nc \u0026lt; 0 || nc \u0026gt;= 4 || isVisited[nr][nc]) continue; if (temp.children[boggle[nr][nc] - \u0026#39;A\u0026#39;] == null) continue; // 다음 단어 추가 sb.append(boggle[nr][nc]); dfs(boggle, result, nr, nc, temp.children[boggle[nr][nc] - \u0026#39;A\u0026#39;], sb, isVisited); // DFS 복귀 후 값 초기화 sb.deleteCharAt(sb.length() - 1); isVisited[nr][nc] = false; } } // 정답 출력 static int[] scoreMap = {0, 0, 0, 1, 1, 2, 3, 5, 11}; static void printAnswer(Set\u0026lt;String\u0026gt; result) { int maxLength = 0; int score = 0; List\u0026lt;String\u0026gt; maxs = new ArrayList\u0026lt;\u0026gt;(); for (String r : result) { // 총점에 현재단어 길이 점수 추가 score += scoreMap[r.length()]; if (r.length() \u0026gt; maxLength) { maxLength = r.length(); maxs.clear(); maxs.add(r); // 가장 긴 단어 여러개일 경우 저장 } else if (r.length() == maxLength) { maxs.add(r); } } // 가장 긴 단어 여러개일 경우 오름차순 정렬 maxs.sort(Comparator.naturalOrder()); // 총점 + 가장 긴 단어 + 결과 크기 출력 System.out.println(score + \u0026#34; \u0026#34; + maxs.get(0) + \u0026#34; \u0026#34; + result.size()); } } 결과 소요시간 : 1:55:30 리뷰 자식 노드를 탐색하는 과정에서 발생한 런타임 오류(IndexOutOfBounds)를 해결하는데 너무 오래 걸렸습니다.\nDFS를 재귀 형식으로 구현하는게 구현 속도는 빠르지만 디버딩 툴 없이 디버깅하기가 까다로워서 잘 고민해봐야겠습니다.\n또한 문제를 풀고 보니 시간 제한이 10초로, 다른 분들의 풀이를 보니 Trie를 활용하지 않고도 Backtracking만 잘 구현한다면 시간복잡도 내로 충분히 풀 수 있는 것 같습니다.\n효율적인 알고리즘으로 푸는 것보다, 주어진 시간복잡도 내에서 가장 빨리 구현할 수 있는 알고리즘을 찾는게 중요함을 항상 주의해야겠습니다.\nReferences URL 게시일자 방문일자 작성자 Backtracking 2022.02.08. 2024.12.10. Wikipedia Trie 자료구조에 대한 자세한 설명은 다음 링크를 참고하시기 바랍니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/cote/bj_9202/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://www.acmicpc.net/problem/9202\"\u003e백준 9202 Boggle\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"backtracking\"\u003eBacktracking\u003c/h3\u003e\n\u003cp\u003e특정 단어를 한 글자씩 찾아들어가면서 단어 사전에 존재하는지 확인하는 방식은 전형적인 \u003ccode\u003eDFS\u003c/code\u003e의 형태입니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e주어진 단어의 개수가 \u003ccode\u003ew \u0026lt; 300,000\u003c/code\u003e 이지만 탐색해야 하는 전체 \u003ccode\u003eBoggle\u003c/code\u003e 보드의 크기가 \u003ccode\u003e4X4\u003c/code\u003e이고, 이러한 \u003ccode\u003eBoggle\u003c/code\u003e의 개수가 \u003ccode\u003eb \u0026lt; 30\u003c/code\u003e이기 때문에 DFS 자체는 많은 시간복잡도가 필요하지 않습니다.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eDFS\u003c/code\u003e 탐색 과정에서 다음 글자를 추가했을 때, 단어 사전에 포함된 단어를 만들 수 있는지를 빠르게 확인하여 불가능한 경우를 가지치기(\u003ccode\u003epruning\u003c/code\u003e)한다면, 시간복잡도를 더욱 줄일 수 있습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e이러한 기법을 \u003ca href=\"https://ko.wikipedia.org/wiki/%ED%87%B4%EA%B0%81%EA%B2%80%EC%83%89\"\u003eBacktracking\u003c/a\u003e이라고 합니다.\u003c/p\u003e","title":"[Java]백준 9202 Boggle"},{"content":"도입 지난 포스팅 [Java]Spring Security WebMVC 기본 구조 [Java]Spring Security 예외처리, 캐싱, 로깅 [Java]Spring Security 인증(Authentication)과 인가(Authorization) [Java]Spring Security(With TDD) 기본 인증 및 인가 구현하기 지금까지 Spring Security의 기본 개념을 학습하고, 기본 인증 및 인가를 구현했습니다.\n이번 시간에는 JWT의 기본 개념을 익히고, 프로젝트의 인증 및 인가 로직에 활용해보겠습니다.\nJWT JWT는 JSON Web Token의 준말로 RFC 7519 명세에 정의된 토큰입니다.\n위 명세에 따르면, JWT는 다음과 같이 정의할 수 있습니다. JSON Web Token (JWT)는 두 당사자 간에 전송되는 클레임(claims)을 표현하기 위한 간결하고 URL에 안전한 수단입니다. JWT의 클레임은 JSON 객체로 인코딩되며, 이는 JSON Web Signature (JWS) 구조의 페이로드(payload)로 사용되거나 JSON Web Encryption (JWE) 구조의 평문(plaintext)으로 사용됩니다. 이를 통해 클레임은 디지털 서명되거나 메시지 인증 코드(Message Authentication Code, MAC)를 사용하여 무결성이 보호되거나 암호화될 수 있습니다.\n이러한 JWT 인증의 장단점을 살펴보면 다음과 같습니다.\n장점\nstateless1한 특성이 있어 서버 부담을 줄일 수 있습니다.\n서버에 사용자의 정보들을 별도로 저장하지 않고도, JWT를 복호화함으로써 인증된 사용자임을 바로 파악할 수 있기 때문입니다.\nCookie 저장소2를 지원하지 않는 등 기존의 Session - Cookie 방식을 사용할 수 없는 환경에서도 인증이 가능합니다.\n프론트엔드와 백엔드가 분리된 아키텍쳐로 개발되는 현대 웹앱에 적합한 형태입니다.\n특히, 백엔드 서버가 Scale Out3이나 MSA4 등으로 구조가 복잡해지면 필연적으로 인증 동기화의 어려움을 겪는데, JWT를 활용하면 토큰 유효성만 검사하면 되기 때문에 이러한 문제를 쉽게 해결할 수 있습니다.\n단점\n해당 방식을 사용하려면 프론트엔드에서 JWT를 어딘가에 보관했다가 요청 시 함께 전송해야 하는데, 이러한 과정에서 토큰을 탈취당할 수 있습니다.\n토큰 탈취를 막기 위해 Refresh Token5을 도입할 수 있지만, 이를 저장 및 검증하는 과정에서 추가적인 서버 부하가 발생합니다.\n인코딩되는 토큰의 길이가 길기 때문에, 토큰 전송 과정에서 네트워크 부하가 발생합니다.\nPayload(Body)에 담기는 정보는 암호화되지 않기 때문에, 중요하거나 취약한 정보를 담을 수 없습니다.\nSession - Cookie의 장단점은 JWT와 정반대라고 생각하시면 되겠습니다. Session - Cookie 방식을 사용할지 JWT를 사용할지는 요구사항에 따라 다를 수 있으니, 장단점을 따져보고 취사선택 하시면 되겠습니다.\n설계 JWT를 구현하는 두가지 방법이 있습니다.\nFilter 직접 구현 Spring Security oauth2-resource-server 활용 실무나 실제 프로젝트에서는 2번 방식이 적절하겠지만6, JWT 인증의 내부 로직을 확인해보기 위해 1번 방식으로 우선 구현해보겠습니다.\nJWT 의존성 추가 다음과 같이 build.gradle에 JWT 파싱을 위한 라이브러리를 추가해줍니다.\n// for jjwt implementation \u0026#39;io.jsonwebtoken:jjwt-api:0.12.6\u0026#39; implementation \u0026#39;io.jsonwebtoken:jjwt-impl:0.12.6\u0026#39; implementation \u0026#39;io.jsonwebtoken:jjwt-jackson:0.12.6\u0026#39; 저는 jjwt를 사용하겠습니다. 다양한 라이브러리가 있지만, 대부분 기능은 유사하니 사용이 편한 라이브러리를 사용하시면 되겠습니다.\n요구사항 분석 JWT를 사용해서 달성하려는 요구사항을 정리해보면 다음과 같습니다.\n로그인 실패 시 jwt를 발행하지 않는다. 정상 로그인 시 jwt를 발행한다.(Happy Case) 잘못된 jwt로 인증할 수 없다. 권한이 부족한 jwt에 인가할 수 없다. 정상적인 jwt로 특정 권한의 api를 사용할 수 있다.(Happy Case) 통합테스트 작성 Spring의 통합테스트 모듈인 MockMVC에서는 jwt를 쉽게 Mocking할 수 있는 메서드를 제공합니다. 다음과 같이 사용할 수 있습니다. public testClass { /* 생략 */ mockMvc.perform(get(\u0026#34;/jwt/admin/resources\u0026#34;) // 특정 권한을 가진 JWT 요청에 함께 전송 .with(jwt().authorities(new SimpleGrantedAuthority(role)))); /* 생략 */ } 더 자세한 사용법은 다음 링크를 참고하시기 바랍니다.\npackage com.springsecurity.jwt.integration; /* 생략 */ @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = JwtSecurityConfig.class) @WebAppConfiguration @Import(IntegrationTestConfig.class) public class JWTIntegrationTest { MockMvc mockMvc; JwtUtil jwtUtil; @Autowired WebApplicationContext context; @BeforeEach void init() { jwtUtil = new JwtUtil(); mockMvc = MockMvcBuilders.webAppContextSetup(context) .apply(springSecurity()).build(); } @Test @DisplayName(\u0026#34;1. 로그인 실패 시 jwt를 발행하지 않는다.\u0026#34;) void testLoginFailure() throws Exception { // given : 유저와 관리자 아이디, 잘못된 패스워드 String userId = \u0026#34;user\u0026#34;, adminId = \u0026#34;admin\u0026#34;; String password = \u0026#34;badPassword\u0026#34;; // when : 사용자 토큰 발급 시도 mockMvc.perform(post(\u0026#34;/jwt/token\u0026#34;) .param(\u0026#34;username\u0026#34;, userId) .param(\u0026#34;password\u0026#34;, password)) // then : 401(Unauthenticated) 오류 .andExpect(status().is(401)); // when : 관리자 토큰 발급 시도 mockMvc.perform(post(\u0026#34;/jwt/token\u0026#34;) .param(\u0026#34;username\u0026#34;, adminId) .param(\u0026#34;password\u0026#34;, password)) // then : 401(Unauthenticated) 오류 .andExpect(status().is(401)); } @Test @DisplayName(\u0026#34;2. 정상 로그인 시 jwt를 발행한다.(Happy Case)\u0026#34;) void testLoginSuccess() throws Exception { // given : 유저와 관리자 아이디, 패스워드 String userId = \u0026#34;user\u0026#34;, adminId = \u0026#34;admin\u0026#34;; String userPass = \u0026#34;user1234\u0026#34;, adminPass = \u0026#34;admin1234\u0026#34;; // when : 유저 및 관리자 토큰 발급 String userToken = mockMvc.perform(post(\u0026#34;/jwt/token\u0026#34;) .param(\u0026#34;username\u0026#34;, userId).param(\u0026#34;password\u0026#34;, userPass)) .andExpect(status().is(200)) .andReturn().getResponse().getContentAsString(); String adminToken = mockMvc.perform(post(\u0026#34;/jwt/token\u0026#34;) .param(\u0026#34;username\u0026#34;, adminId).param(\u0026#34;password\u0026#34;, adminPass)) .andExpect(status().is(200)) .andReturn().getResponse().getContentAsString(); // then : 정상 토큰여부 확인(JwtUtil) jwtUtil.validate(userToken); jwtUtil.validate(adminToken); } @Test @DisplayName(\u0026#34;3. 잘못된 jwt로 인증할 수 없다.\u0026#34;) void testAuthentication() throws Exception { // given : 잘못된 jwt 토큰 String badToken = \u0026#34;bearer asdf1234\u0026#34;; // when : User API 접근 mockMvc.perform(get(\u0026#34;/jwt/user/resources\u0026#34;) .header(HttpHeaders.AUTHORIZATION, badToken)) // then : 401 오류 .andExpect(status().is(401)); } @Test @DisplayName(\u0026#34;4. 권한이 부족한 jwt에 인가할 수 없다.\u0026#34;) void testAuthorization() throws Exception { // given : USER 권한 String role = \u0026#34;ROLE_USER\u0026#34;; // when : Admin API 접근 mockMvc.perform(get(\u0026#34;/jwt/admin/resources\u0026#34;) .with(jwt().authorities(new SimpleGrantedAuthority(role)))) // then : 403(Forbidden) 오류 .andExpect(status().is(403)); } @Test @DisplayName(\u0026#34;5. 정상적인 jwt로 특정 권한의 api를 사용할 수 있다.`(Happy Case)`\u0026#34;) void testHappyCase() throws Exception { // given : ADMIN 권한 String role = \u0026#34;ROLE_ADMIN\u0026#34;; // 권한 없이 PUBLIC API 접근 mockMvc.perform(get(\u0026#34;/jwt/public/resources\u0026#34;)) .andExpect(status().is(200)); // Admin 권한으로 USER API 접근 mockMvc.perform(get(\u0026#34;/jwt/user/resources\u0026#34;) .with(jwt().authorities(new SimpleGrantedAuthority(role)))) .andExpect(status().is(200)); // Admin 권한으로 Admin API 접근 mockMvc.perform(get(\u0026#34;/jwt/admin/resources\u0026#34;) .with(jwt().authorities(new SimpleGrantedAuthority(role)))) .andExpect(status().is(200)); } } 위 통합테스트를 작성하면서, JWT 발행 및 파싱을 위해 기존에 없던 JWTUtil이라는 클래스를 임시로 만들었습니다.\nTDD를 하는 만큼 테스트 과정에서 필요한 클래스는 그때그때 만들면서 대응하는 것이 좋습니다.\n아직 세부사항은 전혀 구현되지 않았으나, 일단은 테스트를 실행할 수 있도록 컴파일 오류를 해결하기 위해 다음과 utility 패키지를 만들고, JwtUtil 클래스를 생성합니다.\npackage com.springsecurity.jwt.utility; public class JwtUtil { public void validate(String token) { } } 통합테스트를 실행하면 다음과 같이 4개의 테스트가 실패합니다.\n구현 JwtUtil 단위테스트 작성 우선, 임시로 만든 JwtUtil클래스의 기능을 채우기 위해 단위테스트를 작성하겠습니다.\n처음에는 어색할 수 있지만, TDD를 위해서는 테스트를 먼저 작성하고 구현하는 것에 익숙해져야 합니다.\n이를 위해 요구사항을 분석하자면 다음과 같습니다. 유효하지 않은 토큰 검증 시 런타임 오류를 반환한다. 유효한 토큰 검증 시 오류가 발생하지 않는다.(Happy Case) 잘못된 권한의 토큰 발급 시 런타임 오류를 반환한다. 사용자 권한별 정상적인 토큰을 발급한다.(Happy Case) 유효하지 않은 토큰 파싱 시 런타임 오류를 반환한다. 정상적인 토큰을 파싱한다.(Happy Case) 토큰 검증 뿐 아니라, 토큰 API에서 사용할 권한별 토큰을 만드는 로직과 파싱하는 로직을 포함시켰습니다.\npackage com.springsecurity.jwt.utility; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.*; class JwtUtilTest { JwtUtil suit; // JWT 형식이 아닌 토큰 String invalidToken1 = \u0026#34;INVALIDATE_TOKEN\u0026#34;; // https://jwt.io 에서 만든 예제 토큰 String invalidToken2 = \u0026#34;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\u0026#34;; // sub : admin, role : admin 이지만 유효하지 않은 secret key 를 사용한 토큰 String invalidToken3 = \u0026#34;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.wpTc_a19-bJNZ7oeYghAmxks3tk2mjcP6xTqYe2u86c\u0026#34;; // sub : admin, role : ADMIN, 유효한 secret key 를 사용한 토큰 String validToken = \u0026#34;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJBRE1JTiJ9.VYYrrkyu4kmM4zWtl_gFk9leBM8xu-XxxIYUtY9_2n0\u0026#34;; @BeforeEach void init() { suit = new JwtUtil(); } @Test @DisplayName(\u0026#34;1. 유효하지 않은 토큰 검증 시 런타임 오류를 반환한다.\u0026#34;) void testInvalidateToken() { assertThatThrownBy(() -\u0026gt; suit.validate(invalidToken1)) .isInstanceOf(RuntimeException.class); assertThatThrownBy(() -\u0026gt; suit.validate(invalidToken2)) .isInstanceOf(RuntimeException.class); assertThatThrownBy(() -\u0026gt; suit.validate(invalidToken3)) .isInstanceOf(RuntimeException.class); } @Test @DisplayName(\u0026#34;2. 유효한 토큰 검증 시 오류가 발생하지 않는다.(Happy Case)\u0026#34;) void testValidateToken() { suit.validate(validToken); } @Test @DisplayName(\u0026#34;3. 잘못된 권한의 토큰 발급 시 런타임 오류를 반환한다.\u0026#34;) void testIllegalRoleIssue() { String userName = \u0026#34;test_user\u0026#34;; String role = \u0026#34;ILLEGAL_ROLE\u0026#34;; assertThatThrownBy(() -\u0026gt; suit.issue(userName, role)) .isInstanceOf(RuntimeException.class); } @Test @DisplayName(\u0026#34;4. 사용자 권한별 정상적인 토큰을 발급한다.(Happy Case)\u0026#34;) void testLegalRoleIssue() { // given String userName = \u0026#34;user\u0026#34;; String userRole = \u0026#34;USER\u0026#34;; String adminName = \u0026#34;admin\u0026#34;; String adminRole = \u0026#34;ADMIN\u0026#34;; // when String userToken = suit.issue(userName, userRole); String adminToken = suit.issue(adminName, adminRole); // then suit.validate(userToken); suit.validate(adminToken); } @Test @DisplayName(\u0026#34;5. 유효하지 않은 토큰 파싱 시 런타임 오류를 반환한다.\u0026#34;) void testIllegalTokenParse() { assertThatThrownBy(() -\u0026gt; suit.parseName(invalidToken1)) .isInstanceOf(RuntimeException.class); assertThatThrownBy(() -\u0026gt; suit.parseName(invalidToken2)) .isInstanceOf(RuntimeException.class); assertThatThrownBy(() -\u0026gt; suit.parseName(invalidToken3)) .isInstanceOf(RuntimeException.class); assertThatThrownBy(() -\u0026gt; suit.parseRole(invalidToken1)) .isInstanceOf(RuntimeException.class); assertThatThrownBy(() -\u0026gt; suit.parseRole(invalidToken2)) .isInstanceOf(RuntimeException.class); assertThatThrownBy(() -\u0026gt; suit.parseRole(invalidToken3)) .isInstanceOf(RuntimeException.class); } @Test @DisplayName(\u0026#34;6. 정상적인 토큰을 파싱한다.(Happy Case)\u0026#34;) void testLegalTokenParse() { // given String userName = \u0026#34;user\u0026#34;; String userRole = \u0026#34;USER\u0026#34;; String userToken = suit.issue(userName, userRole); // when String name = suit.parseName(userToken); String role = suit.parseRole(userToken); // then assertThat(name).isEqualTo(userName); assertThat(role).isEqualTo(userRole); } } JWTUtil 구현 우선 테스트를 실행하기 위해 다음과 같이 JwtUtil을 수정합니다.\npackage com.springsecurity.jwt.utility; public class JwtUtil { public void validate(String token) { } public String issue(String userName, String Role) { return null; } public String parseRole(String token) { return null; } public String parseName(String userToken) { return null; } } 이후 단위테스트 결과는 다음과 같습니다.\n우선 토큰 검증부터 차근차근 구현해 보겠습니다.\n토큰 검증을 위해서 맨 처음에 추가했던 jjwt 라이브러리를 활용하겠습니다.\njjwt 깃허브의 Readme를 참고해서 다음과 같이 토큰을 검증하도록 했습니다.\n// JwtUtil.java /* 생략 */ public void validate(String token) { Jwts.parser() .verifyWith(secretKey()) .build() .parseSignedClaims(token); } private SecretKey secretKey() { // https://randomkeygen.com/ 에서 생성한 504-bit WPA Key return Keys.hmacShaKeyFor(\u0026#34;+*jhLeu04kw7M~tQew\u0026lt;Ym\u0026lt;d%,\\\u0026#34;{(PC$p64acJ}lH_;d:\u0026#39;nD/^s+y7O=j!FBia5b\u0026#34;.getBytes(StandardCharsets.UTF_8)); } 참고로 예제 프로젝트이므로 비밀키를 노출했지만, 실제 프로젝트라면 별도의 환경변수로 분리해서 노출되지 않도록 주의해야 합니다.\n이후 다음과 같이 검증 로직 테스트가 정상 동작합니다.\n다음으로 토큰 발행 로직을 다음 페이지를 참고하여 구현하였습니다.\n// JwtUtil.java /* 생략 */ public String issue(String userName, String Role) { return Jwts.builder() .subject(userName) .claim(\u0026#34;role\u0026#34;,Role) .signWith(secretKey()) .compact(); } 이후 다음과 같이 발행 테스트의 Happy Case가 정상 동작하지만, ADMIN 혹은 USER 권한을 제외한 다른 권한은 사용할 수 없다는 3번 테스트는 실패합니다.\n단순히 String 값 비교로 구현할 수 있지만, 차후 확장성을 위해 Role을 담은 별도의 enum으로 분리하고 다음과 같이 메서드를 수정합니다.\n// Role.java package com.springsecurity.jwt; public enum Role { USER, ADMIN; public static boolean isValid(String role) { for (Role r : Role.values()) { if (r.name().equalsIgnoreCase(role)) return true; } return false; } } // JwtUtil.java /* 생략 */ public String issue(String userName, String role) { if (!Role.isValid(role)) throw new EnumConstantNotPresentException(Role.class, role); return Jwts.builder() .subject(userName) .claim(\u0026#34;role\u0026#34;, role) .signWith(secretKey()) .compact(); } /* 생략*/ 이제 발행 메서드에 관한 모든 테스트가 성공합니다.\n마지막으로 다음 페이지를 참고하여 토큰 파싱 로직을 작성합니다.\n// JwtUtil.java /* 생략 */ // JwtUtil.java /* 생략 */ public String parseName(String token) { return getPayload(token).getSubject(); } public String parseRole(String token) { return (String) getPayload(token).get(\u0026#34;role\u0026#34;); } private Claims getPayload(String token) { return Jwts.parser() .verifyWith(secretKey()) .build() .parseSignedClaims(token) .getPayload(); } 이제 모든 단위 테스트가 성공합니다.\n또한, JwtUtil의 커버리지를 분석해보면, 다음과 같이 모든 메서드가 테스트되고 있음을 알 수 있습니다.\nJwtUtil의 모든 메서드에 대한 단위테스트가 성공했기 때문에, 통합테스트에서는 해당 클래스에 대한 세부적인 테스트를 작성하지 않아도 됩니다.\nAPI 구현 이제 본격적으로 통합테스트를 성공시키기 위해, 우선 토큰과 자원을 반환하는 API를 다음과 같이 JwtApiController에 생성하겠습니다.\n예제이므로, 사용자 정보를 검증하는 별도의 Service를 만들지 않고 Controller에서 요청 정보를 모두 검증하도록 하겠습니다.\n// JwtApiController.java package com.springsecurity.jwt.api; import com.springsecurity.jwt.Role; import com.springsecurity.jwt.utility.JwtUtil; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @RequestMapping(\u0026#34;/jwt\u0026#34;) public class JwtApiController { private final JwtUtil jwtUtil; @GetMapping(\u0026#34;/admin/resources\u0026#34;) public String getAdminResources() { return \u0026#34;ADMIN 자원 획득\u0026#34;; } @GetMapping(\u0026#34;/user/resources\u0026#34;) public String getUserResources() { return \u0026#34;USER 자원 획득\u0026#34;; } @GetMapping(\u0026#34;/public/resources\u0026#34;) public String getPublicResources() { return \u0026#34;PUBLIC 자원 획득\u0026#34;; } @PostMapping(\u0026#34;/token\u0026#34;) public ResponseEntity\u0026lt;String\u0026gt; getToken(@RequestParam String username, @RequestParam String password) { // USER 회원 로그인 시 USER 권한 토큰 발행 if (username.equals(\u0026#34;user\u0026#34;) \u0026amp;\u0026amp; password.equals(\u0026#34;user1234\u0026#34;)) return new ResponseEntity\u0026lt;\u0026gt;(jwtUtil.issue(\u0026#34;user\u0026#34;, Role.USER.name()), HttpStatus.OK); // Admin 회원 로그인 시 ADMIN 권한 토큰 발행 if (username.equals(\u0026#34;admin\u0026#34;) \u0026amp;\u0026amp; password.equals(\u0026#34;admin1234\u0026#34;)) return new ResponseEntity\u0026lt;\u0026gt;(jwtUtil.issue(\u0026#34;admin\u0026#34;, Role.ADMIN.name()), HttpStatus.OK); // 로그인 실패 시 401 오류 return new ResponseEntity\u0026lt;\u0026gt;(HttpStatus.UNAUTHORIZED); } } 하지만, 통합테스트를 실행해보면 여전히 3번 테스트를 제외하고는 모든 테스트가 실패하고 있습니다.\n테스트 실패 메시지를 확인해보면 403 오류가 발생하고 있는데, SecurityFilterChain에 해당 API와 관련된 설정을 해주지 않고 있어 Spring Security에서 해당 EntryPoint를 차단했기 때문입니다.\nJwtSecurityConfig 구현 Spring Security 설정을 위해 다음과 같이 JWT 관련된 설정을 JwtSecurityConfig에 작성 후 Bean으로 등록합니다.\nSecurityMatcher URL 설정 : 지난 시간에 작성했던 SecurityConfig와 URL기반으로 설정을 분리하기 위함입니다. CSRF 토큰 해제 : 해당 옵션을 해제하지 않고 JWT 로그인 시 CSRF 토큰이 없으면 오류가 발생할 수 있습니다. JWT는 stateless한 인증 방식이기 때문에 CSRF토큰을 구현하기가 제한됩니다. Session Management 해제 : JWT는 Session 방식을 사용하지 않기 때문에 이를 해제해주어야 세션 정보를 서버에 별도로 저장하지 않습니다. 권한별 인가 로직 작성 : API별 필요한 권한을 명시합니다. CustomAuthenticationEntryPoint 등록 : 지난 시간에 생성한 해당 클래스를 ExceptionHandler로 등록해야 인증 실패 시 302오류가 발생하지 않습니다. // JwtSecurityConfig.java package com.springsecurity.jwt.config; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class JwtSecurityConfig { @Bean // CustomAuthenticationEntryPoint Bean 파라미터 주입(Spring DI) public SecurityFilterChain jwtFilterChain(HttpSecurity http, CustomAuthenticationEntryPoint customAuthenticationEntryPoint) throws Exception { return http .securityMatcher(\u0026#34;/jwt/**\u0026#34;) .csrf(AbstractHttpConfigurer::disable) .sessionManagement(session -\u0026gt; session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorize -\u0026gt; authorize .requestMatchers(\u0026#34;/jwt/public/**\u0026#34;).permitAll() .requestMatchers(\u0026#34;/jwt/user/**\u0026#34;).hasRole(\u0026#34;USER\u0026#34;) .requestMatchers(\u0026#34;/jwt/admin/**\u0026#34;).hasRole(\u0026#34;ADMIN\u0026#34;) .requestMatchers(\u0026#34;/jwt/token\u0026#34;).permitAll() .anyRequest().authenticated()) .exceptionHandling(handler -\u0026gt; handler .authenticationEntryPoint(customAuthenticationEntryPoint)) .build(); } } 또한, 지난 시간에 SecurityConfig 내부에 등록했던 mvcHandlerMappingIntrospector는 통합테스트에만 사용하기 때문에 IntegrationTestConfig 내부로 이동하겠습니다.\n이와 함께 테스트에 필요한 의존성들(JwtApiController, JwtUtil, CustomAuthenticationEntryPoint)도 @Import어노테이션을 활용해 IntegrationTestConfig 내부에서 불러오겠습니다.\n/* 테스트 경로(src \u0026gt; test \u0026gt; java \u0026gt; com \u0026gt; springsecurity \u0026gt; jwt \u0026gt; config)에 설정 클래스 생성 */ // IntegrationTestConfig.java package com.springsecurity.jwt.config; import com.springsecurity.jwt.api.JwtApiController; import com.springsecurity.jwt.utility.JwtUtil; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; @Configuration // IntegrationTest에 필요한 의존성 불러오기 @Import({JwtApiController.class, JwtUtil.class, CustomAuthenticationEntryPoint.class}) public class IntegrationTestConfig { @Bean(name = \u0026#34;mvcHandlerMappingIntrospector\u0026#34;) public HandlerMappingIntrospector mvcHandlerMappingIntrospector() { return new HandlerMappingIntrospector(); } } // JwtIntegrationTest.java /* 생략 */ @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = JwtSecurityConfig.class) @WebAppConfiguration // IntegrationTestConfig 불러오기 @Import({IntegrationTestConfig.class}) public class JWTIntegrationTest { MockMvc mockMvc; JwtUtil jwtUtil; @Autowired WebApplicationContext context; @BeforeEach void init() { // JwtUtil 객체 초기화 jwtUtil = new JwtUtil(); mockMvc = MockMvcBuilders.webAppContextSetup(context) .apply(springSecurity()).build(); } /* 생략 */ } 위와 같이 수정 후 테스트를 돌려보면, 다음과 같이 1 ~ 3번 테스트가 성공합니다.\nJWTAuthenticationFilter 단위테스트 작성 4 ~ 5번 테스트가 실패하는 원인은 현재 Authorization Header의 JWT를 통해 권한 정보를 불러오는 기능을 만들지 않았기 때문입니다.\n이를 구현하기 위해 JWT를 통해 권한을 가져오는 AuthenticationFilter를 SecurityFilterChain에 등록해야 합니다.7\n우선 다음과 같이 요구사항에 맞는 테스트를 작성합니다.\npackage com.springsecurity.jwt.config; import com.springsecurity.jwt.utility.JwtUtil; import jakarta.servlet.ServletException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import java.io.IOException; import static org.assertj.core.api.Assertions.assertThat; class JwtAuthenticationFilterTest { @Test @DisplayName(\u0026#34;Bearer 제거 테스트\u0026#34;) void testDeleteBearer() { String header = \u0026#34;Bearer test\u0026#34;; String token = header.split(\u0026#34;Bearer \u0026#34;)[1]; assertThat(token).isEqualTo(\u0026#34;test\u0026#34;); } @Test @DisplayName(\u0026#34;Bearer 파싱 테스트\u0026#34;) void testDeleteBearer2() throws ServletException, IOException { // given JwtUtil jwtUtil = new JwtUtil(); JwtAuthenticationFilter suit = new JwtAuthenticationFilter(jwtUtil); MockHttpServletRequest request = new MockHttpServletRequest(); request.addHeader(HttpHeaders.AUTHORIZATION, \u0026#34;Bearer \u0026#34; + jwtUtil.issue(\u0026#34;user\u0026#34;, \u0026#34;USER\u0026#34;)); // when suit.doFilterInternal(request, new MockHttpServletResponse(), new MockFilterChain()); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // then assertThat(authentication.getAuthorities()).anySatisfy(auth -\u0026gt; assertThat(auth.getAuthority()).isEqualTo(\u0026#34;ROLE_USER\u0026#34;)); } } JWTAuthenticationFilter 구현 다음과 같이 JwtAuthenticationFilter를 구현합니다.\n첫번째 단위테스트와 같은 방식으로 split()메서드를 통해 토큰의 prefix인 Bearer 를 제거할 수 있습니다.\npackage com.springsecurity.jwt.config; import com.springsecurity.jwt.Role; import com.springsecurity.jwt.utility.JwtUtil; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.List; @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String header = request.getHeader(HttpHeaders.AUTHORIZATION); if (header != null \u0026amp;\u0026amp; header.startsWith(\u0026#34;Bearer \u0026#34;)) { String token = header.split(\u0026#34;Bearer \u0026#34;)[1]; String name = jwtUtil.parseName(token); String role = jwtUtil.parseRole(token); SecurityContextHolder.getContext().setAuthentication( UsernamePasswordAuthenticationToken.authenticated(name, token, List.of(new SimpleGrantedAuthority(Role.getAuthority(role))))); } filterChain.doFilter(request, response); } } // Role.class /* 생략 */ public static String getAuthority(String role) { switch (Role.valueOf(role)) { case USER -\u0026gt; { return \u0026#34;ROLE_USER\u0026#34;; } case ADMIN -\u0026gt; { return \u0026#34;ROLE_ADMIN\u0026#34;; } } throw new EnumConstantNotPresentException(Role.class, role); } 해당 필터에서는 request header에 있는 토큰을 파싱하여 토큰에 명시된 권한이 있는 인증 객체를 생성하고, 이를 SecurityContext에 보관하는 역할을 수행합니다.\n이제 모든 단위테스트가 통과합니다.\n이후 다음과 같이 JwtSecurityFilter와 테스트 설정에 해당 클래스를 등록합니다.\n// JwtSecurityFilter.class /* 생략 */ @Bean // CustomAuthenticationEntryPoint, JwtAuthenticationFilter Bean DI public SecurityFilterChain jwtFilterChain(HttpSecurity http, CustomAuthenticationEntryPoint customAuthenticationEntryPoint, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception { return http .securityMatcher(\u0026#34;/jwt/**\u0026#34;) .csrf(csrf -\u0026gt; csrf.ignoringRequestMatchers(\u0026#34;/jwt/token\u0026#34;)) .sessionManagement(session -\u0026gt; session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // UsernamePasswordAuthenticationFilter 이후에 JWT 인증 필터 실행 .addFilterAfter(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .authorizeHttpRequests(authorize -\u0026gt; authorize .requestMatchers(\u0026#34;/jwt/public/**\u0026#34;).permitAll() .requestMatchers(\u0026#34;/jwt/user/**\u0026#34;).hasRole(\u0026#34;USER\u0026#34;) .requestMatchers(\u0026#34;/jwt/admin/**\u0026#34;).hasRole(\u0026#34;ADMIN\u0026#34;) .requestMatchers(\u0026#34;/jwt/token\u0026#34;).permitAll() .anyRequest().authenticated()) .exceptionHandling(handler -\u0026gt; handler .authenticationEntryPoint(customAuthenticationEntryPoint)) .build(); } /* 생략 */ // JwtIntegrationTest.class /* 생략 */ @Configuration // JwtAuthenticationFilter 추가 @Import({JwtApiController.class, JwtUtil.class, JwtAuthenticationFilter.class, CustomAuthenticationEntryPoint.class}) public class IntegrationTestConfig { @Bean(name = \u0026#34;mvcHandlerMappingIntrospector\u0026#34;) public HandlerMappingIntrospector mvcHandlerMappingIntrospector() { return new HandlerMappingIntrospector(); } } /* 생략 */ 이제 다음과 같이 모든 테스트케이스가 성공하는 것을 확인할 수 있습니다.\n기존 테스트들도 모두 성공하는지 돌려보겠습니다.\n리팩토링하는 과정에서 mvcHandlerMappingIntrospector를 다른 경로로 옮기면서 기존 테스트가 실패했습니다. 이를 성공시키기 위해 다음과 같이 설정을 변경합니다.\n// AuthenticationTest.class /* 생략 */ @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = SecurityConfig.class) @WebAppConfiguration // 통합테스트 설정(IntegrationConfig) 추가 @Import({ApiController.class, IntegrationTestConfig.class}) class AuthenticationTest { /* 생략 */ } 이제 모든 테스트가 성공합니다.\n또한, 다음과 같이 테스트 코드가 모든 코드의 커버리지를 만족하고 있습니다.\n모든 로직을 검증하고 있으니, 리팩토링 시마다 버그가 발생할 가능성도 현저히 낮아집니다. 이게 TDD의 가장 큰 장점입니다!\n결론 이번 시간에는 TDD를 활용해 JWT 인증 필터를 구현해보았습니다.\n분량이 너무 길어져서 한번 끊고 가야하나 생각했지만, TDD의 흐름이 끊기는 것 같아서 일단 한 포스트에 전부 작성했습니다. 다음부터는 개념적인 내용과 구현을 분리해서 분량을 잘 조절해보겠습니다.\n해당 프로젝트의 전체 코드는 다음 깃허브 링크에서 확인하실 수 있습니다. 다음 시간에는 Spring oauth2-resource-server를 활용해서 JWT를 구현해보겠습니다.\n다음 포스팅 [Java]Spring Security(With TDD) JWT 라이브러리 활용해서 간편하게 구현하기\nReferences URL 게시일자 방문일자 작성자 Spring 공식문서 - 2024.12.06. Spring RFC 7519 2015.05. 2024.12.06. IETF stateless는 무상태 프로토콜을 뜻하며, 웹 통신의 기초인 HTTP 프로토콜도 이러한 특성을 가집니다. 서버에서 세션이나 기타 상태를 보관하지 않기 때문에 수평적인 확장(Scale-out)이 용이합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\ncookie 저장소는 서버가 클라이언트에게 전송하는 작은 데이터 조각입니다. 웹 브라우저와 같은 대부분의 클라이언트는 이를 별도의 저장소에 저장하고 있다가, 서버의 응답에 이를 포함하여 전송합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nScale Out은 수평적 확장을 뜻합니다. 많은 사용자(트래픽)들을 감당하기 위해 서버는 동일한 역할을 수행하는 여러 개의 작은 서버를 두고, 부하를 골고루 분산하여 이에 대응합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nMSA는 느슨하게 결합된 작은 서비스들을 구조화하여 큰 서비스를 수행하는 개발 기법입니다. 큰 애플리케이션을 작은 단위로 모듈화하여 작은 팀들이 독립적으로 서비스를 개발, 확장, 리팩터링 가능하게 합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nrefresh token은 access token의 탈취를 대비하여 함께 발행하는 토큰입니다.\naccess token이 탈취되면 해당 사용자의 권한이 모두 탈취되기 때문에, 이를 방지하기 위해 보통 토큰 만료시간을 짧게 설정합니다. 이러한 경우, 사용자는 매번 다시 인증과정을 거쳐 토큰을 발급받아야 하는데, 이러한 번거로움을 줄이기 위해 유효한 Refresh Token을 보유한 사용자는 즉시 Access Token을 재발급해줍니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSpring oauth2-resource-server는 Spring Security와 가장 호환성이 좋은 방식으로 구현되었으며, 이미 검증된 라이브러리이기 때문에 안정성이 높습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n해당 인증 로직의 구체적인 아키텍쳐는 다음 포스트를 참고하시기 바랍니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/spring_security/5/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003ch3 id=\"지난-포스팅\"\u003e지난 포스팅\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/1\"\u003e[Java]Spring Security WebMVC 기본 구조\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/2\"\u003e[Java]Spring Security 예외처리, 캐싱, 로깅\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/3\"\u003e[Java]Spring Security 인증(Authentication)과 인가(Authorization)\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/4\"\u003e[Java]Spring Security(With TDD) 기본 인증 및 인가 구현하기\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e지금까지 \u003ccode\u003eSpring Security\u003c/code\u003e의 기본 개념을 학습하고, 기본 인증 및 인가를 구현했습니다.\u003c/p\u003e\n\u003cp\u003e이번 시간에는 \u003ccode\u003eJWT\u003c/code\u003e의 기본 개념을 익히고, 프로젝트의 인증 및 인가 로직에 활용해보겠습니다.\u003c/p\u003e\n\u003ch3 id=\"jwt\"\u003eJWT\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003eJWT\u003c/code\u003e는 \u003ccode\u003eJSON Web Token\u003c/code\u003e의 준말로 \u003ca href=\"https://datatracker.ietf.org/doc/html/rfc7519\"\u003eRFC 7519\u003c/a\u003e 명세에 정의된 토큰입니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e위 명세에 따르면, JWT는 다음과 같이 정의할 수 있습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003ccode\u003eJSON Web Token (JWT)는 두 당사자 간에 전송되는 클레임(claims)을 표현하기 위한 간결하고 URL에 안전한 수단입니다. JWT의 클레임은 JSON 객체로 인코딩되며, 이는 JSON Web Signature (JWS) 구조의 페이로드(payload)로 사용되거나 JSON Web Encryption (JWE) 구조의 평문(plaintext)으로 사용됩니다. 이를 통해 클레임은 디지털 서명되거나 메시지 인증 코드(Message Authentication Code, MAC)를 사용하여 무결성이 보호되거나 암호화될 수 있습니다.\u003c/code\u003e\u003c/p\u003e","title":"[Java]Spring Security(With TDD) JWT 직접 구현하기"},{"content":"도입 지난 포스팅 [Java]Spring Security WebMVC 기본 구조 [Java]Spring Security 예외처리, 캐싱, 로깅 [Java]Spring Security 인증(Authentication)과 인가(Authorization) 지금까지 Spring Security의 핵심 개념에 대해 알아보았습니다. 이번 시간에는 TDD1로 실제 프로젝트를 생성하고 인증 및 인가 로직을 구현해보겠습니다.\n프로젝트 생성 1. Springboot Project Setup Spring 프로젝트 생성 페이지에서 다음과 같이 세팅 및 4개의 의존성을 추가해줍니다. 2. 디렉터리 구조 생성 패키지는 다음과 같이 생성하겠습니다. api 클라이언트와의 통신 인터페이스입니다. 컨트롤러 클래스를 포함시킬 예정입니다. config Spring Security를 포함한 각종 설정 클래스를 포함시킬 예정입니다. 3. 기본 로그인 페이지 생성 Spring Security 의존성을 추가 후 JwtApplication.class의 Main 메서드를 실행한 뒤, 브라우저를 통해 http://localhost:8080에 접속하면 다음과 같이 기본적으로 로그인 페이지를 제공합니다. 하지만, 위 로그인 페이지의 패스워드는 마치 OTT2처럼, 매번 SpringBoot 서버가 실행될 때마다 패스워드가 변경됩니다.\n계정별 기본 패스워드를 설정하기 위해 예제 페이지를 참고해서 다음과 같이 config 패키지 내부에 SecurityConfig 클래스를 생성합니다.\npackage com.springsecurity.jwt.config; /* import 생략 */ @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public UserDetailsService userDetailsService() { List\u0026lt;UserDetails\u0026gt; userDetails = List.of( User.withDefaultPasswordEncoder().username(\u0026#34;user\u0026#34;).password(\u0026#34;user1234\u0026#34;).roles(\u0026#34;USER\u0026#34;).build(), User.withDefaultPasswordEncoder().username(\u0026#34;admin\u0026#34;).password(\u0026#34;admin1234\u0026#34;).roles(\u0026#34;ADMIN\u0026#34;).build()); return new InMemoryUserDetailsManager(userDetails); } @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } } 위와 같이 설정하면 User 계정과 Admin 계정을 통한 로그인이 가능해집니다.\nwithDefaultPasswordEncoder()메서드는 예제용이기 때문에 실제 프로젝트의 인증 로직에서 활용하면 취약할 수 있습니다.\n4. 기본 API 생성 다음과 같이 세 가지 API가 있다고 가정하겠습니다.\n누구나 접근 가능한 API USER 권한 이상의 사용자만 접근 가능한 API ADMIN 권한의 사용자만 접근 가능한 API 이러한 자원을 다음과 같이 api 패키지 내부의 ApiController로 구현하겠습니다.\npackage com.springsecurity.jwt.api; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ApiController { @GetMapping(\u0026#34;/admin/resources\u0026#34;) public String getAdminResources() { return \u0026#34;ADMIN 자원 획득\u0026#34;; } @GetMapping(\u0026#34;/user/resources\u0026#34;) public String getUserResources() { return \u0026#34;USER 자원 획득\u0026#34;; } @GetMapping(\u0026#34;/public/resources\u0026#34;) public String getPublicResources() { return \u0026#34;PUBLIC 자원 획득\u0026#34;; } } 여기까지는 프로젝트 구조를 빠르게 잡기 위해 테스트코드를 작성하지 않았습니다. 이후부터는 TDD 스타일로 테스트를 우선 작성하고, 실제 구현을 이어나가겠습니다.\n설계 요구사항 분석 인증 및 인가를 사용해서 달성하려는 요구사항은 다음과 같이 3가지로 분석할 수 있습니다.\n실제로는 훨씬 많은 요구사항이 있을 수 있지만, 예제이므로 최소한의 기능만을 테스트하겠습니다.\n권한이 없는 사용자가 인증이 필요한 요청 시 401 응답(인증 오류) 권한이 부족한 사용자가 상위 권한의 요청 시 403 응답(인가 오류) 적절한 권한이 있는 사용자는 인증이 필요한 요청 시 200 응답(Happy Case) TDD를 할때는 작성하기 쉬운 Happy Case보다 예외상황과 경계값 등 놓치기 쉬운 테스트케이스를 먼저 작성하는게 좋습니다.\n통합테스트 작성 다음 페이지를 참고해서 위 요구사항에 맞는 통합테스트를 작성해보겠습니다.\n보통 TDD는 단위테스트로 구현되지만, 외부 의존성인 Spring Security의 로직을 테스트하기 위해서는 Mocking3이 필수적입니다.\npackage com.springsecurity.jwt.config; import com.springsecurity.jwt.api.ApiController; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Import; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = SecurityConfig.class) @WebAppConfiguration @Import(ApiController.class) class AuthenticationTest { MockMvc mockMvc; @Autowired WebApplicationContext context; @BeforeEach void init() { mockMvc = MockMvcBuilders.webAppContextSetup(context) .apply(springSecurity()).build(); } @Test @DisplayName(\u0026#34;권한이 없는 사용자가 인증이 필요한 요청 시 401 응답(인증 오류)\u0026#34;) void testAuthenticationError() throws Exception { mockMvc.perform(get(\u0026#34;/user/resources\u0026#34;)) .andExpect(status().is(401)); } @Test @DisplayName(\u0026#34; 권한이 부족한 사용자가 상위 권한의 요청 시 403 응답(인가 오류)\u0026#34;) void testAuthorizationError() throws Exception { mockMvc.perform(get(\u0026#34;/admin/resources\u0026#34;).with(user(\u0026#34;user\u0026#34;).roles(\u0026#34;USER\u0026#34;))) .andExpect(status().is(403)); } @Test @DisplayName(\u0026#34;적절한 권한이 있는 사용자는 인증이 필요한 요청 시 200 응답(Happy Case)\u0026#34;) void testHappyCase() throws Exception { // 권한 없는 사용자 -\u0026gt; Public API mockMvc.perform(get(\u0026#34;/public/resources\u0026#34;)) .andExpect(status().is(200)); // User -\u0026gt; User API mockMvc.perform(get(\u0026#34;/user/resources\u0026#34;).with(user(\u0026#34;user\u0026#34;).roles(\u0026#34;USER\u0026#34;))) .andExpect(status().is(200)); // Admin -\u0026gt; User API mockMvc.perform(get(\u0026#34;/user/resources\u0026#34;).with(user(\u0026#34;admin\u0026#34;).roles(\u0026#34;ADMIN\u0026#34;))) .andExpect(status().is(200)); // Admin -\u0026gt; Admin API mockMvc.perform(get(\u0026#34;/admin/resources\u0026#34;).with(user(\u0026#34;admin\u0026#34;).roles(\u0026#34;ADMIN\u0026#34;))) .andExpect(status().is(200)); } } 위와 같이 mockMvc의 perform()메서드로 API 요청을 Mocking할 때, with()메서드의 파라미터로 SecurityMockMvcRequestPostProcessors.user()메서드를 사용하면 임시 사용자를 만들어 권한을 설정할 수 있습니다.\n구현 위에서 작성한 테스트를 실행해보면 인증 테스트는 통과하지만, 인가 테스트와 Happy Case가 실패합니다.\n현재 인증 관련 로직을 작성하지는 않았지만, Spring Security 의존성을 추가하면 자동으로 인증 관련 로직이 추가되기 때문에 모든 요청에서 401오류가 발생하여 첫번째 테스트가 통과합니다.\nAPI별 권한 부여 먼저, API별로 필요한 권한을 설정하기 위해 다음과 같이 SecurityConfig를 수정합니다.\n/* 생략 */ @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(authorize -\u0026gt; authorize // Public API는 모든 요청에 허용 .requestMatchers(\u0026#34;/public/**\u0026#34;).permitAll() // User API는 USER 권한 필요 .requestMatchers(\u0026#34;/user/**\u0026#34;).hasRole(\u0026#34;USER\u0026#34;) // Admin API는 ADMIN 권한 필요 .requestMatchers(\u0026#34;/admin/**\u0026#34;).hasRole(\u0026#34;ADMIN\u0026#34;)) .formLogin(Customizer.withDefaults()) .build(); } // MockMVC에서 HandlerMappingIntrospector를 사용하기 위함 @Bean(name = \u0026#34;mvcHandlerMappingIntrospector\u0026#34;) public HandlerMappingIntrospector mvcHandlerMappingIntrospector() { return new HandlerMappingIntrospector(); } /* 생략 */ mvcHandlerMappingIntrospector를 Bean으로 등록한 이유는 MockMvc가 실행될 때 해당 Bean이 없으면 requestMatcher를 정상적으로 실행하지 못하기 때문입니다.4\n다시 테스트를 실행해보면, 다음과 같이 2번째(인가) 테스트 케이스만 성공하는 것을 확인할 수 있습니다. 1번(인증) 테스트가 실패하는 원인은 SecurityFilterChain에 필요한 권한이 설정되었지만, Spring Security의 기본 정책 상 권한이 부족할 경우 로그인 페이지로 이동하는 302를 응답하기 때문입니다. 3번(Happy Case) 테스트가 실패하는 원인은 ADMIN 역할이 USER 역할을 갖고 있지 않기 때문에 User API에 접근할 권한이 없기 때문에 403을 응답하기 때문입니다. AuthenticationEntryPoint 설정 1번(인증) 테스트를 성공하기 위해 권한이 부족할 경우 401 응답을 보내도록 AuthenticationEntryPoint를 수정하겠습니다.5\n@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(authorize -\u0026gt; authorize .requestMatchers(\u0026#34;/public/**\u0026#34;).permitAll() .requestMatchers(\u0026#34;/user/**\u0026#34;).hasRole(\u0026#34;USER\u0026#34;) .requestMatchers(\u0026#34;/admin/**\u0026#34;).hasRole(\u0026#34;ADMIN\u0026#34;)) .formLogin(Customizer.withDefaults()) .exceptionHandling(handler -\u0026gt; handler .authenticationEntryPoint(new CustomAuthenticationEntryPoint())) .build(); } static class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setCharacterEncoding(\u0026#34;UTF-8\u0026#34;); response.sendError(401, \u0026#34;인증 오류\u0026#34;); } } 다시 테스트를 실행해보면, 다음과 같이 1, 2번째(인가) 테스트 케이스가 성공하는 것을 확인할 수 있습니다. RoleHierarchy 설정 마지막(Happy Case) 테스트를 성공하기 위해 다음과 같이 RoleHierarchy(계층적 권한)을 Bean으로 등록하겠습니다.6\n@Bean static RoleHierarchy roleHierarchy() { return RoleHierarchyImpl.withDefaultRolePrefix() .role(\u0026#34;ADMIN\u0026#34;).implies(\u0026#34;USER\u0026#34;) .build(); } 다시 테스트를 실행해보면, 드디어 모든 테스트가 성공합니다! 결론 지금까지 예제 프로젝트를 생성 후, 인증과 인가 요구사항을 통합테스트로 작성하고 모든 요구사항을 달성했습니다.\n해당 프로젝트의 전체 코드는 다음 깃허브 링크에서 확인하실 수 있습니다. 다음 포스팅에서는 현재 프로젝트에 JWT를 활용한 인증 및 인가를 도입해보겠습니다.\n다음 포스팅 [Java]Spring Security(With TDD) JWT 구현하기 References URL 게시일자 방문일자 작성자 Spring 공식문서 - 2024.12.06. Spring RFC 7519 2015.05. 2024.12.06. IETF 모의 객체 2024.05.16. 2024.12.07. Wikipedia Spring 이슈 20848 2017.12.14. 2024.12.07. spring-projects-issues Test Driven Development의 준말로, 단위 테스트를 먼저 작성한 후 구현하는 방식의 개발 방법론입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nOne Time Token의 준말로, 1회성 토큰을 뜻합니다. 웹에서 회원가입을 할 때, 이메일이나 문자로 받는 토큰이 여기에 해당합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n모의 객체를 생성하여 테스트하는 것을 말합니다.\n실제 모듈을 사용하는 테스트는 변경에 취약하고 깨지기 쉬울 뿐 아니라 내부 로직을 잘 이해하지 못하면 작성이 어렵기 때문에, 외부 의존성에는 Mocking을 활용하여 효율적으로 테스트할 수 있습니다.\n\u0026#160;\u0026#x21a9;\u0026#xfe0e; 관련 내용은 다음 이슈를 참고하시기 바랍니다.\n원래 테스트를 성공시키기 위해 설정 코드를 추가하는 것은 바람직하지 않으나, 아직은 프로젝트 규모가 크지 않고 테스트에 집중하기 위해 별도의 테스트 설정 파일을 생성하지는 않았습니다.\n\u0026#160;\u0026#x21a9;\u0026#xfe0e; AuthenticationEntryPoint에 대한 자세한 설명은 인증 오류 관련 이전 포스팅을 참고바랍니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nRoleHierarchy에 대한 자세한 설명은 인가 관련 이전 포스팅을 참고바랍니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/spring_security/4/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003ch3 id=\"지난-포스팅\"\u003e지난 포스팅\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/1\"\u003e[Java]Spring Security WebMVC 기본 구조\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/2\"\u003e[Java]Spring Security 예외처리, 캐싱, 로깅\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/3\"\u003e[Java]Spring Security 인증(Authentication)과 인가(Authorization)\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e지금까지 Spring Security의 핵심 개념에 대해 알아보았습니다.\n이번 시간에는 TDD\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e로 실제 프로젝트를 생성하고 인증 및 인가 로직을 구현해보겠습니다.\u003c/p\u003e\n\u003ch2 id=\"프로젝트-생성\"\u003e프로젝트 생성\u003c/h2\u003e\n\u003ch3 id=\"1-springboot-project-setup\"\u003e1. \u003cstrong\u003eSpringboot Project Setup\u003c/strong\u003e\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://start.spring.io/\"\u003eSpring 프로젝트 생성 페이지\u003c/a\u003e에서 다음과 같이 세팅 및 4개의 의존성을 추가해줍니다.\n\u003cimg alt=\"start spring\" loading=\"lazy\" src=\"/posts/spring_security/4/spring_init.png\"\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"2-디렉터리-구조-생성\"\u003e2. \u003cstrong\u003e디렉터리 구조 생성\u003c/strong\u003e\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e패키지는 다음과 같이 생성하겠습니다.\n\u003cimg alt=\"디렉터리 구조\" loading=\"lazy\" src=\"/posts/spring_security/4/directory.png\"\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eapi\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003e클라이언트와의 통신 인터페이스입니다.\u003c/li\u003e\n\u003cli\u003e컨트롤러 클래스를 포함시킬 예정입니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003econfig\u003c/strong\u003e\n\u003cul\u003e\n\u003cli\u003eSpring Security를 포함한 각종 설정 클래스를 포함시킬 예정입니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"3-기본-로그인-페이지-생성\"\u003e3. \u003cstrong\u003e기본 로그인 페이지 생성\u003c/strong\u003e\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eSpring Security 의존성을 추가 후 \u003ccode\u003eJwtApplication.class\u003c/code\u003e의 Main 메서드를 실행한 뒤, 브라우저를 통해 http://localhost:8080에 접속하면 다음과 같이 기본적으로 로그인 페이지를 제공합니다.\n\u003cimg alt=\"로그인 페이지\" loading=\"lazy\" src=\"/posts/spring_security/4/login.png\"\u003e\u003c/p\u003e","title":"[Java]Spring Security(With TDD) 기본 인증 및 인가 구현하기"},{"content":"출처 2021 KAKAO BLIND RECRUITMENT 순위 검색 접근 와일드카드 검색 와일드카드(-) 없이 검색을 구현하는 것은 각 지원자의 속성에 코딩테스트 점수를 저장하면, 속성별로 검색이 가능하기 때문에 큰 어려움이 없습니다.\n와일드카드가 없는 상황은 다음과 같이 트리 구조로 데이터를 저장할 수 있습니다. 트리 구조로 지원자 정보를 저장합니다. 리프 노드에는 리스트로 코딩테스트 점수를 저장합니다.\n하지만, 문제에서 주어진 것처럼 쿼리 시 와일드카드가 나오면 모든 정보를 포함하도록 탐색을 해야 하기 때문에 이를 구현하기 위해 별도의 공간에 와일드카드 속성들을 저장해야 합니다.\n와일드카드가 있는 상황에서 빠르게 검색을 하기 위해 와일드카드 트리를 별도로 생성해서 저장합니다. 와일드카드 노드를 별도로 저장합니다.\n위와 같이 와일드카드 노드를 별도로 생성하고 해당 노드의 단말에 코딩테스트 점수를 모두 저장하면, 해당 노드에 접근했을 때 O(1)의 시간복잡도로 모든 지원자 점수를 불러올 수 있습니다.\n저는 시간복잡도를 최적화하기 위해 루트 ~ 리프노드까지의 속성을 key로 갖는 해시맵 자료구조로 구현했지만, 메모리가 부족한 상황이라면 트리 구조로 구현하는 것이 더 좋습니다.\n이분 탐색 이렇게 저장된 지원자들의 코딩테스트 정보를 정렬한 뒤, 이분 탐색을 통해 빠르게 찾을 수 있습니다.\n리프 노드에 저장되어 있는 코딩테스트 정보들을 정렬한 뒤, 이분 탐색을 하면\n시간복잡도를 O(N) -\u0026gt; O(logN)으로 최적화할 수 있습니다. 이분 탐색으로 해당 점수(200)보다 크거나 같은 지원자들을 3번만에 찾을 수 있습니다.\n이 떄, 해당 점수 이상이 되는 모든 지원자들을 찾아야 하므로 Lower Bound를 찾은 뒤, 전체 지원자 숫자에서 빼주어야 합니다.\n풀이 import java.util.*; class Solution { Map\u0026lt;String, List\u0026lt;Integer\u0026gt;\u0026gt; map; public int[] solution(String[] info, String[] query) { int[] answer = new int[query.length]; map = new HashMap\u0026lt;\u0026gt;(); // DFS로 맵 생성 for (String i : info) { String[] is = i.split(\u0026#34; \u0026#34;); int score = Integer.parseInt(is[is.length - 1]); dfs(new StringBuilder(), 0, is, score); } // 생성된 리스트 정렬 for (List\u0026lt;Integer\u0026gt; l : map.values()) l.sort(Comparator.naturalOrder()); // 쿼리 수행 int idx = 0; for (String q : query) { String[] qs = q.split(\u0026#34; \u0026#34;); // 해시맵 키 생성 StringBuilder key = new StringBuilder(); for (int i = 0; i \u0026lt; qs.length; i+=2) key.append(qs[i]); int targetScore = Integer.parseInt(qs[qs.length - 1]); // NullPointer 방지 List\u0026lt;Integer\u0026gt; list = map.get(key.toString()); if (list == null) list = List.of(0); answer[idx++] = getCount(list, targetScore); } return answer; } void dfs(StringBuilder key, int cur, String[] infos, int score) { if (cur == infos.length - 1) { List\u0026lt;Integer\u0026gt; list = map.get(key.toString()); if (list == null) { list = new ArrayList\u0026lt;\u0026gt;(); map.put(key.toString(), list); } list.add(score); return; } // 기본 키 StringBuilder temp = new StringBuilder(key.toString()); temp.append(infos[cur]); dfs(temp, cur + 1, infos, score); // wildcard 키 temp = new StringBuilder(key.toString()); temp.append(\u0026#34;-\u0026#34;); dfs(temp, cur + 1, infos, score); } // BinarySearch로 정렬된 리스트에서 최소 위치 찾기 int getCount(List\u0026lt;Integer\u0026gt; list, int target) { int left = 0, right = list.size(), middle = 0; while (left \u0026lt; right) { middle = (left + right) / 2; if (list.get(middle) \u0026gt;= target) right = middle; else left = middle + 1; } return list.size() - left; } } 결과 리뷰 소요시간 : 2시간 초과 처음에 문제 아이디어를 떠올리는건 어렵지 않았으나, 트리를 구현하는 과정에서 너무 오랜 시간이 걸려셔 공식 풀이를 읽어보았습니다.\n사실 공식풀이에도 풀이 아이디어를 간략히 소개했을 뿐, 코드나 구현이 적혀있는 것은 아니어서 저에게는 큰 힌트가 되지 못했습니다.\n트리 뿐 아니라 이분탐색까지 활용해야 하는 점에서 자료구조의 특성을 이해하고 빠르게 탐색할 수 있는지를 묻는 문제인 것 같습니다.\n확실히 카카오 문제는 핵심 아이디어를 빠르게 구현하는 능력이 무엇보다 중요한 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 카카오 공식풀이 2021.01.25. 2024.12.03. Kakao tech ","permalink":"https://leaf-nam.github.io/cote/programmers_72412/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/72412\"\u003e2021 KAKAO BLIND RECRUITMENT 순위 검색\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"와일드카드-검색\"\u003e와일드카드 검색\u003c/h3\u003e\n\u003cp\u003e와일드카드(\u003ccode\u003e-\u003c/code\u003e) 없이 검색을 구현하는 것은 각 지원자의 속성에 코딩테스트 점수를 저장하면, 속성별로 검색이 가능하기 때문에 큰 어려움이 없습니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e와일드카드가 없는 상황은 다음과 같이 트리 구조로 데이터를 저장할 수 있습니다.\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"solve1.png\"\n         alt=\"트리 구조\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003e트리 구조로 지원자 정보를 저장합니다. 리프 노드에는 리스트로 코딩테스트 점수를 저장합니다.\u003c/p\u003e\n        \u003c/figcaption\u003e\n\u003c/figure\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e하지만, 문제에서 주어진 것처럼 쿼리 시 \u003cstrong\u003e와일드카드가 나오면 모든 정보를 포함하도록 탐색\u003c/strong\u003e을 해야 하기 때문에 이를 구현하기 위해 별도의 공간에 와일드카드 속성들을 저장해야 합니다.\u003c/p\u003e","title":"[Java]Programmers 순위 검색(2021 KAKAO BLIND RECRUITMENT)"},{"content":"출처 백준 개미 접근 완전탐색(DFS) 자식 방에서 부모(Root) 방향으로 DFS를 하면서 지상에 도달하는데 걸리는 시간을 확인합니다.\n개미 방의 개수가 최대 10^5이므로, 최악의 경우인 방이 일렬로 나열된 경우를 고려해야 합니다.1 선형적인 DFS로는 시간복잡도가 초과할 것이라고 생각해서 BinarySearch로 최적화를 했는데, 선형으로 조상들을 탐색해도 시간초과가 되지 않는 것으로 보아 DFS 만으로도 풀 수 있을 것 같습니다.\nTREE + BinarySearch 문제를 다시 읽어보면 자신 부모들의 집합, 즉, 조상들을 하나씩 타고 올라가면서 갈 수 있는 최대 조상의 번호를 반환해야 함을 알 수 있습니다.\n조상의 정보를 배열로 저장할 수 있다면, 현재 개미가 가진 에너지로 도달할 수 있는 위치를 이분탐색으로 찾을 수 있습니다.\n주어진 예제를 다음과 같이 Tree 형태로 그릴 수 있습니다. 예제를 트리 형태로 나타낼 수 있습니다.\n각 노드에 조상들의 정보를 배열로 저장합니다. 각 노드별로 조상의 정보를 저장합니다.\n위와 같이 본인을 포함한 자신의 조상들의 정보(조상노드 번호(num), 다음 부모까지의 거리(dist))를 배열에 저장할 수 있습니다. 예를 들어, 3번 노드는 부모인 2번 노드까지 거리가 10 소요되고, 조상인 1번 노드까지 거리가 20 소요되므로 조상 배열은 [[3, 10], [2, 20], [1, 20]]이 됩니다.\n최대로 갈 수 있는 조상 번호를 구합니다. 각 노드별로 조상의 정보를 저장합니다.\n문제에서 주어진 에너지를 통해 본인의 조상 노드 중 최대로 갈 수 있는 노드의 위치를 구합니다. 이 때, 조상과의 거리가 오름차순으로 증가하기 때문에, BinarySearch2를 통해 시간복잡도를 O(N) -\u0026gt; O(logN)으로 최적화할 수 있습니다. 테스트케이스에 최악의 시나리오는 없는지 이분탐색을 하지 않아도 시간초과가 발생하지는 않습니다.\n풀이 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.*; public class Main { // 방 노드 해시맵에 별도로 저장 static Map\u0026lt;Integer, Room\u0026gt; rooms = new HashMap\u0026lt;\u0026gt;(); public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine()); // 개미 에너지 초기화 int[] ants = new int[n + 1]; List\u0026lt;int[]\u0026gt;[] adjList = new List[n + 1]; for (int i = 1; i \u0026lt;= n; i++) { ants[i] = Integer.parseInt(br.readLine()); adjList[i] = new ArrayList\u0026lt;\u0026gt;(); } // 간선 리스트 초기화 StringTokenizer st; for (int i = 1; i \u0026lt; n; i++) { st = new StringTokenizer(br.readLine()); int from = Integer.parseInt(st.nextToken()); int to = Integer.parseInt(st.nextToken()); int dist = Integer.parseInt(st.nextToken()); adjList[from].add(new int[]{to, dist}); adjList[to].add(new int[]{from, dist}); } // DFS로 개미집 초기화 Room root = new Room(1); rooms.put(1, root); dfs(root, adjList); // 각 개미들의 Root 찾기 StringBuilder answer = new StringBuilder(); for (int i = 1; i \u0026lt;= n; i++) { answer.append(rooms.get(i).getFarRoomLeaner(ants[i])); answer.append(\u0026#34;\\n\u0026#34;); } System.out.println(answer); } // DFS로 전체 개미집 구조 생성 static void dfs(Room root, List\u0026lt;int[]\u0026gt;[] adjList) { Stack\u0026lt;Room\u0026gt; stack = new Stack\u0026lt;\u0026gt;(); boolean[] visited = new boolean[adjList.length]; stack.push(root); visited[1] = true; while (!stack.isEmpty()) { Room cur = stack.pop(); // 부모 집으로부터 자식 집 생성 for (int[] child : adjList[cur.num]) { if (visited[child[0]]) continue; visited[child[0]] = true; Room croom = new Room(child[0], child[1], cur); stack.push(croom); rooms.put(child[0], croom); } } } static class Room { int num; Room parent; int[][] ancestor; List\u0026lt;Room\u0026gt; children; Room(int num) { this.num = num; this.children = new ArrayList\u0026lt;\u0026gt;(); this.ancestor = new int[][] {{1, 0}}; rooms.put(num, this); } // 자식의 조상은 부모 조상에 현재 굴 길이 추가하기 Room(int num, int dist, Room parent) { this.num = num; this.parent = parent; this.ancestor = new int[parent.ancestor.length + 1][2]; this.children = new ArrayList\u0026lt;\u0026gt;(); for (int i = 0; i \u0026lt; parent.ancestor.length; i++) { // 현재 ~ 부모까지의 거리를 모든 조상 거리에 더해줌 ancestor[i + 1] = new int[] {parent.ancestor[i][0], parent.ancestor[i][1] + dist}; } // 첫번째 조상에 [자신, 부모까지의 거리] 추가 ancestor[0] = new int[] {this.num, dist}; } // 선형으로 가장 멀리 떨어진 방 찾기 public int getFarRoomLeaner(int energy) { for (int[] ints : ancestor) { if (ints[1] \u0026gt; energy) return ints[0]; } return ancestor[ancestor.length - 1][0]; } // 이분탐색으로 가장 멀리 떨어진 방 찾기 public int getFarRoomBinary(int energy) { int left = 0, right = ancestor.length; while (left \u0026lt; right) { int middle = (left + right) / 2; if (energy \u0026lt; ancestor[middle][1]) { right = middle; } else left = middle + 1; } return left == ancestor.length? 1 : ancestor[left][0]; } } } 결과 소요시간 : 2시간 초과 리뷰 선형탐색도 시간이 충분한데 이분탐색으로 안하면 시간복잡도가 터질 것 같아서 괜히 UpperBound를 구현한다고 더 시간이 걸린 것 같습니다.\n플래티넘 문제라서 당연히 시간이 빡빡할거라고 생각했는데\u0026hellip; 과한 최적화는 역시 항상 주의해야겠습니다.\nReferences URL 게시일자 방문일자 작성자 방이 일렬이라고 가정하면, 다음과 같이 최악의 경우에 Depth가 10^5인 DFS를 수행해야 합니다.\n개미굴이 일렬로 되어있으면 최대 깊이가 10000까지 커질 수 있습니다.\n방이 10^5개 이고, 깊이가 1씩 증가하니 총 방문횟수는 등차수열의 합공식에 의해 다음과 같습니다. 1 + 2 + ... + 10^5 = (10^5) * (10^5 + 1) / 2 ∴ O(N) = 10^10 \u0026#160;\u0026#x21a9;\u0026#xfe0e; 예를 들어, 다음과 같이 부모가 [[10, 1], [9, 10]], [7, 20], [5, 40], [4, 40], [3, 50], [1, 100]이고 개미의 에너지가 40인 경우, BinarySearch의 UpperBound를 활용하면 3번만에 최대 도달 가능한 조상의 위치인 3를 찾는 것이 가능합니다.\n\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/cote/bj_14942/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.acmicpc.net/problem/14942\"\u003e백준 개미\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"완전탐색dfs\"\u003e완전탐색(DFS)\u003c/h3\u003e\n\u003cp\u003e자식 방에서 부모(Root) 방향으로 DFS를 하면서 지상에 도달하는데 걸리는 시간을 확인합니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e개미 방의 개수가 최대 \u003ccode\u003e10^5\u003c/code\u003e이므로, 최악의 경우인 방이 일렬로 나열된 경우를 고려해야 합니다.\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e\n\u003cblockquote\u003e\n\u003cp\u003e선형적인 DFS로는 시간복잡도가 초과할 것이라고 생각해서 BinarySearch로 최적화를 했는데, 선형으로 조상들을 탐색해도 시간초과가 되지 않는 것으로 보아 DFS 만으로도 풀 수 있을 것 같습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"tree--binarysearch\"\u003eTREE + BinarySearch\u003c/h3\u003e\n\u003cp\u003e문제를 다시 읽어보면 자신 부모들의 집합, 즉, 조상들을 하나씩 타고 올라가면서 갈 수 있는 최대 조상의 번호를 반환해야 함을 알 수 있습니다.\u003c/p\u003e","title":"[Java]백준 14942 개미"},{"content":"도입 지난 포스팅 [Java]Spring Security WebMVC 기본 구조 [Java]Spring Security 예외처리, 캐싱, 로깅 Spring Security의 WebMVC와 기타 기능들에 이어 다양한 기능을 지원하는 인가 로직에 대해 알아보겠습니다.\n이전의 포스팅들과 이번 포스팅까지 제대로 이해한다면, 기본적인 Spring Security의 기능들을 사용하는데 큰 어려움이 없으실 겁니다.\n인증과 인가 너무 추상적이지만 Spring Security를 이해하는데 중요한 개념이므로 짚고 넘어가겠습니다.\n인증(Authentication) : 데이터나 시스템의 접근 권한을 가졌는지 검증하는 것입니다. 인가(Authorization) : 특정 리소스의 접근 권한을 가졌는지 확인하여 허용 또는 거부하는 것입니다. 즉, 인증은 로그인하는 행위 그 자체인 반면, 인가는 로그인한 사용자의 권한을 확인해서 접근 제어를 하는 것입니다.\n둘은 유사해 보이지만, 인증 오류(401)와 인가 오류(403)가 구분되어 있는 것처럼 보안에서 두 개념을 분리해서 생각하는 것은 중요합니다.1\n인증과 인가 : 인증된 사용자인지 먼저 확인하고, 이후 권한을 확인합니다.\nAuthentication 인증은 Spring Security의 핵심 로직입니다.\n인증이 성공하면, 사용자의 자격을 확인하고, 권한을 부여합니다. 인증이 실패하면, 실패 메시지를 전송하거나 인증 URL로 Redirect를 하는 등의 작업을 수행합니다. SecurityContext 현재 요청(사용자)의 인증 객체(Authentication)를 담고 있는 문맥, 혹은 컨테이너입니다. 인증이 완료된 인증 객체를 저장하는 역할을 합니다.\n이러한 Context는 쓰레드 로컬 저장소인 SecurityContextHolder 내부에 위치하고 있습니다.2 Authentication Object 사용자 식별자, 증명, 권한 등의 인증 정보를 담고 있는 객체입니다. 인증된 객체는 SecurityContext에 담아 요청 전역에서 사용할 수 있습니다.\nprincipal : 사용자 식별자입니다. 사용자를 구분할 수 있는 고유한 값이 필요합니다. credentials : 비밀번호와 같은 증명입니다. 인증이 완료된 이후 외부 노출을 막기 위해 초기화됩니다. authorities : 해당 사용자의 인증과 동시에 승인되는 권한입니다. 인가를 설명하면서 더 자세히 살펴보겠습니다. 인증된 객체는 ThreadLocal 내부에 저장되어 요청 전역에 사용됩니다.\nAuthenticationManager 인증을 관리하기 위한 인터페이스(API)입니다. Spring Security는 해당 인터페이스를 사용해서 인증을 수행하기 때문에 이를 구현해야 활용할 수 있습니다.\n만약 Spring Security를 사용하지 않고 Filter에서 직접 SecurityContext에 접근한다면 구현할 필요는 없습니다.\n다만, Spring Security에서 제공하는 다양한 캐싱과 로깅, 최적화 등의 기능을 잘 활용하려면 이를 구현하는게 좋습니다.\nProviderManager\nAuthenticationManager를 구현한 구현체입니다. 인증 서비스를 제공하는 AuthenticationProvider들을 리스트로 담아서 관리하며 인증 필요 시 사용자 요청에서 Id, Password, Token등을 확인하여 인증을 시도합니다.\n만약 AuthenticationProvider가 등록되지 않은 상태로 ProviderManager를 사용한다면 인증 관련 오류인 ProviderNotFoundException이 발생합니다.\nAuthenticationProvider\n인증을 제공하는 객체입니다. ProviderManager에 등록되어 순서대로 실행됩니다.\n기본적으로 AuthenticationProvider는 증명(credentials)을 외부에 노출하지 않기 위해 인증과 동시에 비우게 됩니다.3 인증 제공자(AuthenticationProvider)는 단순한 Username, Password 뿐 아니라, OTT, Anonymous 등 다양한 방식의 인증을 제공합니다. 인증 제공자들에 대한 자세한 설명은 공식 문서를 참고하시기 바랍니다.\n인증 관리자는 인증 제공자들을 리스트로 가지며, 인증 여부 확인을 위임하여 다양한 인증 수단을 활용합니다.\nAbstractAuthenticationProcessingFilter 인증의 주요 흐름을 담고 있는 필터입니다. ExceptionTranslationFilter에서 이미 살펴보았던 AuthenticationEntryPoint를 통해 받은 요청에서 사용자 정보(principals) 및 증명(credentials)을 가져와서 인증을 시도합니다.\n인증 필터의 주요 흐름\n인증 오류를 처리한다는 점에서 ExceptionTranslationFilter와 동작이 유사합니다. 다만, 내부적으로 발생할 수 있는 오류들을 Try-Catch로 잡아서 처리하기 때문에 인증 과정에서 실패하더라도 ExceptionTranslationFilter까지 도달하지 않고 설정된 AuthenticationFailureHandler를 사용합니다.4\nAuthorization 인증된 객체는 권한을 인가받게 되는데, 권한에 따라 자원(URL) 별로 접근제어가 가능합니다.\nAuthorities 위에서 설명한 것처럼, 인증 객체는 생성과 동시에 권한(authorities)을 리스트로 갖게 됩니다.\n승인된 권한이라는 뜻에서 GrantedAuthority라는 클래스의 객체로 저장됩니다.5 AuthorizationManager 인가를 관리하기 위한 인터페이스(API)입니다. AuthorizationManager는 승인된 권한을 바탕으로, 접근을 허용할지 아니면 거부할지를 결정합니다.\n내부적으로 다음과 같이 구현되어, 구현된 check 메서드를 default 메서드인 verify에서 위임하여 실행하는 식으로 동작합니다.\nAuthorizationDecision check(Supplier\u0026lt;Authentication\u0026gt; authentication, Object secureObject); default void verify(Supplier\u0026lt;Authentication\u0026gt; authentication, Object secureObject) throws AccessDeniedException { // ... } check\n해당 요청을 승인할지, 거부할지를 결정합니다. 승인 : new AuthorizationDecision(true)를 반환합니다. 요청을 정상적으로 진행합니다. 거부 : new AuthorizationDecision(false)를 반환합니다. AccessDeniedException 예외가 발생합니다. 이러한 API를 구현한 다양한 구현체가 있으며, 어플리케이션의 특성에 맞게 접근권한을 관리하는 것이 가능합니다.\n자세한 구현체들은 공식 문서에서 확인할 수 있으니, 본인의 상황에 맞는 구현체를 선택하거나, 필요 시 커스터마이징 하여 사용하시면 됩니다.\n인가 관리자 구현체(Spring Security Reference Doc)\n계층적 역할(Hierarchical Roles) 대부분의 어플리케이션에서 역할6은 특정한 계층을 가지고 있습니다.\n예를 들어, 관리자 권한은 사용자가 가진 모든 권한을 사용 가능하고 거기에 추가로 관리자의 권한이 더해지는 식입니다. 그러나 이러한 계층적 권한은 어플리케이션을 복잡하게 만들 수 있습니다. 전체 관리자와 중간 관리자, 사용자 세가지 권한이 있다고 하면 사용자의 권한을 중간관리자는 모두 가져야 하고, 전체 관리자는 중간 관리자와 사용자 관리자의 권한을 모두 가져야 하는 복잡한 상황이 발생합니다.\n이를 쉽게 구현하기 위해, Spring Security는 계층적으로 권한을 관리할 수 있는 API를 제공합니다. @Bean static RoleHierarchy roleHierarchy() { return RoleHierarchyImpl.withDefaultRolePrefix() .role(\u0026#34;ADMIN\u0026#34;).implies(\u0026#34;STAFF\u0026#34;) .role(\u0026#34;STAFF\u0026#34;).implies(\u0026#34;USER\u0026#34;) .role(\u0026#34;USER\u0026#34;).implies(\u0026#34;GUEST\u0026#34;) .build(); } @Bean static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) { DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); expressionHandler.setRoleHierarchy(roleHierarchy); return expressionHandler; } 위와 같이 설정하게 되면, ADMIN 계정은 STAFF, USER, GUEST 권한 목록을 갖고, STAFF 계정은 USER, GUEST 권한 목록을 갖고, USER 계정은 GUEST 권한 목록을 갖게 됩니다. 물론 위 설정에서처럼 MethodSecurityExpressionHandler를 통해 설정된 권한 계층을 AuthorizationManager에서 사용하지 않으면 적용되지 않습니다.\n역할 계층 구조도[ADMIN \u0026gt; STAFF \u0026gt; USER \u0026gt; GUEST] : 하위 계층의 권한을 상위 계층이 기본적으로 포함하고 있습니다.\n결론 Spring Security의 로직은 다음 API를 구현하여 활용할 수 있습니다.\n인증(Authentication) : AuthenticationManager 인가(Authorization) : AuthorizationManager 다음 포스팅에서는 Spring Security를 활용하여 TDD7 스타일로 인증 및 인가 로직을 개발해보겠습니다.\n다음 포스팅 [Java]Spring Security(With TDD) 기본 인증 및 인가 구현하기 References URL 게시일자 방문일자 작성자 인증(Authorization) 2024.11.22. 2024.11.29. Wikipedia 인가(Authentication) 2024.11.6. 2024.11.29. Wikipedia Understanding 403 Forbidden 2011.7.18. 2024.11.29. Daniel Irvine Spring Security Authentication - 2024.11.29. Spring Spring Security Authorization - 2024.12.02. Spring 401은 인증 오류이지만, 영문명은 Unauthorized입니다. 이러한 개념을 이해하기 좋은 문서가 있어 첨부합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSecurityContext는 사용자 요청별로 관리되어야 하기 때문에 Thread-Safety하도록 ThreadLocal 저장소에 보관됩니다.\nThread-Safety가 보장되지 않는다면 현재 사용자가 아닌 다른 사용자의 인증 객체에 접근할 가능성이 있어 보안 이슈가 발생할 수 있습니다.\n\u0026#160;\u0026#x21a9;\u0026#xfe0e; 만약 사용자 요청을 캐싱하여 반환할 경우, 증명(Credentials)이 지워진 상태로 저장될 수 있습니다. 따라서 캐싱된 요청을 다시 인증하는게 불가능하기 때문에 별도의 인증 로직을 구성하거나 이러한 옵션을 해제해야 합니다.\n캐싱된 인증은 자격증명이 삭제된 상태로 저장될 수 있습니다.\n\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n실제로 디버깅을 통해 로그인 요청이 실패하여 재로그인 시도 시 ExceptionTranslationFilter의 AuthenticationEntryPoint로 이동하지 않고, AbstractAuthenticationProcessingFilter내부의 AuthenticationFailureHandler를 사용하여 로그인 페이지로 Redirect 하는 것을 확인할 수 있었습니다. \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nGrantedAuthority객체는 기본적으로 SimpleGrantedAuthority 구현체를 사용할 수 있는데, 권한 요청 메서드인 getAuthority()를 실행했을 때 ROLE_형태의 Prefix를 가지는 권한(String)을 가져올 수 있습니다.\n이러한 권한 객체의 Prefix는 다음과 같이 변경해서 사용할 수 있습니다.\n@Bean static GrantedAuthorityDefaults grantedAuthorityDefaults() { return new GrantedAuthorityDefaults(\u0026#34;MYPREFIX_\u0026#34;); } \u0026#160;\u0026#x21a9;\u0026#xfe0e; 역할은 권한들의 집합입니다.\n예를 들어, 사용자 역할(ROLE_USER) 은 다음과 같이 로그인, 게시글 조회, 본인 게시글 수정, 본인 게시글 삭제 의 권한을 가질 수 있습니다.\nROLE_USER = {LOGIN_AUTHORITY, GET_POST_AUTHORITY, PATCH_MY_POST_AUTHORITY, DELETE_MY_POST_AUTHORITY}\n\u0026#160;\u0026#x21a9;\u0026#xfe0e; Test Driven Development의 준말로, 단위 테스트를 먼저 작성한 후 구현하는 방식의 개발 방법론입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/spring_security/3/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003ch3 id=\"지난-포스팅\"\u003e지난 포스팅\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/1\"\u003e[Java]Spring Security WebMVC 기본 구조\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/2\"\u003e[Java]Spring Security 예외처리, 캐싱, 로깅\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eSpring Security의 WebMVC와 기타 기능들에 이어 다양한 기능을 지원하는 인가 로직에 대해 알아보겠습니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e이전의 포스팅들과 이번 포스팅까지 제대로 이해한다면, 기본적인 Spring Security의 기능들을 사용하는데 큰 어려움이 없으실 겁니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"인증과-인가\"\u003e인증과 인가\u003c/h2\u003e\n\u003cp\u003e너무 추상적이지만 Spring Security를 이해하는데 중요한 개념이므로 짚고 넘어가겠습니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://en.wikipedia.org/wiki/Authentication\"\u003e인증(Authentication)\u003c/a\u003e : 데이터나 시스템의 접근 권한을 가졌는지 검증하는 것입니다.\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://en.wikipedia.org/wiki/Authorization\"\u003e인가(Authorization)\u003c/a\u003e : 특정 리소스의 접근 권한을 가졌는지 확인하여 허용 또는 거부하는 것입니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e즉, 인증은 로그인하는 행위 그 자체인 반면, 인가는 로그인한 사용자의 권한을 확인해서 접근 제어를 하는 것입니다.\u003c/p\u003e","title":"[Java]Spring Security 인증(Authentication)과 인가(Authorization)"},{"content":"출처 프로그래머스 숫자 타자 대회 접근 완전탐색 이전 숫자에서 왼손과 오른손을 움직여서 다음 숫자를 누르는 모든 경우를 방문하게 되면 매번 2번씩 방문이 발생합니다.\n1,000개의 숫자를 왼손과 오른손으로 모두 누르려면 2^1000의 시간복잡도가 필요합니다.1 이는 불가능하므로, 완전탐색만으로는 풀이할 수 없습니다.\nDP 문제에서 필요한 값은 최솟값이니, 완전탐색 중 최솟값들만을 저장하면서 계산하면 100*1000 = 10^6으로 시간복잡도를 줄일 수 있습니다.\n이전 값들 중 최솟값만 저장하면서 완전탐색을 최적화합니다.\n위 과정을 자세히 살펴보면, 처음 위치[l : 4], [r : 6]에서 0번 패드를 누르려면 2가지 경우가 발생합니다. 이 경우에는 중복되는 가짓수가 없으니 진행합니다. 다음 위치[l : 0, 4], [r : 6, 0]에서 1번 패드를 누르려면 각각 2가지씩 총 4가지 경우가 발생합니다. 이 경우에도 중복되는 가짓수가 없으니 진행합니다. 다음 위치[l : 1, 1, 0, 4], [r : 6, 0, 1, 1]에서 3번 패드를 누르려면 각각 4가지씩 총 8가지 경우가 발생합니다. 이 경우 더 작은 값만을 저장하여 가지치기가 가능합니다. 좀 더 자세히 살펴보기 위해, 다른 경우는 제외하고 첫번째 중복의 경우만 추적해보겠습니다. 두 경우의 총 가중치를 비교해서 작은 값만을 남깁니다.\n처음 위치[l : 4], [r : 6]에서 왼손을 옮겨 0으로 이동하면 가중치 5, 오른손을 옮겨 0으로 이동하면 가중치 5가 발생합니다. 다음 위치[l : 0, 4], [r : 6, 0]에서 오른손을 옮겨 1로 이동하면 가중치가 각각 5, 7이 발생합니다. 다음 위치[l : 1, 1, 0, 4], [r : 6, 0, 1, 1]에서 왼손을 옮겨 3으로 이동하면 가중치가 각각 7, 5가 발생합니다. 두 경우의 가중치는 모두 같으니 17을 저장합니다. 이후 탐색할 경우의 수가 1개 줄었습니다. 위와 같은 방식으로 중복되는 경우를 줄이면, 최대 100[l : 0 ~ 9, r : 0 ~ 9]개 이상으로 경우의 수가 커지지 않습니다.\n숫자패드 누르기 숫자패드를 누르는 것은 단순 구현이므로, 다양한 방법이 있습니다.\n모든 경우 직접 계산해보기\n가능한 경우를 하나씩 세면 2차원 배열로 나타낼 수 있습니다.\n// 가중치를 구하기 위한 배열 int[][] add = { {1, 7, 6, 7, 5, 4, 5, 3, 2, 3}, {7, 1, 2, 4, 2, 3, 5, 4, 5, 6}, {6, 2, 1, 2, 3, 2, 3, 5, 4, 5}, {7, 4, 2, 1, 5, 3, 2, 6, 5, 4}, {5, 2, 3, 5, 1, 2, 4, 2, 3, 5}, {4, 3, 2, 3, 2, 1, 2, 3, 2, 3}, {5, 5, 3, 2, 4, 2, 1, 5, 3, 2}, {3, 4, 5, 6, 2, 3, 5, 1, 2, 4}, {2, 5, 4, 5, 3, 2, 3, 2, 1, 2}, {3, 6, 5, 4, 5, 3, 2, 4, 2, 1}}; 함수 만들기\n각 숫자패드의 거리를 계산했을 때, 가로의 차이와 세로의 차이를 비교하여 전체 가중치를 구할 수 있습니다.\nint rd = Math.abs(sp[0] - ep[0]), cd = Math.abs(sp[1] - ep[1]); // 가로와 세로 거리차이가 없으면 같은 위치이므로 1 반환 if (rd == 0 \u0026amp;\u0026amp; cd == 0) return 1; // 가로와 세로의 거리차 중 작은 값만큼 대각선 이동(3) -\u0026gt; 이후 큰 값과 작은 값 차이만큼 평행 이동(2) return Math.min(rd, cd) * 3 + (Math.max(rd, cd) - Math.min(rd, cd)) * 2; 풀이 import java.util.*; class Solution { public int solution(String numbers) { char[] numbers_ = numbers.toCharArray(); // 가중치 저장을 위한 DP 배열 생성 int[][][] dp = new int[numbers_.length][10][10]; // 배열 내부의 값 초기화 Arrays.stream(dp).forEach(d -\u0026gt; Arrays.stream(d).forEach(p -\u0026gt; Arrays.fill(p, Integer.MAX_VALUE))); // 시작점 초기화 dp[0][numbers_[0] - \u0026#39;0\u0026#39;][6] = cal(numbers_[0] - \u0026#39;0\u0026#39;, 4); dp[0][4][numbers_[0] - \u0026#39;0\u0026#39;] = cal(numbers_[0] - \u0026#39;0\u0026#39;, 6); // DP를 통해 탐색 최적화 for (int i = 1; i \u0026lt; numbers_.length; i++) { int n = numbers_[i] - \u0026#39;0\u0026#39;; // 좌우 손가락을 움직이면서 최솟값만 저장 for (int l = 0; l \u0026lt; 10; l++) { for (int r = 0; r \u0026lt; 10; r++) { // 왼손과 오른손이 같은 위치에 올 수 없음 주의 if (l == r || dp[i - 1][l][r] == Integer.MAX_VALUE) continue; dp[i][n][r] = Math.min(dp[i][n][r], dp[i-1][l][r] + cal(n, l)); dp[i][l][n] = Math.min(dp[i][l][n], dp[i-1][l][r] + cal(n, r)); } } } // 마지막 배열에서 최솟값 반환 return Arrays.stream(dp[numbers_.length - 1]).flatMapToInt(Arrays::stream).min().orElse(0); } // 가중치 구하는 함수 int[][] pad = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {-1, 0, -1}}; int cal(int s, int e) { // 각 패드의 위치 좌표 구하기 int[] sp = null, ep = null; for (int r = 0; r \u0026lt; 4; r++) { for (int c = 0; c \u0026lt; 3; c++) { if (pad[r][c] == s) sp = new int[]{r, c}; if (pad[r][c] == e) ep = new int[]{r, c}; } } // 가로와 세로 가리 차이 구하기 int rd = Math.abs(sp[0] - ep[0]), cd = Math.abs(sp[1] - ep[1]); // 가로와 세로 거리차이가 없으면 같은 위치이므로 1 반환 if (rd == 0 \u0026amp;\u0026amp; cd == 0) return 1; // 가로와 세로의 거리차 중 작은 값만큼 대각선 이동(3) -\u0026gt; 이후 큰 값과 작은 값 차이만큼 평행 이동(2) return Math.min(rd, cd) * 3 + (Math.max(rd, cd) - Math.min(rd, cd)) * 2; } } 결과 소요시간 40:17\n리뷰 DP문제인 것은 쉽게 알 수 있었지만, 구현 난이도가 있는 편이라고 생각합니다. DP는 항상 그렇지만 많은 문제를 풀어보는것 외에는 정도가 없는 것 같습니다.\n손가락을 움직이는 가중치를 구현하는게 오히려 시간이 더 걸린 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 1초에 1억(10^8)번씩 계산해도 총 2^1000 / 10^8 = 10^300 / 10^8 = 10^292초가 소요됩니다. 1년이 대략 3*10^7라고 하니 10^292 / (3 * 10^7), 즉 3 * 10^284년이 걸립니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/cote/programmers_136797/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/136797\"\u003e프로그래머스 숫자 타자 대회\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"완전탐색\"\u003e완전탐색\u003c/h3\u003e\n\u003cp\u003e이전 숫자에서 왼손과 오른손을 움직여서 다음 숫자를 누르는 모든 경우를 방문하게 되면 매번 \u003ccode\u003e2\u003c/code\u003e번씩 방문이 발생합니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e1,000개의 숫자를 왼손과 오른손으로 모두 누르려면 \u003ccode\u003e2^1000\u003c/code\u003e의 시간복잡도가 필요합니다.\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e 이는 불가능하므로, 완전탐색만으로는 풀이할 수 없습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"dp\"\u003eDP\u003c/h3\u003e\n\u003cp\u003e문제에서 필요한 값은 최솟값이니, 완전탐색 중 최솟값들만을 저장하면서 계산하면 \u003ccode\u003e100*1000 = 10^6\u003c/code\u003e으로 시간복잡도를 줄일 수 있습니다.\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"solve1.png\"\n         alt=\"이전 값들 중 최솟값만 저장하면서 완전탐색을 최적화합니다.\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003e이전 값들 중 최솟값만 저장하면서 완전탐색을 최적화합니다.\u003c/p\u003e\n        \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cul\u003e\n\u003cli\u003e위 과정을 자세히 살펴보면,\u003c/li\u003e\n\u003c/ul\u003e\n\u003col\u003e\n\u003cli\u003e처음 위치\u003ccode\u003e[l : 4], [r : 6]\u003c/code\u003e에서 0번 패드를 누르려면 2가지 경우가 발생합니다. 이 경우에는 중복되는 가짓수가 없으니 진행합니다.\u003c/li\u003e\n\u003cli\u003e다음 위치\u003ccode\u003e[l : 0, 4], [r : 6, 0]\u003c/code\u003e에서 1번 패드를 누르려면 각각 2가지씩 총 4가지 경우가 발생합니다. 이 경우에도 중복되는 가짓수가 없으니 진행합니다.\u003c/li\u003e\n\u003cli\u003e다음 위치\u003ccode\u003e[l : 1, 1, 0, 4], [r : 6, 0, 1, 1]\u003c/code\u003e에서 3번 패드를 누르려면 각각 4가지씩 총 8가지 경우가 발생합니다. 이 경우 더 작은 값만을 저장하여 가지치기가 가능합니다.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003e좀 더 자세히 살펴보기 위해, \u003cstrong\u003e다른 경우는 제외\u003c/strong\u003e하고 첫번째 중복의 경우만 추적해보겠습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"solve2.png\"\n         alt=\"두 경우의 총 가중치를 비교해서 작은 값만을 남깁니다.\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003e두 경우의 총 가중치를 비교해서 작은 값만을 남깁니다.\u003c/p\u003e","title":"[Java]Programmers 숫자 타자 대회"},{"content":"출처 프로그래머스 혼자 놀기의 달인 접근 숫자 더미를 여러 그룹으로 분리한 뒤, 가장 큰 그룹과 두번째로 큰 그룹을 선택합니다.\n완전탐색(DFS) DFS를 통해 숫자 더미를 여러 그룹으로 분리할 수 있습니다.\n문제에서 주어진 카드의 최대 개수가 100 이하이므로, 한번씩 방문한다면 O(N)으로 해결이 가능합니다. 100개의 카드가 1개의 그룹인 경우, DFS의 최대 Depth가 100이므로 재귀 호출을 하더라도 StackOverFlow1가 발생하지 않습니다.\n재귀 호출을 통해 그룹 분리하기\n풀이 import java.util.*; class Solution { // 카드 그룹을 넣을 전역변수 List\u0026lt;Integer\u0026gt; group; public int solution(int[] cards) { group = new ArrayList\u0026lt;\u0026gt;(); // 방문 체크를 위한 변수 boolean[] visited = new boolean[cards.length + 1]; // 카드를 돌면서 그룹화 for (int card : cards) { // 이미 방문한 카드는 다시 방문하지 않음 if (visited[card]) continue; // DFS dfs(card, 0, cards, visited); } // 그룹이 1개면 0 반환 if (group.size() == 1) return 0; // 내림차순 정렬 후 가장 큰 그룹과 두번째로 큰 그룹 곱해서 반환 group.sort(Comparator.reverseOrder()); return group.get(0) * group.get(1); } // DFS void dfs(int now, int cnt, int[] cards, boolean[] visited) { // 이미 방문한 그룹이 다시 등장 -\u0026gt; 같은 사이클(그룹)이므로 그룹에 현재 개수 추가 if (visited[now]) { group.add(cnt); return; } // 방문 체크 visited[now] = true; // now에서 1을 빼줘야 배열 범위를 벗어나지 않음, 방문할 때마다 현재 그룹의 개수 1개씩 추가 dfs(cards[now - 1], cnt + 1, cards, visited); } } 결과 소요시간 7:41 리뷰 DFS를 통해 전체 탐색을 할 수 있는지 묻는 문제입니다.\n아이디어와 구현 난이도는 쉬운 편이지만, 실제로 코딩테스트에서 접했을 때 빠르게 풀 수 있는 연습이 필요할 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 Oracle Java 8 공식문서 - 2024.11.25. Oracle 프로그래머스 컴파일 옵션을 확인해보면 다음과 같이 스택 크기는 별도 설정되어 있지 않아 기본값을 사용하는 것을 알 수 있습니다. 프로그래머스 컴파일 옵션\nJVM의 -Xss 옵션을 지정하여 스택 크기를 증가시킬 수 있는데, 64비트에서 기본값은 1(MB)로 되어있습니다. Java 8의 기본 쓰레드 스택 사이즈\nOracle Java 8 공식문서 참고\n1MB(1,000,000Byte)의 스택 사이즈를 사용해서 100번의 재귀호출 시, 각 메서드의 메모리를 최대 10KB(10,000Byte)까지 사용 가능합니다.\n\u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/cote/programmers_131130/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/131130#\"\u003e프로그래머스 혼자 놀기의 달인\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cp\u003e숫자 더미를 여러 그룹으로 분리한 뒤, 가장 큰 그룹과 두번째로 큰 그룹을 선택합니다.\u003c/p\u003e\n\u003ch3 id=\"완전탐색dfs\"\u003e완전탐색(DFS)\u003c/h3\u003e\n\u003cp\u003eDFS를 통해 숫자 더미를 여러 그룹으로 분리할 수 있습니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e문제에서 주어진 카드의 최대 개수가 100 이하이므로, 한번씩 방문한다면 O(N)으로 해결이 가능합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e100개의 카드가 1개의 그룹인 경우, DFS의 최대 Depth가 100이므로 재귀 호출을 하더라도 StackOverFlow\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e가 발생하지 않습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"solve3.png\"\n         alt=\"재귀 호출을 통해 그룹 분리하기\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003e재귀 호출을 통해 그룹 분리하기\u003c/p\u003e\n        \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003ch2 id=\"풀이\"\u003e풀이\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.util.*\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eSolution\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 카드 그룹을 넣을 전역변수\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003egroup\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003esolution\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecards\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003egroup\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eArrayList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 방문 체크를 위한 변수\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003evisited\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ecards\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elength\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 카드를 돌면서 그룹화\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecard\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecards\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 이미 방문한 카드는 다시 방문하지 않음\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evisited\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ecard\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// DFS\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003edfs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecard\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecards\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003evisited\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 그룹이 1개면 0 반환\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003egroup\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esize\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 내림차순 정렬 후 가장 큰 그룹과 두번째로 큰 그룹 곱해서 반환\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003egroup\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esort\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eComparator\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereverseOrder\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003egroup\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003egroup\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// DFS\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003edfs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecnt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecards\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003evisited\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 이미 방문한 그룹이 다시 등장 -\u0026gt; 같은 사이클(그룹)이므로 그룹에 현재 개수 추가\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evisited\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003egroup\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecnt\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 방문 체크\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003evisited\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// now에서 1을 빼줘야 배열 범위를 벗어나지 않음, 방문할 때마다 현재 그룹의 개수 1개씩 추가\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003edfs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecards\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecnt\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecards\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003evisited\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"결과\"\u003e결과\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e소요시간 7:41\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cimg alt=\"result\" loading=\"lazy\" src=\"/cote/programmers_131130/result.png\"\u003e\u003c/p\u003e","title":"[Java]Programmers 혼자 놀기의 달인"},{"content":"상황 최근, Vercel을 활용해서 배포한 홈페이지에서 SSL 인증서 만료 관련 문구가 발생했습니다.\n인증서 만료일자가 오늘 이전이라 크롬으로 홈페이지 접속이 거부되었습니다.\n원래 Vercel에서 자동으로 인증서를 갱신하게 되어있는데, 갱신되지 않아서 만료가 되었습니다.\n원인 위 페이지 하단에서 다음과 같은 문구를 발견할 수 있었습니다.\n도메인 설정이 잘못되면, 갱신이 되지 않을 수 있습니다.\n도메인이 잘못 구성되어 있으면 인증서가 갱신되지 않을 가능성이 높습니다. 도메인의 구성 상태를 확인하려면 프로젝트 설정(Project settings)으로 이동한 후 도메인 섹션(Domains section)으로 이동하면 됩니다.\nTry1 : 해당 도메인 삭제 후 재등록 다음 페이지를 참고해서 도메인을 삭제 후 다시 등록하면 자동으로 설정이 가능하다고 하여 도메인 삭제 후 재등록을 시도했습니다.\n도메인 삭제 후 재등록 로그\nVercel의 Project Settings - dommains 에서 도메인을 삭제 후 재등록해보았습니다. 지금은 정상 등록되어 있습니다.\n지금은 정상이지만, 당시는 계속 Refresh가 반복되면서 Domain이 연결되지 않았습니다. 아마 인증서가 아직 남아있어서 그런 것 같았습니다.\nTry2 : 해당 프로젝트 삭제 후 재연결 아예 전체 프로젝트를 삭제하면 해당 SSL 인증서도 함께 삭제되지 않을까 하는 생각에 레포지토리를 완전히 삭제했습니다.\n프로젝트 삭제 후 재등록 로그\n그러나, 프로젝트를 삭제 후 다시 연결하고 원래 도메인을 설정하면 이전과 같이 Refresh가 반복되며 Domain이 연결되지 않았습니다. 아마 Vercel에서 해당 프로젝트 삭제 후에도 일정기간 SSL 인증서와 레포지토리 연결 정보를 보관하고 있어 재등록 시 해당 서버를 그대로 사용하는 것 같았습니다.\nTry3 : 가비아 TXT 도메인 변경 다음 페이지를 참고해서 가비아 DNS 관리 탭에서 레코드를 수정해보았습니다.\n가비아 DNS Record 설정 변경\n동일하게 Refresh가 반복되면서 Domain이 연결되지 않았습니다.\n해결! Vercel 공식문서를 보면서 해결방법을 찾던 중 Vercel 네임서버를 사용하는 것을 권장한다는 것을 발견했습니다.\nvercel 네임서버 사용을 권장합니다.\n자동 DNS 레코드: 네임서버가 Vercel로 지정된 도메인의 경우, 최상위 도메인(apex domain)이나 1단계 서브도메인(first-level subdomains)에 대해 명시적으로 DNS 레코드를 생성할 필요가 없습니다. 이러한 레코드들은 자동으로 생성됩니다. 이는 도메인이나 서브도메인을 프로젝트에 추가할 때 DNS 레코드에 대해 신경 쓸 필요가 없음을 의미합니다. 따라서 실수를 줄일 수 있을 뿐만 아니라, 프로젝트에서 사용하려는 여러 서브도메인이 있는 경우 각 서브도메인마다 CNAME 레코드를 수동으로 입력할 필요도 없어집니다.\n위 페이지를 통해 네임서버를 Vercel로 변경하면 자동 설정이 되면서 문제가 해결되지 않을까 하는 생각에 다음 페이지에서 가비아 네임서버를 Vercel 네임서버로 변경하였습니다.\n가비아 DNS Server 설정 변경\n이후 아래 페이지와 같이 자동으로 vercel 설정이 변경되면서 SSL인증서를 재발급받을 수 있었습니다.\nvercel DNS Record 자동 변경\n결론 가비아와 Vercel을 연동할때는 네임서버를 Vercel로 변경하는 것이 좋다! 공식문서를 잘보자! References URL 게시일자 방문일자 작성자 Vercel renewal certificates - 2024.11.19. Vercel Vercel working with nameservers - 2024.11.19. Vercel Vercel Discussion 1 2023.03.03. 2024.11.19. 3a1b2c3 Vercel Discussion 2 2023.08.18. 2024.11.19. amyegan ","permalink":"https://leaf-nam.github.io/tips/241120/","summary":"\u003ch2 id=\"상황\"\u003e상황\u003c/h2\u003e\n\u003cp\u003e최근, Vercel을 활용해서 배포한 홈페이지에서 SSL 인증서 만료 관련 문구가 발생했습니다.\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"expired.jpeg\"\n         alt=\"인증서 만료일자가 오늘 이전이라 크롬으로 홈페이지 접속이 거부되었습니다.\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003e인증서 만료일자가 오늘 이전이라 크롬으로 홈페이지 접속이 거부되었습니다.\u003c/p\u003e\n        \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cblockquote\u003e\n\u003cp\u003e원래 \u003ca href=\"https://vercel.com/guides/renewal-of-ssl-certificates-with-a-vercel-domain\"\u003eVercel에서 자동으로 인증서를 갱신\u003c/a\u003e하게 되어있는데, 갱신되지 않아서 만료가 되었습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"원인\"\u003e원인\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://vercel.com/guides/renewal-of-ssl-certificates-with-a-vercel-domain\"\u003e위 페이지\u003c/a\u003e 하단에서 다음과 같은 문구를 발견할 수 있었습니다.\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"renewal.png\"\n         alt=\"도메인 설정이 잘못되면, 갱신이 되지 않을 수 있습니다.\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003e도메인 설정이 잘못되면, 갱신이 되지 않을 수 있습니다.\u003c/p\u003e\n        \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003e도메인이 잘못 구성되어 있으면 인증서가 갱신되지 않을 가능성이 높습니다. 도메인의 구성 상태를 확인하려면 프로젝트 설정(Project settings)으로 이동한 후 도메인 섹션(Domains section)으로 이동하면 됩니다.\u003c/code\u003e\u003c/p\u003e","title":"[DNS] 가비아 도메인으로 Vercel 홈페이지 배포 시 SSL 인증서 만료 해결"},{"content":"출처 HackerRank No Prefix Set 문제 설명 주어진 단어 집합 중 Prefix(접두사)가 존재하는 단어가 있으면 BAD SET와 함께 처음으로 접두사가 발생한 단어를 출력합니다. 그런 단어가 없으면 GOOD SET을 출력합니다. 접근 완전탐색 가장 단순한 접근은 각 단어마다 나머지 단어들을 돌면서 prefix가 존재하는지 확인하는 것입니다. 문제에서 주어진 단어의 수는 최대 10^5 이므로, O(N^2)1 이상의 알고리즘을 사용할 수 없습니다. TRIE Trie2 자료구조를 통해 단어들의 탐색을 최적화할 수 있습니다.\nTrie 자료구조의 형태는 다음과 같습니다. Trie 자료구조 형태\n위와 같이 트리 형태로 알파벳을 저장하는 자료구조입니다.\n만약 ab라는 단어가 존재하는지 확인하려면 다음과 같이 트리의 높이인 두 번만에 탐색할 수 있습니다.\nab 찾기\n만약 존재하지 않는 단어(ak)가 있어도 트리의 높이만큼 탐색하면 바로 확인이 가능합니다.3 ak 찾기\n이러한 과정을 통해 단어가 존재하는지 탐색하는 과정을 O(N) -\u0026gt; O(logN) : 트리 높이로 줄일 수 있습니다.\n주의사항 예를 들어 [abc, bcd, b, abcd] 라는 Test Case에서 bcd 이후 b가 나오면 첫 오류가 발생한 시점인 b가 출력되어야 합니다.\n저도 구현 후 예외처리에서 막혀서 테스트 케이스를 하나 열어보고 알았습니다.\n풀이 // Trie 자료구조 정의 public static class Node { char c; boolean isExist; Node[] children; public Node(char c) { this.c = c; // 주어지는 알파벳 : a(0) ~ j(9) 이므로 크기 10으로 고정 children = new Node[10]; } // 다음 알파벳 가져오기 public Node next(char c) { Node node = children[c - \u0026#39;a\u0026#39;]; // 다음 위치의 알파벳이 없으면 새로 만들어서 넣기 if (node == null) { node = new Node(c); children[c - \u0026#39;a\u0026#39;] = node; } return node; } // 자식들이 모두 비어있다면, 다음 위치의 알파벳이 없음 public boolean hasNext() { for (Node c : children) { if (c != null) return true; } return false; } } public static void noPrefix(List\u0026lt;String\u0026gt; words) { // 루트 노드 초기화 Node root = new Node(\u0026#39;r\u0026#39;); for (String word : words) { // 루트부터 탐색 Node temp = root; for (char c : word.toCharArray()) { // 다음 알파벳 위치로 이동 temp = temp.next(c); // 다음 알파벳이 존재하는 문자열이면, prefix가 되므로 현재 단어 출력 if (temp.isExist) { System.out.println(\u0026#34;BAD SET\u0026#34;); System.out.println(word); return; } } // 마지막 위치까지 이동했는데 해당 위치의 자식이 존재하면 현재 단어가 prefix가 되므로 현재 단어 출력 if (temp.hasNext()) { System.out.println(\u0026#34;BAD SET\u0026#34;); System.out.println(word); return; } // 마지막 위치 표시해두기 temp.isExist = true; } System.out.println(\u0026#34;GOOD SET\u0026#34;); } 결과 리뷰 Trie 자료구조를 알면 바로 풀 수 있는 문제여서 난이도 자체는 크게 높지 않은 것 같습니다.\n하지만 Trie를 모르면서 풀 수 있는 사람은 몇이나 될지 모르겠습니다..\nReferences URL 게시일자 방문일자 작성자 2^10 = (약)10^3 이므로 주어진 단어의 수를 모두 도는데 필요한 시간복잡도는(10^5)^2 = 10^10 = 2^30 입니다. 이정도도 시간복잡도가 간당간당하지만, 단어의 길이가 최대 60이므로 시간초과가 발생합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nTrie의 어원은 검색을 뜻하는 Retrieval의 중간 이름에서 나왔다고 합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n다음과 같이 자식을 배열로 정의하고 a ~ i까지 알파벳 순서대로 채워넣으면, k(11)를 넣었을 때 존재 여부를 한번에 탐색이 가능합니다.\nchar[] children = new char[26]; for (int i = 0; i \u0026lt; 10; i++) { children[i] = i + \u0026#39;a\u0026#39;; } /* * 출력 결과 : null */ System.out.println(children[k - \u0026#39;a\u0026#39;]); \u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/cote/hackerrank_no_prefix_set/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.hackerrank.com/challenges/one-week-preparation-kit-no-prefix-set/problem\"\u003eHackerRank No Prefix Set\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"문제-설명\"\u003e문제 설명\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e주어진 단어 집합 중 Prefix(접두사)가 존재하는 단어가 있으면 \u003ccode\u003eBAD SET\u003c/code\u003e와 함께 \u003ccode\u003e처음으로 접두사가 발생한 단어\u003c/code\u003e를 출력합니다.\u003c/li\u003e\n\u003cli\u003e그런 단어가 없으면 \u003ccode\u003eGOOD SET\u003c/code\u003e을 출력합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"완전탐색\"\u003e완전탐색\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e가장 단순한 접근은 각 단어마다 나머지 단어들을 돌면서 prefix가 존재하는지 확인하는 것입니다.\u003c/li\u003e\n\u003cli\u003e문제에서 주어진 단어의 수는 최대 \u003ccode\u003e10^5\u003c/code\u003e 이므로, \u003ccode\u003eO(N^2)\u003c/code\u003e\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e 이상의 알고리즘을 사용할 수 없습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"trie\"\u003eTRIE\u003c/h3\u003e\n\u003cp\u003e\u003ca href=\"https://ko.wikipedia.org/wiki/%ED%8A%B8%EB%9D%BC%EC%9D%B4_(%EC%BB%B4%ED%93%A8%ED%8C%85)\"\u003eTrie\u003c/a\u003e\u003csup id=\"fnref:2\"\u003e\u003ca href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e2\u003c/a\u003e\u003c/sup\u003e 자료구조를 통해 단어들의 탐색을 최적화할 수 있습니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eTrie 자료구조의 형태는 다음과 같습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"solve1.png\"\n         alt=\"Trie 자료구조 형태\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003eTrie 자료구조 형태\u003c/p\u003e","title":"[Java]HackerRank No Prefix Set"},{"content":"상황 JPA를 사용해서 엔티티를 저장하는 로직에서 종종 발생하는 오류입니다.\n저는 DataJpaTest를 작성하다가 em.persist(member) 메서드를 실행하면 발생했습니다.\n오류 메시지 detached entity passed to persist오류 메시지는 다음과 같습니다.\ndetached entity passed to persist: pull_up.infra.database.entity.Member jakarta.persistence.EntityExistsException: detached entity passed to persist: pull_up.infra.database.entity.Member at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:126) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:167) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:173) at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:763) at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:741) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:569) at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:319) at jdk.proxy3/jdk.proxy3.$Proxy148.persist(Unknown Source) at pull_up.global.entity.BaseEntityTest.testCreateAt(BaseEntityTest.java:35) at java.base/java.lang.reflect.Method.invoke(Method.java:569) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: pull_up.infra.database.entity.Member at org.hibernate.event.internal.DefaultPersistEventListener.persist(DefaultPersistEventListener.java:88) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:77) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:54) at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127) at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:757) ... 11 more 해석하기 오류 메시지(detached entity passed to persist)를 그대로 해석하면 다음과 같습니다.\n분리된 엔티티를 저장하려고 시도했습니다.\n그렇다면 엔티티가 분리되었다는 것은 무슨 뜻일까요? JPA Javadoc 우선 가장 위에 적힌 오류인 EntityExistsException을 JPA Javadoc에서 찾아보았고, 다음과 같은 문구를 확인했습니다.\nThrown by the persistence provider when EntityManager.persist(Object) is called and the entity already exists. 이를 해석하면 다음과 같습니다.\nEntityManager.persist(Object) 메서드가 호출될 때, 해당 엔티티가 이미 존재하면 지속성 제공자(persistence provider)가 예외를 던집니다.\n대충 이미 저장된 엔티티를 다시 저장하려고 해서 발생했구나! 라고 눈치챌 수 있었습니다.\n그런데 저는 테스트 도중에 저장하지 않은 객체를 persist하려고 시도했는데 이러한 오류가 발생했습니다. 이에 좀더 찾아보았습니다.\nHibernate Javadoc 이번에는 구현체인 Hibernate에서 발생한 원인인 PersistentObjectException 오류를 Hibernate Javadoc에서 찾아보았습니다.\nThrown when the user passes a persistent instance to a Session method that expects a transient instance.\n이를 해석하면 다음과 같습니다.\n사용자가 세션(EntityManager 구현체)에게 임시 객체라고 판단되는 객체를 영구 객체로 전달했을때 발생합니다.\n즉, 세션(Entity Manager)에게 이미 저장된 임시 객체를 줘놓고 저장하라고 했으니 오류가 발생한 것입니다.\n그렇다면 세션은 이러한 객체를 어떻게 임시 객체라고 판단할까요?\nSpring Data JPA Reference Docs 이는 Hibernate를 공식 구현체로 사용하는 Spring Data JPA의 공식문서에 좀 더 자세히 나와있습니다.\nBy default Spring Data JPA inspects first if there is a Version-property of non-primitive type. If there is, the entity is considered new if the value of that property is null. Without such a Version-property Spring Data JPA inspects the identifier property of the given entity. If the identifier property is null, then the entity is assumed to be new. Otherwise, it is assumed to be not new.\n해석 아래는 위 원본을 해석한 것입니다.\n기본적으로 Spring Data JPA는 다음 순서로 엔티티를 검사하여 새 엔티티인지 판단합니다: Version 속성 확인1 Version 속성이 존재하고, 해당 속성이 **비-기본 타입(Wrapper 클래스 등)**일 경우, Version 속성의 값이 null이라면 새로운 엔티티로 간주합니다. Version 속성의 값이 null이 아니면 기존 엔티티로 간주합니다. Version 속성이 없는 경우 Identifier(식별자) 속성(Primary Key)을 검사합니다.\nIdentifier 속성의 값이 null이라면 새로운 엔티티로 간주합니다. Identifier 속성의 값이 null이 아니면 기존 엔티티로 간주합니다. 결론 detached entity passed to persist가 발생하는 주요 원인은 다음과 같습니다.\n이미 저장된 객체를 다시 저장하려고 시도했을 때 Version 속성이 null이 아닌 객체를 저장하려고 했을 떄 식별자(id)가 null이 아닌 객체를 저장하려고 했을 때 저의 경우는 테스트에서 임시로 생성한 Member 객체에 id를 설정한 뒤 저장하려고 시도해서 위 오류가 발생했습니다.\nReferences URL 게시일자 방문일자 작성자 JPA Javadoc - 2024.11.18. Jakarta Hibernate Javadoc - 2024.11.18. Hibernate SpringDataJPA 공식문서 - 2024.11.18. Spring Version 속성은 JPA의 낙관적 락(Optimistic Locking)을 설정할 때 주로 사용한다고 합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/tips/241118/","summary":"\u003ch2 id=\"상황\"\u003e상황\u003c/h2\u003e\n\u003cp\u003eJPA를 사용해서 엔티티를 저장하는 로직에서 종종 발생하는 오류입니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e저는 DataJpaTest를 작성하다가 \u003ccode\u003eem.persist(member)\u003c/code\u003e 메서드를 실행하면 발생했습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"오류-메시지\"\u003e오류 메시지\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003edetached entity passed to persist\u003c/code\u003e오류 메시지는 다음과 같습니다.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edetached\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eentity\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epassed\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epersist\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epull_up\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einfra\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003edatabase\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eentity\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eMember\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ejakarta\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003epersistence\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eEntityExistsException\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edetached\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eentity\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epassed\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epersist\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epull_up\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einfra\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003edatabase\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eentity\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eMember\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehibernate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eExceptionConverterImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003econvert\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eExceptionConverterImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e126\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehibernate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eExceptionConverterImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003econvert\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eExceptionConverterImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e167\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehibernate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eExceptionConverterImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003econvert\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eExceptionConverterImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e173\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehibernate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eSessionImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003efirePersist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSessionImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e763\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehibernate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eSessionImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003epersist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSessionImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e741\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ebase\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ejdk\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereflect\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eNativeMethodAccessorImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einvoke0\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eNative\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eMethod\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ebase\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ejdk\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereflect\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eNativeMethodAccessorImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einvoke\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eNativeMethodAccessorImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e77\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ebase\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ejdk\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereflect\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eDelegatingMethodAccessorImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einvoke\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eDelegatingMethodAccessorImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e43\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ebase\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elang\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereflect\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eMethod\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einvoke\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eMethod\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e569\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003espringframework\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eorm\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejpa\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eSharedEntityManagerCreator$SharedEntityManagerInvocationHandler\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einvoke\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSharedEntityManagerCreator\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e319\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejdk\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eproxy3\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ejdk\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eproxy3\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003e$Proxy148\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003epersist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eUnknown\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eSource\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epull_up\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eglobal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eentity\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eBaseEntityTest\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003etestCreateAt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eBaseEntityTest\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e35\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ebase\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003elang\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereflect\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eMethod\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einvoke\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eMethod\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e569\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ebase\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eutil\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eArrayList\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eforEach\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eArrayList\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e1511\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ebase\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eutil\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eArrayList\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eforEach\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eArrayList\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e1511\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eCaused\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eby\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehibernate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ePersistentObjectException\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edetached\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eentity\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epassed\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eto\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epersist\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epull_up\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einfra\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003edatabase\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eentity\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eMember\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehibernate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eevent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eDefaultPersistEventListener\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003epersist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eDefaultPersistEventListener\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e88\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehibernate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eevent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eDefaultPersistEventListener\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eonPersist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eDefaultPersistEventListener\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e77\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehibernate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eevent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eDefaultPersistEventListener\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eonPersist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eDefaultPersistEventListener\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e54\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehibernate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eevent\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eEventListenerGroupImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003efireEventOnEachListener\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eEventListenerGroupImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e127\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"n\"\u003eat\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eorg\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ehibernate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eSessionImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003efirePersist\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSessionImpl\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ejava\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"n\"\u003e757\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\t\u003c/span\u003e\u003cspan class=\"p\"\u003e...\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e11\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emore\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"해석하기\"\u003e해석하기\u003c/h2\u003e\n\u003cp\u003e오류 메시지(\u003ccode\u003edetached entity passed to persist\u003c/code\u003e)를 그대로 해석하면 다음과 같습니다.\u003c/p\u003e","title":"[Java] JPA detached entity passed to persist 오류 해결하기"},{"content":"공백 제거의 필요성 자바로 웹개발을 하다 보면, 입력값에 들어온 공백(White space)을 제거해야 할 일이 심심치 않게 있습니다. 특히, UTF-8에는 다음과 같은 다양한 공백이 있기 때문에 모든 경우의 수에 대응해야 합니다.\nU+0020 : 일반 스페이스 U+0009 : 탭(\\t) U+000A : 줄 바꿈(\\n) U+000D : 캐리지 리턴(\\r) U+000C : 폼 피드(\\f) U+3000 : 넓은 공백 공백 제거하기 String.trim() JDK 1.0부터 존재한 역사가 있는 메서드로, 문자열 앞뒤의 아스키 공백1을 내부 char배열에서 제거합니다.\n문자열 가운데는 제거하지 않으며, 나머지 유니코드 공백도 제거하지 않습니다.\n사용법\npublic class TrimExample { public static void main(String[] args) { String str = \u0026#34; Hello, Java! \u0026#34;; System.out.println(\u0026#34;Before trim: [\u0026#34; + str + \u0026#34;]\u0026#34;); System.out.println(\u0026#34;After trim: [\u0026#34; + str.trim() + \u0026#34;]\u0026#34;); } } /* * [결과] * Before trim: [ Hello, Java!　] * After trim: [Hello, Java!] */ String.strip() Java11에서 등장한 메서드로, trim()메서드와 다르게 문자열 앞뒤의 모든 유니코드 공백을 제거합니다.\n모든 유니코드 공백을 제거하는 정규 표현식인 \\\\s를 사용합니다.\n사용법 public class StripExample { public static void main(String[] args) { String str = \u0026#34;\\u2003Hello, Java!\\u3000\u0026#34;; // 유니코드 공백 포함 System.out.println(\u0026#34;Before strip: [\u0026#34; + str + \u0026#34;]\u0026#34;); System.out.println(\u0026#34;After strip: [\u0026#34; + str.strip() + \u0026#34;]\u0026#34;); } } /* * [결과] * Before strip: [ Hello, Java!　] * After strip: [Hello, Java!] */ String.replaceAll() 정규표현식을 직접 사용해서 공백을 제거할 수 있습니다. 다만, trim()이나 strip()과는 다르게 전체 문자열에서 공백을 제거합니다.\n사용법 public class replaceAllExample { public static void main(String[] args) { String str = \u0026#34;안 녕 하 세 요 \\t 여 기 는 UTF-8 공 백 입니다! \\u3000\u0026#34;; System.out.println(\u0026#34;Before replace: [\u0026#34; + str + \u0026#34;]\u0026#34;); System.out.println(\u0026#34;After replace: [\u0026#34; + str.replaceAll(\u0026#34;\\\\s+\u0026#34;, \u0026#34;\u0026#34;) + \u0026#34;]\u0026#34;); } } /* * [결과] * Before replace: [\u0026#34;안 녕 하 세 요 \\t 여 기 는 UTF-8 공 백 입니다! ] * After replace: [안녕하세요여기는UTF-8공백입니다!] */ References URL 게시일자 방문일자 작성자 String.trim() - 2024.11.16. Oracle String.strip() - 2024.11.16. Oracle String.replaceAll() - 2024.11.16. Oracle 일반 스페이스(U+0020)를 뜻합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/tips/241116/","summary":"\u003ch2 id=\"공백-제거의-필요성\"\u003e공백 제거의 필요성\u003c/h2\u003e\n\u003cp\u003e자바로 웹개발을 하다 보면, 입력값에 들어온 공백(White space)을 제거해야 할 일이 심심치 않게 있습니다.\n특히, \u003cstrong\u003eUTF-8에는 다음과 같은 다양한 공백\u003c/strong\u003e이 있기 때문에 모든 경우의 수에 대응해야 합니다.\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eU+0020 : 일반 스페이스\nU+0009 : 탭(\\t)\nU+000A : 줄 바꿈(\\n)\nU+000D : 캐리지 리턴(\\r)\nU+000C : 폼 피드(\\f)\nU+3000 : 넓은 공백\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"공백-제거하기\"\u003e공백 제거하기\u003c/h2\u003e\n\u003ch3 id=\"stringtrim\"\u003eString.trim()\u003c/h3\u003e\n\u003cp\u003eJDK 1.0부터 존재한 역사가 있는 메서드로, \u003cstrong\u003e문자열 앞뒤의 아스키 공백\u003c/strong\u003e\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e을 내부 char배열에서 제거합니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e문자열 가운데는 제거하지 않으며, 나머지 유니코드 공백도 제거하지 않습니다.\u003c/p\u003e","title":"[Java]String에서 공백 제거하기"},{"content":"출처 HackerRank New Year Chaos 문제 설명 영문 사이트이므로 문제를 간단히 설명하겠습니다.\n롤러코스터에 승객들이 n명 탑승중입니다. 각자는 탑승 순서대로 번호를 부여받습니다. 롤러코스터 승객들은 앞 사람과 최대 두 번까지 순서를 변경할 수 있습니다. bribe라는 단어를 처음 보고 해석이 안됐는데 \u0026lsquo;매수하다\u0026rsquo; 라는 뜻으로, 앞 사람 자리를 매수한다는 뜻인 것 같습니다.\n이렇게 변경된 롤러코스터의 상태가 큐로 주어집니다. 만약 3번 이상 변경한 사람이 있으면 \u0026ldquo;Too Chaotic\u0026quot;이라는 문자열을 출력합니다. 그런 사람이 없다면 승객들의 변경 횟수를 출력합니다. 접근 완전 탐색? 큐의 크기(n)가 최대 10^5이므로 O(N^2) 로직은 실행이 불가능하므로 완전탐색은 불가능합니다. Greedy 현재 승객이 타고있는 지점(idx)과 갖고 있는 번호의 관계를 통해 어떻게 변경이 이루어졌는지 유추해볼 수 있습니다.\n앞 사람과는 두 번 까지만 자리를 변경할 수 있습니다.\n본인의 현재위치가 실제위치보다 2칸 이상 차이나면 앞 사람과 두 번을 초과해서 변경했음을 알 수 있습니다. 따라서 \u0026ldquo;Too Chaotic\u0026quot;을 출력합니다.\n본인의 원래 위치보다 2칸 앞에서 현재 위치까지 확인합니다.\n본인의 실제 위치보다 2칸까지1 작은 위치를 보면서 본인보다 큰 사람이 있다면, 해당 사람과 교체했음을 알 수 있습니다. 자신보다 앞에 큰 번호가 있다는 것은 무조건 본인과 변경이 이루어져야 큰 번호가 그 위치로 갈 수 있기 때문입니다.\n풀이 public static void minimumBribes(List\u0026lt;Integer\u0026gt; q) { int cnt = 0; for (int i = 0; i \u0026lt; q.size(); i++) { // 본인의 현재위치가 실제위치보다 2칸 이상 차이나면 2번을 초과하여 변경 if (q.get(i) - (i + 1) \u0026gt; 2) { System.out.println(\u0026#34;Too chaotic\u0026#34;); return; } // 본인 실제위치보다 2칸 앞까지부터 현재 위치까지 중 본인보다 큰 숫자의 개수 세기 for (int j = q.get(i) - 2; j \u0026lt; i; j++) { // 0보다 작으면 배열 범위 초과오류 발생 if (j \u0026lt; 0) continue; if (q.get(j) \u0026gt; q.get(i)) cnt++; } } System.out.println(cnt); } 결과 리뷰 그리디는 아이디어만 확실히 이해하면 정말 쉬운데, 그걸 모르면 도저히 풀 수가 없는 것 같습니다.\n저도 이 문제를 세번째 풀고있는데도 이전 풀이가 기억이 안나면 풀 수 있을지 모르겠네요\u0026hellip; 다양한 문제를 풀어보는 것 말고는 방법이 없는 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 2칸 앞까지 확인하는 이유는 본인이 움직일 수 있는 범위가 원래 위치에서 2칸 이전 ~ 현재 위치까지이기 때문에 본인이 위치할 수 있는 모든 범위를 최적화하기 위함입니다.\n맨앞에서부터 현재 위치까지를 탐색할 경우 큐의 최대 크기가 10^5이므로 다음과 같이 시간초과가 발생합니다.\npublic static void minimumBribes(List\u0026lt;Integer\u0026gt; q) { int cnt = 0; for (int i = 0; i \u0026lt; q.size(); i++) { if (q.get(i) - (i + 1) \u0026gt; 2) { System.out.println(\u0026#34;Too chaotic\u0026#34;); return; } for (int j = 0; j \u0026lt; i; j++) { if (q.get(j) \u0026gt; q.get(i)) cnt++; } } System.out.println(cnt); } 맨앞에서부터 탐색 시 시간초과\n\u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/cote/hackerrank_new_year_chaos/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.hackerrank.com/challenges/one-week-preparation-kit-new-year-chaos/problem\"\u003eHackerRank New Year Chaos\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"문제-설명\"\u003e문제 설명\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e영문 사이트이므로 문제를 간단히 설명하겠습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003col\u003e\n\u003cli\u003e롤러코스터에 승객들이 n명 탑승중입니다. 각자는 탑승 순서대로 번호를 부여받습니다.\u003c/li\u003e\n\u003cli\u003e롤러코스터 승객들은 \u003cstrong\u003e앞 사람과 최대 두 번까지\u003c/strong\u003e 순서를 변경할 수 있습니다.\n\u003cblockquote\u003e\n\u003cp\u003ebribe라는 단어를 처음 보고 해석이 안됐는데 \u0026lsquo;매수하다\u0026rsquo; 라는 뜻으로, 앞 사람 자리를 매수한다는 뜻인 것 같습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003cli\u003e이렇게 변경된 롤러코스터의 상태가 큐로 주어집니다.\u003c/li\u003e\n\u003cli\u003e만약 3번 이상 변경한 사람이 있으면 \u0026ldquo;Too Chaotic\u0026quot;이라는 문자열을 출력합니다.\u003c/li\u003e\n\u003cli\u003e그런 사람이 없다면 승객들의 변경 횟수를 출력합니다.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"완전-탐색\"\u003e완전 탐색?\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e큐의 크기(n)가 최대 \u003ccode\u003e10^5\u003c/code\u003e이므로 \u003ccode\u003eO(N^2)\u003c/code\u003e 로직은 실행이 불가능하므로 완전탐색은 불가능합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"greedy\"\u003eGreedy\u003c/h3\u003e\n\u003cp\u003e현재 승객이 타고있는 지점(idx)과 갖고 있는 번호의 관계를 통해 어떻게 변경이 이루어졌는지 유추해볼 수 있습니다.\u003c/p\u003e","title":"[Java]HackerRank New Year Chaos"},{"content":"출처 Programmers 2개 이하로 다른 비트 접근 완전탐색 가장 직관적인 풀이는 주어진 숫자를 1씩 증가시키면서 현재 비트와의 개수 차이를 구하는 것입니다. 그러나 이렇게 풀이하면 주어진 조건에서 시간복잡도가 초과합니다. numbers[i] \u0026lt;= 10^15 == 2^501이므로 50개의 비트열로 표현되는데, 비트를 비교하는 과정에서 숫자가 1개 증가할 때마다 50^2 = 2500의 시간복잡도가 소모됩니다.2\n규칙성 찾기 완전탐색은 불가능하니 두 비트 중 다른 지점이 2개 이하이면서 가장 작은 수를 찾는 규칙을 찾아야 합니다. 현재 숫자와 다음 숫자의 이진수의 비트차이가 커지는 순간을 생각해보면, 맨 뒤에서부터 1이 쌓여있을 때 1을 더하면 비트차이가 크게 발생합니다. 255에서 256으로 증가하는 시점의 비트 차이\n또한, 이렇게 차이가 발생했을 때 비트 차이가 2이하로 줄어들게 하려면 맨 앞에서 두번째까지 0을 1로 채워야 합니다. 255과 비트차이를 2 이하로 하는 첫번째 숫자는 383입니다.\n이를 일반화하면, 뒤에서부터 1의 개수를 세고, 처음 0이 나오는 지점과 그 다음지점을 변경한 값이 정답이 됩니다. shift를 통해 정답 구하기\nString Utility 사용하기 간편하게 구현하기 위해 Long을 2진수의 String으로 변환한 뒤, 자릿수를 바꾸고 다시 Long으로 변환하는 방식으로 구현하겠습니다.\n자바에 특화된 구현이긴 하지만, String의 메서드를 활용하는 측면에서 도움이 될 것 같습니다.\nLong.toString(l, n) long 타입의 l를 n진수로 변환한 String을 반환합니다. Integer.toString(i, n)도 가능합니다.\nstring.lastIndexOf(s) s라는 문자열이 처음 등장하는 위치를 뒤에서부터 찾아서 반환합니다. string.substring(a, b) a부터 b까지의 부분 문자열을 반환합니다. 마지막은 b보다 작은 idx까지임을 주의합니다. a \u0026lt;= idx \u0026lt; b\nLong.parseLong(s, n) n진수의 s문자열을 long으로 변환합니다. 마찬가지로 Integer.parseInt(s, n)도 가능합니다.\n풀이 class Solution { public long[] solution(long[] numbers) { long[] answer = new long[numbers.length]; for (int i = 0; i \u0026lt; numbers.length; i++) { // 맨앞에 Padding 1개 + 이진수 문자열로 변환 String s = \u0026#34;0\u0026#34; + Long.toString(numbers[i], 2); // 첫번째로 0이 나오는 지점 찾기 int zero = s.lastIndexOf(\u0026#34;0\u0026#34;); // 2진수 문자열이 나온 지점이 뒤에서 두번째보다 작으면 오류가 발생하므로, 삼항연산자 사용 answer[i] = (s.length() - zero \u0026lt; 2)? // 오류 발생지점(xxx...01, xxx...10)은 현재 크기 + 1만 해주면 정답 numbers[i] + 1 : // 첫번째로 0이 나오는 지점과, 다음 지점을 변환(01 -\u0026gt; 10) 후 long으로 파싱 Long.parseLong(s.substring(0, zero) + \u0026#34;10\u0026#34; + s.substring(zero + 2), 2); } return answer; } } 결과 리뷰 문제 아이디어는 금방 떠올렸으나 구현하는데 생각보다 시간이 걸렸습니다.\nString 메서드를 찾아보지 않고 구현했을 때는 for문과 StringBuilder로 온몸비틀기3하며 구현했는데 확실히 편의메서드는 많이 알아두면 좋은 것 같습니다..\nReferences URL 게시일자 방문일자 작성자 2^10 = 1024는 약 10^3 이므로, 10^15 = (10^3)^5 = (2^10)^5 입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n문제에서 주어진 배열의 길이가 최대 100,000이므로 바로 다음 숫자가 정답이라고 가정해도 2500 x 100,000 = 250,000,000으로 시간복잡도를 초과합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nString 메서드를 사용하기 전 코드입니다.\nclass Solution { public long[] solution(long[] numbers) { long[] answer = new long[numbers.length]; for (int i = 0; i \u0026lt; numbers.length; i++) { answer[i] = getAns(numbers[i]); } return answer; } long getAns(long l) { String s = \u0026#34;0\u0026#34; + Long.toString(l, 2); int cnt = 0; for (int i = s.length() - 1; i \u0026gt;= 0; i--) { if (s.charAt(i) - \u0026#39;0\u0026#39; == 1) cnt++; else break; } if (cnt \u0026lt;= 1) return l + 1; int rcnt = s.length() - cnt; StringBuilder sb = new StringBuilder(); for (int i = 0; i \u0026lt; rcnt - 1; i++) sb.append(s.charAt(i)); sb.append(\u0026#34;10\u0026#34;); for (int i = rcnt + 1; i \u0026lt; s.length(); i++) sb.append(s.charAt(i)); return Long.parseLong(sb.toString(), 2); } } \u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/cote/programmers_77885/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/77885\"\u003eProgrammers 2개 이하로 다른 비트\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"완전탐색\"\u003e완전탐색\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e가장 직관적인 풀이는 주어진 숫자를 1씩 증가시키면서 현재 비트와의 개수 차이를 구하는 것입니다.\u003c/li\u003e\n\u003cli\u003e그러나 이렇게 풀이하면 주어진 조건에서 시간복잡도가 초과합니다.\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003enumbers[i] \u0026lt;= 10^15 == 2^50\u003c/code\u003e\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e이므로 50개의 비트열로 표현되는데, 비트를 비교하는 과정에서 숫자가 1개 증가할 때마다 \u003ccode\u003e50^2 = 2500\u003c/code\u003e의 시간복잡도가 소모됩니다.\u003csup id=\"fnref:2\"\u003e\u003ca href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e2\u003c/a\u003e\u003c/sup\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"규칙성-찾기\"\u003e규칙성 찾기\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e완전탐색은 불가능하니 두 비트 중 다른 지점이 2개 이하이면서 가장 작은 수를 찾는 규칙을 찾아야 합니다.\u003c/li\u003e\n\u003cli\u003e현재 숫자와 다음 숫자의 이진수의 비트차이가 커지는 순간을 생각해보면, 맨 뒤에서부터 1이 쌓여있을 때 1을 더하면 비트차이가 크게 발생합니다.\n\u003cfigure\u003e\n      \u003cimg loading=\"lazy\" src=\"solve1.png\"\n           alt=\"255에서 256으로 증가하는 시점의 비트 차이\"/\u003e \u003cfigcaption\u003e\n              \u003cp\u003e255에서 256으로 증가하는 시점의 비트 차이\u003c/p\u003e","title":"[Java]Programmers 2개 이하로 다른 비트"},{"content":"출처 해커 랭크 : Gridland Metro 문제 설명 영문 사이트이므로 문제를 간단히 설명하겠습니다.\n가로등을 설치해야 하는데, 철도가 있는 지점에는 가로등을 설치할 수 없습니다. 철도는 가로로만 설치되며, 철도끼리는 겹칠 수 있습니다. 문제에는 철도의 개수(k)와 철도의 시작점(r, c1)과 끝점(r, c2)이 주어지며, 이 때 가로등을 설치할 수 있는 지점의 개수를 구해야 합니다. 접근 시간복잡도 계산 철도의 개수 k \u0026lt;= 1000인 반면 전체 좌표의 크기 (n, m) \u0026lt; 10^9 입니다. 따라서 각 좌표를 한번씩 방문하는 것은 불가능하며 주어진 철도의 범위로 문제를 해결해야 합니다.\n각 Row에서 철도가 있는 지점 최적화 철도가 있는 지점을 빠르게 구하기 위해, 다음과 같은 라인 스위핑 알고리즘을 사용할 수 있습니다. 해당 알고리즘을 간단히 설명하자면, 특정한 선이 좌표나 평면을 이동하면서 특정 지점에서 한번씩 멈춰서 계산을 하고, 선이 모든 객체를 통과하면 계산이 종료되는 방식의 알고리즘입니다. 원래라면 각 지점을 한번씩 방문해서 해당 지점에 철도가 있는지 확인해야 하지만, 정렬 후 라인 스위핑을 통해 철도가 겹치는 지점을 넓혀가는 방식으로 최적화할 수 있습니다.\n다음은 (1x6) 크기의 지도에서 철도(k = 4)가 track = [[1, 1, 2], [1, 1, 3], [1, 2, 3], [1, 5, 6]] 으로 주어졌을 때 라인 스위핑을 통해 철도의 범위를 계산하는 과정입니다. 1x6 지도에 4개의 철도가 설치되어 있습니다.\n주어진 철도를 col의 시작점(track[1]) 기준으로 정렬합니다. 철도를 시작점 기준으로 정렬합니다.\n첫번째 철도를 탐색하면서 현재 철도의 시작점과 끝점인 left와 right를 설정합니다. 철도의 시작점과 끝점을 설정합니다.\n다음 철도의 시작점이 현재 철도의 끝점(right)보다 작다면 끝점을 늘립니다.\n철도의 끝점을 늘립니다.\n이 때, 끝점이 현재 철도의 끝점(right)보다 작다면([1,2,3]의 경우) 끝점을 늘릴 필요가 없습니다.\n다음 철도의 시작점이 현재 철도의 끝점보다 크다면, 현재까지 철도를 전체 크기에 추가하고 다시 시작점과 끝점을 설정합니다. 현재 철도를 추가한 뒤 해당 철도를 다시 시작점과 끝점으로 설정합니다.\n이제 위 알고리즘을 사용해서 전체 크기에서 철도가 있는 지점의 크기를 빼주면 됩니다.\nOverflow 문제에서 주어진 (n, m) \u0026lt;= 10^9 이라는 조건으로 전체 지도의 크기를 구하면 n * m \u0026lt;= 10^18 이므로 정수 범위를 초과하게 됩니다.\n-2^32 \u0026lt; int \u0026lt; 2^32 이므로, 대략 4 x -10^9 \u0026lt; int \u0026lt; 4 x 10^9 범위를 초과하면 Overflow가 발생합니다.\n따라서 전체 맵은 long 타입으로 선언해서 overflow를 방지해야 합니다.\n풀이 // [주의]반환값 long으로 변환 필요 public static long gridlandMetro(int n, int m, int k, List\u0026lt;List\u0026lt;Integer\u0026gt;\u0026gt; track) { // 철도 정렬 : row 순 -\u0026gt; 철도 시작점 순 track.sort((o1, o2) -\u0026gt; { if (o1.get(0) == o2.get(0)) return Integer.compare(o1.get(1), o2.get(1)); return Integer.compare(o1.get(0), o2.get(0)); }); // 전체 맵 크기 초기화(overflow 주의) long total = (long)n * m; int r = 0, left = 0, right = 0; // 전체 철도 탐색 for (List\u0026lt;Integer\u0026gt; t : track) { // 현재 row보다 큰 값이면 새로운 row에서 탐색하기 위해 변수 초기화 if (r \u0026lt; t.get(0)) { r = t.get(0); // 저장된 철도의 크기가 0보다 크면 전체 맵에서 제외 if (right \u0026gt; 0) total -= (right - left + 1); left = 0; right = 0; } // 철도 시작점이 현재 오른쪽보다 크다면 현재까지의 철도 크기 전체 맵에서 제외 if (t.get(1) \u0026gt; right) { if (right \u0026gt; 0) total -= (right - left + 1); // 현재 철도로 다시 세팅 left = t.get(1); right = t.get(2); } // 철도 시작점이 현재 오른쪽보다 작을 때, right 갱신(right가 현재보다 작다면 갱신하지 않음) else if (t.get(2) \u0026gt; right) right = t.get(2); } // 남아있는 철도 전체 맵에서 제외 if (right \u0026gt; 0) total -= (right - left + 1); return total; } 결과 리뷰 주어진 조건을 토대로 정렬 후 탐색을 최적화하는 방법을 아는지 묻는 문제였습니다.\n정렬된 상태에서 탐색하는 기법은 투포인터나 이분탐색만 알고 있었는데 sweep line을 통해 탐색하는 방법을 추가로 배울 수 있었습니다.\n또한, 처음 주어진 반환값이 int여서 overflow를 의심하지 않았는데, 히든 케이스를 하나 까보니 overflow가 발생할 수 있음을 알게 되었습니다.\nHackerRank에서는 주어진 템플릿을 크게 신뢰하면 안되는 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 Sweep line algorithm 2023.11.20. 2024.11.13. Wikipedia ","permalink":"https://leaf-nam.github.io/cote/hackerrank_gridland_metro/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.hackerrank.com/challenges/gridland-metro/problem\"\u003e해커 랭크 : Gridland Metro\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"문제-설명\"\u003e문제 설명\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e영문 사이트이므로 문제를 간단히 설명하겠습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003col\u003e\n\u003cli\u003e가로등을 설치해야 하는데, 철도가 있는 지점에는 가로등을 설치할 수 없습니다.\u003c/li\u003e\n\u003cli\u003e철도는 가로로만 설치되며, \u003cstrong\u003e철도끼리는 겹칠 수 있습니다.\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003e문제에는 철도의 개수(k)와 철도의 시작점(r, c1)과 끝점(r, c2)이 주어지며, 이 때 가로등을 설치할 수 있는 지점의 개수를 구해야 합니다.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"시간복잡도-계산\"\u003e시간복잡도 계산\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e철도의 개수 k \u0026lt;= 1000인 반면 전체 좌표의 크기 (n, m) \u0026lt; 10^9 입니다.\n\u003cblockquote\u003e\n\u003cp\u003e따라서 각 좌표를 한번씩 방문하는 것은 불가능하며 주어진 철도의 범위로 문제를 해결해야 합니다.\u003c/p\u003e","title":"[Java]HackerRank Gridland Metro"},{"content":"출처 해커 랭크 : Non Divisible Subset 문제 설명 영문 사이트이므로 문제를 간단히 설명하겠습니다. 중복되지 않는 정수 배열(집합)이 주어집니다. 문제 예시에는 10이 중복되어 있는데 오타인 것 같습니다.\nK가 주어졌을 때, 두 수의 합이 k로 나누어 떨어지지 않는 부분 집합을 찾는 문제입니다. 부분 집합에서 임의의 두 수를 골라도 k로 나누어 떨어지지 않아야 하며, 부분 집합이 여러개가 있을 때는 최대 길이를 구해야 합니다. 접근 완전탐색 부분 집합을 만들 수 있는 각각의 경우에서 2개씩 고른 뒤 K로 나누어 떨어지는지를 일일히 확인하면 시간복잡도를 초과합니다. O(N! x nC_2)\n시간복잡도 줄이기 Modular 연산을 통해 시간복잡도 줄이기\nGreedy한 접근을 위해 Modular연산을 통해 문제를 단순화할 수 있습니다. 두 수의 합이 k로 나누어 떨어진다면, 두 수를 각각 k로 나머지 연산을 한 뒤, 나머지끼리 더했을 때 k가 된다. 이렇게 Modular 연산을 통해 두 수의 합이 k로 나누어 떨어지는지 확인하면, 집합의 원소를 K크기의 배열로 줄일 수 있습니다. S[i] \u0026lt;= 10^9, k \u0026lt;= 100 이므로 시간복잡도를 크게 줄일 수 있습니다.\n따라서 각 배열의 원소를 k로 나눈 뒤, 이러한 나머지가 몇 개 있는지 세면 Greedy하게 푸는 것이 가능합니다. Greedy 나머지 원소들끼리 합해서 k가 되는 경우 찾기\n위와 같이 나머지들을 정렬하면 나머지가 k/2인 값을 기준으로 두 수의 합이 k가 될 때 더 큰 값만 부분집합에 추가하는 방식으로 구현할 수 있습니다. 이 때, 주의해야 할 원소는 나머지가 0이거나 k/2와 같을 때입니다. 이 때는 부분집합에 최대 1개만 추가하는 것이 가능합니다. 나머지가 0인 원소가 2개 모이면 k로 나누어 떨어지고, 마찬가지로 k/2인 원소가 2개 모이면 k로 나누어 떨어지기 때문입니다.\n풀이 public static int nonDivisibleSubset(int k, List\u0026lt;Integer\u0026gt; s) { // 나머지 연산 후 각 나머지의 개수를 저장할 배열 int[] cnt = new int[k]; for (int i : s) cnt[i % k]++; int ret = 0; // 나머지가 0인 원소가 1개 이상이면 1개 추가 if (cnt[0] \u0026gt; 0) ret++; // 나머지가 k/2인 원소가 1개 이상이면 1개 추가 if (k % 2 == 0 \u0026amp;\u0026amp; cnt[k / 2] \u0026gt; 0) ret++; // k가 홀수일 때와 짝수일 때 모두 적용하기 위해 k-1 이후 2로 나눔 for (int i = 1; i \u0026lt;= (k - 1) / 2; i++) { // Greedy하게 부분집합의 크기 추가 ret += Math.max(cnt[i], cnt[k - i]); } return ret; } 결과 리뷰 나머지 연산을 통해 시간복잡도를 줄이는 기법도 상당히 자주 보이는 것 같습니다.\n완전탐색같은 문제인데 주어진 N이 크다는 것은 대부분 Greedy혹은 DP문제로 풀라는 힌트이니, 해당 부분을 잘 착안해야겠습니다.\nReferences URL 게시일자 방문일자 작성자 ","permalink":"https://leaf-nam.github.io/cote/hackerrank_non_divisible_subset/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.hackerrank.com/challenges/non-divisible-subset/problem\"\u003e해커 랭크 : Non Divisible Subset\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"문제-설명\"\u003e문제 설명\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e영문 사이트이므로 문제를 간단히 설명하겠습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003col\u003e\n\u003cli\u003e중복되지 않는 정수 배열(집합)이 주어집니다.\n\u003cblockquote\u003e\n\u003cp\u003e문제 예시에는 10이 중복되어 있는데 오타인 것 같습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003cli\u003eK가 주어졌을 때, 두 수의 합이 k로 나누어 떨어지지 않는 부분 집합을 찾는 문제입니다.\u003c/li\u003e\n\u003cli\u003e부분 집합에서 임의의 두 수를 골라도 k로 나누어 떨어지지 않아야 하며, 부분 집합이 여러개가 있을 때는 최대 길이를 구해야 합니다.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"완전탐색\"\u003e완전탐색\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e부분 집합을 만들 수 있는 각각의 경우에서 2개씩 고른 뒤 K로 나누어 떨어지는지를 일일히 확인하면 시간복잡도를 초과합니다.\n\u003cblockquote\u003e\n\u003cp\u003eO(N! x nC_2)\u003c/p\u003e","title":"[Java]HackerRank Non Divisible Subset"},{"content":"도입 지난 포스팅 [Java]Spring Security WebMVC 기본 구조 지난 시간의 기본 구조(링크)에 이어, Spring Security에서 제공하는 예외처리와 캐싱, 로깅에 대해 자세히 알아보겠습니다.\n현재 포스팅에서 다루는 부분이 로직상 핵심이 되는 것은 아니나, 이를 모르고 Spring Security를 사용하게 되면 다양한 기능을 제대로 활용할 수 없을 뿐 아니라 문제가 발생했을 때 디버깅하기 매우 어렵기 때문에 반드시 이해하고 구현으로 넘어가는 것을 추천드립니다.\n예외처리 Spring Security에서 처리하는 예외는 크게 2가지입니다.\nAuthenticationException : 인증 예외(401)1 AccessDeniedException : 인가 예외(403)2 이러한 오류를 받아서 처리하는 필터와 예외 객체에 대해 자세히 알아보도록 하겠습니다.\nExceptionTranslationFilter Security Filter Chain에는 ExceptionTranslationFilter가 기본으로 세팅되어 있습니다. ExceptionTranslationFilter는 하위 필터의 오류를 처리합니다.\n해당 필터는 위와 같이 인증이 필요한 요청 이전에 선언되어 해당 필터 다음의 요청에서 인증 및 인가 오류가 발생하면 이를 잡아서 처리하는 역할을 합니다.\n인증 객체가 없거나 AuthenticationException 오류 발생 시\nSecurityContextHolder 초기화 RequestCache에 현재 요청 저장 AuthenticationEntryPoint 실행 해당 처리과정이 복잡해서 아래에서 좀 더 자세히 다루겠습니다.\nAccessDenialException 오류 발생 시\n인증 객체가 있으면서 권한이 없는 경우입니다. AccessDeniedHandler에게 요청을 처리하도록 위임합니다. AccessDeniedHandler에서는 보통 403(Forbidden) 오류를 전송하여 권한이 부족함을 알립니다.3 AccessDenialException은 간단히 403 오류를 처리하는 로직만 작성하면 됩니다.\nAuthenticationException 인증 관련 오류가 발생하면 다음과 같은 순서로 처리됩니다.\nSecurityContextHolder 초기화 SecurityContext는 하나의 요청에 하나씩만 생성되는 보안 관련된 문맥입니다. 인증 객체가 들어있는 Container라고 생각해도 좋을 것 같습니다. ContextHolder는 이러한 Context 객체를 요청 처리 전반에서 전역적으로 사용할 수 있도록 들고 있습니다. 인증 객체 관련된 부분은 Spring Security에서 매우 핵심이기 때문에 다음 포스팅에서 다루겠습니다.\n인증 관련 오류가 발생했기 때문에, 현재 ContextHolder를 초기화해서 인증 현재의 객체를 더이상 사용할 수 없도록 만듭니다. RequestCache에 현재 요청정보 저장 사용자가 인증을 완료한 후, 현재 진행중이던 요청을 즉시 수행할 수 있도록 요청 정보를 캐시에 저장합니다. 캐싱 관련된 부분은 아래에서 자세히 다룰 예정입니다. AuthenticationEntryPoint 실행 보통 사용자가 다시 인증을 받을 수 있도록 401 오류와 함께 인증 요청을 전송합니다.4 예를 들어, 해당 사용자를 로그인 페이지로 Redirect하거나 토큰을 요청하는 등의 방식입니다. 캐싱 위에서 설명한 것처럼, 필터를 통과하는 과정에서 예외가 발생하면 현재 요청정보를 캐시에 저장합니다.\nHttpServletRequest 객체를 그대로 저장하기 때문에 원본 요청을 그대로 다시 수행할 수 있습니다.\n기본적으로는 HttpSessionRequestCache를 사용하기 때문에 HttpSession에서 저장된 요청을 꺼내올 수 있습니다.5 즉, 세션의 구현방식(InMemory, Redis 등) 에 따라 다양하게 캐싱이 가능합니다.\n세션에 저장하는 파라미터 이름은 다음과 같이 설정할 수 있습니다.(예시에서는 continue로 설정하고 있습니다.) @Bean DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception { HttpSessionRequestCache requestCache = new HttpSessionRequestCache(); requestCache.setMatchingRequestParameterName(\u0026#34;continue\u0026#34;); http // ... .requestCache((cache) -\u0026gt; cache .requestCache(requestCache) ); return http.build(); } 이후, 저장된 Request 객체를 RequestCacheAwareFilter에서 사용하여 요청을 처리하도록 할 수 있습니다. 다음과 같이 인증 과정에서 요청을 저장하지 않게 설정하는 것도 가능합니다. @Bean SecurityFilterChain springSecurity(HttpSecurity http) throws Exception { RequestCache nullRequestCache = new NullRequestCache(); http // ... .requestCache((cache) -\u0026gt; cache .requestCache(nullRequestCache) ); return http.build(); } 로깅 마지막으로, Spring Security는 DEBUG와 TRACE 레벨의 로깅을 지원하기 때문에, 디버깅에 많은 도움을 받을 수 있습니다.\n통상 보안을 위해 401이나 403오류가 발생하더라도 응답에는 오류 원인을 포함시키지 않습니다.\n만약 브라우저나 응답을 확인해도 인증관련 오류 원인을 찾기 힘들 때 로깅 레벨을 조정한 후 메시지를 확인하여 문제를 빠르게 해결하는 것이 가능합니다.\n아래는 공식문서의 예제인데, TRACE 메시지를 통해 필터의 진행경과를 확인할 수 있으며 DEBUG 메시지를 통해 CSRF와 관련된 오류로 403이 발생하였다는 것을 즉시 확인할 수 있습니다.\n2023-06-14T09:44:25.797-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Securing POST /hello 2023-06-14T09:44:25.797-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking DisableEncodeUrlFilter (1/15) 2023-06-14T09:44:25.798-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (2/15) 2023-06-14T09:44:25.800-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking SecurityContextHolderFilter (3/15) 2023-06-14T09:44:25.801-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking HeaderWriterFilter (4/15) 2023-06-14T09:44:25.802-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking CsrfFilter (5/15) 2023-06-14T09:44:25.814-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.security.web.csrf.CsrfFilter : Invalid CSRF token found for http://localhost:8080/hello 2023-06-14T09:44:25.814-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.s.w.access.AccessDeniedHandlerImpl : Responding with 403 status code 2023-06-14T09:44:25.814-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match request to [Is Secure] 결론 Spring Security는 예외 처리가 잘 분리되어 있고, 처리 과정에서 캐싱과 로깅이 잘 구현되어 있어 개발 및 디버깅하기 쉽다!\n다음 포스팅 [Java]Spring Security 인증(Authentication)과 인가(Authorization) References URL 게시일자 방문일자 작성자 HTTP Unauthorized - 2024.11.10. MDN HTTP Forbidden - 2024.11.10. MDN Spring Session Management - 2024.11.11. Spring Unauthorized : 인증 오류는 사용자를 확인할 수단(토큰, 아이디/패스워드, 세션ID 등)이 없거나, 잘못된 방법으로 요청을 시도했을 때 발생합니다.\nEx) 권한이 필요한 페이지에 권한 없는 사용자가 요청 시도 Ex) 잘못된 인증 토큰(서버에서 발행하지 않았거나, 만료 혹은 변조 등)으로 요청 시도 \u0026#160;\u0026#x21a9;\u0026#xfe0e; Forbidden : 인가 오류는 사용자가 해당 요청에 대한 권한이 불충분할 때 발생합니다.\nEx) 기본 유저가 관리자 권한이 필요한 요청을 시도 \u0026#160;\u0026#x21a9;\u0026#xfe0e; 403 오류는 인증을 다시 시도하더라도 해소되지 않기 때문에, 401과는 다르게 로그인을 다시 시도할 필요가 없습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nMDN의 401 오류 공식문서를 보면, 401 오류 발생시에는 반드시 WWW-Authenticate 헤더에 인증 방법을 1가지 이상 명시하여 반환해야 한다고 설명되어 있습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n처음에 해당 단락을 보면서 요청이 저장된다고 해도 동일한 브라우저임을 어떻게 검증하는지가 궁금했는데, 해당 페이지를 읽어보니 세션은 동일한 세션 ID(통상 JSESSIONID)를 저장하고 있기 때문에, 요청의 Cookie에 설정된 세션 ID를 통해 가져오기 때문에 동일한 브라우저임을 알 수 있다고 합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/spring_security/2/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003ch3 id=\"지난-포스팅\"\u003e지난 포스팅\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://leaf-nam.github.io/posts/spring_security/1/\"\u003e[Java]Spring Security WebMVC 기본 구조\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e지난 시간의 기본 구조(링크)에 이어, Spring Security에서 제공하는 예외처리와 캐싱, 로깅에 대해 자세히 알아보겠습니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e현재 포스팅에서 다루는 부분이 로직상 핵심이 되는 것은 아니나, 이를 모르고 Spring Security를 사용하게 되면 다양한 기능을 제대로 활용할 수 없을 뿐 아니라 문제가 발생했을 때 디버깅하기 매우 어렵기 때문에 반드시 이해하고 구현으로 넘어가는 것을 추천드립니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"예외처리\"\u003e예외처리\u003c/h2\u003e\n\u003cp\u003eSpring Security에서 처리하는 예외는 크게 2가지입니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eAuthenticationException : 인증 예외(401)\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e\u003c/li\u003e\n\u003cli\u003eAccessDeniedException : 인가 예외(403)\u003csup id=\"fnref:2\"\u003e\u003ca href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e2\u003c/a\u003e\u003c/sup\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e이러한 오류를 받아서 처리하는 필터와 예외 객체에 대해 자세히 알아보도록 하겠습니다.\u003c/p\u003e","title":"[Java]Spring Security 예외처리, 캐싱, 로깅"},{"content":"출처 https://school.programmers.co.kr/learn/courses/30/lessons/84512 접근 단어의 길이가 5 이하이기 때문에, 완전탐색을 해도 시간복잡도가 초과되지 않습니다. 각 단어 Idx별로 단어를 1개씩 선택하는 경우의 수 이므로 O(N^N) = 5^5 입니다.\n단어사전을 탐색하다가, 주어진 문자열과 동일한 단어일 때의 크기를 반환하면 됩니다. 단어사전에 부모노드부터 나오기 때문에(A -\u0026gt; AA -\u0026gt; \u0026hellip;), PreOrder DFS1로 단어를 탐색합니다. 중간에 해당 단어가 나오면 탐색을 중단하여 최적화합니다.2 백트래킹3에서 자주 사용하는 기법으로, 재귀 탈출조건에서 값을 반환(아래 예시에서는 boolean)하여 탐색 종료여부를 결정할 수 있습니다.\n풀이 class Solution { // 재귀함수에서 탐색 순서를 저장하기 위한 변수 int answer, cnt; public int solution(String word) { dfs(new StringBuilder(), word); return answer; } boolean dfs(StringBuilder sb, String word) { // 탈출조건 1 : 단어가 같으면 True 반환하여 탐색 종료 if (sb.toString().equals(word)) return true; // 탈출조건 2 : 단어가 5 이상이면 false 반환하여 탐색 계속 if (sb.length() \u0026gt;= 5) return false; // 문자열을 char 배열로 변환 후 for loop for (char c : \u0026#34;AEIOU\u0026#34;.toCharArray()) { // 단어 추가 및 탐색 카운트 sb.append(c); cnt++; // 추가된 단어가 주어진 단어와 같으면 정답 갱신 후 True 반환하여 탐색 종료 if (dfs(sb, word)) { answer = cnt; return true; } // 마지막에 추가한 단어 삭제(다음 탐색에 영향을 주지 않기 위함) sb.deleteCharAt(sb.length() - 1); } return false; } } 결과 리뷰 부모 -\u0026gt; 자식 순으로 탐색하는 PreOrder DFS를 정확히 구현해야 오류가 발생하지 않기 때문에 각 노드의 탐색순서를 지정할 수 있어야 합니다.\n저도 문제만 보고 간단히 완전탐색만 구현하면 되는 쉬운 문제라고 생각했는데, 생각보다 DFS의 탐색 순서를 고려하는 과정에서 시간이 걸렸습니다.\n문제의 구현 난이도 자체는 높지 않은 것 같고, DFS의 탐색 순서를 정확히 이해하고 있는지 묻는 문제인 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 PreOrder는 부모를 먼저 출력 후, 자식들을 탐색하면서 출력하는 방식의 순회입니다. PreOrder DFS로 \u0026lsquo;AAAAE\u0026rsquo;를 탐색하는 경우\n\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n이 문제에서 주어진 단어 1개만 찾으면 되기 때문에 시간초과가 발생하지 않지만, 테스트케이스가 많은 경우 모든 단어를 찾아놓거나 백트래킹 등으로 시간복잡도를 줄여야 합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n더 이상 탐색할 필요가 없는 경우를 정의한 후, 해당 경우에는 탐색하지 않도록 하여 완전탐색의 시간복잡도를 줄이는 기법입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/cote/programmers_84512/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/84512\"\u003ehttps://school.programmers.co.kr/learn/courses/30/lessons/84512\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e단어의 길이가 5 이하이기 때문에, 완전탐색을 해도 시간복잡도가 초과되지 않습니다.\n\u003cblockquote\u003e\n\u003cp\u003e각 단어 Idx별로 단어를 1개씩 선택하는 경우의 수 이므로 O(N^N) = 5^5 입니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003cli\u003e단어사전을 탐색하다가, 주어진 문자열과 동일한 단어일 때의 크기를 반환하면 됩니다.\u003c/li\u003e\n\u003cli\u003e단어사전에 부모노드부터 나오기 때문에(A -\u0026gt; AA -\u0026gt; \u0026hellip;), PreOrder DFS\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e로 단어를 탐색합니다.\u003c/li\u003e\n\u003cli\u003e중간에 해당 단어가 나오면 탐색을 중단하여 최적화합니다.\u003csup id=\"fnref:2\"\u003e\u003ca href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e2\u003c/a\u003e\u003c/sup\u003e\n\u003cblockquote\u003e\n\u003cp\u003e백트래킹\u003csup id=\"fnref:3\"\u003e\u003ca href=\"#fn:3\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e3\u003c/a\u003e\u003c/sup\u003e에서 자주 사용하는 기법으로, 재귀 탈출조건에서 값을 반환(아래 예시에서는 boolean)하여 탐색 종료여부를 결정할 수 있습니다.\u003c/p\u003e","title":"[Java]Programmers 모음 사전"},{"content":"출처 https://school.programmers.co.kr/learn/courses/30/lessons/86052 접근 문제의 예시를 잘 살펴보면, 모든 위치에서 네 방향을 적어도 한번씩 방문하는 것을 볼 수 있습니다. 즉, 빛이 갈 수 있는 모든 경로를 탐색하면서 한번 탐색을 돌면서 방문한 경로는 같은 사이클이라고 할 수 있습니다. 이러한 사이클의 개수를 세서 정렬 후 출력하면 됩니다. 모든 경로를 탐색하는 점에서 DFS나 BFS 모두 구현이 가능합니다. 저는 DFS를 통해 빛의 경로를 따라서 탐색하는 것이 문제를 이해하는데 더 직관적이라고 생각하여 DFS로 구현하였습니다.\ngrid의 길이가 500 이하이므로 O(N^2) 알고리즘에서 4방향 탐색 시 최대 500 X 500 X 4 = 100,000의 시간복잡도가 필요해서 재귀적으로는 풀이할 수 없습니다.1 구현 구현에 도움이 되는 두 가지 스킬을 설명드리겠습니다.\n1. 방향 전환\n\u0026lsquo;S\u0026rsquo;인 칸은 방향전환을 할 필요가 없으니 기존 진행방향으로 통과시키면 됩니다. \u0026lsquo;R\u0026rsquo;인 칸은 시계방향으로 방향전환을 해야 하니, L -\u0026gt; U -\u0026gt; R -\u0026gt; D 순으로 방향을 전환해야 합니다. \u0026lsquo;L\u0026rsquo;인 칸은 반시계방향으로 전환을 해야 하니, L -\u0026gt; D -\u0026gt; R -\u0026gt; U 순으로 방향을 전환해야 합니다. 이를 다음과 같이 구현할 수 있습니다.\n// d : 0 -\u0026gt; 1 -\u0026gt; 2 -\u0026gt; 3 (진입방향 int로 변경) // D : L -\u0026gt; U -\u0026gt; R -\u0026gt; D (진입방향 좌 -\u0026gt; 상 -\u0026gt; 우 -\u0026gt; 하) // R : 0 -\u0026gt; -1 -\u0026gt; 0 -\u0026gt; 1 (상하방향 움직임) // C : -1 -\u0026gt; 0 -\u0026gt; 1 -\u0026gt; 0 (좌우방향 움직임) int[] dr = {0, -1, 0, 1}; int[] dc = {-1, 0, 1, 0}; int d; // 초기 진입하는 방향 switch(g) { // g : 현재 격자 칸에 적힌 알파벳 // \u0026#39;S\u0026#39; : 그대로 진행 case \u0026#39;S\u0026#39;: break; // \u0026#39;R\u0026#39; : 현재 방향에서 1 증가시킴 case \u0026#39;R\u0026#39;: d++; d %= 4; break; // \u0026#39;L\u0026#39; : 현재 방향에서 1 감소시킴, 0보다 작아질 수 있으니 Modular로 회전 case \u0026#39;L\u0026#39;: d--; d += 4; d %= 4; break; } 2. 격자 끝으로 이동 시 반대쪽으로 복귀\n다음 위치로 이동했을 때 격자 끝이면, 반대쪽으로 나와야 합니다. 이를 다음과 같이 구현할 수 있습니다.\nint nr = r + dr[d]; // 다음 위치로 이동 nr += R; nr %= R; // 벽 크기만큼 더해준 뒤, 모듈러 연산을 통해 반대쪽 위치로 이동 int nc = c + dc[d]; // 다음 위치로 이동 nc += C; nc %= C; // 벽 크기만큼 더해준 뒤, 모듈러 연산을 통해 반대쪽 위치로 이동 Modular 연산을 통해 반대방향으로 이동하는 동작 원리는 연속 부분 수열 합의 개수 풀이에 자세히 설명되어 있습니다.\n풀이 import java.util.*; class Solution { // 격자 크기 int R, C; // 정답을 담을 동적 배열 List\u0026lt;Integer\u0026gt; answer = new ArrayList\u0026lt;\u0026gt;(); public int[] solution(String[] grid) { // 격자 크기 초기화 R = grid.length; C = grid[0].length(); // String[] 배열로부터 char[][] 배열 생성 char[][] grid_ = new char[R][C]; for (int i = 0; i \u0026lt; R; i++) grid_[i] = grid[i].toCharArray(); // 방문처리를 위한 배열 생성 boolean[][][] visited = new boolean[R][C][4]; // 각 격자 4방향 탐색 for (int r = 0; r \u0026lt; R; r++) { for (int c = 0; c \u0026lt; C; c++) { for (int d = 0; d \u0026lt; 4; d++) { // 방문한 방향의 빛은 재탐색하지 않음 if (visited[r][c][d]) continue; // DFS 수행(현재 위치 및 방향에서 1번 이동한 후 다음위치부터 DFS) dfs(new int[] {r, c, d}, move(grid_[r][c], r, c, d), grid_, visited); } } } // 동적배열 정렬 후 정적배열로 변환 answer.sort(Comparator.naturalOrder()); int[] ret = new int[answer.size()]; for (int i = 0; i \u0026lt; answer.size(); i++) ret[i] = answer.get(i); return ret; } // 깊이우선 탐색 void dfs(int[] start, int[] current, char[][] grid_, boolean[][][] visited) { // 스택을 활용한 깊이우선 탐색 Stack\u0026lt;int[]\u0026gt; s = new Stack\u0026lt;\u0026gt;(); // 처음 위치 방문처리 후 스택에 삽입 visited[current[0]][current[1]][current[2]] = true; s.push(new int[] {current[0], current[1], current[2], 1}); // 스택이 빌때까지 완전탐색 수행 while (!s.isEmpty()) { current = s.pop(); // 시작지점으로 돌아오면 DFS 종료 if (start[0] == current[0] \u0026amp;\u0026amp; start[1] == current[1] \u0026amp;\u0026amp; start[2] == current[2]) { answer.add(current[3]); return; } // 다음위치로 이동 int[] next = move(grid_[current[0]][current[1]], current[0], current[1], current[2]); // 방문처리 후 스택에 삽입 visited[next[0]][next[1]][next[2]] = true; s.push(new int[] {next[0], next[1], next[2], current[3] + 1}); } } // 빛의 이동 구현(현재위치, 방향, 알파벳을 토대로 다음 빛의 위치와 이동방향 반환) int[] dr = {0, -1, 0, 1}; int[] dc = {-1, 0, 1, 0}; int[] move(char g, int r, int c, int d) { // 방향 전환 switch(g) { case \u0026#39;S\u0026#39;: break; case \u0026#39;R\u0026#39;: d++; d %= 4; break; case \u0026#39;L\u0026#39;: d--; d += 4; d %= 4; break; } // 벽에 닿을 때 처리 int nr = r + dr[d]; nr += R; nr %= R; int nc = c + dc[d]; nc += C; nc %= C; return new int[] {nr, nc, d}; } } 결과 리뷰 완전탐색이라는 아이디어는 문제를 보자마자 바로 알 수 있어, 풀이방법을 생각하는건 어렵지 않은 문제인 것 같습니다. 구현이 까다로운 편인데, 배열을 탐색하면서 방향전환을 하는 부분이 처음 접하면 조금 힘들게 다가왔을 것 같습니다. 처음에 조건을 생각하지 않고 재귀적으로 DFS를 구현해서 StackOverflow가 발생했는데, 기본 조건을 잘 살펴보는 습관을 가져야겠습니다. References Link 게시일자 방문일자 작성자 OpenJDK 14 공식문서 - 2024.11.10. OpenJDK Oracle Java 8 공식문서 - 2024.11.10. Oracle 프로그래머스 컴파일 옵션을 확인해보면 다음과 같이 스택 크기는 별도 설정되어 있지 않아 기본값을 사용하는 것을 알 수 있습니다. 프로그래머스 컴파일 옵션\nJVM의 -Xss 옵션을 지정하여 스택 크기를 증가시킬 수 있는데, 64비트에서 기본값은 1(MB)로 되어있습니다. Java 8의 기본 쓰레드 스택 사이즈\nOpenJDK 14의 공식문서를 찾아보았지만, 기본 쓰레드 스택 사이즈 값을 찾을 수 없어 Oracle Java 8 공식문서를 확인했습니다. OpenJDK 14에서도 동일한 기본값을 사용할 것으로 보입니다.\n1MB(1,000,000Byte)의 스택 사이즈를 사용해 주어진 100,000번의 메서드를 호출하기 위해서는 메서드의 크기를 10Byte 이내로 사용해야 하지만 이는 불가능하므로 재귀 호출 시 StackOverflow가 발생합니다.\n\u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/cote/programmers_86052/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/86052\"\u003ehttps://school.programmers.co.kr/learn/courses/30/lessons/86052\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e문제의 예시를 잘 살펴보면, 모든 위치에서 네 방향을 적어도 한번씩 방문하는 것을 볼 수 있습니다.\u003c/li\u003e\n\u003cli\u003e즉, 빛이 갈 수 있는 모든 경로를 탐색하면서 한번 탐색을 돌면서 방문한 경로는 같은 사이클이라고 할 수 있습니다.\u003c/li\u003e\n\u003cli\u003e이러한 사이클의 개수를 세서 정렬 후 출력하면 됩니다.\u003c/li\u003e\n\u003cli\u003e모든 경로를 탐색하는 점에서 DFS나 BFS 모두 구현이 가능합니다.\n\u003cblockquote\u003e\n\u003cp\u003e저는 DFS를 통해 빛의 경로를 따라서 탐색하는 것이 문제를 이해하는데 더 직관적이라고 생각하여 DFS로 구현하였습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003cli\u003egrid의 길이가 500 이하이므로 O(N^2) 알고리즘에서 4방향 탐색 시 최대 500 X 500 X 4 = 100,000의 시간복잡도가 필요해서 재귀적으로는 풀이할 수 없습니다.\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"구현\"\u003e구현\u003c/h3\u003e\n\u003cp\u003e구현에 도움이 되는 두 가지 스킬을 설명드리겠습니다.\u003c/p\u003e","title":"[Java]Programmers 빛의 경로 사이클"},{"content":"출처 https://school.programmers.co.kr/learn/courses/30/lessons/131701 접근 원소의 길이가 1,000 이하이기 때문에 N^2 알고리즘을 사용해도 풀이가 가능합니다. 각 수열의 시작점에서 인덱스를 1칸씩 증가시키면서 합의 가지수를 찾는 문제입니다. 인덱스는 원형으로 연결되어 있기 때문에, 전체 수열의 길이보다 큰 인덱스는 다시 0부터 시작해야 합니다. 원형 배열을 구현하기 위해 다음과 같이 modular연산을 사용하면 쉽게 구현이 가능합니다. 예시에서 주어진 수열 [7, 9, 1, 1, 4]에서 원형 배열을 더하는 방법을 알아보겠습니다. 현재 값이 9(idx == 1)일때 이후의 값들은 다음과 같습니다. 현재 값이 9일 때, 이후의 값 위치\nnow + 1 ~ 3 까지는 쉽게 구할 수 있지만, now + 4는 인덱스를 초과해버립니다. 이 때, 다음과 같이 배열의 길이만큼 modular 연산을 하면 해당 인덱스를 다시 0부터 시작하도록 만들 수 있습니다. modular를 사용해서 원형 배열처럼 만들기\n현재 값이 1(idx == 2)일때도 modular 연산을 통해 다음과 같이 원형 배열로 만들 수 있습니다. modular를 사용해서 원형 배열처럼 만들기2\n이후에는 현재 값부터 원형으로 현재 값 이전까지 모든 값들을 더하면서 합의 가짓수를 찾아나가면 됩니다. 저는 set를 활용해서 중복되는 값을 제거하였습니다.\n풀이 import java.util.*; class Solution { public int solution(int[] elements) { // 합 저장 Set\u0026lt;Integer\u0026gt; set = new HashSet\u0026lt;\u0026gt;(); for (int i = 0; i \u0026lt; elements.length; i++) { set.add(elements[i]); int sum = elements[i]; // 현재 인덱스 + 1 ~ 현재 인덱스 -1까지 확인 for (int j = i + 1; j \u0026lt; i + elements.length; j++) { // modular 연산을 통해 원형 수열 구현 int k = j % elements.length; sum += elements[k]; set.add(sum); } } // 전체 합 개수 반환 return set.size(); } } 결과 리뷰 배열의 인덱스를 원형처럼 사용하는 방법만 알면 쉽게 풀 수 있는 문제입니다. 이러한 접근방법은 알고리즘 문제에서 자주 등장하기도 하고, 자료구조 중 배열로 Dequeue를 구현할 때에도 자주 사용되는 방법이니 알아두시면 좋을 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 ","permalink":"https://leaf-nam.github.io/cote/programmers_131701/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://school.programmers.co.kr/learn/courses/30/lessons/131701\"\u003ehttps://school.programmers.co.kr/learn/courses/30/lessons/131701\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e원소의 길이가 1,000 이하이기 때문에 N^2 알고리즘을 사용해도 풀이가 가능합니다.\u003c/li\u003e\n\u003cli\u003e각 수열의 시작점에서 인덱스를 1칸씩 증가시키면서 합의 가지수를 찾는 문제입니다.\u003c/li\u003e\n\u003cli\u003e인덱스는 원형으로 연결되어 있기 때문에, 전체 수열의 길이보다 큰 인덱스는 다시 0부터 시작해야 합니다.\u003c/li\u003e\n\u003cli\u003e원형 배열을 구현하기 위해 다음과 같이 modular연산을 사용하면 쉽게 구현이 가능합니다.\n\u003cul\u003e\n\u003cli\u003e예시에서 주어진 수열 [7, 9, 1, 1, 4]에서 원형 배열을 더하는 방법을 알아보겠습니다.\u003c/li\u003e\n\u003cli\u003e현재 값이 9(idx == 1)일때 이후의 값들은 다음과 같습니다.\n\u003cfigure\u003e\n        \u003cimg loading=\"lazy\" src=\"solve1.png\"\n             alt=\"현재 값이 9일 때, 이후의 값 위치\"/\u003e \u003cfigcaption\u003e\n                \u003cp\u003e현재 값이 9일 때, 이후의 값 위치\u003c/p\u003e","title":"[Java]Programmers 연속 부분 수열 합의 개수"},{"content":"도입 작성 배경 : 이번 토이프로젝트에서 Spring Security를 다루고 있는데, 기본 개념을 제대로 이해하지 못해서 많은 어려움을 겪었습니다. 이렇게 정리해두지 않으면 분명 나중에 또 까먹기 때문에, 공식 문서를 보면서 이해한 부분을 정리해두고 나중에 찾아보기 위해 기본 구조와 함께 예제들을 정리해볼 계획입니다. 사실 제가 내부구조를 정확히 아는 것도 아니고, 제 지식의 수준이 매우 얕기 때문에 Spring Security 공식문서에 적혀 있지 않은 내용은 잘못된 정보를 재생산 할 수 있다고 생각했습니다. 따라서 본 포스팅은 거의 Spring Security 6.3.4 레퍼런스 문서를 한글로 의역? 해석? 하는 수준에서 가볍게 봐주시면 좋을 것 같습니다.\nFilter : 공식문서에서도 Spring Security WebMVC의 구조와 관련된 페이지를 가보면, 대부분 필터에 대한 내용입니다. 결국 필터가 어떻게 동작하는지 이해하면 WebMVC에서 Spring Security의 기본 구조와 동작순서를 이해할 수 있기 때문에, 제목은 구조이지만 필터에 대한 설명 위주로 작성했습니다. Spring Reactive에서는 Servlet(WebMVC)에서와 전혀 다르게 동작한다고 하는데, 이 부분은 차후에 다루도록 하겠습니다.\n필터 Java servlet container의 필터 처리과정\n필터는 WebMVC 형태의 WAS를 다뤄보신 분들이라면 다들 사용해 보셨을 것 같습니다.\n사용자의 요청이 Controller(Spring에서는 DispatcherServlet)로 이동하기 전, 해당 요청을 검증하기 위한 단계를 나타냅니다.\n일종의 AOP1라고 할 수 있겠네요.\n필터는 스프링에서만 제공하는게 아닌, 자바 표준으로서 WAS(Servlet Container)에서 사용하기 위한 표준 스펙입니다.\n실제로 자바 표준 스펙에서 기본 제공하는 Filter 인터페이스의 형태는 다음과 같습니다.\npublic interface Filter { default void init(FilterConfig filterConfig) throws ServletException {} void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException; default void destroy() {} } doFilter 메소드의 인자를 보시면, 필터 인터페이스를 통해 가능한 역할은 크게 3가지입니다.\nServletRequest : 사용자 요청 검증(URL, Header 등) ServletResponse: 서버 응답 조작(status code, message body 등) FilterChain : 다음 필터로 요청 전송할지, 현재 필터에서 필터링할지 여부 결정 즉, 필터는 사용자 요청을 검증하고, 사전에 응답이 필요하면 세팅한 후 다음 필터로 요청을 전송하거나 필터링하는 일련의 과정이 반복됩니다. 이를 Filter Chaining이라고 표현합니다.\nSpring Security 필터 Spring에서는 자바 표준 필터를 Spring Container2에 호환하기 위해 다양한 기법을 사용합니다.\n아래 DelegatingFilterProxy와 FilterChainProxy는 AOP와 객체지향의 프록시 패턴3을 이해하지 않고 있다면, 조금 이해가 어려울 수 있습니다.\nDelegatingFilterProxy Bean으로 등록된 Filter를 품고 있는 DelegatingFilterProxy\nSpring이 Servlet Container(Java 표준)에서 사용하는 Filter를 Bean으로 등록 및 동작시키기 위한 프록시 객체입니다.\n여기서 Filters 내부의 Filter0, Filter2는 Spring이 관리하는 필터가 아닌 Servlet Container의 필터입니다.\n내부에 Spring Bean으로 등록된 Filter를 가지고 있으며, 해당 Bean에게 요청을 위임하여 동작시킵니다.\nServlet Container에서 사용하는 필터 인터페이스의 동작 사이에 Spring의 Bean을 끼워넣을 수 있는 일종의 JoinPoint4를 만들었다고 생각하시면 좋을 것 같습니다.\nFilterChainProxy Spring에서는 위의 DelegatingFilterProxy에 위임된 Bean Filter를 여러개 묶어서 Chain으로 연결한 것처럼 동작시킵니다. 해당 체인이 Spring Security에서 가장 핵심 로직인 SecurityFilterChain입니다.\nFilterChainProxy은 DelegatingFilterProxy로부터 위임된 필터 작업을 다음에 나올 SecurityFilterChain에게 요청을 위임하기 위한 프록시 객체입니다. SecurityFilterChain Proxy를 통해 SecurityFilterChain에게 요청이 위임됩니다.\n공식문서에서 위의 Proxy들을 언급한 이유는 결국 SecurityFilterChain의 구조를 설명하기 위함입니다.\n위의 그림에서처럼 두 개의 proxy 객체 덕분에 SecurityFilterChain에게 필터링을 위임할 수 있습니다.\n또한, 이러한 요청은 Matcher 로직을 통해 URI Path 기반으로 특정 패턴의 필터에게 요청을 위임하며, 패턴 별로 다른 로직의 필터를 동작시킬 수 있습니다.5\n앞의 필터에서 처리되지 않은 요청들은 모두 마지막 필터(/**)가 처리하겠죠?\n이러한 필터가 적용되는 순서 또한 중요합니다.\n예를 들어, 인가 관련 로직을 처리하기 전에 반드시 인증을 통해 해당 사용자에게 어떤 권한이 있는지를 확인해야 합니다.\n다음 소스코드를 참고하면 각각의 필터들이 내부적으로 어떤 순서로 동작하는지 확인할 수 있습니다. Custom Filter 등록하기 Filter를 구현하는 방법은 다음과 같습니다.\n필터를 상속받는 Bean을 구현합니다.\n이 때, Spring Security에서 제공하는 추상 클래스인 OncePerRequestFilter를 상속받아 사용할 것을 권장하고 있습니다. 해당 추상 클래스의 추상 메서드인 doFilterInternal를 구현하면, 템플릿 메서드 패턴6으로 하나의 요청에서 한번만 실행되는 필터를 생성할 수 있습니다. 당연히 하나의 요청에 한번만 실행되는거 아닌가..?하는 생각이 들어 찾아보니 servlet들끼리 dispatch를 하는 과정에서 여러번 실행될 수 있어 이런 필터를 통해 이번 요청에 이미 필터링을 거쳤는지 확인한다고 합니다.\nSecurityConfig에 등록합니다.\n@Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // ... .addFilterBefore(new TenantFilter(), AuthorizationFilter.class); return http.build(); } 결론 스프링에서 사용하는 필터는 자바 표준스펙과 호환되며, 중간에 다양한 기능을 끼워넣기 위해 잘 설계되어 있어 Custom Filter를 쉽게 등록할 수 있다!\n예외처리, 캐싱 관련된 내용도 한번에 작성하려고 했는데, 분량이 너무 길어져서 다음 포스트로 넘기겠습니다.\n다음 포스팅 [Java]Spring Security 예외처리, 캐싱, 로깅 [Java]Spring Security 인증(Authentication)과 인가(Authorization) References Link 게시일자 방문일자 작성자 Spring Security Docs - 2024.11.05. Spring Java Servlet Filter Docs - 2024.11.05. Oracle Spring Security Filter Order Source Code - 2024.11.05. Spring Once Per Request Filter When to Use 2018.03.18. 2024.11.05. StackOverflow Aspect Orient Programming - 관점 지향 프로그래밍 : 특정 로직의 핵심 관점(종단 관심사)과 부가 관점(횡단 관심사)을 나눈 뒤 각각을 모듈화하는 기법을 말합니다. 필터는 핵심 비즈니스 로직 전단계의 전처리 과정을 모듈화하는 Aspect(횡단 관심사)의 일종입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nApplication Context, Bean Factory, Bean Container 혹은 IOC Container라고도 합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nProxy Pattern - 프록시 패턴 : 본인 내부에 다른 객체의 참조를 갖고 있다가, 특정 작업의 요청을 내부 객체에 위임하는 형태의 디자인 패턴입니다. 이를 통해 접근 제어를 하거나 특정 부가기능을 수행할 수 있습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nAspectJ에서 사용하는 개념으로, 횡단 관심사를 처리할 로직을 끼워넣을 지점을 결정합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSpring에서는 이러한 SecurityFilterChain을 DelegatingFilterChain이 아닌 FilterChainProxy로 등록하는데, 이는 DelegatingFilterChain을 그대로 사용하면 기존 필터와 동일한 구조로 동작해야 하기 때문이라고 합니다. FilterChainProxy로 등록함으로써 스프링의 다양한 성능 최적화나 메모리 누수 방지 등등의 다양한 동작을 적용할 수 있고, 무엇보다 URL을 보고 필터 동작여부를 결정하는 것이 아니라, RequestMatcher를 활용할 수 있는 것도 FilterChainProxy로서 등록되기 때문이라고 하네요! 기존 로직에 부가기능을 추가한 뒤 다른 객체에게 위임하여 동작시킨다는 측면에서 일종의 데코레이터 패턴이라고 볼 수 있겠네요.\n\u0026#160;\u0026#x21a9;\u0026#xfe0e; Template Method Pattern - 템플릿 메소드 패턴 : 상위 객체의 알고리즘을 하위 객체에서 구현하도록 설계하여 전체 구조를 변경하지 않고 특정 단계의 로직만 변경할 수 있게 합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/spring_security/1/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e작성 배경\u003c/strong\u003e :\n이번 토이프로젝트에서 Spring Security를 다루고 있는데, 기본 개념을 제대로 이해하지 못해서 많은 어려움을 겪었습니다. 이렇게 정리해두지 않으면 분명 나중에 또 까먹기 때문에, 공식 문서를 보면서 이해한 부분을 정리해두고 나중에 찾아보기 위해 기본 구조와 함께 예제들을 정리해볼 계획입니다.\n\u003cblockquote\u003e\n\u003cp\u003e사실 제가 내부구조를 정확히 아는 것도 아니고, 제 지식의 수준이 매우 얕기 때문에 Spring Security 공식문서에 적혀 있지 않은 내용은 잘못된 정보를 재생산 할 수 있다고 생각했습니다. 따라서 본 포스팅은 거의 \u003ca href=\"https://docs.spring.io/spring-security/reference/index.html\"\u003eSpring Security 6.3.4\u003c/a\u003e 레퍼런스 문서를 한글로 의역? 해석? 하는 수준에서 가볍게 봐주시면 좋을 것 같습니다.\u003c/p\u003e","title":"[Java]Spring Security WebMVC 기본 구조"},{"content":"출처 해커 랭크 : Lego Blocks 문제 설명 영문 사이트이므로 문제를 간단히 설명하겠습니다. 다음과 같은 총 네 가지 종류의 레고 블럭이 무한히 있습니다. d\th\tw 1\t1\t1 [] : 길이가 1이고, 높이가 1인 레고 블럭 1\t1\t2 [ ] : 길이가 2이고, 높이가 1인 레고 블럭 1\t1\t3 [ ] : 길이가 3이고, 높이가 1인 레고 블럭 1\t1\t4 [ ] : 길이가 4이고, 높이가 1인 레고 블럭 문제에서 주어지는 높이와 길이만큼 레고 블럭을 쌓으려고 합니다.\n이 때, 다음과 같이 수직으로 동일한 블럭이 쌓여있는 형태의 블럭은 단단하지 않아서1 만들 수 없습니다. Good layouts는 단단하게 연결되어 있지만, Bad layouts는 연결되어 있지 않아 분리될 수 있습니다.\n주어진 높이 n, 길이 m의 블럭을 만들 수 있는 모든 경우의 수를 구해야 합니다.\n접근 레고의 높이(n), 길이(m)가 최대 1000이므로 완전탐색으로 모든 경우의 수를 구하는 것은 불가능하며, 길이 2, 높이 1000의 블럭을 쌓는 모든 경우의 수만 해도 벌써 2^1000입니다.2 그래서 문제에서도 소수 모듈러 연산으로 크기를 줄이고 있습니다.\n이 문제에서는 전체 경우의 수를 귀납적으로 구하고, 전체에서 단단하지 않은 레고의 경우를 빼는 방식으로 단단한 레고의 경우를 구해야 합니다. 흐름 큰 문제해결의 흐름은 다음과 같습니다.\n해당 길이만큼 레고를 만들 수 있는 모든 경우의 수 찾기 row[m]\n해당 높이만큼 레고를 쌓기 row[m] ^ n = col[m]\n모든 경우의 수에서 단단하지 않은 레고의 경우의 수 빼기 col[m] - notSolidCol[m] = solidCol[m]\n풀이 위 흐름에서처럼 총 3단계로 걸쳐 단계별 설명과 코드를 함께 확인해보겠습니다.\n귀납적으로 레고 블럭 개수 구하기 레고 블럭의 길이가 4 이하인 경우와, 이상인 경우를 분리해서 개수를 구해보겠습니다.\n레고 블럭의 길이가 1 증가할 때마다 발생하는 경우의 수\n[N = 1일 때]\n그냥 1인 블럭 1개만 사용 가능합니다.\nrow[1] = 1\n[N = 2일 때]\n맨 앞을 크기가 1인 블럭으로 고정시키면, 뒤에는 1인 블럭 1개(row[1])만 올 수 있습니다. 크기가 2인 블럭 1개를 사용할 수 있습니다. row[2] = row[1] + 1 = 2\n[N = 3일 때]\n맨 앞을 N = 1인 블럭으로 고정시켰을 때, 뒤의 2칸은 row[2]인 경우의 수와 같습니다. 맨 앞을 N = 2인 블럭으로 고정시켰을 때, 뒤의 1칸은 row[1]인 경우의 수와 같습니다. 크기가 3인 블럭 1개를 사용할 수 있습니다. row[3] = (row[2] + row[1]) + 1 = 4\n[N \u0026lt;= 4일 때]\n레고 블럭의 길이가 1 증가할 때마다, 전체 경우의 수는 다음과 같이 구할 수 있습니다.\n(맨 앞 블럭을 (N = 1 ~ N-1)까지 고정시킨 경우의 수의 합) + 현재 블럭(1)\nrow[N] = (row[N - 1] + row[N - 2] + ... + row[1]) + 1\n[N \u0026gt; 4일 때]\n레고 블럭의 길이는 최대 4까지만 증가하므로3, 맨 앞 블럭을 1 ~ 4까지만 고정시킬 수 있으므로 다음과 같습니다.\n맨 앞 블럭을 (N = 1 ~ 4)까지 고정시킨 경우의 수의 합\nrow[N] = row[N - 1] + row[N - 2] + row[N - 3] + row[N - 4]\n위의 귀납식을 다음과 같이 DP를 사용한 코드로 나타낼 수 있습니다.\npublic static int legoBlocks(int n, int m) { // Row 개수 구하기 long[] row = new long[m + 1]; for (int i = 1; i \u0026lt;= m; i++) { if (i \u0026lt;= 4) { row[i] = 1; for (int j = 1; j \u0026lt; i; j++) row[i] += row[j]; } else { // Overflow 방지를 위한 MOD 연산 // MOD = (int)Math.pow(10, 9) + 7; row[i] = (row[i - 1] + row[i - 2] + row[i - 3] + row[i - 4]) % MOD; } } // ... } 레고 블럭을 쌓는 모든 경우의 수 구하기 단단한 블럭인지를 확인하지 않고, 모든 경우를 구하면 되므로 다음과 같이 구할 수 있습니다.\n해당 길이의 블럭의 수 ^ 블럭의 높이 col[m] = row[m] ^ n\n이를 코드로 다음과 같이 나타낼 수 있습니다\nMath.pow(x, y) 연산을 하게 되면 Overflow가 발생하므로 다음과 같이 곱셈 후 모듈러 연산을 해야합니다.\npublic static int legoBlocks(int n, int m) { // ...(생략) // 레고 블럭을 쌓는 모든 경우의 수 저장하기 long[] col = new long[m + 1]; for (int i = 0; i \u0026lt;= m; i++) { col[i] = 1; for (int j = 1; j \u0026lt;= n; j++) { col[i] *= row[i]; // Overflow 방지를 위한 MOD 연산 col[i] %= MOD; } } // ...(생략) } 단단하지 않은 블럭인 경우 빼기 단단하지 않은 블럭의 수 구하기는 앞에서 구했던 귀납적으로 블럭의 수 구하기와 유사합니다.\n레고 블럭의 길이가 1 증가할 때마다 발생하는 경우의 수\n위와 같이 j를 고정시켜두면, 나머지 길이(m - j)에서 어떻게 블럭을 쌓더라도 단단하지 않은 경우가 됩니다. 이 때, (m - j)에서 블럭을 쌓는 모든 경우의 수는 위에서 구한 값을 활용할 수 있습니다.\n길이가 (m - j)인 단단하지 않은 블럭 : col[m - j] 이 때 j위치로 고정되어 있는 블럭의 왼쪽은 단단한 상태여야 하므로, 전체 경우의 수는 solid[j] * col[m - j]가 됩니다.4\n이를 코드로 표현하면 다음과 같습니다.\npublic static int legoBlocks(int n, int m) { // ...(생략) // Solid하지 않은 경우 빼서 Solid한 경우만 구하기 long[] solidCol = new long[m + 1]; solidCol[1] = 1; // 길이가 1일 때는 크기가 1인 블럭을 쌓는 1가지 경우밖에 없음 for (int i = 2; i \u0026lt;= m; i++) { long temp = col[i]; // 전체 경우의 수로 초기화 for (int j = 1; j \u0026lt; i; j++) { // 길이를 1씩 증가시키면서 단단하지 않은 경우 빼기 temp -= solidCol[j] * col[i - j]; // Overflow 방지를 위한 MOD 연산 temp %= MOD; } // Overflow 방지를 위한 MOD 연산2 solidCol[i] = (temp + MOD) % MOD; // 연산 전 MOD를 더해서 계산 결과가 -인 경우 처리 } // m번째 단단한 레고블럭 개수 출력 return (int)solidCol[m]; } 결과 전체 코드\npublic static int legoBlocks(int n, int m) { // Row 개수 구하기 long[] row = new long[m + 1]; for (int i = 1; i \u0026lt;= m; i++) { if (i \u0026lt;= 4) { row[i] = 1; for (int j = 1; j \u0026lt; i; j++) row[i] += row[j]; } else { row[i] = (row[i - 1] + row[i - 2] + row[i - 3] + row[i - 4]) % MOD; } } // Row X Col 총 개수 구하기 long[] col = new long[m + 1]; for (int i = 0; i \u0026lt;= m; i++) { col[i] = 1; for (int j = 1; j \u0026lt;= n; j++) { col[i] *= row[i]; col[i] %= MOD; } } // Solid하지 않은 경우 빼서 Solid한 경우만 구하기 long[] solidCol = new long[m + 1]; solidCol[1] = 1; for (int i = 2; i \u0026lt;= m; i++) { long temp = col[i]; for (int j = 1; j \u0026lt; i; j++) { temp -= solidCol[j] * col[i - j]; temp %= MOD; } solidCol[i] = (temp + MOD) % MOD; } return (int)solidCol[m]; } 결과 리뷰 넥슨 코딩테스트를 준비하면서 HackerRank의 문제들을 처음 풀어봤습니다.\n문제별로 난이도 차이가 있는 것 같은데, 이 문제만 유난히 어려워서 많이 고민했고 결국 Discussion에 있는 풀이들을 보고 나서야 이해할 수 있었습니다.\nDP의 개념을 익힐 수 있는 좋은 문제인 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 https://www.hackerrank.com/challenges/one-week-preparation-kit-lego-blocks/forum?isFullScreen=true\u0026amp;h_l=interview\u0026amp;playlist_slugs%5B%5D=preparation-kits\u0026amp;playlist_slugs%5B%5D=one-week-preparation-kit\u0026amp;playlist_slugs%5B%5D=one-week-day-six - 2024.10.26. - 영문으로는 one solid structure를 만들어야 한다고 하는데, 레고 블럭이 두개로 분리되지 않는 형태를 말합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n길이가 2인 블럭은 2가지 경우가 있고([][], [ ]), 이를 높이 1000으로 쌓는 경우이므로 2^1000입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n문제 설명에서 보듯이 레고 블럭은 길이 1 ~ 4까지 총 4가지 종류만 있습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n자른 위치가 j일 때 단단하지 않은 모든 경우는 다음과 같은 두 경우를 곱해서 구할 수 있습니다.\nsolid[j] * not_solid[i - j]\n전체 길이가 i, 블럭을 자른 위치가 j일때 단단하지 않은 경우 구하기\n\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/cote/hackerrank_lego_blocks/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.hackerrank.com/challenges/one-week-preparation-kit-lego-blocks/problem?isFullScreen=true\u0026amp;h_l=interview\u0026amp;playlist_slugs%5B%5D=preparation-kits\u0026amp;playlist_slugs%5B%5D=one-week-preparation-kit\u0026amp;playlist_slugs%5B%5D=one-week-day-six\"\u003e해커 랭크 : Lego Blocks\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"문제-설명\"\u003e문제 설명\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e영문 사이트이므로 문제를 간단히 설명하겠습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003col\u003e\n\u003cli\u003e다음과 같은 총 네 가지 종류의 레고 블럭이 무한히 있습니다.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003ed\th\tw\n1\t1\t1   []        : 길이가 1이고, 높이가 1인 레고 블럭\n1\t1\t2   [  ]      : 길이가 2이고, 높이가 1인 레고 블럭\n1\t1\t3   [    ]    : 길이가 3이고, 높이가 1인 레고 블럭\n1\t1\t4   [      ]  : 길이가 4이고, 높이가 1인 레고 블럭\n\u003c/code\u003e\u003c/pre\u003e\u003col start=\"2\"\u003e\n\u003cli\u003e\n\u003cp\u003e문제에서 주어지는 높이와 길이만큼 레고 블럭을 쌓으려고 합니다.\u003c/p\u003e","title":"[Java]HackerRank Lego Blocks"},{"content":"한줄평 추상적인 객체지향의 개념을 구현하기 위한 책\n책을 읽게 된 계기 지인 추천으로 읽게 되었습니다.\n작가 소개 조영호\n객체지향 설계와 도메인-주도 설계에 관심이 많으며 행복한 팀과 깔끔한 코드가 훌륭한 소프트웨어를 낳는다는 믿음을 증명하기 위해 노력하고 있다. LG-CNS, 네이버, 쿠팡을 거치며 개발이라는 창조적인 작업의 즐거움을 만끽했으며, NHN NEXT에서 후배들을 양성하며 지식을 공유하는 즐거움을 누리기도 했다. 현재는 다음카카오에서 사용자에게 가치를 제공할 수 있는 다양한 서비스 개발에 참여하고 있다. 소프트웨어 개발과 관련된 경험과 정보를 공유하기 위해 ‘이터너티(Eternity)’라는 필명으로 블로그(http://aeternum.egloos.com/)를 운영하고 있다.\n핵심요약 저자는 객체지향의 특성을 다양한 측면에서 바라보면서 본질이 무엇인지 계속 고민해나갑니다. 그리고 이러한 과정에서 잘못 알려진 개념과 지식들을 바로잡습니다.\n객체와 타입은 다르며, 객체를 심볼, 외연, 내연으로 분류한 것이 타입이다. 타입은 개념과 동의어이다. 또한, 클래스와 타입은 다르며, 클래스는 타입을 구현하기 위한 수단일 뿐이다. 또한, 클래스는 코드를 재사용할 때에도 사용되기 때문에 타입이라고 할 수 없다. 역할, 책임, 협력을 통해 여러 객체가 상호작용하여 시스템을 구성한다. 이러한 객체의 상호작용은 \u0026lsquo;메시지\u0026rsquo;를 통해서만 전달되며, 이러한 메시지는 공용 인터페이스로 표현된다. 책임은 \u0026lsquo;자율적\u0026rsquo;으로 구성되어야 하며, 이는 다른 객체에게 너무 많은 메시지를 통해 행동을 제약하지 않아야 한다는 것이다. 프로그램을 설계하는 과정에서 객체를 먼저 고민하지 말고, 어떤 메시지를 주고받을지를 먼저 고민하면 훨씬 안정된 구조를 만들 수 있다. 지도 하나를 통해 다양한 길을 찾을 수 있는 것처럼, 도메인 모델을 만들면 다양한 문제를 해결할 수 있다. 인상깊은 구절 재귀적으로 컴퓨터를 객체라고 불리는 더 작은 컴퓨터로 분할1 위 구절이 객체지향 프로그래밍에서의 객체를 가장 본질적으로 나타냈다는 생각이 들어 인상깊었습니다. 서버2와 시스템, 심지어 JVM도 컴퓨터 내에서 동작하는 객체이면서 작은 컴퓨터라는 생각이 들었습니다. 결국 이러한 객체들은 작은 컴퓨터처럼 저장공간(객체 - 속성 / 컴퓨터 - 메모리)에 있는 데이터를 통해 특정 연산(객체 - 메서드 / 컴퓨터 - CPU)을 수행합니다.\n평가 추상적인 개념을 다양한 방향에서 접근할 수 있어 좋았습니다. 장님이 코끼리를 보려면 다양한 각도에서 직접 만져봐야 하는 것처럼, 추상적인 개념을 알기 위해서는 다양한 방향에서 확인하는 것이 효과적인 것 같습니다. 중간에 \u0026lsquo;이상한 나라의 앨리스\u0026rsquo;의 상황이나, 실제 커피 주문 시스템의 설계 과정을 통해 더욱 쉽게 이해할 수 있었습니다. 다만, 객체지향을 처음 접하는 분이라면 추상적인 이야기가 많아 조금 이해하기 어려울 수 있을 것 같습니다. 느낀점 어렴풋이 언어적 느낌으로만 알던 추상적인 지식이 책의 설명으로 구체화된 것 같습니다. 나중에 좀더 객체지향에 대해 깊이 이해하고 다시 읽으면 또 다른 느낌일 것 같아 주기적으로 읽어보려고 합니다. 객체지향을 잘하는 개발자들은 비유를 참 잘 드는 것 같습니다. 추천 객체지향의 개념이 헷갈리거나, 어느 정도의 경험은 있지만 이론적인 지식이 필요한 분들이 읽으면 좋을 것 같습니다. 저처럼 초보 개발자들이 어떻게 공부해가야 할지 감을 잡을 수 있도록 쉬운 예시와 다양한 레퍼런스 서적들이 제시되고 있어, 초보 개발자들에게 더욱 추천합니다. References URL 게시일자 방문일자 작성자 https://www.kyobobook.co.kr/service/profile/information?chrcCode=1001590703 미확인 240710 교보문고 203p 그림 6.12\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n실제로 SpringBoot에서는 Tomcat이라는 서버 객체가 Application Context 내 객체로 존재합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/review/oop_fact_mislead/","summary":"\u003ch2 id=\"한줄평\"\u003e한줄평\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e추상적인 객체지향의 개념을 구현하기 위한 책\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"책을-읽게-된-계기\"\u003e책을 읽게 된 계기\u003c/h2\u003e\n\u003cp\u003e지인 추천으로 읽게 되었습니다.\u003c/p\u003e\n\u003ch2 id=\"작가-소개\"\u003e작가 소개\u003c/h2\u003e\n\u003cp\u003e조영호\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e객체지향 설계와 도메인-주도 설계에 관심이 많으며 행복한 팀과 깔끔한 코드가 훌륭한 소프트웨어를 낳는다는 믿음을 증명하기 위해 노력하고 있다. LG-CNS, 네이버, 쿠팡을 거치며 개발이라는 창조적인 작업의 즐거움을 만끽했으며, NHN NEXT에서 후배들을 양성하며 지식을 공유하는 즐거움을 누리기도 했다. 현재는 다음카카오에서 사용자에게 가치를 제공할 수 있는 다양한 서비스 개발에 참여하고 있다. 소프트웨어 개발과 관련된 경험과 정보를 공유하기 위해 ‘이터너티(Eternity)’라는 필명으로 블로그(\u003ca href=\"http://aeternum.egloos.com/\"\u003ehttp://aeternum.egloos.com/\u003c/a\u003e)를 운영하고 있다.\u003c/p\u003e","title":"객체지향의 사실과 오해"},{"content":"출처 https://www.acmicpc.net/problem/16236 접근 알고리즘 시작지점부터 가장 가까운 물고기부터 탐색합니다. 문제 조건에서 주어진 물고기를 선택하는 기준을 만족하기 위해, Heap1에 다음 물고기를 넣어 정렬하였습니다. 다음 물고기가 먹을 수 있는 물고기라면, 해당 위치를 시작점으로 다시 BFS를 수행합니다. 시간복잡도 고민 BFS로 물고기를 먹으러 이동하면서 걸리는 시간 : O(N^2) 이동하면서 물고기를 정렬하는 시간(Priority Queue 사용 시) : O(NlogN) N = 20 이므로 시간복잡도 내 충분히 가능하다고 판단했습니다.\n유의사항 PriorityQueue를 정렬할 때, Integer의 유틸리티 메서드인 compare()를 활용하면 좋다고 합니다.2 물고기를 먹은 뒤 해당 위치에서 BFS를 다시 수행하기 위해 방문 배열을 초기화하는 로직이 필요합니다. 풀이 package solving; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.*; /* * [조건] * 시간 제한 : 2초 / 메모리 제한 512MB * [풀이] * 오른쪽 아래부터 역으로 공간을 돌면서 가장 가까운 물고기의 위치를 확인한다.(N^2) * 해당 물고기를 먹으러 이동하면서 걸리는 시간을 측정한다.(N^2) * 더이상 이동할 수 없거나, 모든 칸이 빌때까지 이를 반복한다(N^2) * =\u0026gt; N^6 = 20^6 = 32 x 10^6 이므로 시간복잡도 내 가능 * 시작지점부터 가장 가까운 물고기를 탐색한다. * 해당 지점까지 bfs로 이동하여 해당 지점이 나오면 걸린 시간을 현재시간에 더한다. * 더이상 이동이 불가능하다면 현재 시간을 출력한다. * 이동이 가능하다면 먹은 횟수를 더하고, 먹은 횟수가 상어의 크기만큼 쌓이면 상어 크기를 1 늘린다. */ public class bj_16236_아기_상어 { static int N, eatCount, size = 2; static int[] start; static int[][] space; public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); N = Integer.parseInt(br.readLine()); space = new int[N][N]; start = new int[2]; for (int i = 0; i \u0026lt; N; i++) { StringTokenizer st = new StringTokenizer(br.readLine()); for (int j = 0; j \u0026lt; N; j++) { int now = Integer.parseInt(st.nextToken()); if (now == 9) start = new int[]{i, j}; space[i][j] = now; } } System.out.println(bfs()); } // 먹을 수 있는 물고기가 더 없을떄까지 bfs 이동 static int[] dr = {-1, 0, 0, 1}; static int[] dc = {0, -1, 1, 0}; private static int bfs() { boolean[][] visited = new boolean[N][N]; // 우선순위 큐 : 거리 \u0026gt; 위 \u0026gt; 왼쪽 순으로 내부 정렬 PriorityQueue\u0026lt;int[]\u0026gt; q = new PriorityQueue\u0026lt;\u0026gt;((o1, o2) -\u0026gt; { if (o1[2] != o2[2]) return Integer.compare(o1[2], o2[2]); // Integer.compare() 메서드 활용 if (o1[0] != o2[0]) return Integer.compare(o1[0], o2[0]); return Integer.compare(o1[1], o2[1]); }); int ret = 0; q.offer(new int[] {start[0], start[1], 0}); visited[start[0]][start[1]] = true; while (!q.isEmpty()) { int[] now = q.poll(); // 자신보다 작은 물고기를 만났을 때 : 해당 물고기를 먹은 후 큐를 비우고 다시 bfs 시작 int fish = space[now[0]][now[1]]; if (fish != 0 \u0026amp;\u0026amp; fish \u0026lt; size) { if (++eatCount \u0026gt;= size) { size++; eatCount = 0; } // 이동시간 갱신 ret += now[2]; // bfs 초기화 + 방문배열 초기화 q.clear(); q.offer(new int[]{now[0], now[1], 0}); for (int j = 0; j \u0026lt; N; j++) Arrays.fill(visited[j], false); visited[now[0]][now[1]] = true; // 공간 변화 : 아기상어 위치 이동 + 최초위치 아기상어 칸 비우기 space[now[0]][now[1]] = 9; space[start[0]][start[1]] = 0; start = new int[]{now[0], now[1]}; continue; } for (int i = 0; i \u0026lt; 4; i++) { int nr = now[0] + dr[i]; int nc = now[1] + dc[i]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= N) continue; if (visited[nr][nc] || space[nr][nc] \u0026gt; size) continue; visited[nr][nc] = true; q.offer(new int[] {nr, nc, now[2] + 1}); } } return ret; } } 결과 리뷰 먹을 수 있는 가장 가까운 물고기의 위치를 구하기 위해 BFS를 문제의 조건에 맞게 구현하는 문제였습니다. 최단거리를 구할 때 기본적으로 BFS를 고려하는 습관을 들여야겠습니다.\nCompareTo를 구현하기 위해 Integer의 유틸리티 메서드를 활용하는 것이 좋다는 것을 이펙티브 자바를 읽으며 알게 되었고, 실제로 활용해볼 수 있었습니다. 접근방법을 알면 생각보다 간단히 구현할 수 있는 문제이지만, 구현 과정에서 디버깅하면서 1시간 정도 소요되었습니다. 이럴 때 IDE의 디버깅 모드를 활용할지 고민이지만, 프로그래머스 같은 웹 환경에서 코딩테스트에 익숙해지기 위해 최대한 console만으로 디버깅하는 연습을 하려고 노력중입니다. 구현 문제는 꾸준히 연습해야 시간을 단축할 수 있는 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 Heap을 구현한 PriorityQueue를 사용했습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n이펙티브 자바(Effective Java 3/E) \u0026ldquo;Item 14 : Comparable을 구현할지 고려하라\u0026rdquo; 참조\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/cote/bj_16236/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.acmicpc.net/problem/16236\"\u003ehttps://www.acmicpc.net/problem/16236\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003ch3 id=\"알고리즘\"\u003e알고리즘\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e시작지점부터 가장 가까운 물고기부터 탐색합니다.\u003c/li\u003e\n\u003cli\u003e문제 조건에서 주어진 물고기를 선택하는 기준을 만족하기 위해, Heap\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e에 다음 물고기를 넣어 정렬하였습니다.\u003c/li\u003e\n\u003cli\u003e다음 물고기가 먹을 수 있는 물고기라면, 해당 위치를 시작점으로 다시 BFS를 수행합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"시간복잡도-고민\"\u003e시간복잡도 고민\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eBFS로 물고기를 먹으러 이동하면서 걸리는 시간 : O(N^2)\u003c/li\u003e\n\u003cli\u003e이동하면서 물고기를 정렬하는 시간(Priority Queue 사용 시) : O(NlogN)\n\u003cblockquote\u003e\n\u003cp\u003eN = 20 이므로 시간복잡도 내 충분히 가능하다고 판단했습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"유의사항\"\u003e유의사항\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003ePriorityQueue를 정렬할 때, Integer의 유틸리티 메서드인 compare()를 활용하면 좋다고 합니다.\u003csup id=\"fnref:2\"\u003e\u003ca href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e2\u003c/a\u003e\u003c/sup\u003e\u003c/li\u003e\n\u003cli\u003e물고기를 먹은 뒤 해당 위치에서 BFS를 다시 수행하기 위해 방문 배열을 초기화하는 로직이 필요합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"풀이\"\u003e풀이\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003epackage\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003esolving\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.io.BufferedReader\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.io.IOException\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.io.InputStreamReader\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.util.*\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e/*\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * [조건]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 시간 제한 : 2초 / 메모리 제한 512MB\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * [풀이]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 오른쪽 아래부터 역으로 공간을 돌면서 가장 가까운 물고기의 위치를 확인한다.(N^2)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 해당 물고기를 먹으러 이동하면서 걸리는 시간을 측정한다.(N^2)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 더이상 이동할 수 없거나, 모든 칸이 빌때까지 이를 반복한다(N^2)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * =\u0026gt; N^6 = 20^6 = 32 x 10^6 이므로 시간복잡도 내 가능\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 시작지점부터 가장 가까운 물고기를 탐색한다.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 해당 지점까지 bfs로 이동하여 해당 지점이 나오면 걸린 시간을 현재시간에 더한다.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 더이상 이동이 불가능하다면 현재 시간을 출력한다.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 이동이 가능하다면 먹은 횟수를 더하고, 먹은 횟수가 상어의 크기만큼 쌓이면 상어 크기를 1 늘린다.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003ebj_16236_아기_상어\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eeatCount\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esize\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[][]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003espace\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eIOException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedReader\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInputStreamReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ein\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereadLine\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003espace\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003eStringTokenizer\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eStringTokenizer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereadLine\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextToken\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e9\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003espace\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebfs\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 먹을 수 있는 물고기가 더 없을떄까지 bfs 이동\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003ebfs\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"o\"\u003e[][]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003evisited\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 우선순위 큐 : 거리 \u0026gt; 위 \u0026gt; 왼쪽 순으로 내부 정렬\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003ePriorityQueue\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ePriorityQueue\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e((\u003c/span\u003e\u003cspan class=\"n\"\u003eo1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eo2\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eo1\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eo2\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecompare\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eo1\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eo2\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"c1\"\u003e// Integer.compare() 메서드 활용\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eo1\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eo2\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecompare\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eo1\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eo2\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ecompare\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eo1\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eo2\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eret\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eoffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003evisited\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]][\u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ewhile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eisEmpty\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003epoll\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 자신보다 작은 물고기를 만났을 때 : 해당 물고기를 먹은 후 큐를 비우고 다시 bfs 시작\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efish\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003espace\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]][\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efish\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efish\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esize\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"n\"\u003eeatCount\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esize\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003esize\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eeatCount\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 이동시간 갱신\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eret\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"c1\"\u003e// bfs 초기화 + 방문배열 초기화\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclear\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eoffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eArrays\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003efill\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evisited\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003evisited\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]][\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 공간 변화 : 아기상어 위치 이동 + 최초위치 아기상어 칸 비우기\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003espace\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]][\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e9\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003espace\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]][\u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edc\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evisited\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003espace\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esize\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003evisited\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eoffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eret\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"결과\"\u003e결과\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"result\" loading=\"lazy\" src=\"/cote/bj_16236/result.png\"\u003e\u003c/p\u003e","title":"[Java]백준 16236 아기 상어"},{"content":"출처 https://www.acmicpc.net/problem/2573 접근 각 칸이 10이하이고 전체 칸은 10,000이므로 빙산을 모두 녹이려면 10 _ 10,000 _ 10,000이지만, 네 변이 노출되면 금방 사라지므로 완전탐색이 가능하다고 판단했습니다. 매 회마다 빙산을 녹이고, 빙산을 bfs(dfs)로 탐색해서 두 덩어리가 있는지 확인한다. 모든 빙산이 다 녹았지만 2개로 분리되지 않는 경우를 주의한다.1 풀이 package solving; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.*; /* * [조건] * 시간제한 : 1초, 메모리 제한 : 256MV * 이차원 배열의 크기가 300^2 이하이며 전체 칸의 개수는 10,000개 이하, 각 칸의 크기는 10 이하 * [풀이] * 각 칸이 10이하이고 전체 칸은 10,000이므로 빙산을 모두 녹이려면 10 * 10,000 * 10,000이지만, 네 변이 노출되면 금방 사라지므로 완전탐색 * 매 회마다 빙산을 녹이고, 빙산을 bfs로 탐색해서 두 덩어리가 있는지 확인한다. */ public class bj_2573_빙산 { static int N, M, count; static int[] dr = {0, 0, 1, -1}; static int[] dc = {1, -1, 0, 0}; static int[][] pole; public static void main(String[] args) throws IOException { // 초기화 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); N = Integer.parseInt(st.nextToken()); M = Integer.parseInt(st.nextToken()); pole = new int[N][M]; for (int i = 0; i \u0026lt; N; i++) { st = new StringTokenizer(br.readLine()); for (int j = 0; j \u0026lt; M; j++) pole[i][j] = Integer.parseInt(st.nextToken()); } count = 0; // 빙산을 녹이면서 두 개로 분리되는지 확인 while (!isFinished()) { melt(); count++; } System.out.println(count); } // 빙산 녹이기(녹은 값을 별도의 리스트에 저장 후 마지막에 반영) private static void melt() { List\u0026lt;int[]\u0026gt; melted = new ArrayList\u0026lt;\u0026gt;(); for (int i = 0; i \u0026lt; N; i++) { for (int j = 0; j \u0026lt; M; j++) { if (pole[i][j] == 0) continue; int now = pole[i][j]; for (int k = 0; k \u0026lt; 4; k++) { int nr = i + dr[k]; int nc = j + dc[k]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= M || pole[nr][nc] != 0) continue; now --; } if (now \u0026lt; 0) now = 0; melted.add(new int[] {i, j, now}); } } for (int[] now : melted) pole[now[0]][now[1]] = now[2]; } // 2개로 분리되었는지 확인(BFS) private static boolean isFinished() { // 두 덩이로 분리되지 않고 모든 빙산이 녹았을때 예외처리(중요) int sum = 0; for (int i = 0; i \u0026lt; N; i++) { for (int j = 0; j \u0026lt; M; j++) { sum += pole[i][j]; } } if (sum == 0) { count = 0; return true; } // BFS 초기화 Queue\u0026lt;int[]\u0026gt; q = new ArrayDeque\u0026lt;\u0026gt;(); boolean[][] isVisited = new boolean[N][M]; boolean isOneBlock = false; // 한 얼음을 만날때까지 확인 for (int i = 0; i \u0026lt; N; i++) { for (int j = 0; j \u0026lt; M; j++) { if (!isVisited[i][j] \u0026amp;\u0026amp; pole[i][j] != 0) { // 이미 얼음을 만났음에도 다시 만난 경우 true(빙산이 2개로 나눠짐) if (isOneBlock) return true; isOneBlock = true; // BFS를 돌면서 방문처리 q.offer(new int[] {i, j}); isVisited[i][j] = true; while (!q.isEmpty()) { int[] now = q.poll(); for (int k = 0; k \u0026lt; 4; k++) { int nr = now[0] + dr[k]; int nc = now[1] + dc[k]; if (nr \u0026lt; 0 || nr \u0026gt;= N || nc \u0026lt; 0 || nc \u0026gt;= M || pole[nr][nc] == 0 || isVisited[nr][nc]) continue; isVisited[nr][nc] = true; q.offer(new int[] {nr, nc}); } } } } } return false; } } 결과 리뷰 BFS를 사용한 간단한 구현문제였습니다. 2등분 되기 전에 얼음이 모두 녹아버리는 경우를 예외처리하지 않아 시간초과가 발생2해서 헤맸습니다. 실제 구현하는데는 오래 걸리지 않았지만 시간초과가 발생해서 최적화 문제로 생각했고, 예외상황을 생각하는데 많은 시간이 걸렸습니다. 문제 풀이가 막히거나 시간 초과가 발생한다면 최적화보다는 예외상황을 우선 점검하는게 좋을 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 문제에도 위 경우에는 0으로 처리하라는 주의사항이 있습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n제 풀이에서 얼음이 모두 녹아버리면 while문을 빠져나오지 못해 시간초과가 발생합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/cote/bj_2573/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.acmicpc.net/problem/2573\"\u003ehttps://www.acmicpc.net/problem/2573\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e각 칸이 10이하이고 전체 칸은 10,000이므로 빙산을 모두 녹이려면 10 _ 10,000 _ 10,000이지만, 네 변이 노출되면 금방 사라지므로 완전탐색이 가능하다고 판단했습니다.\u003c/li\u003e\n\u003cli\u003e매 회마다 빙산을 녹이고, 빙산을 bfs(dfs)로 탐색해서 두 덩어리가 있는지 확인한다.\u003c/li\u003e\n\u003cli\u003e모든 빙산이 다 녹았지만 2개로 분리되지 않는 경우를 주의한다.\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"풀이\"\u003e풀이\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003epackage\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003esolving\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.io.BufferedReader\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.io.IOException\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.io.InputStreamReader\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.util.*\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e/*\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * [조건]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 시간제한 : 1초, 메모리 제한 : 256MV\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 이차원 배열의 크기가 300^2 이하이며 전체 칸의 개수는 10,000개 이하, 각 칸의 크기는 10 이하\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * [풀이]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 각 칸이 10이하이고 전체 칸은 10,000이므로 빙산을 모두 녹이려면 10 * 10,000 * 10,000이지만, 네 변이 노출되면 금방 사라지므로 완전탐색\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 매 회마다 빙산을 녹이고, 빙산을 bfs로 탐색해서 두 덩어리가 있는지 확인한다.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003ebj_2573_빙산\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecount\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[][]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epole\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eIOException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 초기화\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedReader\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInputStreamReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ein\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eStringTokenizer\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eStringTokenizer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereadLine\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextToken\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextToken\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003epole\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eStringTokenizer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereadLine\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epole\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextToken\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003ecount\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 빙산을 녹이면서 두 개로 분리되는지 확인\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ewhile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"n\"\u003eisFinished\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003emelt\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003ecount\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecount\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 빙산 녹이기(녹은 값을 별도의 리스트에 저장 후 마지막에 반영)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emelt\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emelted\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eArrayList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epole\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epole\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edc\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epole\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e--\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003emelted\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emelted\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epole\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]][\u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 2개로 분리되었는지 확인(BFS)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eisFinished\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 두 덩이로 분리되지 않고 모든 빙산이 녹았을때 예외처리(중요)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003esum\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003esum\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epole\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esum\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003ecount\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// BFS 초기화\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eQueue\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eArrayDeque\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"o\"\u003e[][]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eisVisited\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eisOneBlock\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 한 얼음을 만날때까지 확인\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"n\"\u003eisVisited\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epole\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 이미 얼음을 만났음에도 다시 만난 경우 true(빙산이 2개로 나눠짐)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eisOneBlock\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eisOneBlock\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// BFS를 돌면서 방문처리\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eoffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003eisVisited\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"k\"\u003ewhile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eisEmpty\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003epoll\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                            \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                            \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enow\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edc\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ek\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003epole\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e||\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eisVisited\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003econtinue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                            \u003c/span\u003e\u003cspan class=\"n\"\u003eisVisited\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                            \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eoffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"결과\"\u003e결과\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"result\" loading=\"lazy\" src=\"/cote/bj_2573/result.png\"\u003e\u003c/p\u003e","title":"[Java]백준 2573 빙산"},{"content":"출처 https://www.acmicpc.net/problem/1753 접근 각 정점에서 다른 정점으로의 최솟값을 다익스트라1를 통해 구합니다. 정점으로부터 최단거리의 간선으로만 이동하기 때문에 O(ElogV) ≈ 1,200,0002으로 모든 간선을 확인하는 것이 가능합니다. 탐색하는 간선 개수를 최적화하기 위해 LinkedList로 저장합니다.3 문제에서 주어진 예제를 그래프로 표현했습니다.\n그래프 1 2 3 4 5 초기화 0 INF INF INF INF 1 -\u0026gt; 2, 3 0 2 3 INF INF 2 -\u0026gt; 3, 4 0 2 3 7 INF 3 -\u0026gt; 4 0 2 3 7 INF 5 -\u0026gt; 1 0 2 3 7 INF 위와 같이 거리 배열을 초기화한 후, 각 정점의 간선들을 탐색하며 배열을 채워나갑니다. 이 때, Greedy 알고리즘인 다익스트라가 도입되는데, 각 지점에서 가중치의 최솟값인 경로만 선택하면서 목표지점까지 이동하면 최소 경로를 구할 수 있다는 것입니다.\n풀이 package solving; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Arrays; import java.util.PriorityQueue; import java.util.StringTokenizer; /* * [조건] * 시간 제한 : 1초 / 메모리 제한 : 256MB * V \u0026lt;= 20,000 / E \u0026lt;= 300,000 * [풀이] * 각 정점에서 다른 정점으로의 최솟값을 다익스트라를 통해 구한다. * 정점으로부터 최단거리의 간선으로만 이동하기 때문에 O(E * logV) ≈ 1,500,000으로, 모든 간선을 확인하는 것이 가능하다. * 탐색하는 간선 개수를 최적화하기 위해 linkedList로 저장한다.(V^2 \u0026gt;\u0026gt; E) */ public class bj_1753_다익스트라 { static int INF = Integer.MAX_VALUE; static int V, E, K; static Node[] adj; static class Node { int v; int w; Node next; public Node(int v, int w, Node next) { this.v = v; this.w = w; this.next = next; } } public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); V = Integer.parseInt(st.nextToken()); E = Integer.parseInt(st.nextToken()); K = Integer.parseInt(br.readLine()); // 간선 리스트 초기화 adj = new Node[V + 1]; for (int i = 0; i \u0026lt; E; i++) { st = new StringTokenizer(br.readLine()); int from = Integer.parseInt(st.nextToken()); int to = Integer.parseInt(st.nextToken()); int w = Integer.parseInt(st.nextToken()); adj[from] = new Node(to, w, adj[from]); } // 다익스트라 int[] dijkstra = dijkstra(); for (int i = 1; i \u0026lt;= V; i++) System.out.println(dijkstra[i] == INF? \u0026#34;INF\u0026#34; : dijkstra[i]); } // 각 목표지점까지의 다익스트라 private static int[] dijkstra() { PriorityQueue\u0026lt;int[]\u0026gt; pq = new PriorityQueue\u0026lt;\u0026gt;((o1, o2) -\u0026gt; o1[1] - o2[1]); // 가중치의 최솟값 저장 pq.offer(new int[] {K, 0}); // int[0] : 정점, int[1] : 현재까지의 최단경로 int[] distance = new int[V + 1]; // 거리 배열 초기화 Arrays.fill(distance, INF); distance[K] = 0; while (!pq.isEmpty()) { int[] now = pq.poll(); if (distance[now[0]] \u0026lt; now[1]) continue; // 현재 거리배열보다 작은값은 사용하지 않음(최적화) for (Node n = adj[now[0]]; n != null; n = n.next) { int total = now[1] + n.w; // 이전 최단경로에서 가중치를 더하여 현재 최단경로를 구함 if (total \u0026lt; distance[n.v]) { distance[n.v] = total; // 거리배열에 현재까지의 최솟값 저장 pq.offer(new int[] {n.v, total}); // 우선순위 큐에 현재 최단경로 추가 } } } return distance; } } 결과 리뷰 처음 문제를 보고 모든 지점까지의 경로를 계산하는 부분에서 플로이드-워샬 문제라고 생각했지만, 시간복잡도를 계산해보니 불가능함을 알았습니다. 보통은 다익스트라를 통해 특정 지점까지의 최솟값을 구하지만, 다익스트라의 특성상 한 정점에서 모든 탐색을 완료하면 나머지 지점까지의 최솟값을 얻을 수 있는 것을 활용한 문제였습니다. 다익스트라 문제를 오랜만에 풀다가 구현이 막혀서 다른 블로그의 개념을 참고했습니다. 항상 개념이 부족하면 구현이 안되는 것 같습니다. 각 노드의 최솟값을 저장하고, 방문배열처럼 사용하는 것과 이를 통해 최적화하는 부분의 로직을 잘 기억해야겠습니다. References URL 게시일자 방문일자 작성자 https://velog.io/@panghyuk/%EC%B5%9C%EB%8B%A8-%EA%B2%BD%EB%A1%9C-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98 2022.02.07. 2024.06.21. PANGHYUK 가중치가 있는 그래프에서 최단 경로를 Greedy로 탐색하는 알고리즘입니다. 다음 블로그에 잘 정리가 되어있습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n우선순위큐에 모든 간선을 집어넣어서 정렬을 해야하므로 O(E x logE)이지만, 중복 간선은 존재하지 않기 때문에 근사적으로 O(ElogE) = O(ElogV^2) = O(2ElogV)로 표현이 가능합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n간선을 매트릭스 형태로 저장하게 되면 V^2 = 20,000 x 20,000 이지만, 간선 리스트로 저장하면 E = 300,000으로 시간복잡도를 훨씬 절약할 수 있습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/cote/bj_1753/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.acmicpc.net/problem/1753\"\u003ehttps://www.acmicpc.net/problem/1753\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e각 정점에서 다른 정점으로의 최솟값을 다익스트라\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e를 통해 구합니다.\u003c/li\u003e\n\u003cli\u003e정점으로부터 최단거리의 간선으로만 이동하기 때문에 O(ElogV) ≈ 1,200,000\u003csup id=\"fnref:2\"\u003e\u003ca href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e2\u003c/a\u003e\u003c/sup\u003e으로 모든 간선을 확인하는 것이 가능합니다.\u003c/li\u003e\n\u003cli\u003e탐색하는 간선 개수를 최적화하기 위해 LinkedList로 저장합니다.\u003csup id=\"fnref:3\"\u003e\u003ca href=\"#fn:3\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e3\u003c/a\u003e\u003c/sup\u003e\n\u003cfigure\u003e\n      \u003cimg loading=\"lazy\" src=\"solve.jpeg\"\n           alt=\"문제에서 주어진 예제를 그래프로 표현했습니다.\"/\u003e \u003cfigcaption\u003e\n              \u003cp\u003e문제에서 주어진 예제를 그래프로 표현했습니다.\u003c/p\u003e\n          \u003c/figcaption\u003e\n  \u003c/figure\u003e\n\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e그래프\u003c/th\u003e\n          \u003cth\u003e1\u003c/th\u003e\n          \u003cth\u003e2\u003c/th\u003e\n          \u003cth\u003e3\u003c/th\u003e\n          \u003cth\u003e4\u003c/th\u003e\n          \u003cth\u003e5\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e초기화\u003c/td\u003e\n          \u003ctd\u003e0\u003c/td\u003e\n          \u003ctd\u003eINF\u003c/td\u003e\n          \u003ctd\u003eINF\u003c/td\u003e\n          \u003ctd\u003eINF\u003c/td\u003e\n          \u003ctd\u003eINF\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e1 -\u0026gt; 2, 3\u003c/td\u003e\n          \u003ctd\u003e0\u003c/td\u003e\n          \u003ctd\u003e2\u003c/td\u003e\n          \u003ctd\u003e3\u003c/td\u003e\n          \u003ctd\u003eINF\u003c/td\u003e\n          \u003ctd\u003eINF\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e2 -\u0026gt; 3, 4\u003c/td\u003e\n          \u003ctd\u003e0\u003c/td\u003e\n          \u003ctd\u003e2\u003c/td\u003e\n          \u003ctd\u003e3\u003c/td\u003e\n          \u003ctd\u003e7\u003c/td\u003e\n          \u003ctd\u003eINF\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e3 -\u0026gt; 4\u003c/td\u003e\n          \u003ctd\u003e0\u003c/td\u003e\n          \u003ctd\u003e2\u003c/td\u003e\n          \u003ctd\u003e3\u003c/td\u003e\n          \u003ctd\u003e7\u003c/td\u003e\n          \u003ctd\u003eINF\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e5 -\u0026gt; 1\u003c/td\u003e\n          \u003ctd\u003e0\u003c/td\u003e\n          \u003ctd\u003e2\u003c/td\u003e\n          \u003ctd\u003e3\u003c/td\u003e\n          \u003ctd\u003e7\u003c/td\u003e\n          \u003ctd\u003eINF\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cblockquote\u003e\n\u003cp\u003e위와 같이 거리 배열을 초기화한 후, 각 정점의 간선들을 탐색하며 배열을 채워나갑니다.\n이 때, Greedy 알고리즘인 다익스트라가 도입되는데, 각 지점에서 가중치의 최솟값인 경로만 선택하면서 목표지점까지 이동하면 최소 경로를 구할 수 있다는 것입니다.\u003c/p\u003e","title":"[Java]백준 1753 다익스트라"},{"content":"출처 https://www.acmicpc.net/problem/1976 접근 일반적인 DFS로 구현하면 각 여행경로를 확인하는데 O(N^3)1가 소요됩니다. 도시가 1000개이므로 200 x 200 x 200 x 1000 = 8,000,000,000(80억)으로 시간초과가 발생합니다.\n해당 문제에서는 다른 도시를 몇번이든 방문할 수 있기 때문에, 일일히 경로를 방문하는 것은 시간초과를 유발합니다. 이를 최적화하기 위해 모든 여행계획이 같은 네트워크(루트를 공유)에 포함되는지 확인하기만 하면 됩니다. 이는 유니온 파인드(Union-find)로 구현할 수 있습니다. 풀이 package solving; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.StringTokenizer; /* * [조건] * 시간 제한 : 2초, 메모리 제한 : 128MB * N \u0026lt;= 200, M \u0026lt;= 1000 * [구현] * 유니온 파인드를 통해 연결그래프를 구하고, 주어지는 여행계획이 같은 루트에 속하는지 확인한다. */ public class bj_1976_여행_가자 { static int N, M; static int[] root; public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st; N = Integer.parseInt(br.readLine()); M = Integer.parseInt(br.readLine()); root = new int[N + 1]; // 유니온 초기화 for (int i = 1; i \u0026lt;= N; i++) root[i] = i; for (int i = 1; i \u0026lt;= N; i++) { st = new StringTokenizer(br.readLine()); for (int j = 1; j \u0026lt;= N; j++) { // 두 도시를 같은 집합(루트)에 소속 if (st.nextToken().equals(\u0026#34;1\u0026#34;)) union(i, j); } } int[] plan = new int[M + 1]; st = new StringTokenizer(br.readLine()); for (int i = 1; i \u0026lt;= M; i++) plan[i] = Integer.parseInt(st.nextToken()); System.out.println(solve(plan)); } private static void union(int i, int j) { int ri = find(i); int rj = find(j); if (ri != rj) root[rj] = ri; } private static int find(int i) { if (i != root[i]) root[i] = find(root[i]); return root[i]; } private static String solve(int[] plan) { int r = find(plan[1]); for (int i = 2; i \u0026lt;= M; i++) { if (find(plan[i]) != r) return \u0026#34;NO\u0026#34;; } return \u0026#34;YES\u0026#34;; } } 결과 리뷰 시행착오 마지막에 각 도시의 root를 비교할때 find로 안찾고 root를 그대로 사용하는 시행착오가 있었습니다. /* ...생략 */ private static String solve(int[] plan) { int r = root[plan[1]]; for (int i = 2; i \u0026lt;= M; i++) { if (root[plan[i]] != r) return \u0026#34;NO\u0026#34;; } return \u0026#34;YES\u0026#34;; } 경로압축2을 통해 모든 도시가 같은 루트를 바라보게 될 것이라고 생각했는데, 다시 생각해보니 경로압축이 되지 않은 도시가 충분히 존재할 수 있어 보입니다.\n주어진 조건에서 DFS로는 구현이 안될 것 같다고 예상했는데, 이렇게 실제 구현 전에 시간복잡도 및 메모리를 잘 고려하여 많은 시간을 절약할 수 있었습니다. 유니온 파인드 알고리즘은 이제 어느정도 이해하는 것 같은데, 매번 다시 구현하는데 시간이 걸리는 것 같습니다. 다음에 유사한 문제를 만나면 좀더 빨리 풀 수 있기를 기대합니다. References URL 게시일자 방문일자 작성자 가장 멀리 떨어진 도시까지 모든 도시를 돌면서(N) 연결여부를 확인(N^2)하므로 총 O(N x N^2) = O(N^3) 입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n아래 코드를 통해 find() 메서드를 실행하며 만나는 모든 도시들의 root를 최종 root로 변경합니다.\nprivate static int find(int i) { // 현재 메서드의 root[i]를 재귀적으로 다음에서 찾은 root로 변경 if (i != root[i]) root[i] = find(root[i]); return root[i]; } \u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/cote/bj_1976/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.acmicpc.net/problem/1976\"\u003ehttps://www.acmicpc.net/problem/1976\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e일반적인 DFS로 구현하면 각 여행경로를 확인하는데 O(N^3)\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e가 소요됩니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cblockquote\u003e\n\u003cp\u003e도시가 1000개이므로 200 x 200 x 200 x 1000 = 8,000,000,000(80억)으로 시간초과가 발생합니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cul\u003e\n\u003cli\u003e해당 문제에서는 다른 도시를 몇번이든 방문할 수 있기 때문에, 일일히 경로를 방문하는 것은 시간초과를 유발합니다.\u003c/li\u003e\n\u003cli\u003e이를 최적화하기 위해 모든 여행계획이 같은 네트워크(루트를 공유)에 포함되는지 확인하기만 하면 됩니다.\u003c/li\u003e\n\u003cli\u003e이는 유니온 파인드(Union-find)로 구현할 수 있습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"풀이\"\u003e풀이\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003epackage\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003esolving\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.io.BufferedReader\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.io.IOException\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.io.InputStreamReader\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.util.StringTokenizer\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e/*\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * [조건]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 시간 제한 : 2초, 메모리 제한 : 128MB\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * N \u0026lt;= 200, M \u0026lt;= 1000\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * [구현]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 유니온 파인드를 통해 연결그래프를 구하고, 주어지는 여행계획이 같은 루트에 속하는지 확인한다.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003ebj_1976_여행_가자\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eIOException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedReader\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInputStreamReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ein\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eStringTokenizer\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereadLine\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereadLine\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 유니온 초기화\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eStringTokenizer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereadLine\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 두 도시를 같은 집합(루트)에 소속\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextToken\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003eequals\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eunion\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eplan\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eStringTokenizer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereadLine\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eplan\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextToken\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esolve\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eplan\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eunion\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eri\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efind\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erj\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efind\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eri\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003erj\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003erj\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eri\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003efind\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efind\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eroot\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003esolve\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eplan\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efind\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eplan\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efind\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eplan\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;NO\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;YES\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"결과\"\u003e결과\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"result\" loading=\"lazy\" src=\"/cote/bj_1976/result.png\"\u003e\u003c/p\u003e","title":"[Java]백준 1976 여행 가자"},{"content":"출처 https://www.acmicpc.net/problem/14503 접근 N, M \u0026lt;= 50, O(N^5) = 312,500,000 이므로 시간복잡도는 여유롭게 구현 가능합니다. 로봇 청소기의 이동 방식을 BFS로 구현했습니다. 다시 생각해보니 BFS형태가 아니더라도 구현 가능한데 습관적으로 BFS를 사용한 것 같습니다.\n문제에서 주어진 방향설정이 중요합니다. 북(-1, 0), 동(0, 1), 남(1, 0), 서(0, -1)\n친절하게 방의 둘레는 모두 벽(1)로 채워져 있기 때문에 ArrayIndexOutOfBoundsException1은 처리하지 않아도 됩니다. 풀이 package solving; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayDeque; import java.util.Queue; import java.util.StringTokenizer; /* * N, M이 50 이하이므로, 시간복잡도와 메모리제한은 고려하지 않음 * [풀이] * 로봇 청소기의 이동 방식을 bfs로 구현한다. * 방향 : 북(-1, 0), 동(0, 1), 남(1, 0), 서(0, -1) * 청소를 마치면 0 -\u0026gt; -1로 변경 * 로봇의 동작은 각각 별도의 메서드로 분리 */ public class bj_14503_로봇_청소기 { static int N; static int M; static int[][] room; static int[] dr = {-1, 0, 1, 0}; static int[] dc = {0, 1, 0, -1}; public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); N = Integer.parseInt(st.nextToken()); M = Integer.parseInt(st.nextToken()); room = new int[N + 1][M + 1]; st = new StringTokenizer(br.readLine()); int[] start = {Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken())}; int direction = Integer.parseInt(st.nextToken()); for (int i = 0; i \u0026lt; N; i++) { st = new StringTokenizer(br.readLine()); for (int j = 0; j \u0026lt; M; j++) room[i][j] = Integer.parseInt(st.nextToken()); } solve(start, direction); } private static void solve(int[] start, int direction) { Queue\u0026lt;int[]\u0026gt; q = new ArrayDeque\u0026lt;\u0026gt;(); q.offer(new int[]{start[0], start[1], direction}); int count = 0; while (!q.isEmpty()) { int[] cur = q.poll(); int r = cur[0], c = cur[1], d = cur[2]; // 현재 칸이 청소되지 않은 경우 if (room[r][c] == 0) { count++; room[r][c] = -1; } // 주변 4칸 중 빈칸이 없을 경우 if (!findDirty(r, c)) { int[] back = moveBack(r, c, d); if (back == null) break; q.offer(back); } // 주변 4칸 중 빈칸이 있을 경우 else { for (int i = 0; i \u0026lt; 4; i++) { d = d == 0 ? 3 : d == 3 ? 2 : d == 2 ? 1 : 0; int[] forward = moveForward(r, c, d); if (forward != null) { q.offer(forward); break; } } } } System.out.println(count); } private static int[] moveForward(int r, int c, int nd) { int nr = r + dr[nd]; int nc = c + dc[nd]; // 더이상 이동할 수 없을 때 if (room[nr][nc] != 0) return null; else return new int[]{nr, nc, nd}; } private static int[] moveBack(int r, int c, int d) { int nd = d == 0 ? 2 : d == 1 ? 3 : d == 2 ? 0 : 1; int nr = r + dr[nd]; int nc = c + dc[nd]; // 더이상 물러날 곳이 없을때 if (room[nr][nc] == 1) return null; else return new int[]{nr, nc, d}; } private static boolean findDirty(int r, int c) { for (int i = 0; i \u0026lt; 4; i++) { int nr = r + dr[i]; int nc = c + dc[i]; if (room[nr][nc] == 0) return true; } return false; } } 결과 리뷰 문제에 주어진 로직을 그대로 구현하는 문제여서 난이도는 높지 않았습니다. 로직을 메서드 단위로 분리해서 구현하니 좀더 직관적으로 구현이 가능했습니다. BFS를 구현하기 위해 불필요한 Queue 객체를 추가로 생성한 것 같아 아쉽습니다. BFS나 DFS를 사용하기에 앞서 탐색 시 기존 경로를 저장할 필요가 있는지를 따져보고 도입하는게 좋을 것 같습니다. References URL 게시일자 방문일자 작성자 유효하지 않은 배열 인덱스에 접근할때 발생하는 오류입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/cote/bj_14503/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.acmicpc.net/problem/14503\"\u003ehttps://www.acmicpc.net/problem/14503\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eN, M \u0026lt;= 50, O(N^5) = 312,500,000 이므로 시간복잡도는 여유롭게 구현 가능합니다.\u003c/li\u003e\n\u003cli\u003e로봇 청소기의 이동 방식을 BFS로 구현했습니다.\n\u003cblockquote\u003e\n\u003cp\u003e다시 생각해보니 BFS형태가 아니더라도 구현 가능한데 습관적으로 BFS를 사용한 것 같습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003cli\u003e문제에서 주어진 방향설정이 중요합니다.\n\u003cblockquote\u003e\n\u003cp\u003e북(-1, 0), 동(0, 1), 남(1, 0), 서(0, -1)\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003c/li\u003e\n\u003cli\u003e친절하게 방의 둘레는 모두 벽(1)로 채워져 있기 때문에 ArrayIndexOutOfBoundsException\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e은 처리하지 않아도 됩니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"풀이\"\u003e풀이\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003epackage\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003esolving\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.io.BufferedReader\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.io.IOException\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.io.InputStreamReader\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.util.ArrayDeque\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.util.Queue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ejava.util.StringTokenizer\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e/*\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * N, M이 50 이하이므로, 시간복잡도와 메모리제한은 고려하지 않음\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * [풀이]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 로봇 청소기의 이동 방식을 bfs로 구현한다.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 방향 : 북(-1, 0), 동(0, 1), 남(1, 0), 서(0, -1)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 청소를 마치면 0 -\u0026gt; -1로 변경\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e * 로봇의 동작은 각각 별도의 메서드로 분리\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cm\"\u003e */\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003ebj_14503_로봇_청소기\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[][]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eroom\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emain\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eargs\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eIOException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedReader\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eBufferedReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInputStreamReader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ein\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eStringTokenizer\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eStringTokenizer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereadLine\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextToken\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextToken\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eroom\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eStringTokenizer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereadLine\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextToken\u003c/span\u003e\u003cspan class=\"p\"\u003e()),\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextToken\u003c/span\u003e\u003cspan class=\"p\"\u003e())};\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edirection\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextToken\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eN\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eStringTokenizer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereadLine\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eM\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eroom\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ej\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eparseInt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003enextToken\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003esolve\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edirection\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003esolve\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edirection\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eQueue\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eArrayDeque\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eoffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003estart\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edirection\u003c/span\u003e\u003cspan class=\"p\"\u003e});\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecount\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ewhile\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eisEmpty\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003epoll\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ed\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ecur\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 현재 칸이 청소되지 않은 경우\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroom\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003ecount\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eroom\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 주변 4칸 중 빈칸이 없을 경우\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e!\u003c/span\u003e\u003cspan class=\"n\"\u003efindDirty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eback\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emoveBack\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ed\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eback\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eoffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eback\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 주변 4칸 중 빈칸이 있을 경우\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"n\"\u003ed\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ed\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e?\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e3\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ed\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e3\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e?\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ed\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e?\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eforward\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emoveForward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ed\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eforward\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                        \u003c/span\u003e\u003cspan class=\"n\"\u003eq\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eoffer\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eforward\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                        \u003c/span\u003e\u003cspan class=\"k\"\u003ebreak\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eSystem\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eprintln\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecount\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emoveForward\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003end\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003end\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edc\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003end\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 더이상 이동할 수 없을 때\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroom\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e!=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003end\u003c/span\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003emoveBack\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ed\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003end\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ed\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e?\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ed\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e?\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e3\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ed\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e2\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e?\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003end\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edc\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003end\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 더이상 물러날 곳이 없을때\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroom\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ed\u003c/span\u003e\u003cspan class=\"p\"\u003e};\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003estatic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003efindDirty\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e4\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e++\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003er\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edr\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003edc\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eroom\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003enr\u003c/span\u003e\u003cspan class=\"o\"\u003e][\u003c/span\u003e\u003cspan class=\"n\"\u003enc\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003efalse\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"결과\"\u003e결과\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"result\" loading=\"lazy\" src=\"/cote/bj_14503/result.png\"\u003e\u003c/p\u003e","title":"[Java]백준 14503 로봇 청소기"},{"content":"출처 https://www.acmicpc.net/problem/1806 접근 문제는 굉장히 심플하나, 시간제한이 0.5초이며 메모리 제한이 128MB인 것으로 보아 최적화 문제임을 알 수 있습니다. 주어지는 수열의 부분합을 구해야 하는데, 수열의 길이가 100,000이므로 O(N^2)으로는 시간 초과가 발생합니다. 부분합의 최대 크기를 100,000,000 ≒ 2^30 정도로 제한해주었기 때문에 int로 총합을 구해도 Overflow가 발생하지 않습니다. 풀이 포인터를 2개 두고 합이 S보다 커질때까지 부분수열의 크기를 늘리다가, S보다 커지는 시점부터 부분수열의 크기를 줄여나가면 됩니다. ①에서 점점 부분수열을 늘리다가 ②처럼 다시 15보다 작을때까지 사이즈를 줄여나갑니다.\n위와 같은 로직을 반복하면 각 숫자를 최대 2번씩 확인하기 때문에 O(2N) = O(N)의 시간복잡도로 해결이 가능합니다. import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.StringTokenizer; /* * [조건] * 시간 제한 : 1s / 메모리 제한 : 128MB * N \u0026lt; 100,000 / s \u0026lt;= 100,000,000 / 최대 O(Nlog(N)) / Int로 총합 구해도 Overflow 발생하지 않음 * [풀이] * 합이 S보다 커질떄까지 앞에서부터 부분수열 더하기, S보다 클때의 길이 저장 * 합이 S보다 커지면 S보다 작아질때까지 앞에서부터 부분수열 빼기, S보다 클때의 길이 저장 * 길이의 최솟값 출력 */ public class bj_1806_부분합 { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); int N = Integer.parseInt(st.nextToken()); int S = Integer.parseInt(st.nextToken()); int[] arr = new int[N]; st = new StringTokenizer(br.readLine()); for (int i = 0; i \u0026lt; N; i++) arr[i] = Integer.parseInt(st.nextToken()); System.out.print(solve(N, S, arr)); } private static int solve(int N, int S, int[] arr) { int min = N + 1, sum = arr[0], p0 = 0, p1 = 0; while (p1 \u0026lt; N) { if (sum \u0026gt;= S) { min = Math.min(p1 - p0 + 1, min); sum -= arr[p0++]; } else { if (p1 == N - 1) break; sum += arr[++p1]; } } return min != N + 1? min : 0; } } 결과 리뷰 예전에 파이썬으로 풀었던 적이 있는 문제였습니다. 풀이는 금방 떠올렸는데 이를 구현하려고 하니 조금 복잡하게 느껴졌습니다. 설계한 로직을 빠르고 직관적으로 코드로 옮기는 과정을 좀더 연습해야겠다고 느꼈습니다. 동일한 로직을 파이썬으로 풀었을 때1 전체 시간이 160ms정도로 더 작게 나왔는데, 자바에서는 최초 배열을 생성하고 loop를 한번 도는 로직이 추가되어 그런 듯 합니다. References URL 게시일자 방문일자 작성자 아래는 파이썬 코드입니다.\nimport sys input = sys.stdin.readline N, S = map(int, input().split()) arr = list(map(int, input().split())) min_sum_length = 100000000 left, right = 0, 0 judge = arr[0] while right \u0026lt; len(arr): if judge \u0026gt;= S: min_sum_length = min(min_sum_length, right - left + 1) judge -= arr[left] left += 1 elif judge \u0026lt; S: if right == len(arr) - 1: break right += 1 judge += arr[right] print(0 if min_sum_length == 100000000 else min_sum_length) \u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/cote/bj_1806/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.acmicpc.net/problem/1806\"\u003ehttps://www.acmicpc.net/problem/1806\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e문제는 굉장히 심플하나, 시간제한이 0.5초이며 메모리 제한이 128MB인 것으로 보아 최적화 문제임을 알 수 있습니다.\u003c/li\u003e\n\u003cli\u003e주어지는 수열의 부분합을 구해야 하는데, 수열의 길이가 100,000이므로 O(N^2)으로는 시간 초과가 발생합니다.\u003c/li\u003e\n\u003cli\u003e부분합의 최대 크기를 100,000,000 ≒ 2^30 정도로 제한해주었기 때문에 int로 총합을 구해도 Overflow가 발생하지 않습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"풀이\"\u003e풀이\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e포인터를 2개 두고 합이 S보다 커질때까지 부분수열의 크기를 늘리다가, S보다 커지는 시점부터 부분수열의 크기를 줄여나가면 됩니다.\n\u003cfigure\u003e\n      \u003cimg loading=\"lazy\" src=\"solve1.jpeg\"\n           alt=\"①에서 점점 부분수열을 늘리다가 ②처럼 다시 15보다 작을때까지 사이즈를 줄여나갑니다.\"/\u003e \u003cfigcaption\u003e\n              \u003cp\u003e①에서 점점 부분수열을 늘리다가 ②처럼 다시 15보다 작을때까지 사이즈를 줄여나갑니다.\u003c/p\u003e","title":"[Java]백준 1806 부분합"},{"content":"출처 https://www.acmicpc.net/problem/4195 접근 처음에는 문제가 잘 이해되지 않았습니다.1 몇 번 읽어보니, 두 정점이 주어지면 두 정점을 연결하고 같은 그래프 내에 있는 모든 친구 개수를 출력하는 문제임을 알았습니다. ①, ②을 통해 연결된 네트워크는 각각 친구가 2명이며, ③을 통해 두 네트워크가 연결되면 총 친구는 4명입니다.\n친구관계가 연결되어 같은 그래프에 포함되는 과정이 Union-Find 알고리즘과 동일하기 때문에, 이를 활용하여 해결할 수 있습니다. 유니온 파인드(Union Find) Union과 Find 메서드를 통해 서로소 집합2을 연결하는 알고리즘입니다. 각 집합의 단위는 Root로부터 하위 원소들로 구성되며, 모든 원소들은 동일한 Root를 바라보는 특징이 있습니다. Union과 Find를 간략히 그림으로 나타내면 다음과 같습니다. 노란색 : 부모 / 빨간색 : 유니온(Union) 메서드 / 파란색 : 파인드(find) 메서드\nRoot는 본인을 바라보는 노드이며, 최초 네트워크가 구성될 때 본인을 바라보도록 만듭니다. Union union 연산을 수행하면, 서로 교집합이 없는 두 네트워크의 Root를 한쪽을 바라보도록 연결합니다. 이 때, 한쪽 네트워크의 부모를 다른 네트워크의 부모를 바라보도록 변경해주면 되므로 연결 과정은 O(1)로 연산이 가능합니다. Find find 연산을 수행하면, 부모가 본인인 노드(Root)가 나올때까지 탐색을 수행합니다. 만약 네트워크 끝에서부터 탐색한다면(위 그림에서 3, 5번 노드), 최대 O(N)의 시간복잡도가 필요합니다. 이를 줄이기 위해 Find과정에서 만나는 부모들을 모두 부모를 바라보도록 변경하는 작업을 수행하면, 처음 탐색속도는 동일하게 O(N)이지만 이후 탐색속도는 O(1)이 되도록 개선할 수 있습니다.3 풀이 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.StringTokenizer; /* * [조건] * 시간제한 : 3초 / 메모리제한 : 256MB * F \u0026lt;= 100,000 / name.length() \u0026lt;= 20 * [풀이] * Union find 알고리즘을 통해 공통 친구 네트워크의 개수를 구한다. * 친구 집합의 root를 find를 통해 찾는다. * root가 아직 정해지지 않았다면, 공통 집합에 가입시킨다. * 두 친구관계가 만나면 Union을 통해 공통 집합에 가입시키고, 공통 집합의 크기를 더한다. */ public class bj_4195_친구_네트워크 { static int groupId = 0; static HashMap\u0026lt;String, String\u0026gt; friendship; // 친구관계(부모)를 가리키는 해시맵 static HashMap\u0026lt;String, Integer\u0026gt; rootCount; // 네트워크 크기를 루트와 매핑하는 해시맵 public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int T = Integer.parseInt(br.readLine()); for (int i = 0; i \u0026lt; T; i++) { int F = Integer.parseInt(br.readLine()); friendship = new HashMap\u0026lt;\u0026gt;(); rootCount = new HashMap\u0026lt;\u0026gt;(); for (int j = 0; j \u0026lt; F; j++) { StringTokenizer st = new StringTokenizer(br.readLine()); String f1 = st.nextToken(); String f2 = st.nextToken(); System.out.println(getFriends(f1, f2)); } } } /* * 집합(친구관계)을 생성하는 메서드 */ static void makeSet(String s) { friendship.put(s, s); // 최초 친구 : 1명 rootCount.put(s, 1); } /* * 각 네트워크의 루트를 찾아(Find) 두 관계를 합치는 메서드 * 왼쪽값의 root에 오른쪽을 편입시킴 */ static int union(String s1, String s2) { String root1 = find(s1); String root2 = find(s2); // 이미 친구일 경우 예외처리 if (root1.equals(root2)) return rootCount.get(root1); // 네트워크 편입 friendship.put(root2, root1); // 편입 시 두 네트워크의 합으로 루트 해시맵의 값을 변경, 네트워크의 합 return int value = rootCount.get(root1) + rootCount.get(root2); rootCount.put(root1, value); return value; } /* * 부모를 찾는 메서드 * HashMap의 값이 본인이 아니면 본인이 나올때까지 재귀호출하여 path compression * HashMap의 값 == 본인 이면 root */ static String find(String me) { // Recursive Path Compression(재귀적으로 부모값을 루트로 변경시켜 이후 탐색속도 높임) if (!me.equals(friendship.get(me))) friendship.put(me, find(friendship.get(me))); return friendship.get(me); } /* * 두 네트워크를 합치고 공통의 친구 개수를 찾는 메서드 */ static int getFriends(String f1, String f2) { if (!friendship.containsKey(f1)) makeSet(f1); if (!friendship.containsKey(f2)) makeSet(f2); return union(f1, f2); } } 결과 리뷰 오랜만에 유니온 파인드 알고리즘을 만나서 복습해볼 수 있었습니다. 중간에 같은 네트워크에 이미 소속된 두 친구가 만났을 경우를 예외처리하지 않아서 조금 헤맸습니다. 확실히 구현문제는 예외 상황들을 미리 그려놓고 문제 풀이에 들어가야 빠르게 처리할 수 있는 것 같습니다. References URL 게시일자 방문일자 작성자 개인적으로 영어 원문으로 변경하니 더 이해가 쉬웠던 것 같습니다. \u0026#160;\u0026#x21a9;\u0026#xfe0e;\n서로 중복(교집합)이 없는 집합을 말합니다. 애초에 교집합이 있다면 같은 네트워크 안에 포함되어 있기 때문에 유니온 연산이 불가능합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n위 과정을 경로 압축(Path Compression)이라고 부릅니다. 이번 문제에서 경로압축을 하지않으면 O(N)의 탐색을 N(100,000)회 수행하므로 O(N^2) ≒ 2^40 으로 시간초과가 발생합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/cote/bj_4195/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.acmicpc.net/problem/4195\"\u003ehttps://www.acmicpc.net/problem/4195\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e처음에는 문제가 잘 이해되지 않았습니다.\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e\u003c/li\u003e\n\u003cli\u003e몇 번 읽어보니, 두 정점이 주어지면 두 정점을 연결하고 같은 그래프 내에 있는 모든 친구 개수를 출력하는 문제임을 알았습니다.\n\u003cfigure\u003e\n      \u003cimg loading=\"lazy\" src=\"solve2.jpeg\"\n           alt=\"①, ②을 통해 연결된 네트워크는 각각 친구가 2명이며, ③을 통해 두 네트워크가 연결되면 총 친구는 4명입니다.\"/\u003e \u003cfigcaption\u003e\n              \u003cp\u003e①, ②을 통해 연결된 네트워크는 각각 친구가 2명이며, ③을 통해 두 네트워크가 연결되면 총 친구는 4명입니다.\u003c/p\u003e\n          \u003c/figcaption\u003e\n  \u003c/figure\u003e\n\u003c/li\u003e\n\u003cli\u003e친구관계가 연결되어 같은 그래프에 포함되는 과정이 Union-Find 알고리즘과 동일하기 때문에, 이를 활용하여 해결할 수 있습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"유니온-파인드union-find\"\u003e유니온 파인드(Union Find)\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eUnion과 Find 메서드를 통해 서로소 집합\u003csup id=\"fnref:2\"\u003e\u003ca href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e2\u003c/a\u003e\u003c/sup\u003e을 연결하는 알고리즘입니다.\u003c/li\u003e\n\u003cli\u003e각 집합의 단위는 Root로부터 하위 원소들로 구성되며, 모든 원소들은 동일한 Root를 바라보는 특징이 있습니다.\u003c/li\u003e\n\u003cli\u003eUnion과 Find를 간략히 그림으로 나타내면 다음과 같습니다.\n\u003cfigure\u003e\n      \u003cimg loading=\"lazy\" src=\"solve3.jpeg\"\n           alt=\"노란색 : 부모 / 빨간색 : 유니온(Union) 메서드 / 파란색 : 파인드(find) 메서드\"/\u003e \u003cfigcaption\u003e\n              \u003cp\u003e노란색 : 부모 / 빨간색 : 유니온(Union) 메서드 / 파란색 : 파인드(find) 메서드\u003c/p\u003e","title":"[Java]백준 4195 친구 네트워크"},{"content":"출처 https://www.acmicpc.net/problem/12904 접근 처음에는 DP문제인줄 알고 접근했으나, 시간 초과로 실패했습니다.1 S로부터 T로 갈때는 여러 경로가 존재해서 DP와 같은 최적화가 필요해보입니다. S에서 시작할 경우 2^(T의 길이 - S의 길이) 만큼의 탐색이 필요합니다.\n그러나 반대로 T에서 S로 갈때는 경로가 1개만 존재하므로, (T의 길이 - S의 길이)만큼만 탐색하면 S를 구할 수 있습니다. T에서 S로 갈때는 T의 맨뒤 값으로 이전 노드를 예측하는 것이 가능합니다.\n이를 활용하면 다음과 같은 접근을 통해 O(N)으로 해결이 가능합니다. T의 맨뒤값을 확인하여 A일 경우, 맨끝을 제거합니다. B일 경우, 맨끝을 제거하고 문자열을 뒤집습니다. S의 길이가 될때까지 반복 후, 두 문자열이 같은지 비교합니다. 풀이 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public class bj_12904_A와_B_2트 { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String S = br.readLine(); String T = br.readLine(); if (solve(S, T)) System.out.print(1); else System.out.print(0); } /* * [조건] * 시간제한 : 2초 / 메모리제한 : 512MB * S.length() \u0026lt;= 999, T.length() \u0026lt;= 1000, S.length() \u0026lt; T.length() * [풀이] * T의 맨뒤를 보면 이전값을 유추할 수 있다. * T를 맨뒤에서부터 이전으로 돌려간다. */ static boolean solve(String s, String t) { int l = s.length(); String nt = t; while (nt.length() \u0026gt; l) { // nt의 길이가 s가 될때까지 반복 char[] tc = nt.toCharArray(); char end = tc[nt.length() - 1]; // 맨뒤값 가져오기 nt = nt.substring(0, nt.length() - 1); // 맨뒤값 제거하기 if (end == \u0026#39;B\u0026#39;) nt = new StringBuilder(nt).reverse().toString(); // B일 경우 뒤집기 } if (nt.equals(s)) return true; // nt와 s가 같으면 true else return false; // nt와 s가 다르면 false } } 결과 리뷰 한쪽 방향으로 생각했을때는 탐색 과정에서 자식이 2개씩 있는 이진 트리 형태라고 생각했는데, 반대로 생각하니 부모는 1개만 존재했습니다. 처음에 DP로 풀려고 하다가 시간을 1시간정도 써서 아쉬웠습니다. DP로 풀수있을지 약간 망설여졌는데, 지금 생각해보면 1000개나 되는 자식을 이진트리로 검색하는건 아무리 최적화해도 힘들 듯합니다. 앞으로는 코드 작성 전 더 많은 경우의 수를 고민하고, 충분히 해결 가능하다 싶을때 풀이에 들어가는 습관을 길러야 하겠습니다. References URL 게시일자 방문일자 작성자 잘못된 풀이는 다음과 같습니다.\nimport java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public class bj_12904_A와_B { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String S = br.readLine(); String T = br.readLine(); if (solve(S, T)) System.out.print(1); else System.out.print(0); } /* * [조건] * 시간제한 : 2초 / 메모리제한 : 512MB * S.length() \u0026lt;= 999, T.length() \u0026lt;= 1000, S.length() \u0026lt; T.length() * [풀이] * DP를 통해 해당 길이에서 나올 수 있는 모든 경우의 수를 저장한다. * 해당 길이의 경우의 수는 이전 길이의 2배이므로 처음 주어진 S의 2배씩 저장공간이 필요하다. * 그대로 저장하려면 2^(T - S)개가 필요하므로 최대 2^999의 저장공간이 필요하므로 최적화해야 한다. * 최적화를 위해 T가 될 수 없는 경우의 수를 가지치기한다. / 될수없는 경우 : T의 일부가 아니면서 뒤집은 T의 일부가 아닐때 */ static boolean solve(String s, String t) { List\u0026lt;String\u0026gt; dp = new ArrayList\u0026lt;\u0026gt;(); // 이전값들 중 가능한 값들만 저장하는 dp 리스트 dp.add(s); int l = s.length(); while(l \u0026lt; t.length()) { getNs1(dp, t); // 뒤에 A 추가하기 getNs2(dp, t); // 뒤집은 후 B 추가하기 l++; } for (String ns : dp) if (ns.equals(t)) return true; return false; } static void getNs1(List\u0026lt;String\u0026gt; dp, String t) { List\u0026lt;String\u0026gt; addList = new ArrayList\u0026lt;\u0026gt;(); for (String ns : dp) { StringBuilder sb = new StringBuilder(ns); ns = sb.append(\u0026#34;A\u0026#34;).toString(); if (isPossible(dp, ns, t)) addList.add(ns); } dp.addAll(addList); } static void getNs2(List\u0026lt;String\u0026gt; dp, String t) { List\u0026lt;String\u0026gt; addList = new ArrayList\u0026lt;\u0026gt;(); for (String ns : dp) { StringBuilder sb = new StringBuilder(ns); sb.reverse(); ns = sb.append(\u0026#34;B\u0026#34;).toString(); if (isPossible(dp, ns, t)) addList.add(ns); } dp.addAll(addList); } static boolean isPossible(List\u0026lt;String\u0026gt; dp, String ns, String t) { // dp에 이미 포함되어있는지 확인 for (String d : dp) if (d.equals(ns)) return false; // Ns가 t의 부분문자열인지 확인 int l = ns.length(); String rt = new StringBuilder(t).reverse().toString(); for (int i = 0; i \u0026lt;= t.length() - l; i++) { String nt = t.substring(i, i + l); String nrt = rt.substring(i, i + l); if (nt.equals(ns)) return true; if (nrt.equals(ns)) return true; } return false; } } \u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/cote/bj_12904/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.acmicpc.net/problem/12904\"\u003ehttps://www.acmicpc.net/problem/12904\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e처음에는 DP문제인줄 알고 접근했으나, 시간 초과로 실패했습니다.\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e\u003c/li\u003e\n\u003cli\u003eS로부터 T로 갈때는 여러 경로가 존재해서 DP와 같은 최적화가 필요해보입니다.\n\u003cfigure\u003e\n      \u003cimg loading=\"lazy\" src=\"solve1.jpeg\"\n           alt=\"S에서 시작할 경우 2^(T의 길이 - S의 길이) 만큼의 탐색이 필요합니다.\"/\u003e \u003cfigcaption\u003e\n              \u003cp\u003eS에서 시작할 경우 2^(T의 길이 - S의 길이) 만큼의 탐색이 필요합니다.\u003c/p\u003e\n          \u003c/figcaption\u003e\n  \u003c/figure\u003e\n\u003c/li\u003e\n\u003cli\u003e그러나 반대로 T에서 S로 갈때는 경로가 1개만 존재하므로, (T의 길이 - S의 길이)만큼만 탐색하면 S를 구할 수 있습니다.\n\u003cfigure\u003e\n      \u003cimg loading=\"lazy\" src=\"solve2.jpeg\"\n           alt=\"T에서 S로 갈때는 T의 맨뒤 값으로 이전 노드를 예측하는 것이 가능합니다.\"/\u003e \u003cfigcaption\u003e\n              \u003cp\u003eT에서 S로 갈때는 T의 맨뒤 값으로 이전 노드를 예측하는 것이 가능합니다.\u003c/p\u003e","title":"[Java]백준 12904 A와 B"},{"content":"출처 https://www.acmicpc.net/problem/16927 접근 돌려야 하는 배열을 1차원 배열로 만들어서 R만큼 이동하면 다음 배열 값을 얻을 수 있습니다. 1차원 배열과 2차원 배열을 변환하는게 조금 귀찮은데, 저는 하 -\u0026gt; 우 -\u0026gt; 상 -\u0026gt; 좌(반시계) 순으로 이동하도록 설계했습니다. 2차원 배열을 위 순서로 탐색해서 1차원 배열로 만듭니다.\n이 때, 1차원 배열의 크기는 2차원 배열의 (가로 + 세로) * 2 에서 네 귀퉁이(위 사진에서 (1) ~ (4))가 중복되므로 4를 빼주어야 합니다.\n이렇게 구한 배열값을 R만큼 이동시키는데, 이때 R \u0026lt; 10^91 이므로 최적화를 위해 해당 배열의 크기로 나머지를 구합니다. 빨간색 화살표(포인터)만큼 배열을 이동시켜야 합니다.\n다시 그래프를 탐색하면서 이번에는 역순으로 1차원 배열의 원소로 2차원 배열을 채워넣습니다.\n1 ~ 3 과정을 행 / 열 중 하나가 1보다 작아질 때까지 depth를 1씩 이동하며 수행합니다. 행 / 열 중 하나가 1보다 작아지면 행렬을 돌릴 수 없으므로 종료합니다.\n풀이 import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.StringTokenizer; public class Main { static int N; static int M; static int R; public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); N = Integer.parseInt(st.nextToken()); M = Integer.parseInt(st.nextToken()); R = Integer.parseInt(st.nextToken()); int[][] NM = new int[N][M]; for (int i = 0; i \u0026lt; N; i++) { st = new StringTokenizer(br.readLine()); for (int j = 0; j \u0026lt; M; j++) { NM[i][j] = Integer.parseInt(st.nextToken()); } } for (int d = 0; d \u0026lt; Math.min(M, N) / 2; d++) { // M, N 중 최솟값의 절반만큼 수행 rotate(d, N - d, d, M - d, NM); } System.out.print(toStringInt2(NM)); // 정답 출력 } /* * [조건] * 시간제한 : 1초 / 메모리 제한 : 512MB * N, M \u0026lt; 300, R \u0026lt; 10^9, 1 \u0026lt; 원소 \u0026lt; 10^8 * min(N, M) % 2 = 0(M + N은 짝수) * [풀이] * 2(m + n) 크기의 배열에 원소들을 담고, R % 2(m + n)의 값만큼 반시계 방향으로 이동하면 해당위치 값을 구할 수 있다. * 모든 깊이에서 해당 로직을 수행하기 위해 (n / 2 \u0026lt;= 1)가 될때까지 위 로직을 수행한다. */ static void rotate(int n0, int n1, int m0, int m1, int[][] NM) { // 배열 크기는 전체 길이에서 네 귀퉁이를 한번씩 빼주기 int[] arr = new int[(n1 - n0 + m1 - m0) * 2 - 4]; // 배열에 반시계방향으로 원소 담기 getArrElement(n0, n1, m0, m1, arr, NM); // 배열 값 이동시키기 int l = arr.length; int[] newArr = new int[l]; int p = R % l; // 최적화를 위해 R을 행렬의 길이로 나눈 나머지를 구해서 이동시키기 for (int i = 0; i \u0026lt; l; i++) { // int np = i - p \u0026gt;= 0? i - p : i - p + l; newArr[i] = arr[np]; } // 이동한 값 배열에 넣기 setArrElement(n0, n1, m0, m1, newArr, NM); } static int[] dr = {1, 0, -1, 0}; // d = 0 : 하 -\u0026gt; d = 1 : 상 -\u0026gt; d = 2 : 좌 -\u0026gt; d = 3 : 우 static int[] dc = {0, 1, 0, -1}; /* * 1차원 배열로 하 -\u0026gt; 우 -\u0026gt; 상 -\u0026gt; 좌 순으로 2차원 배열 채우기 */ static void setArrElement(int n0, int n1, int m0, int m1, int[] arr, int[][] NM) { // 시작점에서부터 하 -\u0026gt; 우 -\u0026gt; 상 -\u0026gt; 좌 순으로 탐색 int[] p = {n0, m0}; NM[n0][m0] = arr[0]; // 1차원 배열로 2차원 배열 채우기 int l = 1; int d = 0; while (l \u0026lt; arr.length) { // 1차원 배열의 길이가 다 채워질때까지 수행 int[] np = {p[0] + dr[d], p[1] + dc[d]}; if (n0 \u0026gt; np[0] || np[0] \u0026gt;= n1 || m0 \u0026gt; np[1] || np[1] \u0026gt;= m1) { // 해당 꼭지점을 벗어날 경우 방향 전환 d++; // 방향 전환 np = new int[] {p[0] + dr[d], p[1] + dc[d]}; // 방향 전환 후 np 덮어쓰기 } NM[np[0]][np[1]] = arr[l]; // 1차원 배열로 2차원 배열 채우기 l++; // while문 탈출조건 p = np; // 위치 다음으로 변경 } } /* * 2차원 배열로 하 -\u0026gt; 우 -\u0026gt; 상 -\u0026gt; 좌 순으로 1차원 배열 채우기 */ static void getArrElement(int n0, int n1, int m0, int m1, int[] arr, int[][] NM) { // 시작점에서부터 하 -\u0026gt; 우 -\u0026gt; 상 -\u0026gt; 좌 순으로 탐색 int[] p = {n0, m0}; arr[0] = NM[n0][m0]; // 2차원 배열로 1차원 배열 채우기 int l = 1; int d = 0; while (l \u0026lt; arr.length) { int[] np = {p[0] + dr[d], p[1] + dc[d]}; if (n0 \u0026gt; np[0] || np[0] \u0026gt;= n1 || m0 \u0026gt; np[1] || np[1] \u0026gt;= m1) { d++; np = new int[] {p[0] + dr[d], p[1] + dc[d]}; } arr[l] = NM[np[0]][np[1]]; // 2차원 배열로 1차원 배열 채우기 l++; p = np; } } /* * 2차원 배열 출력하기 */ static String toStringInt2(int[][] int2) { StringBuilder sb = new StringBuilder(); for (int i = 0; i \u0026lt; int2.length; i++) { for (int j : int2[i]) sb.append(j + \u0026#34; \u0026#34;); if (i != int2.length - 1) sb.append(\u0026#34;\\n\u0026#34;); } return sb.toString(); } } 결과 리뷰 문제 접근은 바로 떠올렸는데, 오랜만에 구현하려고 하니 생각보다 시간이 많이걸렸습니다.\n그리고 2차원 행렬 탐색을 할 때 어줍짢게 for문으로 상하좌우 돌려보려고 했는데 1시간 정도 해보다가 포기했습니다.\n구현방법이 직관적으로 떠오르지 않으면 바로 다른 대안을 찾아 시간을 줄여야겠습니다.\n지금 생각해보니 2차원 \u0026lt;-\u0026gt; 1차원 변환하는 메서드가 중복이 많은데, 플래그를 하나 넣으면 짧게 줄일수도 있을 것 같습니다.\n그리고 매개변수에 각 지점을 하나씩 넣지말고 행과 열을 묶어서 배열로 넣는게 더욱 코드를 이해하기 좋아보입니다.\n개선 코드 /* * 1차원 배열 \u0026lt;-\u0026gt; 2차원 배열 변환 */ static void changeArrElement(int[] n, int[] m, int[] arr, int[][] NM, boolean isTwoToOne) { // 시작점에서부터 하 -\u0026gt; 우 -\u0026gt; 상 -\u0026gt; 좌 순으로 탐색 int[] p = {n[0], m[0]}; if (isTwoToOne) arr[0] = NM[n[0]][m[0]]; // 2차원 배열로 1차원 배열 채우기 else NM[n[0]][m[0]] = arr[0]; // 1차원 배열로 2차원 배열 채우기 int l = 1; int d = 0; while (l \u0026lt; arr.length) { // 1차원 배열의 길이가 다 채워질때까지 수행 int[] np = {p[0] + dr[d], p[1] + dc[d]}; if (n[0] \u0026gt; np[0] || np[0] \u0026gt;= n[1] || m[0] \u0026gt; np[1] || np[1] \u0026gt;= m[1]) { // 해당 꼭지점을 벗어날 경우 방향 전환 d++; // 방향 전환 np = new int[] {p[0] + dr[d], p[1] + dc[d]}; // 방향 전환 후 np 덮어쓰기 } if (isTwoToOne) arr[l] = NM[np[0]][np[1]]; // 2차원 배열로 1차원 배열 채우기 else NM[np[0]][np[1]] = arr[l]; // 1차원 배열로 2차원 배열 채우기 l++; // while문 탈출조건 p = np; // 위치 다음으로 변경 } } References URL 게시일자 방문일자 작성자 R = 10^9이면 원소를 순차적으로 하나씩 이동시켰을때 원소 1개의 시간복잡도가 10^9 이므로 문제에 주어진 10^8개의 원소를 이동시키는데 O(N) = 10^9 * 10^8 = 10^17 이 걸리게 됩니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/cote/bj_16927/","summary":"\u003ch2 id=\"출처\"\u003e출처\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://www.acmicpc.net/problem/16927\"\u003ehttps://www.acmicpc.net/problem/16927\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"접근\"\u003e접근\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e돌려야 하는 배열을 1차원 배열로 만들어서 R만큼 이동하면 다음 배열 값을 얻을 수 있습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e1차원 배열과 2차원 배열을 변환하는게 조금 귀찮은데, 저는 하 -\u0026gt; 우 -\u0026gt; 상 -\u0026gt; 좌(반시계) 순으로 이동하도록 설계했습니다.\n\u003cfigure\u003e\n       \u003cimg loading=\"lazy\" src=\"solve1.jpeg\"\n            alt=\"2차원 배열을 위 순서로 탐색해서 1차원 배열로 만듭니다.\"/\u003e \u003cfigcaption\u003e\n               \u003cp\u003e2차원 배열을 위 순서로 탐색해서 1차원 배열로 만듭니다.\u003c/p\u003e\n           \u003c/figcaption\u003e\n   \u003c/figure\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e이 때, 1차원 배열의 크기는 2차원 배열의 (가로 + 세로) * 2 에서 네 귀퉁이(위 사진에서 (1) ~ (4))가 중복되므로 4를 빼주어야 합니다.\u003c/p\u003e","title":"[Java]백준 16927 배열 돌리기 2"},{"content":"개요 프로젝트를 하던 중, 엔티티 연관관계에서 cascade를 잘못 사용하여 잘못된 엔티티가 삭제되었고, 테스트가 실패하는 상황이 발생했습니다.\n동일한 실수를 반복하지 않도록, 이번 기회에 JPA Cascade 개념과 Orphan Removal과는 어떠한 차이가 있는지 확인해보겠습니다.\nJPA(Hibernate) Cascade 먼저 Java EE 6의 가이드에는 다음과 같이 명시되어 있습니다.\nJava EE6 가이드의 CASCADE 설명\n설명을 읽어보면 Cascade는 영속성 컨텍스트(Persistence Context)에 부모(Cascade옵션을 작성하는 엔티티)가 특정 작업을 수행할 때, 연관된 엔티티(이후 자식이라고 하겠습니다)도 같은 작업을 수행해야 함을 명시하고 있습니다.\n자세한 내용은 Hibernate의 공식문서1 를 살펴보면 더욱 이해하기 좋은 것 같습니다.\n위 문서의 본문 예시를 참조하면 각 옵션의 주요 기능을 알 수 있습니다.2\n1. ALL 아래 모든 기능들을 포함하는 속성입니다. @Entity public class Person { @Id private Long id; private String name; // Cascade가 All로 설정되어 모든 옵션이 적용되어 있습니다. @OneToMany(mappedBy = \u0026#34;owner\u0026#34;, cascade = CascadeType.ALL) private List\u0026lt;Phone\u0026gt; phones = new ArrayList\u0026lt;\u0026gt;(); // Getter, Setter 생략 public void addPhone(Phone phone) { this.phones.add(phone); phone.setOwner(this); } } @Entity public class Phone { @Id private Long id; @Column(name = \u0026#34;`number`\u0026#34;) private String number; @ManyToOne(fetch = FetchType.LAZY) private Person owner; // Getter, Setter 생략 } 2. PERSIST3 DB에 저장될 때 자식을 함께 저장합니다. // given : Person과 phone 엔티티 생성 및 등록 Person person = new Person(); person.setId(1L); person.setName(\u0026#34;John Doe\u0026#34;); Phone phone = new Phone(); phone.setId(1L); phone.setNumber(\u0026#34;123-456-7890\u0026#34;); person.addPhone(phone); // when : person 저장 entityManager.persist(person); // then : 다음과 같이 2개의 Insert 쿼리가 나가면서 연관된 자식을 함께 저장합니다. INSERT INTO Person(name, id) VALUES( \u0026#39;John Doe\u0026#39;,1) INSERT INTO Phone( `number`, person_id, id) VALUES( \u0026#39;123-456-7890\u0026#39;,1,1) 3. MERGE4 부모의 상태를 병합할 때, 자동으로 자식의 상태를 함께 확인해서 병합합니다. // given : DB에서 엔티티 조회, 변경사항 생성 후 영속성 컨텍스트를 clear() -\u0026gt; 엔티티 분리 Phone phone = entityManager.find(Phone.class, 1L); Person person = phone.getOwner(); person.setName(\u0026#34;John Doe Jr.\u0026#34;); phone.setNumber(\u0026#34;987-654-3210\u0026#34;); entityManager.clear(); // when : person 병합(merge) entityManager.merge(person); // then : 객체를 채우기 위해 다음과 같이 자동으로 Fetch Join이 나가서 자식 엔티티의 값을 채웁니다. SELECT p.id as id1_0_1_, p.name as name2_0_1_, ph.owner_id as owner_id3_1_3_, ph.id as id1_1_3_, ph.id as id1_1_0_, ph.\u0026#34;number\u0026#34; as number2_1_0_, ph.owner_id as owner_id3_1_0_ FROM Person p LEFT OUTER JOIN Phone ph on p.id=ph.owner_id WHERE p.id = 1 4. REMOVE 부모가 삭제될 떄 자식을 함께 삭제합니다. 참고로 Hibernate에는 DELETE라는 속성도 있는데 같은 동작이라고 합니다. // given : person을 불러오기 Person person = entityManager.find(Person.class, 1L); // when : person 삭제 entityManager.remove(person); // then : 부모가 삭제되기 전 자식을 먼저 삭제합니다. DELETE FROM Phone WHERE id = 1 DELETE FROM Person WHERE id = 1 5. DETACH5 부모가 분리될 때, 자식도 함께 분리합니다. // given : person을 불러오기 Person person = entityManager.find(Person.class, 1L); Phone phone = person.getPhones().get(0); assertTrue(entityManager.contains(person)); assertTrue(entityManager.contains(phone)); // when : 영속성 컨텍스트에서 Person을 분리할 경우, entityManager.detach(person); // then : 부모가 컨텍스트에서 분리될 떄, 자식도 함께 분리합니다. assertFalse(entityManager.contains(person)); assertFalse(entityManager.contains(phone)); 6. Hibernate 추가 명세 Hibernate에서는 추가로 LOCK, REFRESH, REPLICATE 세가지 옵션을 더 지원합니다. Session에서 사용하는 위 세가지 메서드의 편의를 제공하기 위함입니다. CascadeType.LOCK6\n부모 조회 시 Lock이 될때 자식도 lock에 걸릴 것 같지만, 그렇게 동작하지는 않는다고 합니다.7 Lock 옵션을 적용하여 부모를 영속성 컨텍스트에 다시 불러오면(reattach), 자식도 함께 불러오는 옵션입니다. 아래 예시에서 session8의 Lock() 메서드를 통해 부모를 조회하면 자식 또한 함께 조회되는 것을 볼 수 있습니다. // given : person을 불러오기 Person person = entityManager.find(Person.class, 1L); assertEquals(1, person.getPhones().size()); Phone phone = person.getPhones().get(0); assertTrue(entityManager.contains(person)); assertTrue(entityManager.contains(phone)); // when : 부모 분리 후 lock메서드를 통해 session을 다시 불러올 경우 entityManager.detach(person); assertFalse(entityManager.contains(person)); assertFalse(entityManager.contains(phone)); entityManager.unwrap(Session.class) .lock(person, new LockOptions(LockMode.NONE)); // then : 부모, 자식 한번에 조회 assertTrue(entityManager.contains(person)); assertTrue(entityManager.contains(phone)); CascadeType.REFRESH9\n부모가 새로고침(Refresh) 될 때, 자식도 함께 새로고침하는 옵션입니다. 정합성 보장을 위해 DB와 영속성 컨텍스트를 일치화 후 작업해야 할 때 유용할 것 같습니다. // given : person을 불러오기 Person person = entityManager.find(Person.class, 1L); Phone phone = person.getPhones().get(0); // when : 엔티티 값 변경 후 새로고침하기 person.setName(\u0026#34;John Doe Jr.\u0026#34;); phone.setNumber(\u0026#34;987-654-3210\u0026#34;); entityManager.refresh(person); // then : 변경사항이 반영되지 않고, DB에 있는 값이 그대로 적용됨 assertEquals(\u0026#34;John Doe\u0026#34;, person.getName()); assertEquals(\u0026#34;123-456-7890\u0026#34;, phone.getNumber()); CascadeType.REPLICATE10\n부모가 다른 데이터소스를 수정할 때, 자식도 함께 수정하는 옵션입니다. CQRS 분리나 Scale Out등을 위해 여러 데이터소스를 함께 사용한다면 유용할 것 같습니다. // given : person과 phone 생성(저장하지 않은 상태) Person person = new Person(); person.setId(1L); person.setName(\u0026#34;John Doe Sr.\u0026#34;); Phone phone = new Phone(); phone.setId(1L); phone.setNumber(\u0026#34;(01) 123-456-7890\u0026#34;); person.addPhone(phone); // when : 다른 데이터소스에 있는 값 덮어쓰기 entityManager.unwrap(Session.class).replicate(person, ReplicationMode.OVERWRITE); // then : 다음과 같이 자동으로 다른 데이터소스를 수정하는 쿼리가 나갑니다. SELECT id FROM Person WHERE id = 1 SELECT id FROM Phone WHERE id = 1 UPDATE Person SET name = \u0026#39;John Doe Sr.\u0026#39; WHERE id = 1 UPDATE Phone SET \u0026#34;number\u0026#34; = \u0026#39;(01) 123-456-7890\u0026#39;, owner_id = 1 WHERE id = 1 지금까지 JPA와 Hibernate의 Cascade 옵션에 대해 알아보았습니다. 결국 영속성 컨텍스트에 부모-자식 엔티티를 한번에 불러오거나 생성, 변경, 삭제하는 옵션이라고 할 수 있겠습니다.\nOrphan Removal 다음은 Orphan Removal 옵션입니다.\n해당 옵션에 대한 설명은 JPA 기본 명세 45p에 잘 나와있습니다.(GPT 3.5 번역)\n일대일(OneToOne) 또는 일대다(OneToMany)로 지정된 연관 관계는 orphanRemoval 옵션을 사용할 수 있습니다. orphanRemoval이 적용될 때 다음과 같은 동작이 발생합니다:\n연관 관계의 대상이 되는 엔터티가 연관 관계에서 제거되면(예: 연관 관계를 null로 설정하거나 연관 관계 컬렉션에서 엔터티를 제거함으로써), 고아가 된 엔터티에 대해 삭제 작업이 적용됩니다. 삭제 작업은 플러시(flush) 작업 시점에 적용됩니다. orphanRemoval 기능은 부모 엔터티에 의해 개인적으로 \u0026ldquo;소유\u0026quot;되는 엔터티를 위해 의도된 것입니다. 이 기능을 사용할 경우, 응용 프로그램은 특정한 제거 순서에 의존해서는 안 되며, 고아가 된 엔터티를 다른 연관 관계에 재할당하거나 해당 엔터티를 지속(persist)하려고 시도해서는 안 됩니다. 고아가 된 엔터티가 분리(detached) 상태이거나, 새로 생성된 상태이거나, 삭제된 상태인 경우, orphanRemoval의 의미는 적용되지 않습니다.\n관리되는 소스 엔터티에 대해 삭제 작업이 적용되면, 삭제 작업은 섹션 3.2.311의 규칙에 따라 연관 관계의 대상 엔터티에 전파됩니다(따라서 연관 관계에 대해 cascade=REMOVE를 명시할 필요는 없습니다).\n즉, 일대일 또는 일대다 연관관계에서 부모 엔티티의 참조가 사라지면, 영속성 컨텍스트를 flush하는 시점에 자식 엔티티를 삭제합니다.\n또한, 부모 엔티티를 삭제하면 자동으로 cascade=REMOVE를 적용한 것과 같이 자식을 삭제하는 효과도 줍니다.\n// given : person을 불러오기 Person person = entityManager.find(Person.class, 1L); Phone phone = person.getPhones().get(0); assertEquals(phone.getId(), 1); // when : person에서 phone 참조값 제거 후 flush person.getPhones().set(0, null); entityManager.flush(); // then : 참조가 사라진 자식(고아) 엔티티를 삭제합니다. DELETE FROM Phone WHERE id = 1 결론 제가 잘못 이해하고 있던 부분은 CascadeType.REMOVE는 삭제되는게 아니라 영속성 컨텍스트에서 분리될 때 함께 분리된다고 생각한 점이었습니다(이 기능은 CascadeType.DETACH와 헷갈렸던 것 같습니다.). 사실 Orphan Removal과 CascadeType.REMOVE는 기능적으로는 동일하지만, REMOVE는 삭제(EntityManager.remove())를 명시할때만 발동되는 반면 Orphan Removal은 삭제 뿐 아니라 null이나 참조값 변경 등으로 참조가 없어질 때에도 삭제하는 것이 가장 큰 차이인 것 같습니다. 두루뭉실하게 알고 있던 지식들이 공식문서를 통해 접하니 좀더 확실하게 알게 된 느낌입니다. 앞으로도 잘 모르겠다 싶으면 공식문서를 참고하는 습관을 들여야겠습니다.\nReferences URL 게시일자 방문일자 작성자 https://docs.oracle.com/javaee/6/tutorial/doc/bnbqa.html#gjjnj 2013. 2024.05.13. Oracle https://docs.jboss.org/hibernate/orm/7.0/userguide/html_single/Hibernate_User_Guide.html 2024.05.03. 2024.05.13. Hibernate https://download.oracle.com/otndocs/jcp/persistence-2_2-mrel-eval-spec/index.html 2017.07.17. 2024.05.13. Oracle Hibernate는 JPA 명세의 구현체입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n쉬운 이해를 위해 별도의 예시를 만들기보다 본문 링크에 있는 예시를 최대한 그대로 시용하겠습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSpring Data JPA의 Repository에서 save() 메서드에 해당합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nJPA 공식문서에서는 Merge에 대해 다음과 같이 설명하고 있습니다.\nThe merge operation allows for the propagation of state from detached entities onto persistent entities managed by the entity manager. 병합(merge) 작업은 분리(detached)된 엔티티에서의 상태를 엔티티 매니저(entity manager)가 관리하는 영속 엔티티로 전파할 수 있도록 합니다.\n즉, 위 예시에서는 영속 상태의 엔티티를 영속성 컨텍스트에 불러오는 과정에서 Merge 옵션이 있으면 부모와 자식을 한번에 가져오는 것으로 이해할 수 있습니다. \u0026#160;\u0026#x21a9;\u0026#xfe0e; JPA 공식문서에서는 다음과 같은 상황에서 분리가 발생한다고 설명하고 있습니다.\n트랜잭션 스코프(persistence context)의 영속성 컨텍스트를 사용하는 경우, 트랜잭션 커밋 시 트랜잭션 롤백 시 엔티티를 영속성 컨텍스트에서 분리(detach)하는 경우 영속성 컨텍스트를 비우는 경우 엔티티 매니저를 닫는 경우 엔티티를 직렬화하거나 엔티티를 값으로 전달할 때(예: 다른 애플리케이션 계층으로, 원격 인터페이스를 통해 등) \u0026#160;\u0026#x21a9;\u0026#xfe0e; Lock은 트랜잭션 발생 시 데이터 경합(충돌)이 발생할 것을 예방하기 위해, 조회 시 다른 트랜잭션을 통해 DB가 변경되지 않도록 접근을 통제하는 것을 말합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n이렇게 동작시키기 위해서는 jakarta.persistence.lock.scope = PessimisticLockScope.EXTENDED 값을 사용해야 합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nHibernate의 Session은 JPA의 영속성 컨텍스트를 구현한 개념입니다. 위 예제에서는 EntityManager의 unwrap() 메서드를 사용하여 Session을 획득한 후, lock() 메서드를 통해 잠금을 설정하고 있습니다. 이 때, CascadeType.LOCK 옵션을 통해 영속성 컨택스트에서 분리(detach)된 부모와 자식 엔티티를 한번에 가져오게 됩니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSession에서 영속성 컨텍스트와 실제 Database를 동일하게 맞추는 메서드입니다. 작업 과정에서 DB가 변경되거나 트리거가 실행되어 엔티티와 DB가 다를 때 새로고침을 하여 일치화하는 메서드입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nSession에 있는 엔티티를 다른 데이터소스의 데이터와 일치화하는 메서드입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n섹션 3.2.3은 cascade=REMOVE에 대한 설명입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/tips/240513/","summary":"\u003ch2 id=\"개요\"\u003e개요\u003c/h2\u003e\n\u003cp\u003e프로젝트를 하던 중, 엔티티 연관관계에서 cascade를 잘못 사용하여 잘못된 엔티티가 삭제되었고, 테스트가 실패하는 상황이 발생했습니다.\u003c/p\u003e\n\u003cp\u003e동일한 실수를 반복하지 않도록, 이번 기회에 JPA Cascade 개념과 Orphan Removal과는 어떠한 차이가 있는지 확인해보겠습니다.\u003c/p\u003e\n\u003ch2 id=\"jpahibernate-cascade\"\u003eJPA(Hibernate) Cascade\u003c/h2\u003e\n\u003cp\u003e먼저 \u003ca href=\"https://docs.oracle.com/javaee/6/tutorial/doc/bnbqa.html#gjjnj\"\u003eJava EE 6의 가이드\u003c/a\u003e에는 다음과 같이 명시되어 있습니다.\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"cascade_jpa.png\"\n         alt=\"Java EE6 가이드의 CASCADE 설명\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003eJava EE6 가이드의 CASCADE 설명\u003c/p\u003e\n        \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003cp\u003e설명을 읽어보면 Cascade는 영속성 컨텍스트(Persistence Context)에 부모(Cascade옵션을 작성하는 엔티티)가 특정 작업을 수행할 때, 연관된 엔티티(이후 자식이라고 하겠습니다)도 같은 작업을\n수행해야 함을 명시하고 있습니다.\u003c/p\u003e","title":"JPA Cascade vs Orphan Removal"},{"content":"상황 Controller 테스트 작성 시 mockMvc의 결과가 원하는 값과 일치하는지 확인하는 상황입니다.\n@Test @DisplayName(\u0026#34;[인수] 읽지 않은 알림 요청 시 전체응답(200)\u0026#34;) void testAllNotifications200() throws Exception { // given Cookie[] followingUserLoginCookie = mockMvc.perform(get(\u0026#34;/api/users/login\u0026#34;).header(HttpHeaders.AUTHORIZATION, \u0026#34;Bearer valid_token_2\u0026#34;)).andReturn().getResponse().getCookies(); Cookie[] myLoginCookie = mockMvc.perform(get(\u0026#34;/api/users/login\u0026#34;).header(HttpHeaders.AUTHORIZATION, \u0026#34;Bearer valid_token\u0026#34;)).andReturn().getResponse().getCookies(); // when // following -\u0026gt; user 팔로우 mockMvc.perform(post(\u0026#34;/api/users/\u0026#34; + me.getId() + \u0026#34;/follows\u0026#34;).cookie(followingUserLoginCookie)); // notifications 전체 조회 String responseBody = mockMvc.perform(get(\u0026#34;/api/users/notifications\u0026#34;).cookie(myLoginCookie)) .andExpect(status().is(200)).andReturn().getResponse().getContentAsString(); ListDto\u0026lt;List\u0026lt;NotificationDto\u0026gt;\u0026gt; notificationDtos = objectMapper.readValue(responseBody, new TypeReference\u0026lt;ListDto\u0026lt;List\u0026lt;NotificationDto\u0026gt;\u0026gt;\u0026gt;() {}); // then assertThat(notificationDtos.getList()).hasSize(1); assertThat(notificationDtos.getList().get(0).getDescription()).isEqualTo(\u0026#34;leaf2님이 당신을 팔로우합니다.\u0026#34;); } 테스트 결과 junit 테스트 결과(실패)\n원인 테스트 결과를 보면, 한글 인코딩이 깨져 있습니다. 기존에 MockMvc를 사용하면서 한글을 잘 검증하지 않았는데 이번 테스트에서는 한글을 검증하면서 이런 오류가 발생한 것 같습니다. 기본적으로 MockMVC는 *ISO-8859-1(Latin-1)*로 인코딩된다고 합니다. objectMapper는 UTF-8로만 디코딩하기 때문에 ISO-8859-1로 인코딩 된 값을 읽을 수 없습니다. 해결 다음 세가지 방법 중 하나를 사용하면 해결이 가능합니다. 1) UTF-8로 인코딩 결과값(content)을 UTF-8로 인코딩합니다. String responseBody = mockMvc.perform(get(\u0026#34;/api/users/notifications\u0026#34;).cookie(myLoginCookie)) .andExpect(status().is(200)).andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8); // StandardCharset.UTF_8 추가 2) 결과값 byte 배열로 변환(인코딩 안하기) 다음과 같이 readValue 대상을 String 이 아닌 byte[]로 변경합니다. // notifications 전체 조회 byte[] responseBody = mockMvc.perform(get(\u0026#34;/api/users/notifications\u0026#34;).cookie(myLoginCookie)) // 기존 Return type : String .andExpect(status().is(200)).andReturn().getResponse().getContentAsByteArray(); // 기존 method : getContentAsString() ListDto\u0026lt;List\u0026lt;NotificationDto\u0026gt;\u0026gt; notificationDtos = objectMapper.readValue(responseBody, new TypeReference\u0026lt;ListDto\u0026lt;List\u0026lt;NotificationDto\u0026gt;\u0026gt;\u0026gt;() {}); 3) Server Default Encoding 추가 SpringBoot 서버의 기본 인코딩 설정을 변경합니다.\n# application.yml server: servlet: encoding: charset: UTF-8 force: true 테스트 결과 junit 테스트 결과(성공)\nReferences URL 게시일자 방문일자 작성자 https://github.com/spring-projects/spring-framework/issues/23219 2019.07.01. 2024.05.07. momega https://stackoverflow.com/questions/10004241/jackson-objectmapper-with-utf-8-encoding 2012.04.04. 2024.05.07. Patricio ","permalink":"https://leaf-nam.github.io/tips/240507/","summary":"\u003ch2 id=\"상황\"\u003e상황\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eController 테스트 작성 시 mockMvc의 결과가 원하는 값과 일치하는지 확인하는 상황입니다.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Test\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@DisplayName\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;[인수] 읽지 않은 알림 요청 시 전체응답(200)\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003etestAllNotifications200\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003ethrows\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eException\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// given\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eCookie\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003efollowingUserLoginCookie\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emockMvc\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eperform\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/api/users/login\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"na\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eHttpHeaders\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eAUTHORIZATION\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Bearer valid_token_2\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)).\u003c/span\u003e\u003cspan class=\"na\"\u003eandReturn\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003egetResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003egetCookies\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eCookie\u003c/span\u003e\u003cspan class=\"o\"\u003e[]\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emyLoginCookie\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emockMvc\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eperform\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/api/users/login\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"na\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eHttpHeaders\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eAUTHORIZATION\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Bearer valid_token\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)).\u003c/span\u003e\u003cspan class=\"na\"\u003eandReturn\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003egetResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003egetCookies\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// when\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// following -\u0026gt; user 팔로우\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003emockMvc\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eperform\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003epost\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/api/users/\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eme\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetId\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e+\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/follows\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"na\"\u003ecookie\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003efollowingUserLoginCookie\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// notifications 전체 조회\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eresponseBody\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emockMvc\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eperform\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;/api/users/notifications\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"na\"\u003ecookie\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emyLoginCookie\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eandExpect\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estatus\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003eis\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e200\u003c/span\u003e\u003cspan class=\"p\"\u003e)).\u003c/span\u003e\u003cspan class=\"na\"\u003eandReturn\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003egetResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003egetContentAsString\u003c/span\u003e\u003cspan class=\"p\"\u003e();\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eListDto\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eNotificationDto\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003enotificationDtos\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eobjectMapper\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ereadValue\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eresponseBody\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eTypeReference\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eListDto\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eNotificationDto\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u0026gt;\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{});\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// then\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eassertThat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enotificationDtos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetList\u003c/span\u003e\u003cspan class=\"p\"\u003e()).\u003c/span\u003e\u003cspan class=\"na\"\u003ehasSize\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003eassertThat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enotificationDtos\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetList\u003c/span\u003e\u003cspan class=\"p\"\u003e().\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"na\"\u003egetDescription\u003c/span\u003e\u003cspan class=\"p\"\u003e()).\u003c/span\u003e\u003cspan class=\"na\"\u003eisEqualTo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;leaf2님이 당신을 팔로우합니다.\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e테스트 결과\n\u003cfigure\u003e\n      \u003cimg loading=\"lazy\" src=\"test-result.png\"\n           alt=\"junit 테스트 결과(실패)\"/\u003e \u003cfigcaption\u003e\n              \u003cp\u003ejunit 테스트 결과(실패)\u003c/p\u003e","title":"MockMvc Object mapper nested class utf-8 인코딩 오류 해결하기"},{"content":"한줄평 테스트를 하라. 성공시켜라. 리팩토링하라.\n책을 읽게 된 계기 단위테스트를 작성하다가 자연스럽게 TDD라는 개발 방법론을 접하게 되었습니다. 실제로 가능한건지 몸소 체험해보고 싶었고, 이 책을 통해 기본적인 개념을 느끼고 프로젝트에 직접 적용해보고자 했습니다.\n작가 소개 켄트 벡(Kent Beck)\n켄트 벡(Kent Beck, 1961년~ )은 미국의 소프트웨어 엔지니어이자, 협업적이고 반복적인 디자인 프로세스의 엄격한 사양을 삼가는 소프트웨어 개발 방법론인 익스트림 프로그래밍의 개발자이다. 애자일 소프트웨어 개발의 창설 문서인 애자일 선언문의 17명의 오리지널 서명인 가운데 한 명이었다. 현재 캘리포니아주 샌프란시스코에 거주하고 있으며 소셜 미디어 기업 페이스북에서 종사하였다.1\n핵심요약 테스트 성공 전략 가짜로 구현하기 : 상수를 반환하다가, 점점 변수로 변환해간다. 명백한 구현 사용하기 : 실제 구현을 입력한다. 삼각측량 : 두 테스트를 만들고, 이를 모두 해결하기 위한 코드를 작성한다. TDD Process 작은 테스트를 추가한다. 모든 테스트를 실행하고, 실패하는 것을 확인한다. 코드에 변화를 준다. 모든 테스트를 실행하고, 성공하는 것을 확인한다. 중복을 제거하기 위해 리팩토링한다. TDD에 도움이 되는 디자인 패턴 커맨드 : 계산 작업에 대한 호출을 메시지가 아닌 객체로 표현 값 객체 : 객체가 생성된 후 값이 절대로 변하지 않게 한다. 널 객체 : 계산 작업의 기본 사례를 객체로 표현한다. 템플릿 매서드 : 계산 작업의 변하지 않는 순서를 추상 메서드로 표현한다. 플러거블 객체 : 둘 이상의 구현을 객체를 호출하여 다양성을 표현한다. 플러거블 셀렉터 : 객체별로 서로 다른 메서드가 동적으로 호출되게 하여 필요없는 하위 클래스의 생성을 피한다. 팩토리 메서드 : 생성자 대신 메서드를 통해 객체를 생성한다. 임포스터 : 현존 프로토콜의 다른 구현을 추가하여 시스템에 변이를 도입한다. 컴포지트 : 하나의 객체로 여러 객체의 행위 조합을 표현한다. 수집 매개변수 : 여러 다른 객체에서 계산한 결과를 모으기 위해 매개변수를 여러곳으로 전달한다. 평가 제목에 충실하게 이 책은 예제를 통해 저자가 직접 TDD를 어떻게 적용하는지 보여줍니다.\n구체적인 예시와 실제 테스트 커버리지 분석 등의 결과를 통해 TDD가 얼마나 효율적인지 확인할 수 있습니다.\n또한, 후반부에 제시하는 디자인 패턴들도 TDD와 객체지향을 좀더 깊게 이해하고 적용할 수 있도록 도와줍니다.\n느낀점 예제 코드의 흐름이 지나치게 구체적인 것 같다가도, 어느새 완성되어 가는 코드를 보며 직접 TDD로 코딩을 하는듯한 느낌을 받았습니다.\n이 책을 읽고 TDD를 직접 사용하면서 테스트 주도 개발의 안정성과 녹색 막대를 보는 즐거움을 느꼈고, 앞으로도 TDD를 코딩 습관이자 패러다임으로서 계발해 나갈 생각입니다.\n추천 테스트를 어느정도 짜면서 막연하게 TDD에 대해 알고 있었던 분들의 추상적인 개념을 구체화하는데 많은 도움이 될 것 같습니다.\nReferences URL 게시일자 방문일자 작성자 위키백과 : 켄트 백\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/review/test-driven_development_by_example/","summary":"\u003ch2 id=\"한줄평\"\u003e한줄평\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e테스트를 하라. 성공시켜라. 리팩토링하라.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"책을-읽게-된-계기\"\u003e책을 읽게 된 계기\u003c/h2\u003e\n\u003cp\u003e단위테스트를 작성하다가 자연스럽게 TDD라는 개발 방법론을 접하게 되었습니다. 실제로 가능한건지 몸소 체험해보고 싶었고, 이 책을 통해 기본적인 개념을 느끼고 프로젝트에 직접 적용해보고자 했습니다.\u003c/p\u003e\n\u003ch2 id=\"작가-소개\"\u003e작가 소개\u003c/h2\u003e\n\u003cp\u003e켄트 벡(Kent Beck)\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e켄트 벡(Kent Beck, 1961년~ )은 미국의 소프트웨어 엔지니어이자, 협업적이고 반복적인 디자인 프로세스의 엄격한 사양을 삼가는 소프트웨어 개발 방법론인 익스트림 프로그래밍의 개발자이다.\n애자일 소프트웨어 개발의 창설 문서인 애자일 선언문의 17명의 오리지널 서명인 가운데 한 명이었다.\n현재 캘리포니아주 샌프란시스코에 거주하고 있으며 소셜 미디어 기업 페이스북에서 종사하였다.\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e\u003c/p\u003e","title":"Test Driven Development: By Example"},{"content":"한줄평 테스트에서 오는 결정장애를 해소해주는 처방전\n책을 읽게 된 계기 프로젝트에서 인터페이스를 단위테스트하려고 여기저기 찾아보다가 인터페이스를 테스트하는 것이 안티패턴이라는 여러 블로그 글을 보았습니다. 그 출처가 대부분 이 책이었고, 호기심이 생겨 책까지 읽어보게 되었습니다.\n작가 소개 Vladimir Khorikov(블라디미르 코리코프)\n블라디미르 코리코프(Vladimir Khorikov)는 \u0026lsquo;Unit Testing Principles, Practices, and Patterns\u0026rsquo;라는 책의 저자로, 이 책은 소프트웨어 개발에서 단위 테스팅에 대한 원칙, 실천 방법, 패턴을 다룹니다. 그는 15년 이상의 경력을 가진 소프트웨어 개발자로, 특히 팀을 지도하여 단위 테스팅의 모든 면을 가르치는 데 전문화되어 있습니다. 또한 그는 \u0026lsquo;Enterprise Craftsmanship\u0026rsquo; 블로그의 창시자로 매년 50만 명의 소프트웨어 개발자에게 접근합니다. 처음에는 일반 프로그래밍 주제에 대한 자문가로 시작했지만 최근에는 단위 테스팅에 중점을 두고 있으며, 주요 메시지는 소프트웨어 개발자들에게 단위 테스팅을 쉽게 만드는 방법을 가르치는 것입니다.1\n핵심요약 좋은 테스트의 속성 회귀 방지 : 의도한 대로 기능이 동작하지 않는 것을 방지 리팩터링 내성 : 리팩터링 시 쉽게 테스트가 깨지지 않는 것(거짓 양성 방지) 빠른 피드백 : 빠른 실행과 결과 확인이 가능해야 함 유지 보수성 : 테스트를 이해하기 쉽고, 실행하기 쉬워야 함(외부 종속 방지) 테스트의 속성과 테스트 종류 사이의 관계\n코드의 유형 간단한 코드 : 테스트 작성할 필요 없음 도메인 모델 및 알고리즘 : 단위 테스트 작성 컨트롤러 : 통합 테스트 작성 지나치게 복잡한 코드 : 도메인 모델과 컨트롤러로 리팩터링 코드의 유형 분류\n평가 테스트 코드를 작성하면서 애매했던 부분들을 명료하게 분류해주어서 좋았습니다. 특히 그래프 위주 설명과 용어에 대한 명확한 정의 이후 논리가 전개되어 생각의 흐름대로 이해하기 쉬웠습니다.\n다만, C#과 .NET 프레임워크를 사용하여 일부 코드는 이해하기 어렵거나 해당 환경에 종속적인 부분이 있어 아쉬웠습니다.\n느낀점 테스트를 작성하는 과정에서 막연하게 느꼈던 불편함과 무의미한 작업의 반복을 이론적으로 이해하게 되었습니다.\n특히 코드의 유형에서 지금까지 컨트롤러나 너무 복잡한 코드를 단위테스트하려고 노력하다 보니, 위와 같은 불편함을 느꼈다는 것을 알게 되었습니다.\n테스트를 짜면서 무언가 계속 잘못된 방향으로 가게 된다면, 우선 원본 코드나 설계가 잘못되었는지부터 의심하는 습관을 들여야겠습니다.\n추천 조금 이해하기 어려운 추상적인 개념이나 예제가 있어 처음 테스트를 접하거나 배워보고 싶은 분들에게는 어려울 수 있을 것 같습니다. 반면 실제로 테스트를 짜면서 답답한 기분을 느꼈던 분들에게는 좋은 나침반이 되어 줄 수 있을 것 같아 강력 추천드립니다.\nReferences URL 게시일자 방문일자 작성자 https://www.pluralsight.com/authors/vladimir-khorikov?clickid=SNLXAPSJBxyPTuVxHH1vL11qUkHWyE09awR9R80\u0026amp;irgwc=1\u0026amp;mpid=1970485\u0026amp;aid=7010a000001xAKZAA2\u0026amp;utm_medium=digital_affiliate\u0026amp;utm_campaign=1970485\u0026amp;utm_source=impactradius 미확인 2024.04.14. Vladimir Khorikov Vladimir Khorikov is the author of the book Unit Testing Principles, Practices, and Patterns: https://amzn.to/2QXS2ch He has been professionally involved in software development for over 15 years, including mentoring teams on the ins and outs of unit testing. He\u0026rsquo;s also the founder of the Enterprise Craftsmanship blog, where he reaches 500 thousand software developers yearly. He started as an adviser on general programming topics, but lately has shifted his focus to unit testing with a central message of teaching software developers how to make unit testing painless.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/review/unit_testing/","summary":"\u003ch2 id=\"한줄평\"\u003e한줄평\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e테스트에서 오는 결정장애를 해소해주는 처방전\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"책을-읽게-된-계기\"\u003e책을 읽게 된 계기\u003c/h2\u003e\n\u003cp\u003e프로젝트에서 인터페이스를 단위테스트하려고 여기저기 찾아보다가 인터페이스를 테스트하는 것이 안티패턴이라는 여러 블로그 글을 보았습니다. 그 출처가 대부분 이 책이었고, 호기심이 생겨 책까지 읽어보게 되었습니다.\u003c/p\u003e\n\u003ch2 id=\"작가-소개\"\u003e작가 소개\u003c/h2\u003e\n\u003cp\u003eVladimir Khorikov(블라디미르 코리코프)\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e블라디미르 코리코프(Vladimir Khorikov)는 \u0026lsquo;Unit Testing Principles, Practices, and Patterns\u0026rsquo;라는 책의 저자로, 이 책은 소프트웨어 개발에서 단위 테스팅에 대한 원칙, 실천 방법, 패턴을 다룹니다. 그는 15년 이상의 경력을 가진 소프트웨어 개발자로, 특히 팀을 지도하여 단위 테스팅의 모든 면을 가르치는 데 전문화되어 있습니다. 또한 그는 \u0026lsquo;Enterprise Craftsmanship\u0026rsquo; 블로그의 창시자로 매년 50만 명의 소프트웨어 개발자에게 접근합니다. 처음에는 일반 프로그래밍 주제에 대한 자문가로 시작했지만 최근에는 단위 테스팅에 중점을 두고 있으며, 주요 메시지는 소프트웨어 개발자들에게 단위 테스팅을 쉽게 만드는 방법을 가르치는 것입니다.\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e\u003c/p\u003e","title":"Unit Testing"},{"content":"상황 SpringBoot에서 MongoDB에 데이터를 저장하고, 저장한 데이터를 파싱하는 과정에서 다음과 같은 오류가 발생했습니다.\n실행 코드\n// 몽고DB 컬렉션 클래스 @Document(collection = \u0026#34;indicators\u0026#34;) @Getter @NoArgsConstructor @Builder @AllArgsConstructor @Slf4j public class Indicator { @Id private String id; private List\u0026lt;MatchIndicator\u0026gt; matchIndicators; private MatchIndicatorStatistics matchIndicatorStatistics; // 메서드 생략 } // 컬렉션 조회 메서드 @Override public Indicator getIndicatorInDB(String SummonerId) { Query query = Query.query( Criteria.where(\u0026#34;_id\u0026#34;).is(SummonerId)); Indicator indicators = mongoTemplate.findOne(query, Indicator.class, \u0026#34;indicators\u0026#34;); if (indicators == null) throw new RiotDataException(RiotDataError.NOT_IN_STATISTICS_DATABASE); else log.info(\u0026#34;indicator founded : {}\u0026#34;, indicators.getId()); return indicators; } 오류 코드 Parameter org.springframework.data.mapping.Parameter@691d29ad does not have a name 원인 분석 실제로 MongoDB에서 데이터를 정상 꺼내오는 로그는 다음과 같이 잘 찍혀있었습니다.\nCommand \u0026#34;find\u0026#34; succeeded on database \u0026#34;matchup_statistics_db\u0026#34; in 1.627834 ms using a connection with driver-generated ID 3 and server-generated ID 321 to localhost:3311. The request ID is 6 and the operation ID is 5. Command reply: {\u0026#34;cursor\u0026#34;: {\u0026#34;firstBatch\u0026#34;: [{\u0026#34;_id\u0026#34;: // 생략 또한, 이미 MongoDB에 데이터를 저장할 때는 문제없이 저장되었던 값을 꺼내오는 과정에서 오류가 발생했기 때문에, 꺼내온 값을 spring에서 mapping하는 과정에서 특정 파라미터를 인식하지 못해 발생했다고 생각했습니다.\n해결 1. 기본 생성자 추가 Spring에서 사용하는 mapping은 기본적으로 생성자를 통해 객체를 Reflection하기 때문에, 기본 생성자를 추가해야 합니다.(혹은 lombok의 @NoArgsConstructor) 해당 Collection의 Field에 들어가는 모든 클래스에 기본 생성자를 붙여주었지만 동일한 오류가 계속 발생했습니다.\n2. @Field 추가 MongoDB에서 객체를 생성해서 가져오는 과정에서 해당 어노테이션이 없으면 인식을 못해서 org.springframework.data.mapping.PropertyReferenceException 가 발생할 수 있다고 합니다.1 저와 다른 종류의 오류이기도 하고, 저는 해당 어노테이션 없이 해결이 되었지만 혹시 해결되지 않는 분들은 적용해보시기 바랍니다.\n3. Build Option : Intellij -\u0026gt; Gradle로 변경 해당 코드를 디버깅하면 다음 소스코드에서 null이 발생함을 알 수 있습니다. @Nullable private String[] getParameterNames(Parameter[] parameters) { String[] parameterNames = new String[parameters.length]; for (int i = 0; i \u0026lt; parameters.length; i++) { Parameter param = parameters[i]; if (!param.isNamePresent()) { return null; // null 발생 } parameterNames[i] = param.getName(); } return parameterNames; } 즉, 빌드 시점에 파라미터가 설정되지 않아 발생하는 문제이며 이는 Gradle로 실행 시 자동으로 해결됩니다. 자세한 설명은 해당 블로그를 참고하시기 바랍니다.\n저는 인텔리제이에서 빌드 옵션을 변경했더니 정상 실행이 되었습니다. Intellij에서 Gradle로 변경합니다.\n오류의 원인이 단순히 코드에만 있는 게 아니라, 프레임워크나 IDE등에 의해서도 충분히 발생할 수 있음을 항상 견지해야겠습니다. 그리고 프레임워크의 의존성을 줄여나가는 것도 좋은 방향이라고 생각합니다.\nReferences URL 게시일자 방문일자 작성자 https://ricma.co/posts/tech/dev/migrating-to-spring-61-javac-parameters 2023.09.22. 2024.04.02. Riccardo Macoratti https://stackoverflow.com/questions/36160919/caused-by-org-springframework-data-mapping-model-mappingexception-no-property 2016.03.22. 2024.04.02. user4821194 https://stackoverflow.com/questions/53207049/spring-data-mongo-no-property-b-found-on-entity-class-when-retrieving-entity-by/53210768#53210768 2018.11.08. 2024.04.02. J.Pip 쏘니의 개발블로그:티스토리\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/tips/240402/","summary":"\u003ch2 id=\"상황\"\u003e상황\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eSpringBoot에서 MongoDB에 데이터를 저장하고, 저장한 데이터를 파싱하는 과정에서 다음과 같은 오류가 발생했습니다.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e실행 코드\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 몽고DB 컬렉션 클래스\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Document\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecollection\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;indicators\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Getter\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@NoArgsConstructor\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Builder\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@AllArgsConstructor\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Slf4j\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eIndicator\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Id\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eMatchIndicator\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ematchIndicators\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eMatchIndicatorStatistics\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003ematchIndicatorStatistics\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 메서드 생략\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 컬렉션 조회 메서드\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nd\"\u003e@Override\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eIndicator\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003egetIndicatorInDB\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eSummonerId\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eQuery\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003equery\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eQuery\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003equery\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e                \u003c/span\u003e\u003cspan class=\"n\"\u003eCriteria\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003ewhere\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;_id\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e).\u003c/span\u003e\u003cspan class=\"na\"\u003eis\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSummonerId\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"n\"\u003eIndicator\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eindicators\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emongoTemplate\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003efindOne\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003equery\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eIndicator\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eclass\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;indicators\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eindicators\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e==\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003enull\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003ethrow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003enew\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eRiotDataException\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eRiotDataError\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eNOT_IN_STATISTICS_DATABASE\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003elog\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003einfo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;indicator founded : {}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eindicators\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetId\u003c/span\u003e\u003cspan class=\"p\"\u003e());\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"k\"\u003ereturn\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eindicators\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e오류 코드\u003c/li\u003e\n\u003c/ul\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode class=\"language-log\" data-lang=\"log\"\u003eParameter org.springframework.data.mapping.Parameter@691d29ad does not have a name\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"원인-분석\"\u003e원인 분석\u003c/h2\u003e\n\u003cp\u003e실제로 MongoDB에서 데이터를 정상 꺼내오는 로그는 다음과 같이 잘 찍혀있었습니다.\u003c/p\u003e","title":"Org.springframework.data.mapping.MappingException 오류 해결"},{"content":"인터페이스 생성자 주입 스프링에서는 의존성을 주입받는 다양한 기능이 있습니다. 그중 가장 많이들 사용하시는 롬복을 활용한 생성자 주입은 다음과 같습니다.\n@RequiredArgsConstructor @Service public class MyService { private final MyRepository myRepository; } // 빌드 시 lombok의 @RequiredArgsConstructor에 의해 추가되는 코드 public MyService(MyRepository myRepository) { this.myRepository = myRepository; } 하지만 주입받아야 하는게 인터페이스1라면 어떻게 의존성을 주입받을 수 있을까요?\n인터페이스의 구현체가 1개라면 문제가 없지만, 여러개 있다면 문제가 됩니다. 스프링이 어떤 Bean을 주입해야 할지 선택하지 못하기 때문입니다.(결정장애)\n직접 생성자 주입하기 정석적인 해결책입니다. lombok이 편하게 해주던 작업을 일일히 쳐주고 @Qualifier를 통해 명시해주면 됩니다.\n/** * interface인 MyRepository의 구현체로 jpaRepository와 mybatisRepository가 Bean으로 등록된 상황입니다. * @Repository(value = \u0026#34;jpaRepository\u0026#34;) * @Repository(value = \u0026#34;mybatisRepository\u0026#34;) */ @Service public class MyService { private final MyRepository myRepository; public MyService(@Qualifier(jpaRepository) MyRepository myRepository) { this.myRepository = myRepository; } } Lombok의 의존성을 제거할 수 있기 때문에 훨씬 좋은 코드2이긴 하지만, 위의 작업이 귀찮고 롬복을 계속 사용하고 싶다면 다음 방법들을 참고하시면 됩니다.\nPrimary 설정 만약 1개의 더 선호하는 Repository가 있다면 이 방법도 유용합니다.\n@Repository(value = \u0026#34;jpaRepository\u0026#34;) @Primary // 해당 설정으로 우선순위 부여 public class JpaRepository implements MyRepository{ // 생략 } 그러나 2가지 Repository를 모두 사용해야 한다면 위 방법으로는 해결이 불가능합니다.\nField Name 변경 스프링에서는 Field Name과 등록된 Bean의 이름이 같으면 자동으로 의존성을 주입해줍니다.\n/** * @Repository(value = \u0026#34;jpaRepository\u0026#34;) * @Repository(value = \u0026#34;mybatisRepository\u0026#34;) */ @RequiredArgsConstructor @Service public class MyService { // myRepository -\u0026gt; jpaRepository private final MyRepository jpaRepository; } 가장 간단하게 해결이 가능합니다.\n롬복 설정 변경 롬복 설정을 다음과 같이 바꾸면 @Qualifier를 사용할 수 있습니다.\n// 경로 : src/main/java/lombok.config(없으면 생성) lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier // 위 설정 후 다음과 같이 사용 가능합니다. @RequiredArgsConstructor @Service public class MyService { @Qualifier(\u0026#34;jpaRepository\u0026#34;) // Build시 @Qualifier도 함께 생성해줌! private final MyRepository myRepository; } 롬복 설정을 변경하는게 귀찮기도 하지만, 종종 Field Name으로 DI가 안될때가 있습니다\u0026hellip;(아직 원인은 찾지 못했습니다.)\nReferences URL 게시일자 방문일자 작성자 https://www.inflearn.com/questions/71872/requiredargsconstructor%EA%B3%BC-qualifier%EC%A7%88%EB%AC%B8 2020.10.02. 2024.03.28. vkdlxj3562 인터페이스는 구현체가 아니기 때문에 Bean으로 등록할 수 없습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n단위테스트를 하기 좋은 코드이기도 합니다. 참고로 아래와 같이 Spring에 의존하지 않고도 테스트할 수 있습니다.\n@Test @DisplayName(\u0026#34;스프링 컨테이너에 의존하지 않은 테스트\u0026#34;) void dependencyInjectionWithoutSpringTest() { // given MyRepository myRepository = new JpaRepository(); // when MyService myService = new MyService(myRepository); // then assertThat(myService).isNotNull(); assertThat(myService.getMyRepository()).isNotNull(); assertThat(myService.getMyRepository()).isInstanceOf(MyRepository.class); } \u0026#160;\u0026#x21a9;\u0026#xfe0e; ","permalink":"https://leaf-nam.github.io/tips/240329/","summary":"\u003ch2 id=\"인터페이스-생성자-주입\"\u003e인터페이스 생성자 주입\u003c/h2\u003e\n\u003cp\u003e스프링에서는 의존성을 주입받는 다양한 기능이 있습니다. 그중 가장 많이들 사용하시는 롬복을 활용한 생성자 주입은 다음과 같습니다.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@RequiredArgsConstructor\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Service\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eMyService\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003efinal\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eMyRepository\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emyRepository\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 빌드 시 lombok의 @RequiredArgsConstructor에 의해 추가되는 코드\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003eMyService\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eMyRepository\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emyRepository\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"k\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003emyRepository\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emyRepository\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e하지만 주입받아야 하는게 인터페이스\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e라면 어떻게 의존성을 주입받을 수 있을까요?\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e인터페이스의 구현체가 1개라면 문제가 없지만, 여러개 있다면 문제가 됩니다. 스프링이 어떤 Bean을 주입해야 할지 선택하지 못하기 때문입니다.\u003cdel\u003e(결정장애)\u003c/del\u003e\u003c/p\u003e","title":"Lombok 생성자 주입 시 인터페이스 주입하기"},{"content":"상황 기존 코드를 리팩토링하는 과정에서 엔티티 객체에 있던 @Data를 @Getter로 변경중이었습니다.\n테스트를 위해 임시로 @Data를 붙여놓고 사용중이었습니다.\n@Getter // 기존 : @Data @Builder @AllArgsConstructor public class LaneInfo { private TeamPosition teamPosition; private boolean isBottomLane; private int myTeamId; private int myLaneNumber; private int oppositeLaneNumber; private int myBottomDuoNumber; private int oppositeBottomDuoNumber; //...(생략) } 기존에 잘 동작하던 아래 테스트에서 오류가 발생했습니다.\n//생략 // matchIndicator가 가진 laneInfo와 given에서 주어진 laneInfo 비교 assertThat(matchIndicators.get(0) .getMetadata() .getLaneInfo()) .isEqualTo(laneInfo); 원인 분석 오류 로그는 다음과 같았습니다. Expected :com.ssafy.matchup_statistics.indicator.entity.match.LaneInfo@1150d471 Actual :com.ssafy.matchup_statistics.indicator.entity.match.LaneInfo@6393bf8b 해당 테스트코드는 기존에는 잘 동작했고, 각 필드가 하나라도 달라지면 실패하던 테스트코드였기에 원인이 궁금했습니다.\n@Data 내부를 확인해본 결과, 기존 코드가 성공했던 이유는 @Data 에서 내부적으로 @EqualsAndHashCode를 통해 equals를 구현해주었기 때문이었습니다.\n// 롬복 @Data에 들어가보면 볼 수 있는 Java Doc /** * Generates getters for all fields, a useful toString method, and hashCode and equals implementations that check * all non-transient fields. Will also generate setters for all non-final fields, as well as a constructor. * Equivalent to {@code @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode}. * Complete documentation is found at \u0026lt;a href=\u0026#34;https://projectlombok.org/features/Data\u0026#34;\u0026gt;the project lombok features page for \u0026amp;#64;Data\u0026lt;/a\u0026gt;. * * @see Getter * @see Setter * @see RequiredArgsConstructor * @see ToString * @see EqualsAndHashCode * @see lombok.Value */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) 해결 Equals Overriding @Getter @Builder @AllArgsConstructor @EqualsAndHashCode // 추가 public class LaneInfo { private TeamPosition teamPosition; private boolean isBottomLane; private int myTeamId; private int myLaneNumber; private int oppositeLaneNumber; private int myBottomDuoNumber; private int oppositeBottomDuoNumber; 롬복이 제공하는 @EqualsAndHashCode 어노테이션만으로도 간단하게 해결이 가능하지만, Equals를 별도로 구현하지 않고 해결할 수 있는 다른 방법들도 알아보겠습니다.\n각 필드 직접 비교(비추천) assertThat(matchIndicators.get(0) .getMetadata() .getLaneInfo() .getTeamPosition()) .isEqualTo(laneInfo.getTeamPosition()); assertThat(matchIndicators.get(0) .getMetadata() .getLaneInfo() .getIsBottomLane()) .isEqualTo(laneInfo.getIsBottomLane()); //(생략 : 모든 필드 다 비교) 테스트 코드도 길어지고, 필드값 비교 과정에서 human error가 발생할 수 있으므로 비추천합니다.\n재귀적으로 필드값 비교(추천) assertThat(matchIndicators.get(0) .getMetadata() .getLaneInfo()) .usingRecursiveComparison() .isEqualTo(laneInfo); 위 코드보다 훨씬 깔끔하게 테스트가 가능합니다. assertJ 짱! 참고로 아래 메서드로 비교하지 않을 필드를 제외할 수 있습니다.\n// 무시할 필드값 ignoringFields(String… fieldsToIgnore) // 무시할 정규표현식 ignoreFieldsMatchingRegexes(String… regexes) // 무시할 타입(클래스) ignoringFieldsOfTypes(Class… typesToIgnore) References URL 게시일자 방문일자 작성자 https://umanking.github.io/2021/06/11/assertj-field-recursive-comparision/ 2021.06.11. 2024.03.23. CodeNexus https://assertj.github.io/doc/#assertj-core-recursive-comparison-ignoring-fields 2024.02.17. 2024.03.23. assertj-core ","permalink":"https://leaf-nam.github.io/tips/240323/","summary":"\u003ch2 id=\"상황\"\u003e상황\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e기존 코드를 리팩토링하는 과정에서 엔티티 객체에 있던 @Data를 @Getter로 변경중이었습니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e테스트를 위해 임시로 @Data를 붙여놓고 사용중이었습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Getter\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"c1\"\u003e// 기존 : @Data\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@Builder\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@AllArgsConstructor\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kd\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eLaneInfo\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eTeamPosition\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eteamPosition\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eboolean\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eisBottomLane\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emyTeamId\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emyLaneNumber\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eoppositeLaneNumber\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003emyBottomDuoNumber\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003eprivate\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003eint\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eoppositeBottomDuoNumber\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"c1\"\u003e//...(생략)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e기존에 잘 동작하던 아래 테스트에서 오류가 발생했습니다.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e//생략\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// matchIndicator가 가진 laneInfo와 given에서 주어진 laneInfo 비교\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eassertThat\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ematchIndicators\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003e0\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetMetadata\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetLaneInfo\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eisEqualTo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elaneInfo\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"원인-분석\"\u003e원인 분석\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e오류 로그는 다음과 같았습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode class=\"language-log\" data-lang=\"log\"\u003eExpected :com.ssafy.matchup_statistics.indicator.entity.match.LaneInfo@1150d471\nActual   :com.ssafy.matchup_statistics.indicator.entity.match.LaneInfo@6393bf8b\n\u003c/code\u003e\u003c/pre\u003e\u003cblockquote\u003e\n\u003cp\u003e해당 테스트코드는 기존에는 잘 동작했고, 각 필드가 하나라도 달라지면 실패하던 테스트코드였기에 원인이 궁금했습니다.\u003c/p\u003e","title":"Jnuit 테스트에서 객체 필드명 비교하기"},{"content":"로그레벨 변경하기 단위테스트에서 간단하게 로그 레벨을 변경할 수 있습니다.\n통합테스트는 @SpringBootTest를 사용하면 /src/test/resources/application.properties에 있는 설정정보를 자동으로 불러오지만, 단위테스트에서는 해당 어노테이션이 너무 무겁기 때문에 사용할 수 없습니다.\n바로 코드로 알아보겠습니다.\n// 단위테스트가 필요한 클래스 내부에 해당 코드를 추가하면 완료입니다. import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; @BeforeAll public void setLogLevel() { final Logger logger = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); logger.setLevel(Level.ALL); } 간단히 코드를 설명드리자면, 단위테스트 시작 전 LoggerFactory로 새로운 loogger를 생성하고 레벨을 변경한 후 테스트를 수행하기 위한 코드입니다.\n반드시 logback1 라이브러리에 있는 Logger를 import받으셔야 합니다!\nJunit4에서는 @BeforeaAll 대신 @Before 를 사용하시면 됩니다.\n감사합니다.\nReferences URL 게시일자 방문일자 작성자 https://stackoverflow.com/questions/38778026/how-to-set-the-log-level-to-debug-during-junit-tests 2016.08.04. 2024.03.20 PedroD log4j의 후속 라이브러리입니다. 더 자세한 설명은 해당 페이지를 확인해주세요.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/tips/240320/","summary":"\u003ch2 id=\"로그레벨-변경하기\"\u003e로그레벨 변경하기\u003c/h2\u003e\n\u003cp\u003e단위테스트에서 간단하게 로그 레벨을 변경할 수 있습니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e통합테스트는 @SpringBootTest를 사용하면 /src/test/resources/application.properties에 있는 설정정보를 자동으로 불러오지만, 단위테스트에서는 해당 어노테이션이 너무 무겁기 때문에 사용할 수 없습니다.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e바로 코드로 알아보겠습니다.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-java\" data-lang=\"java\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e// 단위테스트가 필요한 클래스 내부에 해당 코드를 추가하면 완료입니다.\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003eorg.slf4j.LoggerFactory\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ech.qos.logback.classic.Level\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nn\"\u003ech.qos.logback.classic.Logger\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@BeforeAll\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003epublic\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"nf\"\u003esetLogLevel\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"kd\"\u003efinal\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003eLogger\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eLogger\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"n\"\u003eLoggerFactory\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003egetLogger\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eLogger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eROOT_LOGGER_NAME\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"n\"\u003elogger\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003esetLevel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eLevel\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"na\"\u003eALL\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e간단히 코드를 설명드리자면, 단위테스트 시작 전 LoggerFactory로 새로운 loogger를 생성하고 레벨을 변경한 후 테스트를 수행하기 위한 코드입니다.\u003c/p\u003e","title":"Springboot Junit 단위테스트에서 로그 레벨 조정하기"},{"content":"도입 이전 포스팅 참조 : DB 이중화 및 CQRS 패턴의 중요성 \u0026gt; MySQL Replication Database 구현\n실습환경\nDocker : v25.0.3 MySQL : v8.3.0 Java : v17.0.9 Spring : v3.2.4 저번 시간에 생성한 Master / Slave DB에 SpringBoot를 직접 연동해서 CRUD를 하는 실습을 진행합니다.\n프로젝트 생성 Springboot 프로젝트를 생성합니다. 아래 사진과 같이 JPA, Lombok, MySQL Driver 의존성을 추가하겠습니다. 스프링부트 프로젝트를 위와 같이 의존성을 추가하여 생성합니다.\nbuild.gradle 실행 위에서 생성한 프로젝트의 jar파일을 풀고, build.gradle 파일을 intellij 혹은 eclipse로 실행합니다. gradle로 프로젝트를 실행하면 자동으로 소스파일 경로가 생성됩니다.\nJpaConfig 클래스 생성 원래 스프링부트에 JPA 의존성을 추가하면 기본으로 설정된 DataSource를 불러와 사용하지만, 저번 시간에 분리한 Command(쓰기)와 Query(읽기) DB를 DataSource로 사용하기 위해 다음과 같이 JpaConfig를 설정하겠습니다.\npackage com.replication.demo.config; // config 패키지 생성 import org.springframework.context.annotation.Configuration; @Configuration // Spring에 자동으로 Bean을 등록하기 위함 public class JpaConfig {} DataSource 구현 JpaConfig내부에 DataSource를 Bean으로 등록하면, 이후 Spring이 해당 Bean의 설정을 통해 DataSource를 생성하므로 편리하게 DB에 접근할 수 있습니다.\n두 개의 DataSource를 각각 구현하고, Transaction시점에 필요한 DataSource를 결정할 수 있도록 Spring에서는 AbstractRoutingDataSource라는 추상클래스를 제공합니다.\nCommand \u0026amp; Read DataSource Bean 생성 우선, 쓰기와 읽기 DataSource를 다음과 같이 JpaConfig 내부에서 Bean으로 등록합니다.\n@Configuration public class JpaConfig { @Bean(\u0026#34;commandDataSource\u0026#34;) // 원본 DB와 연결된 DataSource public DataSource commandDataSource() { HikariDataSource dataSource = DataSourceBuilder.create() .driverClassName(\u0026#34;com.mysql.cj.jdbc.Driver\u0026#34;) .url(\u0026#34;jdbc:mysql://localhost:3307/target_db\u0026#34;) .username(\u0026#34;master_user\u0026#34;) .password(\u0026#34;1234\u0026#34;) .type(HikariDataSource.class) .build(); dataSource.setMaximumPoolSize(2); // Pool Size도 설정 가능합니다. return dataSource; } @Bean(\u0026#34;queryDataSource\u0026#34;) // Repl DB와 연결된 DataSource public DataSource queryDataSource() { HikariDataSource dataSource = DataSourceBuilder.create() .driverClassName(\u0026#34;com.mysql.cj.jdbc.Driver\u0026#34;) .url(\u0026#34;jdbc:mysql://localhost:3308/target_db\u0026#34;) .username(\u0026#34;slave_user\u0026#34;) .password(\u0026#34;1234\u0026#34;) .type(HikariDataSource.class) .build(); dataSource.setMaximumPoolSize(5); // 보통 읽기전용 작업이 더 많기 때문에 크게 설정하겠습니다. } } jdbc에서 사용하는 port 및 username과 password는 저번 시간에 생성한 DB 환경변수를 참고하시면 됩니다.\nRoutingDataSource 구현 읽기전용 트랜잭션인지 여부에 따라 동적으로 DataSource를 사용해야 하므로, 스프링에게 트랜잭션 시점에 해당 작업에 대해 알려주고, 필요한 DataSource를 동적으로 불러오도록 해야 합니다.\n이를 위해 다음과 같이 추상 클래스인 AbstractRoutingDataSource를 상속받는 사용자 정의 클래스를 생성합니다.\n@Slf4j // AbstractRoutingDataSource 구현 public static class ReplicationRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); log.info(\u0026#34;Use ReadOnly Datasource : {}\u0026#34;, isReadOnly); return isReadOnly ? \u0026#34;replication\u0026#34; : \u0026#34;original\u0026#34;; } } 위 설정에서 determineCurrentLookupKey 메서드를 구현하면 동적으로 DataSource를 라우팅하는 것이 가능한데, 그 key를 TransactionSynchronizationManager1의 속성값(isCurrentTransactionReadOnly; 현재 트랜잭션이 읽기전용인지?)으로 사용해서 DataSource를 읽기 / 쓰기 시점에 결정합니다.\n다음으로 위 클래스를 활용해서 실제로 DataSource를 결정 후 해당 DataSource를 Bean으로 등록합니다.\nReplicationRoutingDataSource 클래스는 AbstractRoutingDataSource를 상속받고 있기 때문에 해당 추상클래스의 메서드를 사용가능하여 다음과 같이 ReadOnly 여부에 따른 데이터소스를 설정하는 것이 가능합니다.\nReadOnly여부는 @Transactional(readOnly = true)인지를 확인하여 결정됩니다. 이 때, org.springframework.transaction.annotation.Transactional을 사용해야 함을 주의합니다. (jakarta.transactional 아님!!)\n@Bean(\u0026#34;routingDataSource\u0026#34;) // DataSource 종류에 따른 DataSource 라우팅(변경) public DataSource routingDataSource(@Qualifier(\u0026#34;commandDataSource\u0026#34;) DataSource commandDataSource, @Qualifier(\u0026#34;queryDataSource\u0026#34;) DataSource queryDataSource) { ReplicationRoutingDataSource routingDataSource = new ReplicationRoutingDataSource(); // DataSource 라우팅 Map\u0026lt;Object, Object\u0026gt; dataSourceMap = new HashMap\u0026lt;\u0026gt;(); dataSourceMap.put(\u0026#34;command\u0026#34;, commandDataSource); dataSourceMap.put(\u0026#34;query\u0026#34;, queryDataSource); // 기본 DataSource 및 ReadOnly 여부에 따른 DataSource 설정 routingDataSource.setDefaultTargetDataSource(commandDataSource); // commandDataSource를 기본 사용 routingDataSource.setTargetDataSources(dataSourceMap); // ReadOnly여부에 따른 DataSource 변경 return routingDataSource; } 다음으로 위의 Bean을 LazyConnectionDataSourceProxy으로 감싸는데, 이는 트랜잭션 진입 시점이 아닌 실제 커넥션이 필요한 시점2에 DataSource를 결정하기 위함입니다.\n@Bean(\u0026#34;routingLazyDataSource\u0026#34;) // Connection 시점에 DataSource 결정하기 위한 Proxy public DataSource routingLazyDataSource(@Qualifier(\u0026#34;routingDataSource\u0026#34;) DataSource routingDataSource) { return new LazyConnectionDataSourceProxy(routingDataSource); } EntityManagerFactory 구현 Spring에서는 트랜잭션의 동시성 문제3를 해결하기 위해 EntityManager를 트랜잭션 시마다 생성하는 Factory Method 패턴을 구현하고 있습니다. 이를 위해 저희도 EntityManagerFactory에 위에서 설정한 DataSource를 직접 주입함으로써 동시성 문제를 해결할 수 있습니다.\n@Bean(\u0026#34;entityManagerFactory\u0026#34;) // Entity 를 관리하기 위한 JPA Manager 설정 LocalContainerEntityManagerFactoryBean entityManagerFactory( @Qualifier(\u0026#34;routingLazyDataSource\u0026#34;) DataSource dataSource) { LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean(); // DataSource 설정 emf.setDataSource(dataSource); // EntityManager 가 관리할 Base Package 설정 emf.setPackagesToScan(\u0026#34;com.replication.demo.*\u0026#34;); // Hibernate Vendor Adaptor 설정 HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter(); hibernateJpaVendorAdapter.setDatabasePlatform(\u0026#34;org.hibernate.dialect.MySQLDialect\u0026#34;); emf.setJpaVendorAdapter(hibernateJpaVendorAdapter); // JPA 및 Hibernate 설정 Properties properties = new Properties(); properties.setProperty(\u0026#34;spring.jpa.hibernate.ddl-auto\u0026#34;, \u0026#34;create-drop\u0026#34;); properties.setProperty(\u0026#34;hibernate.show_sql\u0026#34;,\u0026#34;true\u0026#34;); properties.setProperty(\u0026#34;hibernate.format_sql\u0026#34;,\u0026#34;true\u0026#34;); properties.setProperty(\u0026#34;hibernate.default_batch_fetch_size\u0026#34;, \u0026#34;100\u0026#34;); emf.setJpaProperties(properties); return emf; } Vendor Adaptor는 JPA 구현체를 선택하기 위함이며, 보통 Hibernate를 많이 사용합니다.4 기타 JPA 및 Hibernate 설정은 JPA 공식 문서와 Hibernate 공식 문서에서 더욱 자세한 옵션과 설명을 확인하실 수 있습니다.\nTransactionManager 구현 Spring에서 @Transactional를 통해 트랜잭션이 발생하면, Spring Container에서 TransactinManager를 불러와 트랜잭션을 수행합니다. 이 때, 위에서 구현한 DataSource와 EntityManager를 사용해서 트랜잭션을 수행하도록 하겠습니다.\n@Bean(\u0026#34;transactionManager\u0026#34;) // 트랜잭션 매니저 설정 public PlatformTransactionManager transactionManager( @Qualifier(\u0026#34;entityManagerFactory\u0026#34;) EntityManagerFactory entityManagerFactory) { JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(); jpaTransactionManager.setEntityManagerFactory(entityManagerFactory); return jpaTransactionManager; } PlatformTransactionManager는 다양한 플랫폼의 트랜잭션을 지원하기 위한 클래스이며, 위에서는 JpaTransactionManager를 사용했지만 HibernateTransactionManager나 JdbcTransactionManager등의 플랫폼도 사용 가능합니다.\n테스트 이제 Config 설정이 완료되었으니, 직접 Test를 통해 원하는 기능이 제대로 실행되는지 확인해보겠습니다.\nEntity 생성 테스트코드를 작성하기 전, DB에 저장할 엔티티 클래스를 먼저 생성하겠습니다.\nUser Entity 생성\npackage com.replication.demo.entity; import jakarta.persistence.*; import lombok.*; import java.util.List; @Entity(name = \u0026#34;users\u0026#34;) @NoArgsConstructor @Getter @Setter public class User { @Id @GeneratedValue @Column(name = \u0026#34;user_id\u0026#34;) private Long id; private String name; private Integer age; @OneToMany(mappedBy = \u0026#34;owner\u0026#34;) private List\u0026lt;Computer\u0026gt; computers; } Computer Entity 생성\npackage com.replication.demo.entity; import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Entity(name = \u0026#34;computer\u0026#34;) @NoArgsConstructor @Getter @Setter public class Computer { @Id @GeneratedValue @Column(name = \u0026#34;computer_id\u0026#34;) private Long id; @Enumerated(EnumType.STRING) private ComputerType type; @Column(name = \u0026#34;os\u0026#34;) private String OS; @ManyToOne @JoinColumn(name = \u0026#34;owner_id\u0026#34;) private User owner; } Computer Type 생성\npackage com.replication.demo.entity; public enum ComputerType { MAC, WINDOW, LINUX } 테스트코드 작성 마지막으로, 테스트코드를 통해 Command(readOnly = false) 트랜잭션과 Query(readOnly = true) 트랜잭션을 분리하여 정상적으로 조회되는지 확인해보겠습니다.\npackage com.replication.demo; import com.replication.demo.entity.Computer; import com.replication.demo.entity.ComputerType; import com.replication.demo.entity.User; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import org.junit.jupiter.api.*; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class DemoApplicationTests { @PersistenceContext EntityManager em; @Test @Order(1) void contextLoads() { } @Test @Order(2) @DisplayName(\u0026#34;최초 데이터 입력\u0026#34;) @Transactional(readOnly = false) @Rollback(value = false) void init() { // 사용자 생성 User user = new User(); user.setAge(28); user.setName(\u0026#34;leaf\u0026#34;); // 컴퓨터 1 생성 및 저장 List\u0026lt;Computer\u0026gt; computers = new ArrayList\u0026lt;\u0026gt;(); Computer macCom = new Computer(); macCom.setOS(\u0026#34;Ventura\u0026#34;); macCom.setType(ComputerType.MAC); macCom.setOwner(user); computers.add(macCom); em.persist(macCom); // 컴퓨터 2 생성 및 저장 Computer windowCom = new Computer(); windowCom.setOS(\u0026#34;WINDOW 11\u0026#34;); windowCom.setType(ComputerType.WINDOW); windowCom.setOwner(user); computers.add(windowCom); em.persist(windowCom); // 컴퓨터 사용자에 추가 후 사용자 저장 user.setComputers(computers); em.persist(user); em.flush(); em.clear(); } @Test @Order(3) @DisplayName(\u0026#34;사용자 조회 시 컴퓨터 잘 불러오는지 테스트\u0026#34;) @Transactional(readOnly = true) void userQueryWithComputer() { // given List\u0026lt;User\u0026gt; users = em.createQuery( \u0026#34;select u \u0026#34; + \u0026#34;from users as u \u0026#34; + \u0026#34;join u.computers as c\u0026#34;, User.class) .getResultList(); // when User userInDB = users.get(0); // then // 사용자명, 나이 테스트 assertThat(userInDB.getName()).isEqualTo(\u0026#34;leaf\u0026#34;); assertThat(userInDB.getAge()).isEqualTo(28); assertThat(userInDB.getComputers()).hasSize(2); // 맥북 컴퓨터 가지고 있는지 테스트 assertThat(userInDB.getComputers()).anyMatch(c -\u0026gt; c.getOS().equals(\u0026#34;Ventura\u0026#34;)); assertThat(userInDB.getComputers()).anyMatch(c -\u0026gt; c.getType().equals(ComputerType.MAC)); // 윈도우 컴퓨터 가지고 있는지 테스트 assertThat(userInDB.getComputers()).anyMatch(c -\u0026gt; c.getOS().equals(\u0026#34;WINDOW 11\u0026#34;)); assertThat(userInDB.getComputers()).anyMatch(c -\u0026gt; c.getType().equals(ComputerType.WINDOW)); } } @RollBack(false)를 통해 롤백을 방지한 후 다음 테스트가 실행되도록 설계했습니다.5\n테스트 실행결과, 위와 같이 데이터소스 선택 및 실제 SQL이 잘 나가는 것을 확인할 수 있습니다.\n결론 지금까지 이중화된 DB의 데이터소스를 SpringBoot에서 동적으로 선택하여 실제 DB와 연동 후 테스트까지 수행했습니다. 해당 프로젝트의 소스코드는 깃허브에 올려두었으니, 필요 시 참고하시기 바랍니다.\nReferences URL 게시일자 방문일자 작성자 https://docs.spring.io/spring-data/relational/reference/jdbc/getting-started.html 미확인 2024.03.31. Spring https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.data 미확인 2024.04.10. Spring https://stackoverflow.com/questions/24643863/is-entitymanager-really-thread-safe 2014.07.09. 2024.04.10. Ken Y-N 트랜잭션 동기화 기법을 사용하기 위한 클래스입니다. 보통 여러 트랜잭션을 한번에 커밋 및 롤백하여 정합성을 보장하기 위해 사용합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n특히 Hibernate의 영속성 컨텍스트와 같은 1차 캐시를 사용할 경우, DataSource 접근이 필요하지 않지만 @Transactional로 인해 불필요한 커넥션이 발생하게 됩니다. LazyConnectionDataSourceProxy으로 Proxy객체를 사용할 경우 실제로 Connection이 필요한 시점에 DataSource에 접근하기 때문에 성능상 이점이 많습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nEntity Manager는 Thread-Safe하지 않기 때문에, Factory를 통해 필요 시점에 새로운 Entity Manager를 생성해서 활용해야 합니다. 해당 StackOverflow를 읽어보시면, 이 때 주입되는 Entity Manager는 Proxy형태로, 실제 트랜잭션 시점에 진짜 Entity Manager로 대체되기 때문에 Thread-Safe 할 수 있다고 합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n참고로 JPA의 EntityManagerFactory대신 Hibernate의 SessionFactory를 구현하는 방법도 있지만, 이는 JPA의 구현체에 의존하므로 좋지 않은 것 같습니다(DIP 위반). 하지만 Hibernate만의 특정 기술을 사용해야만 하는 상황에서는 SessionFactory를 구현 후 TransactionManager에 HibernateTransactionManager를 사용하시면 될 것 같습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n원래는 @BeforeEach 를 사용하여 DB를 초기화하면 되지만, 트랜잭션 범위 설정이 클래스 단위로 제한되어 @Transactional(readOnly = true)를 지정하면 init() 메서드에 @Transactional(readOnly = false)를 설정해도 DB에 값이 반영되지 않는 문제가 발생했습니다. 하는 수 없이 위와 같은 방식으로 설계했습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/cqrs/3/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e이전 포스팅 참조 :\n\u003ca href=\"https://leaf-nam.github.io/posts/240310_mysql_springboot_cqrs_%ED%8C%A8%ED%84%B4_%EA%B5%AC%ED%98%84%EC%9D%84_%EC%9C%84%ED%95%9C_db_%EC%9D%B4%EC%A4%91%ED%99%94/240313_mysql_replication_database_%EA%B5%AC%ED%98%84/\"\u003eDB 이중화 및 CQRS 패턴의 중요성\u003c/a\u003e \u0026gt; \u003ca href=\"https://leaf-nam.github.io/posts/240310_mysql_springboot_cqrs_%ED%8C%A8%ED%84%B4_%EA%B5%AC%ED%98%84%EC%9D%84_%EC%9C%84%ED%95%9C_db_%EC%9D%B4%EC%A4%91%ED%99%94/240313_mysql_replication_database_%EA%B5%AC%ED%98%84/\"\u003eMySQL Replication Database 구현\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e실습환경\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eDocker : v25.0.3\u003c/li\u003e\n\u003cli\u003eMySQL : v8.3.0\u003c/li\u003e\n\u003cli\u003eJava : v17.0.9\u003c/li\u003e\n\u003cli\u003eSpring : v3.2.4\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e저번 시간에 생성한 Master / Slave DB에 SpringBoot를 직접 연동해서 CRUD를 하는 실습을 진행합니다.\u003c/p\u003e\n\u003ch2 id=\"프로젝트-생성\"\u003e프로젝트 생성\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\u003ca href=\"https://start.spring.io\"\u003eSpringboot 프로젝트\u003c/a\u003e를 생성합니다.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003e아래 사진과 같이 JPA, Lombok, MySQL Driver 의존성을 추가하겠습니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"springboot.png\"\n         alt=\"스프링부트 프로젝트를 위와 같이 의존성을 추가하여 생성합니다.\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003e스프링부트 프로젝트를 위와 같이 의존성을 추가하여 생성합니다.\u003c/p\u003e\n        \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003col start=\"2\"\u003e\n\u003cli\u003ebuild.gradle 실행\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003e위에서 생성한 프로젝트의 jar파일을 풀고, build.gradle 파일을 intellij 혹은 eclipse로 실행합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"gradle.png\"\n         alt=\"gradle로 프로젝트를 실행하면 자동으로 소스파일 경로가 생성됩니다.\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003egradle로 프로젝트를 실행하면 자동으로 소스파일 경로가 생성됩니다.\u003c/p\u003e","title":"[Java]SpringBoot DataSource 이중화(CQRS 패턴) 구현"},{"content":"도입 이전 포스팅 참조 : DB 이중화 및 CQRS 패턴의 중요성\n실습환경\nDocker : v25.0.3 MySQL : v8.3.0 시작에 앞서 간단히 Replication의 원리를 확인해보겠습니다.\nSlave DB는 Master DB의 로그파일을 참조하여 변경사항을 업데이트 합니다.\nMaster DB는 데이터 변경사항을 Binary log 파일에 저장합니다. Slave DB는 Binary log 파일의 변경사항을 감시하다가, 변경이 발생하면 해당 로그를 확인합니다. 변경사항을 Relay log파일에 적용합니다. SQL Thread는 Relay log파일의 변경사항을 감시하다가, 변경이 발생하면 DB에 반영합니다. 그럼 이제 본격적으로 MySQL Master DB와 Slave DB를 Docker 이미지로 생성하고, Replication 설정을 통해 Master DB의 변경사항을 Slave DB로 복제하는 실습을 진행하겠습니다.\nMySQL Docker 생성 Docker1를 통해 가상 OS에 2개(Master, Slave)의 MySQL 서버를 생성해보겠습니다.\nMySQL Docker 이미지 다운로드 Docker 이미지 경로에서 다운로드 받거나, 아래 명령어를 shell에서 사용합니다. docker pull mysql:8.3.0 Master DB 생성 Master DB를 다운로드 받은 Docker Image를 통해 생성하겠습니다.\n포트는 3307 포트를 사용하겠습니다.\n환경변수2에 주의해서 명령어를 입력해주세요.\ndocker run -it --name mysql_master -p 3307:3307 -e MYSQL_ROOT_PASSWORD=1234 -e MYSQL_DATABASE=target_db -e MYSQL_USER=master_user -e MYSQL_PASSWORD=1234 -d mysql:8.3.0 --log-bin=master-bin --server-id=1 --port=3307 --default_authentication_plugin=mysql_native_password # 붙여넣기를 위해 줄바꿈을 하지 않았습니다. 환경변수와 설정에 대한 자세한 설명은 아래나 공식문서 참고해주세요. # -e MYSQL_ROOT_PASSWORD : 루트 계정의 비밀번호를 설정합니다. # -e MYSQL_DATABASE : 해당 이름의 데이터베이스를 생성합니다. # -e MYSQL_USER : 해당 유저를 생성합니다. # -e MYSQL_PASSWORD : 해당 유저의 비밀번호를 설정합니다. # --log-bin : 변경사항을 저장할 바이너리 로그파일의 이름을 설정합니다. # --server-id : 서버 식별자를 사용합니다. 통상 master id를 1, 나머지 id를 2 ~ 2^32-1로 설정하면 됩니다. # --port : MySQL을 실행할 컨테이너 내부 포트 주소를 변경합니다. # --default_authentication_plugin : 접속 시 암호화된 비밀번호를 사용할지 여부를 결정합니다. 위와 같이 설정하면 암호화를 사용하지 않습니다. # 공식문서 : https://hub.docker.com/_/mysql Slave DB 생성 Slave DB의 내부 데이터베이스는 Master DB를 복제하기 때문에 동일한 이름으로 지정해야 합니다.\n포트는 3308 포트를 사용하겠습니다.\ndocker run -it --name mysql_slave -p 3308:3308 -e MYSQL_ROOT_PASSWORD=1234 -e MYSQL_DATABASE=target_db -e MYSQL_USER=slave_user -e MYSQL_PASSWORD=1234 -d mysql:8.3.0 --log-bin=master-bin --server-id=2 --read-only=1 --port=3308 --default_authentication_plugin=mysql_native_password # --read-only=1 : 읽기전용 DB로 설정하여 Master DB와의 데이터 정합성이 깨지지 않도록 합니다. Master DB 설정 Slave DB가 Master DB의 정보를 받아오기 위해서는 Master 계정을 설정하고 로그파일을 확인해주어야 합니다.\n계정 생성 및 권한 설정 Slave DB를 연결하려면 Master DB에서 Master 계정을 생성한 후 외부 접근 권한을 가능하도록 변경해야 합니다.\nContainer 내부로 접근\ndocker exec -it mysql_master /bin/bash MySQL root 계정에 접속\nmysql -u root -p # 1234 입력 Master 계정 권한 변경\nGRANT REPLICATION SLAVE ON *.* TO \u0026#39;master_user\u0026#39;@\u0026#39;%\u0026#39;; 로그파일 확인 이제 Master DB에서 변경사항이 발생하면 자동으로 업데이트 되는 로그파일을 확인해보겠습니다.\nSlave DB에서 해당 파일을 모니터링하기 때문에, 해당 파일의 이름과 포지션 값을 연동해야 합니다.\nSHOW MASTER STATUS\\G; # +-------------------+----------+--------------+------------------+-------------------+ # | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | # +-------------------+----------+--------------+------------------+-------------------+ # | master-bin.000003 | 385 | | | | # +-------------------+----------+--------------+------------------+-------------------+ # 상태메시지 중 {File명}과 {Position} 확인 후 별도 저장 Docker IP주소 확인 Replication 설정 전에 Master DB의 Docker IP 주소를 확인해야 합니다.\nmysql, Container 종료\nexit #Bye exit #exit Docker inspect를 통해 컨테이너 IP주소 확인\ndocker inspect mysql_master #...(생략) # \u0026#34;Gateway\u0026#34;: \u0026#34;172.17.0.1\u0026#34;, # \u0026#34;GlobalIPv6Address\u0026#34;: \u0026#34;\u0026#34;, # \u0026#34;GlobalIPv6PrefixLen\u0026#34;: 0, # \u0026#34;IPAddress\u0026#34;: \u0026#34;172.17.0.2\u0026#34; #...(생략) Slave DB 설정 그럼 이제 Slave DB가 Master DB의 Log파일을 참조하도록 설정해보겠습니다.\nReplication 설정 Slave DB에 접속해서 Replication 설정을 합니다.\n(master에 접속한 터미널은 그대로 두고, 별도의 터미널을 실행하시는 것을 권장합니다.)\nContainer 내부로 접근\ndocker exec -it mysql_slave /bin/bash MySQL root 계정에 접속\nmysql -u root -p # 1234 입력 slave 설정은 root 권한이 필요합니다.\nReplication 설정을 합니다. 이 때, 쉼표와 자료형 문법에 주의해야 합니다.3\nmysql\u0026gt; CHANGE REPLICATION SOURCE TO # 위에서 확인한 Master DB docker 내부주소를 입력합니다. mysql\u0026gt; SOURCE_HOST=\u0026#39;{Master DB docker 내부주소}\u0026#39;, mysql\u0026gt; SOURCE_PORT=3307, mysql\u0026gt; SOURCE_USER=\u0026#39;master_user\u0026#39;, mysql\u0026gt; SOURCE_PASSWORD=\u0026#39;1234\u0026#39;, # 아까 확인한 바이너리 로그파일의 이름 및 포지션을 적어야 합니다. mysql\u0026gt; SOURCE_LOG_FILE=\u0026#39;{File명}\u0026#39;, mysql\u0026gt; SOURCE_LOG_POS={Position}; Replication 동작을 시작합니다.\nmysql\u0026gt; START SLAVE; # 만약 replication 설정을 변경하려면 \u0026#39;STOP SLAVE;\u0026#39;로 동작을 종료한 후 위 명령어를 통해 변경해야 합니다. 연결 완료여부 확인 이제 Slave 설정이 정상적으로 완료되었는지 확인해보겠습니다.\nSHOW SLAVE STATUS\\G; # +----------------------------------+-------------+-------------+-------------+---------------+-------------------+---------------------+-------------------------------+---------------+-----------------------+------------------+-------------------+-----------------+---------------------+--------------------+------------------------+-------------------------+-----------------------------+------------+------------+--------------+---------------------+-----------------+-----------------+----------------+---------------+--------------------+--------------------+--------------------+-----------------+-------------------+----------------+-----------------------+-------------------------------+---------------+---------------+----------------+----------------+-----------------------------+------------------+--------------------------------------+-------------------------+-----------+---------------------+----------------------------------------------------------+--------------------+-------------+-------------------------+--------------------------+----------------+--------------------+--------------------+-------------------+---------------+----------------------+--------------+--------------------+------------------------+-----------------------+-------------------+ #| Slave_IO_State | Master_Host | Master_User | Master_Port | Connect_Retry | Master_Log_File | Read_Master_Log_Pos | Relay_Log_File | Relay_Log_Pos | Relay_Master_Log_File | Slave_IO_Running | Slave_SQL_Running | Replicate_Do_DB | Replicate_Ignore_DB | Replicate_Do_Table | Replicate_Ignore_Table | Replicate_Wild_Do_Table | Replicate_Wild_Ignore_Table | Last_Errno | Last_Error | Skip_Counter | Exec_Master_Log_Pos | Relay_Log_Space | Until_Condition | Until_Log_File | Until_Log_Pos | Master_SSL_Allowed | Master_SSL_CA_File | Master_SSL_CA_Path | Master_SSL_Cert | Master_SSL_Cipher | Master_SSL_Key | Seconds_Behind_Master | Master_SSL_Verify_Server_Cert | Last_IO_Errno | Last_IO_Error | Last_SQL_Errno | Last_SQL_Error | Replicate_Ignore_Server_Ids | Master_Server_Id | Master_UUID | Master_Info_File | SQL_Delay | SQL_Remaining_Delay | Slave_SQL_Running_State | Master_Retry_Count | Master_Bind | Last_IO_Error_Timestamp | Last_SQL_Error_Timestamp | Master_SSL_Crl | Master_SSL_Crlpath | Retrieved_Gtid_Set | Executed_Gtid_Set | Auto_Position | Replicate_Rewrite_DB | Channel_Name | Master_TLS_Version | Master_public_key_path | Get_master_public_key | Network_Namespace | #+----------------------------------+-------------+-------------+-------------+---------------+-------------------+---------------------+-------------------------------+---------------+-----------------------+------------------+-------------------+-----------------+---------------------+--------------------+------------------------+-------------------------+-----------------------------+------------+------------+--------------+---------------------+-----------------+-----------------+----------------+---------------+--------------------+--------------------+--------------------+-----------------+-------------------+----------------+-----------------------+-------------------------------+---------------+---------------+----------------+----------------+-----------------------------+------------------+--------------------------------------+-------------------------+-----------+---------------------+----------------------------------------------------------+--------------------+-------------+-------------------------+--------------------------+----------------+--------------------+--------------------+-------------------+---------------+----------------------+--------------+--------------------+------------------------+-----------------------+-------------------+ #| Waiting for source to send event | 172.17.0.2 | master_user | 3307 | 60 | master-bin.000003 | 901 | a25cfc1154fc-relay-bin.000002 | 845 | master-bin.000003 | Yes | Yes | | | | | | | 0 | | 0 | 901 | 1063 | None | | 0 | No | | | | | | 0 | No | 0 | | 0 | | | 1 | fb49312a-e4e3-11ee-a432-0242ac110002 | mysql.slave_master_info | 0 | NULL | Replica has read all relay log; waiting for more updates | 10 | | | | | | | | 0 | | | | | 0 | | #+----------------------------------+-------------+-------------+-------------+---------------+-------------------+---------------------+-------------------------------+---------------+-----------------------+------------------+-------------------+-----------------+---------------------+--------------------+------------------------+-------------------------+-----------------------------+------------+------------+--------------+---------------------+-----------------+-----------------+----------------+---------------+--------------------+--------------------+--------------------+-----------------+-------------------+----------------+-----------------------+-------------------------------+---------------+---------------+----------------+----------------+-----------------------------+------------------+--------------------------------------+-------------------------+-----------+---------------------+----------------------------------------------------------+--------------------+-------------+-------------------------+--------------------------+----------------+--------------------+--------------------+-------------------+---------------+----------------------+--------------+--------------------+------------------------+-----------------------+-------------------+ Last_Error 메시지가 없고, Slave_SQL_Running_State에 \u0026lsquo;Replica has read all relay log; waiting for more updates\u0026rsquo; 라고 되어있으면 성공적으로 연결된 상태입니다.\nMaster DB에서 해당 DB에 Table을 생성하고, 컬럼을 추가해보겠습니다.\nContainer 내부로 접근\ndocker exec -it mysql_master /bin/bash MySQL master 계정에 접속\nmysql -u master_user -p # 1234 입력 테이블 및 컬럼 추가\n# master shell mysql\u0026gt; USE target_db; mysql\u0026gt; CREATE TABLE test(id int, name varchar(10)); mysql\u0026gt; INSERT INTO test(id, name) VALUES(1, \u0026#39;leaf\u0026#39;); Slave DB에서 해당 변경사항을 인식하는지 확인해보겠습니다.\nContainer 내부로 접근\ndocker exec -it mysql_slave /bin/bash MySQL slave 계정에 접속\nmysql -u slave_user -p # 1234 입력 컬럼 확인\n# slave shell mysql\u0026gt; USE target_db; mysql\u0026gt; SELECT * FROM test; # +------+------+ # | id | name | # +------+------+ # | 1 | leaf | # +------+------+ # 1 row in set (0.00 sec) # 위와 같이 테이블 및 컬럼이 정상 조회되면 성공입니다. 결론 지금까지 Mysql과 Docker로 Replication을 구현하여 DB를 이중화 해보았습니다.\n다음 시간에는 Master, Slave DB를 SpringBoot와 Datasource로 연동하여 Application 내부에서 CRUD를 수행해 보겠습니다.\nReferences URL 게시일자 방문일자 작성자 https://dev.mysql.com/doc/refman/8.0/en/replication.html - 2024.03.13. MySQL https://hub.docker.com/_/mysql - 2024.03.13. MySQL Docker는 가상화된 컨테이너를 생성 및 관리해주는 프로그램입니다. Docker가 설치되지 않았다면, Docker 홈페이지를 통해 Docker Desktop을 설치 후 진행해주세요.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n전체 환경변수는 Docker 문서에서 확인하실 수 있습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nMySQL문법과 동일하게 값(컬럼) 사이에는 쉼표를 적고, 정수형(Integer)은 따옴표를 적지 않으며 문자형(varchar)은 따옴표를 명시해야 MySQL이 해당 구문을 인식합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/cqrs/2/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e이전 포스팅 참조 :\n\u003ca href=\"https://leaf-nam.github.io/posts/240310_mysql_springboot_cqrs_%ED%8C%A8%ED%84%B4_%EA%B5%AC%ED%98%84%EC%9D%84_%EC%9C%84%ED%95%9C_db_%EC%9D%B4%EC%A4%91%ED%99%94/240313_mysql_replication_database_%EA%B5%AC%ED%98%84/\"\u003eDB 이중화 및 CQRS 패턴의 중요성\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003e실습환경\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eDocker : v25.0.3\u003c/li\u003e\n\u003cli\u003eMySQL : v8.3.0\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e시작에 앞서 간단히 Replication의 원리를 확인해보겠습니다.\u003c/p\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"replication.jpg\"\n         alt=\"Slave DB는 Master DB의 로그파일을 참조하여 변경사항을 업데이트 합니다.\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003eSlave DB는 Master DB의 로그파일을 참조하여 변경사항을 업데이트 합니다.\u003c/p\u003e\n        \u003c/figcaption\u003e\n\u003c/figure\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eMaster DB\u003c/strong\u003e는 데이터 변경사항을 \u003cstrong\u003eBinary log 파일\u003c/strong\u003e에 저장합니다.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSlave DB\u003c/strong\u003e는 Binary log 파일의 변경사항을 감시하다가, \u003cstrong\u003e변경이 발생하면 해당 로그를 확인\u003c/strong\u003e합니다.\u003c/li\u003e\n\u003cli\u003e변경사항을 Relay log파일에 적용합니다.\u003c/li\u003e\n\u003cli\u003eSQL Thread는 Relay log파일의 변경사항을 감시하다가, \u003cstrong\u003e변경이 발생하면 DB에 반영\u003c/strong\u003e합니다.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e그럼 이제 본격적으로 MySQL Master DB와 Slave DB를 Docker 이미지로 생성하고, Replication 설정을 통해 Master DB의 변경사항을 Slave DB로 복제하는 실습을 진행하겠습니다.\u003c/p\u003e","title":"[MySQL]Replication Database 구현"},{"content":"도입 우리가 일상에서 매일 접속하는 인터넷은 전세계의 수많은 사용자가 사용하는 만큼 항상 엄청난 트래픽이 발생합니다. 이러한 트래픽의 가장 중심에는 데이터베이스가 있습니다. 결국 사용자는 데이터베이스를 통해 다양한 정보를 획득합니다.\n이러한 데이터베이스의 성능을 늘리기 위해 개발자들은 다양한 시도를 합니다. 그중에서도 이번에는 데이터베이스 이중화와 CQRS 패턴에 대해 알아보고, 이를 실제로 가상서버 및 코드로 구현해보려 합니다.\nDB 이중화 기법 데이터베이스 이중화에는 다양한 장점이 있습니다. 대표적으로\n장애 대응(Failover) : DB가 1개라면, 해당 DB가 다양한 원인1에 의해 장애가 발생했을 시 서비스 운영이 불가능합니다. DB를 이중화한다면 이러한 장애에 신속히 대응할 수 있으며, 차후 손실된 데이터를 복구하는 것도 가능합니다. 부하 분산(Load Balancing) : 하나의 DB가 받던 부하를 여러 DB로 분산한다면, 병목현상으로 인한 장애를 예방할 수 있을 뿐 아니라, 요청에 대한 신속한 응답을 기대할 수 있습니다. 또한, DB 이중화 기법을 통해 시스템의 두가지 성질을 달성할 수 있습니다.\nHA(High Availability; 고가용성) 시스템을 최대한 중단 시간 없이 운영할 수 있는 특성을 말합니다. HA를 판단하는 기준은 Availability가 있으며, 가용성에 따른 서비스 제공시간2을 계산할 수 있습니다. Availability Downtime per year 90% 36.5일 99.99% 52.6분 99.999% 5분 26초 \u0026hellip; \u0026hellip; 99.9999999% 31.56 밀리초 DR(Disaster Recovery; 재해복구) 자연재해 혹은 인위적 사고로부터 핵심 시스템을 복구하는 절차 혹은 지속성을 말합니다. 이러한 DR의 목표와 실제는 RPO, RTO, RTA로 나뉘게 됩니다. RTO(Recovery Time Objective) : 비즈니스의 지속성을 위한 복구 목표시간입니다. RTA(Recovery Time Actual) : 실제 복구하는데 걸리는 시간입니다. RPO(Recovery Point Objective) : 사고가 발생하면서 실제로 손실이 발생하는 시간입니다. RTO, RPO에 대한 설명\n그럼 이러한 이중화 기법에는 어떠한 것이 있는지 알아보겠습니다.\n사실 인프라나 하드웨어적 측면에서 훨씬 다양한 기법들이 있고, 오늘은 단일 소프트웨어(취준생) 수준의 이중화 기법을 알아보겠습니다.\nReplication 쌍둥이를 만듭니다.\n동일한 데이터를 가지는 복제 데이터베이스를 생성합니다. 마스터(master)와 슬레이브(slave)로 구성되어 슬레이브가 마스터의 데이터를 복제하는 방식으로 동작합니다. Clustering 동일한 작업을 하는 서버를 클러스터로 분리해 가용성을 확보할 수 있습니다.\n동일한 작업을 여러 노드를 통해 수행하는 서비스 방식을 말하며, 동일한 업무를 처리해야 하기에 동일한 정보를 가지고 있어야 합니다. 작업을 분산하거나 동기화하는 과정에서 오버헤드가 발생할 수 있으며, 이러한 작업 스케줄링 및 동기화가 클러스터링에서 해결해야 할 과제입니다. Shading 샤딩은 pk를 분배하는 전략이 중요합니다.\n하나의 작업을 여러 노드에 분산해서 작업하는 서비스 방식을 말합니다. 데이터베이스에서는 데이터를 분산해서 저장하는 방식을 말하며, 이렇게 분산 저장된 데이터를 적절하게 인덱싱하는 것이 중요합니다. Clustering과 헷갈릴 수 있지만, Clustering은 동일한 DB를 여러개 만드는 반면, Shading은 하나의 DB를 쪼개는 방식입니다. Shading은 수평 분할로, 한 테이블의 행을 여러개의 DB로 분리한다고 생각하며 될 것 같습니다.\nCQRS 패턴 지금까지 DB 이중화의 목적과 종류에 대해 알아보았는데요, 그럼 CQRS패턴은 무엇일까요?\n정의 CQRS3패턴은 Command(Create, Update, Delete)와 Query(Read)의 책임을 분리하라는 원칙을 구현한 패턴입니다.\n목적 Command와 Query를 분리하는 이유는 다음과 같습니다.\n읽기와 쓰기의 빈도 차이 : 어플리케이션 로직을 생각해보면, 통상 쓰기보다 읽기가 훨씬 많이 발생합니다. Command와 Query를 분리한다면, Query를 수행하는 DB를 Command용 DB보다 많이 생성함으로써 이러한 비율을 맞춰 서비스를 최적화할 수 있습니다. 확장성 : 위에서 설명한 비율을 고민하며 독립적으로 DB를 확장할 수 있습니다. 트랜잭션 : Command는 데이터를 변경하기에 트랜잭션이 발생하며, Query는 통상 읽기 전용으로 이루어집니다. 트랜잭션을 처리하는 로직이 분리되면 데이터 변경을 위한 로직을 관리하기 쉬워집니다. 보안 : 쓰기 권한을 가진 요청을 통해서만 데이터가 변경되는지 확인하기가 용이해집니다. 구현 이러한 CQRS패턴은 어플리케이션 계층에서도 구현할 수 있지만, 데이터베이스를 분리하여 두 작업을 원천 분리할 수 있습니다.\n그 중 대표적인 방법이 위에서 설명했던 Replication을 응용한 방식입니다. Replication은 위에서 설명한 것처럼 Master DB에서 변경된 데이터를 Slave DB에서 그대로 복제하는 특징을 가지고 있습니다.\n따라서 Command는 Master DB에서 수행하고, Query는 Slave DB에서 수행하는 방식으로 Command와 Query를 분리할 수 있습니다.\n결론 다양한 이점을 가진 DB 이중화의 다양한 방식을 알아보았고, 이를 통해 CQRS 패턴을 구현할 수 있습니다. 다음 시간부터는 MySQL에서 제공하는 Replication기술을 활용해 SpringBoot Server에서 CQRS패턴을 활용하는 예제를 직접 구현해보겠습니다.\n이제야 좀 기술블로그 같은 주제를 다루는 것 같네요. 앞으로도 프로젝트나 다양한 이슈로부터 얻는 노하우와 기술들을 정리해 나가겠습니다.\nReferences URL 게시일자 방문일자 작성자 https://travislife.tistory.com/29 2020.08.03. 2024.03.12. 트레비스의 IT라이프 https://if.kakao.com/ 2022.12.09. 2024.03.12. if-kakao-2022 https://en.wikipedia.org/wiki/Disaster_recovery 2024.03.05. 2024.03.12. wikipedia https://techblog.woowahan.com/2687/ 2020.07.06. 2024.03.12. 송재욱, 전병두 https://learn.microsoft.com/ko-kr/azure/architecture/patterns/cqrs 미등록 2024.03.12. Azure Storage 개발자 혹은 DB관리자의 실수, 정전, 천재지변, 해커의 공격 등 정말 다양한 원인이 있으며 이러한 모든 원인을 회피하는 것은 불가능합니다. 실제로 2022년 10월 15일 발생한 카카오 데이터센터 화재로 1개월 이상 복구작업을 하기도 했습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n고가용성 시스템을 추구하는 Erlang 은 nine nines(99.9999999%)를 달성했다고 하네요.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nCommand and Query Responsibility Segregation; 명령과 쿼리 분리의 원칙입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/cqrs/1/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003cp\u003e우리가 일상에서 매일 접속하는 인터넷은 전세계의 수많은 사용자가 사용하는 만큼 항상 엄청난 트래픽이 발생합니다. 이러한 트래픽의 가장 중심에는 데이터베이스가 있습니다. 결국 사용자는 데이터베이스를 통해 다양한 정보를 획득합니다.\u003c/p\u003e\n\u003cp\u003e이러한 데이터베이스의 성능을 늘리기 위해 개발자들은 다양한 시도를 합니다. 그중에서도 이번에는 데이터베이스 이중화와 CQRS 패턴에 대해 알아보고, 이를 실제로 가상서버 및 코드로 구현해보려 합니다.\u003c/p\u003e\n\u003ch2 id=\"db-이중화-기법\"\u003eDB 이중화 기법\u003c/h2\u003e\n\u003cp\u003e데이터베이스 이중화에는 다양한 장점이 있습니다. 대표적으로\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003e장애 대응(Failover)\u003c/strong\u003e : DB가 1개라면, 해당 DB가 다양한 원인\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e에 의해 장애가 발생했을 시 서비스 운영이 불가능합니다. DB를 이중화한다면 이러한 장애에 신속히 대응할 수 있으며, 차후 손실된 데이터를 복구하는 것도 가능합니다.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e부하 분산(Load Balancing)\u003c/strong\u003e : 하나의 DB가 받던 부하를 여러 DB로 분산한다면, 병목현상으로 인한 장애를 예방할 수 있을 뿐 아니라, 요청에 대한 신속한 응답을 기대할 수 있습니다.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e또한, DB 이중화 기법을 통해 시스템의 두가지 성질을 달성할 수 있습니다.\u003c/p\u003e","title":"DB 이중화 및 CQRS 패턴의 중요성"},{"content":"도입 이전 포스팅 참조 : 개발 블로그의 종류와 선택 \u0026gt; SSG에 대하여 \u0026gt; HUGO 기본 설치 및 사용법 \u0026gt; Git 연동과 정적 페이지 배포\n저번 포스팅까지 블로그를 만들어봤는데요, 댓글이 없으니 뭔가 허전한 느낌입니다. 원래 블로그는 다른 사람과의 소통을 위함이니까요.\n이번 시간에는 블로그 댓글을 생성하는 라이브러리들을 비교해보고, 그중 utterances를 직접 블로그에 적용해 보겠습니다.\n저도 처음에는 어떻게 댓글을 구현할까 하다가, 직접 기능을 만들어보려고도 생각했었습니다. 하지만, 그럼 로그인 기능이나 댓글을 저장하는 DB도 만들어야 하는데 블로그 만드는 것보다 더 많은 노력이 필요할 것 같았습니다. 여기저기 찾아보니 댓글을 구현해주는 다양한 라이브러리가 있어 다행히 쉽게 적용할 수 있었습니다.\n블로그 댓글 구현방법 비교 블로그 종류가 다양한 만큼, 블로그 댓글을 구현하는 방법도 다양합니다.\n그럼 댓글 구현방법에 대한 각각의 장단점을 알아보겠습니다.\nDisqus Disqus는 댓글을 쉽게 구현하도록 도와주는 프레임워크입니다. Disqus 내부에 서버가 있어 댓글을 작성하면 해당 서버에 댓글을 작성하고 불러오는 방식으로 구현됩니다.\n또한, Hugo에서 공식적으로 지원하는 댓글 기능이기도 합니다.1\nHugo 문서에 나와있는 것처럼 config에 Disqus 이름(shortname)을 작성하면 쉽게 연동이 가능합니다.\nservices: disqus: shortname: your-disqus-shortname 장점\nHugo에서 기본으로 지원하는 기능인 만큼 연동성이 좋습니다. 다른 사이트에 남긴 댓글들을 모아서 한번에 조회가 가능하며, 댓글 추이나 통계에 대한 분석도 제공합니다.2 사용자가 많고 특히 해외에서는 해당 서비스 사용자가 많은 것 같습니다. 단점\n댓글을 남기기 위해서는 사용자가 해당 서비스에 가입해야 합니다. 현재까지 유효한 정책인지는 모르겠지만, 과거에는 2년 이상 사용 시 과금을 했다고 합니다. 서비스 기업이다 보니 차후에 요금정책이 변경될 가능성도 있을 것 같습니다. 마찬가지로 해당 서비스가 종료되면 댓글도 사라질 위험이 있습니다. 저는 다른것보다 사용자가 새로운 회원가입을 해야 하는 부분에 불편함이 있을 것 같아 다른 옵션을 찾아보게 되었습니다.\nCommento Commento는 가벼움과 프라이버시에 초점을 맞춘 댓글 플랫폼입니다. Commento 또한 해외에서 많이 사용하는 옵션 중 하나입니다. Commento와 Disqus 사이에 고민하는 포스팅을 많이 볼 수 있었습니다.3\n장점\n프라이버시를 보장하기 위한 익명 댓글 기능이 있습니다. 클라우드 서버나 사용자 DB에 연결되어 댓글이 저장되기 때문에 유실 걱정이 없습니다. 제 기준에서는 UI나 디자인이 제일 깔끔한 것 같습니다. Comment Demo 페이지, 이미지를 누르면 해당 데모 페이지로 이동합니다.\n단점\n클라우드 서비스 사용시 요금 정책이 있습니다. 현재 날짜 기준 월 10$, 연 99$입니다. 클라우드 서비스나 DB에 등록하는 과정이 조금 복잡합니다. 도메인별로 클라우드 서버를 설정하기 때문에 댓글 공유는 되지 않습니다. 요금은 클라우드 서비스를 사용하기 위해 어쩔 수 없이 내야하는 부분인 것 같고, 직접 DB를 구현하면 무료라고 합니다. 저는 차후 블로그 유저가 많아지거나, utterances가 불편한 점이 생기면 commento를 도입해볼 생각도 있습니다.\nUtterances 제 블로그에 도입해서 사용하고 있는 Utterances 입니다.\n작동 원리는 깃허브 issue에 댓글을 남기고 트래킹하는 방식으로 구현이 되어있습니다.\n정말 똑똑한 사람들이 많습니다. 여담이지만 utteranc.es라는 url 정말 잘 만든 것 같습니다.\n코드 한 줄만 추가하면 바로 사용할 수 있어 가볍고 깃허브 아이디만 있으면 쉽게 댓글을 달 수 있습니다.\n장점\n방문자도 깃허브 계정만 있으면 댓글을 남기는 것이 가능합니다. script 기반으로 설치 및 구현이 쉽습니다. 깃허브 이슈와 연동되기 때문에 별도의 저장공간이 불필요합니다. 단점\n이슈번호와 연결이 잘못되면 다른 글에 남긴 댓글이 보이는 등 이상현상이 발생한다고 하네요.(아직 댓글이 없어서 직접 확인은 못했습니다.) 깃허브가 없는 일반 사용자는 댓글을 남길 수 없습니다. css를 설정하지 못하고 정해진 테마(9개) 중 선택해야 합니다.(css 테마가 필요하면 직접 구현해서 오픈소스에 기여해달라고 하네요..) 어차피 깃허브 블로그인 이상 제 블로그의 생명주기는 깃허브와 같아졌으므로, utterances가 가장 합리적인 선택으로 보였습니다. 사용해보고 문제가 있다면 다른 댓글 플랫폼도 사용해보고 싶네요.\nUtterances 설치방법 설치를 시작하기에 앞서 Utterances에서 댓글이 구현되는 원리를 간단히 알아보자면 다음과 같습니다.\nUtterances Bot이 깃허브 앱에 등록이 되고, 사용자가 작성한 스크립트로 페이지에 불러와집니다. 해당 스트립트에 표기된 깃허브 레포지토리에서 현재 페이지에 맞는 댓글을 불러옵니다. 댓글 작성버튼을 누르면 레포지토리에 현재 페이지의 정보와 함께 댓글을 저장합니다. 심플하면서도 깃허브 페이지에 아주 효과적인 전략인 것 같습니다. 그럼 이제 본격적으로 Utterances를 설치해보겠습니다.\nUtterances 기본설정 먼저 Utterances 홈페이지에 들어가서 설정을 해야 합니다. 해당 페이지에 들어가시면 configuration탭이 있는데, 딱 4가지 설정만 하시면 됩니다.\n레포지토리 입력(필수)\n먼저 해당 댓글을 사용할 레포지토리를 입력합니다. 본인의 블로그 레포지토리가 될 수도 있고, 별도의 댓글만 저장하기 위한 전용 레포지토리를 생성하셔도 좋습니다.\n저는 정적 페이지를 위한 레포지토리를 분리해두었기 때문에 해당 레포지토리를 그대로 사용했습니다.\n[1] 레포지토리를 입력합니다.\n이슈 매핑방법(필수)\nUtterances Bot이 깃허브 이슈에 댓글을 저장하고, 불러오기 위해서는 해당 이슈가 어떤 포스트에 해당하는지를 알아야 합니다.\n이를 위해 깃허브 이슈와 댓글을 어떻게 매핑할지 6가지 전략을 선택할 수 있습니다.\n실제로 Utterances로 댓글을 남기면 해당 전략에 따라 레포지토리 이슈에 저장이 되므로 본인이 관리하기 편한 방식을 선택하시는게 좋습니다.\npathname : 현재 페이지의 url 중 경로명을 기반으로 댓글을 저장 및 탐색합니다. 처음에 해당 방식을 사용했는데, 경로명에 한글이 포함되어있으면 utf-8로 인코딩 된 경로가 나오게 되어 나중에 알아보기가 힘들어 title로 변경했습니다.\nURL : url을 기반으로 댓글을 저장 및 탐색합니다. pathname과 다르게 전체 url경로를 모두 사용합니다.\ntitle : 포스팅 제목을 기반으로 댓글을 저장 및 탐색합니다.\nog:title : Open Graph4의 메타 필드를 참조해서 댓글을 저장 및 탐색합니다.\nissue number : 이슈번호를 특정할 수 있습니다. 이슈가 자동생성되지 않아 미리 이슈를 만들어 두고 직접 연결해야 합니다.\nspecific term : 이슈를 탐색할 때 설정한 특정 키워드를 포함하는지 확인합니다. 5번과 유사하지만 처음 댓글을 달면 해당 키워드를 포함하여 자동으로 이슈를 생성해줍니다.\n[2] 매핑전략을 선택합니다.\n이슈 라벨 설정(선택)\n필수는 아니지만 이슈 앞에 부착할 라벨을 설정할 수 있습니다. 만약 이슈와 댓글을 동시에 사용하는 페이지가 있다면 이를 구분할 때 편리할 듯 합니다.\n💬 와 같은 이모지도 넣을 수 있다고 하네요.\n[3] 이슈 라벨을 선택합니다.\n테마 설정(필수) 마지막으로 테마(댓글 템플릿)를 설정해야 합니다. utteranc.es 페이지에서 테마를 선택하면 자동으로 해당 테마에 맞게 페이지가 변경되니, 해당 페이지를 보고 본인의 블로그와 맞는 페이지를 선택하시면 될 것 같습니다.\n[4] 이슈 테마를 선택합니다.\n이제 Utterances 설정이 끝났습니다. 모든 설정을 마치면 다음과 같이 스크립트를 복사할 수 있습니다.\n스크립트 태그를 복사합니다.\nHugo 템플릿 설정 모든 설정이 끝났다면, 해당 설정정보를 블로그로 불러와야 합니다. 저는 Hugo를 사용하고 있기 때문에 Hugo에서 설정하는 방법을 설명드리겠습니다.\n다른 정적 페이지를 사용하신다면 해당 페이지의 html 중 댓글이 필요한 위치에 script를 불러오시면 됩니다.\nHugo에서 댓글을 적용하시려면 우선 Hugo가 어떻게 페이지를 렌더링하는지 이해해야 합니다.5 위 주석을 참고하시면 Hugo의 페이지 렌더링 순서는 다음과 같습니다.\n사용자가 설정한 특정 레이아웃 사용자가 설정한 기본 레이아웃 테마에 설정된 특정 레이아웃 테마에 설정된 기본 레이아웃 저는 PaperMod라는 테마를 사용하고 있습니다. 따라서 댓글을 화면에 표시하기 위해서는 제가 직접 레이아웃 폴더를 생성해서 PaperMod의 Comment 레이아웃(comments.html)을 Override6해주어야 합니다.\n따라서 다음과 같이 새로운 폴더와 comments.html 파일을 생성합니다.\n... ├── README.md ├── archetypes ├── config │ └── _default │ └── hugo.yaml ├── content ├── layouts # layouts 디렉터리를 생성합니다. │ └── partials # partials 디렉터리를 생성합니다. │ └── comments.html # comments.html을 생성하고, 내부에 아래 주석 내용을 붙여넣기합니다. ├── public ├── resources └── themes └── PaperMod # [붙여넣기] comments.html : PaperMod에서 제공하는 기본 템플릿입니다. {{- /* Comments area start */ -}} {{- /* to add comments read =\u0026gt; https://gohugo.io/content-management/comments/ */ -}} {{- /* Comments area end */ -}} 위에서 생성한 comments.html 파일 내부에 아까 생성했던 utterances script 태그를 복사하면 완료입니다.\n{{- /* Comments area start */ -}} # utterances script를 여기에 붙여넣습니다. {{- /* Comments area end */ -}} 다른 Hugo 테마를 사용하신다면, 테마 폴더 내부의 layouts를 찾아보시면 Comments가 구현되어 있으실텐데 해당 파일을 가져오시면 됩니다.\n테마에 Comments가 구현되어 있지 않다면 Hugo 사이트를 참고해서 직접 레이아웃을 구현하셔야 합니다.\nGithub App 등록 Utterances가 레포지토리의 이슈에 접근하려면 깃허브 앱으로 등록해야 합니다. Utterances App 페이지에서 설치가 가능합니다.\n참고로 아래 사진과 같이 댓글을 저장할 레포지토리만 등록하시면 잘 작동합니다.\n필요한 레포지토리만 등록하시면 됩니다.\nCanonical Path 설정 처음 Utterances를 적용하시면 아마 다음 페이지와 같이 로그인하라는 창이 나오실겁니다.\n로그인이 필요합니다.\n로그인 과정은 다음과 같습니다.\n로그인 버튼을 클릭하면 현재 페이지에서 Github의 OAuth 서비스 페이지로 이동합니다. 인증이 완료되면 Github에서 인증 토큰을 발급합니다. Utterances Bot이 해당 토큰을 통해 아까 설정한 레포지토리로 접근합니다. 레포지토리에 접근이 가능해지면 현재 페이지와 레포지토리를 연결한 후, 현재 페이지로 복귀합니다. 이 과정에서 Utterances Bot은 Canonical Path7를 통해 현재 페이지를 인식하게 됩니다.\n그러나 Canonical Path가 제대로 설정되어 있지 않다면, Utterance Bot이 해당 페이지를 인식하지 못합니다. 따라서 Canonical Path를 올바르게 설정해서 Utterance Bot이 다시 해당 페이지로 찾아올 수 있도록 합니다.\n이러한 Canonical Path는 head에 링크 태그로 생성됩니다.\nCanonical Path를 설정하는 방법은 테마마다 달라서 저는 PaperMod 테마 기준으로 설명드리겠습니다.\nPaperMod 테마의 layouts \u0026gt; partials \u0026gt; head.html 템플릿을 확인해보면 다음과 같이 canonical path가 생성됨을 알 수 있습니다.\n// CanonicalURL이 있으면 그걸로 생성, 아니면 Permalink로 생성 \u0026lt;link rel=\u0026#34;canonical\u0026#34; href=\u0026#34;{{ if .Params.canonicalURL -}} {{ trim .Params.canonicalURL \u0026#34; \u0026#34; }} {{- else -}} {{ .Permalink }} {{- end }}\u0026#34;\u0026gt; 따라서 hugo 설정파일 내부에 Params.canonicalURL 설정이 있다면 해당 설정을 통해 canonicalURL을 생성하고, 그렇지 않으면 Permalink8를 따라간다는 것을 알 수 있습니다.\nPermalink는 설정파일의 BaseURL에 현재 콘텐츠의 경로를 붙여서 생성됩니다.\n{{ with resources.Get \u0026#34;images/a.jpg\u0026#34; }} {{ .Permalink }} → https://example.org/images/a.jpg {{ end }} 콘텐츠 경로는 hugo가 빌드하는 과정에서 잘 세팅이 되기 때문에 BaseURL만 해당 블로그의 기본주소로 잘 설정하면 정확한 canonicalURL을 얻을 수 있습니다.\n따라서 기본 설정파일(hugo.yaml or hugo.toml or config.yaml)의 BaseURL을 현재 블로그 페이지의 기본주소로 잘 설정해주어야 합니다.\n# hugo.toml 혹은 hugo.yaml baseURL: \u0026#34;https://{github계정명}.github.io/\u0026#34; # yaml 형식일 경우 baseURL = \u0026#39;https://{github계정명}.github.io/\u0026#39; # toml 형식일 경우 여기까지 설정하셨으면 Github OAuth를 통해 로그인이 잘 되실겁니다.\n결론 오늘은 깃허브 블로그 댓글을 구현하는 다양한 방법에 대해 알아보고, 그 중 Utterances를 통해 직접 구현해보았습니다. 다들 본인만의 블로그를 만드시는데 조금이나마 도움이 되었으면 좋겠습니다.\n앞으로도 Hugo 블로그에 관련한 다양한 세팅에 대해 다뤄볼 예정이니 필요하거나 궁금한 부분이 있으시다면 알려주세요.\n블로그 글이 너무 길어져버렸네요. 앞으로는 배경 설명과 직접 구현하는 포스팅을 잘 분리해야 할 것 같습니다..\nReferences URL 게시일자 방문일자 작성자 https://gohugo.io/ 미등록 2024.03.07. Hugo Authors https://disqus.com/ 미등록 2024.03.07. 2024 Disqus https://docs.commento.io/ 미등록 2024.03.07. Commento https://utteranc.es/ 미등록 2024.03.07. Utterances https://stackshare.io/stackups/commento-vs-disqus 미등록 2024.03.07. Stackshare Hugo의 문서를 보면 공식적으로 Disqus를 지원하고 있습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nDisqus의 다양한 장점은 해당 페이지에서 확인할 수 있습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nCommento vs Disqus\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nOpen Graph는 검색엔진이나 SNS 등에서 해당 페이지에 접속하기 전에 대략적인 페이지 구성 등을 확인할 수 있도록 해주는 프로토콜입니다. \u0026#160;\u0026#x21a9;\u0026#xfe0e;\n어떤 템플릿이 렌더링될까?\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n자바를 사용해보신 분들은 잘 아시겠지만 덮어쓰기한다는 뜻입니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n한 페이지를 이동할 수 있는 여러 개의 URL이 있을때, Bot들이 참고해서 올 수 있는 공식 경로를 뜻합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n자세한 설명은 Hugo 공식 홈페이지를 참조하시기 바랍니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/gitblog/5/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e이전 포스팅 참조 :\n\u003ca href=\"https://leaf-nam.github.io/posts/blog/%EA%B0%9C%EB%B0%9C_%EB%B8%94%EB%A1%9C%EA%B7%B8%EC%9D%98_%EC%A2%85%EB%A5%98%EC%99%80_%EC%84%A0%ED%83%9D_240229/\"\u003e개발 블로그의 종류와 선택\u003c/a\u003e \u0026gt; \u003ca href=\"https://leaf-nam.github.io/posts/blog/ssg%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC_240302/\"\u003eSSG에 대하여\u003c/a\u003e \u0026gt; \u003ca href=\"https://leaf-nam.github.io/posts/240229_%EC%A2%8C%EC%B6%A9%EC%9A%B0%EB%8F%8C_%EA%B9%83%ED%97%88%EB%B8%8C_%EB%B8%94%EB%A1%9C%EA%B7%B8_%EC%83%9D%EC%84%B1%EA%B8%B0/240303_hugo-%EA%B8%B0%EB%B3%B8-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%82%AC%EC%9A%A9%EB%B2%95/\"\u003eHUGO 기본 설치 및 사용법\u003c/a\u003e \u0026gt; \u003ca href=\"https://leaf-nam.github.io/posts/240229_%EC%A2%8C%EC%B6%A9%EC%9A%B0%EB%8F%8C_%EA%B9%83%ED%97%88%EB%B8%8C_%EB%B8%94%EB%A1%9C%EA%B7%B8_%EC%83%9D%EC%84%B1%EA%B8%B0/240304_git_%EC%97%B0%EB%8F%99%EA%B3%BC_%EC%A0%95%EC%A0%81_%ED%8E%98%EC%9D%B4%EC%A7%80_%EB%B0%B0%ED%8F%AC/\"\u003eGit 연동과 정적 페이지 배포\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e저번 포스팅까지 블로그를 만들어봤는데요, 댓글이 없으니 뭔가 허전한 느낌입니다. 원래 블로그는 다른 사람과의 소통을 위함이니까요.\u003c/p\u003e\n\u003cp\u003e이번 시간에는 블로그 댓글을 생성하는 라이브러리들을 비교해보고, 그중 utterances를 직접 블로그에 적용해 보겠습니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e저도 처음에는 어떻게 댓글을 구현할까 하다가, 직접 기능을 만들어보려고도 생각했었습니다. 하지만, 그럼 로그인 기능이나 댓글을 저장하는 DB도 만들어야 하는데 블로그 만드는 것보다 더 많은 노력이 필요할 것 같았습니다. 여기저기 찾아보니 댓글을 구현해주는 다양한 라이브러리가 있어 다행히 쉽게 적용할 수 있었습니다.\u003c/p\u003e","title":"깃허브 블로그 댓글 구현 : Utterances 도입기"},{"content":"도입 이전 포스팅 참조 : 개발 블로그의 종류와 선택 \u0026gt; SSG에 대하여 \u0026gt; HUGO 기본 설치 및 사용법\n지난 시간에 Hugo설치 및 간단한 사용법을 알아보았습니다. 이번에는 깃과 연동하여 버전관리 및 깃허브 페이지에 블로그를 띄워보겠습니다.\n이 포스팅은 Git이 설치되어 있는 것을 전제로 합니다. 혹시 Git이 설치되지 않은 분들은 설치 후 진행해주세요.\n블로그 디렉터리와 Git 연결하기 먼저 지난 시간에 생성한 디렉터리를 Git에 등록해야 합니다. 대부분 Git에 익숙하시겠지만, 아직 어려우신 분들을 위해 자세히 설명드리겠습니다.\n블로그 레포지토리 생성 Git에 특정 디렉터리를 올려 버전관리를 하기 위해서는 레포지토리를 생성해야 합니다. 1.깃허브 메인 페이지에서 레포지토리 생성버튼을 클릭합니다.\n2.레포지토리 이름을 작성하고 public을 선택한 후, Create repository 버튼을 클릭합니다.\n이제 깃허브에 블로그 레포지토리가 생성되었습니다.\n블로그 레포지토리 연결 새로 생성한 레포지토리에 저번에 생성한 블로그 디렉터리를 연결합니다. 우선 git bash 혹은 zsh등의 쉘에서 \u0026lsquo;cd {디렉터리 경로}\u0026rsquo; 명령어를 통해 블로그의 최상위 디렉터리로 이동합니다. git bash 혹은 zsh등의 쉘에서 블로그의 최상위 디렉터리로 이동합니다.\n다음 명령어를 통해 블로그 원격 레포지토리에 커밋합니다. # 새로운 로컬 레포지토리 생성 $ git init # 원격 레포지토리 연결 : 원격 레포지토리 주소는 새로 생성된 레포지토리에 있습니다.(하단 이미지 참조) $ git remote add origin {원격 레포지토리 주소} # 로컬 브랜치 이름 변경(깃허브는 최초 브랜치가 main이고 깃은 master여서 이름을 main으로 변경해야 합니다.) $ git branch -m main # 로컬 레포지토리(블로그 디렉터리) → 원격 레포지토리로 커밋 $ git add . $ git commit -m \u0026#34;first commit\u0026#34; $ git push -u origin main 새로 생성된 레포지토리에서 위 버튼으로 원격 레포지토리 주소를 복사할 수 있습니다.\n이제 블로그 디렉터리와 레포지토리가 연결되어 변경사항을 커밋하면 깃허브에 저장할 수 있습니다.\n배포용 레포지토리 생성하기 블로그 레포지토리를 성공적으로 등록하였으니, 이제 배포용 레포지토리를 생성해야 합니다.\n이렇게 두 개의 레포지토리를 두는 이유는 다음과 같습니다.\nHugo로 빌드한 페이지를 별도의 레포지토리에서 관리하여 블로그 디렉터리와 소스코드 분리 깃허브 계정의 정적페이지 주소와 동일한 이름의 레포지토리를 사용하여 메인 페이지 경로 단일화1 그럼 배포용 레포지토리를 생성해서 연결해보겠습니다.\n배포용 레포지토리 생성 본인의 깃허브 페이지 주소와 동일한 레포지토리를 생성합니다. 위의 블로그 레포지토리 생성과 동일한 절차로 생성하되, 이름을 아래와 같이 설정하시면 됩니다. # 본인 깃허브 페이지 주소 : https://{본인 깃허브 계정명}.github.io {본인 깃허브 계정명}.github.io 제가 현재 Github Pages에 등록해 사용중인 Repository입니다.\n배포용 레포지토리가 생성되었습니다.\n블로그 레포지토리와 연결 새로 생성한 배포용 레포지토리는 블로그 레포지토리 내부의 public 경로에 submodule로 등록해야 합니다. 이렇게 등록 후 블로그 레포지토리에서 새로 페이지를 작성하면, 자동으로 배포용 레포지토리 내부 파일의 변경사항으로 잡히기 때문에 편하게 관리할 수 있습니다. submodule로 등록 및 확인하는 명령어는 다음과 같습니다. # git submodule add {서브모듈 레포지토리 주소} public $ git submodule add https://github.com/leaf-nam/leaf-nam.github.io.git public # 제대로 등록되었는지 확인해보려면, 아래 명령어를 실행합니다. $ git submodule # +72759664014b7a27ad069a24aa876836605e2d51 public (heads/main) 정상 등록 후 커밋 시 위와 같이 깃허브 경로 내부에 배포용 레포지토리가 submodule로 등록됩니다.\n이제 배포용 레포지토리가 서브모듈로 등록되어 배포할 준비를 마쳤습니다.\n정적 사이트 배포하기 생성된 배포용 레포지토리에 정적파일들을 업로드 후 Github Pages를 사용하여 배포하겠습니다.\n배포용 레포지토리에 정적파일 업로드 git bash를 실행하여 블로그 레포지토리에서 정적파일들을 빌드합니다. # 블로그 레포지토리 메인 경로로 이동합니다. $ cd {블로그 레포지토리 경로} # 정적파일을 빌드합니다. $ hugo #Start building sites … #hugo v0.123.4-21a41003c4633b142ac565c52da22924dc30637a+extended #darwin/arm64 BuildDate=2024-02-26T16:33:05Z VendorInfo=brew # | KO #-------------------+----- # Pages | 67 # Paginator pages | 0 # Non-page files | 26 # Static files | 0 # Processed images | 12 # Aliases | 25 # Cleaned | 0 배포용 레포지토리로 이동 후 해당파일들을 커밋합니다. # 배포용 레포지토리로 이동합니다. $ cd public # 해당 파일들을 커밋합니다. $ git add . $ git commit -m \u0026#34;blog 업로드\u0026#34; $ git push origin main 이제 깃허브에 있는 레포지토리에 저희가 생성한 블로그 정적파일들이 업로드되었습니다.\nGithub Pages 설정 배포용 레포지토리에 들어가서 다음 절차를 통해 Github Pages를 배포합니다. 1.레포지토리의 Settings로 이동합니다.\n2.사이드바의 Pages로 이동하면 현재 Branch의 배포 경로가 None으로 되어 있습니다.\n3.배포경로를 main-/(root)로 변경 후 Save합니다.\n4.상단바의 Actions로 들어가보면 배포가 진행되고 있습니다.\n5.배포가 완료된 후, 다시 Settings로 돌아오면 다음과 같이 배포중이라는 메시지가 보입니다.\n해당페이지로 들어가면 정상적으로 블로그가 배포됨을 확인할 수 있습니다.\n결론 지금까지 블로그 페이지를 깃허브와 연동하고 Git Pages를 사용하여 배포까지 완료했습니다. 깃 사용이 익숙하신 분들이라면 금방 적용하셨을 것이라고 생각합니다.\n직접 EC2와 같은 서버에 빌드해보신 분들이라면 아시겠지만, 별다른 세팅 없이 본인의 페이지를 빌드해서 즉시 웹에 게시할 수 있다는 점이 Git Pages의 강력한 점인 것 같습니다.\n이제 블로그 배포까지 완료했으니, 다음 포스팅에서는 다른 사용자와 소통할 수 있는 댓글 기능을 utterances 라이브러리를 사용해 연결해보겠습니다.\nReferences 배포용 레포지토리의 이름을 깃허브 주소와 일치시키지 않으면, https://{깃허브 이름}.github.io/{레포지토리 이름} 형식으로 메인 페이지 URL이 구성됩니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/gitblog/4/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e이전 포스팅 참조 :\n\u003ca href=\"https://leaf-nam.github.io/posts/blog/%EA%B0%9C%EB%B0%9C_%EB%B8%94%EB%A1%9C%EA%B7%B8%EC%9D%98_%EC%A2%85%EB%A5%98%EC%99%80_%EC%84%A0%ED%83%9D_240229/\"\u003e개발 블로그의 종류와 선택\u003c/a\u003e \u0026gt; \u003ca href=\"https://leaf-nam.github.io/posts/blog/ssg%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC_240302/\"\u003eSSG에 대하여\u003c/a\u003e \u0026gt; \u003ca href=\"https://leaf-nam.github.io/posts/blog/hugo-%EA%B8%B0%EB%B3%B8-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%82%AC%EC%9A%A9%EB%B2%95_240303/\"\u003eHUGO 기본 설치 및 사용법\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e지난 시간에 Hugo설치 및 간단한 사용법을 알아보았습니다. 이번에는 깃과 연동하여 버전관리 및 깃허브 페이지에 블로그를 띄워보겠습니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ca href=\"https://git-scm.com/downloads\"\u003e이 포스팅은 Git이 설치되어 있는 것을 전제로 합니다. 혹시 Git이 설치되지 않은 분들은 설치 후 진행해주세요.\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"블로그-디렉터리와-git-연결하기\"\u003e블로그 디렉터리와 Git 연결하기\u003c/h2\u003e\n\u003cp\u003e먼저 지난 시간에 생성한 디렉터리를 Git에 등록해야 합니다. 대부분 Git에 익숙하시겠지만, 아직 어려우신 분들을 위해 자세히 설명드리겠습니다.\u003c/p\u003e\n\u003ch3 id=\"블로그-레포지토리-생성\"\u003e블로그 레포지토리 생성\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eGit에 특정 디렉터리를 올려 버전관리를 하기 위해서는 레포지토리를 생성해야 합니다.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cfigure\u003e\n    \u003cimg loading=\"lazy\" src=\"repository_create.png\"\n         alt=\"1.깃허브 메인 페이지에서 레포지토리 생성버튼을 클릭합니다.\"/\u003e \u003cfigcaption\u003e\n            \u003cp\u003e1.깃허브 메인 페이지에서 레포지토리 생성버튼을 클릭합니다.\u003c/p\u003e","title":"Git 연동과 정적 페이지 배포"},{"content":"도입 이전 포스팅 참조 : 개발 블로그의 종류와 선택 \u0026gt; SSG에 대하여\n어떤 개발 블로그를 사용할지도 정했고, SSG 엔진도 정했으니 이제 진짜로 블로그를 만들어보겠습니다.\n이 글을 읽는 분들은 처음 Hugo를 접하실테니 간단한 구조와 기본적인 명령어, 테마 적용법 정도만 알아보고 자세한 환경설정은 차후 별도의 포스팅으로 다룰 예정입니다.\n그 외 환경설정은 Hugo 공식문서1에 친절하게 (영어로) 정리되어 있으니 해당 페이지를 확인하시면 됩니다.\nHugo 관련된 오류들은 한글로 검색해도 잘 안나와서 자동으로 영어공부를 하게 해줍니다 \u0026#x1f622;\n이 포스팅은 Git이 설치되어 있는 것을 전제로 합니다. 혹시 Git이 설치되지 않은 분들은 설치 후 진행해주세요.\nHugo 설치방법 Hugo의 쉘 스크립트2를 사용하기 위해서는 별도의 설치가 필요합니다.\n맥 유저는 brew를 사용하면 정말 간편하게 설치할 수 있습니다. brew를 사용할 수 없는 윈도우는 직접 압축파일을 받아 설치하시거나 별도의 패키지 매니저를 통해 설치가 가능합니다.\n윈도우 패키지 매니저를 활용해 설치\n환경변수나 기본 세팅을 패키지 매니저를 통해 쉽게 할 수 있습니다.\n패키지 매니저를 다운로드합니다. Hugo 공식문서에서 지원하는 윈도우 패키지 매니저는 Chocolatey, Scoop, Winget 3가지 종류가 있습니다. 저는 윈도우 노트북에서 설치 시 Scoop을 사용해 설치를 진행했기에 Scoop을 설치하는 것을 예시로 설명드리겠습니다.\nScoop 설치방법은 공식 홈페이지에 나와있는 것처럼 윈도우에 기본 설치된 PowerShell을 열어서 다음 스크립트를 실행하시면 됩니다.\n$ Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser $ Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression Hugo를 설치합니다.\n$ scoop install hugo-extended 위 명령어를 사용하면 자동으로 환경변수 세팅까지 완료됩니다.\nHugo 패키지 직접 설치\n패키지 매니저 설치가 귀찮으시면 다음과 같이 진행할 수 있습니다. 다만, 환경변수를 직접 설정해야 합니다.\n공식 깃허브에 최신 패키지가 릴리즈되고 있습니다. 해당 패키지를 다운로드 받습니다. Assets \u0026gt; Show all 24 assets \u0026gt; hugo_{최신버전}_windows-amd64.zip\n다운로드 받은 파일을 hugo \u0026gt; bin 폴더에 압축 해제합니다.(나중에 환경변수를 통해 접근하기 때문에 폴더는 어디에 생성하셔도 무관합니다.3) c:₩{설치경로}₩hugo₩bin₩{압축파일 해제}\n환경변수를 세팅합니다. 명령어를 실행하려면 hugo.exe가 포함된 c:₩{설치경로}₩hugo₩bin 폴더를 환경변수에 등록해야 합니다.4 저는 맥에서 작업하고 있어 구체적인 설명은 윈도우 도움말을 확인하시거나, 환경변수 관련된 블로그 글이 많으니 구글신의 도움을 받읍시다.\n맥 \u0026amp; 리눅스 brew만 설치되어 있으면 코드 1줄이면 끝입니다.\n$ brew install hugo 설치 확인 다음 명령어를 쳤을 때 휴고 버전이 0.112.0 이상이면 정상 설치 완료입니다.\n$ hugo version # hugo v0.123.4-21a41003c4633b142ac565c52da22924dc30637a+extended darwin/arm64 BuildDate=2024-02-26T16:33:05Z VendorInfo=brew 참고로 윈도우의 경우 Git Bash와 같은 리눅스 기반 쉘에서 실행하라는 경고문이 있습니다.5\n디렉터리 생성 및 로컬 실행 이제 Hugo 설치를 마쳤으니, 블로그를 위한 디렉터리를 구성하고 로컬에서 직접 실행해보겠습니다.\n사이트 디렉터리 생성 다음 명령어를 통해 디렉터리를 생성할 수 있습니다.\n# 사이트명 내부에 본인이 원하는 사이트 이름을 적습니다.(괄호는 제외) $ hugo new site {사이트명} 로컬서버 실행 이어서 다음 명령어를 실행하면 해당 디렉터리로부터 서버를 생성해서 로컬로 띄워줍니다.\n$ hugo server # Web Server is available at http://localhost:1313/ (bind address 127.0.0.1) 서버주소는 기본적으로 localhost:1313 입니다.\n정상 실행된다면, 브라우저에서 다음과 같은 화면이 나옵니다.\n디렉터리 구조 생성된 디렉터리 구조는 다음과 같습니다.\n├── archetypes # 새 블로그 글을 쓰면 생성되는 글의 기본 형식을 작성할 수 있습니다. │ └── default.md # 별다른 옵션없이 글을 작성하면 생성되는 글의 포맷입니다. ├── assets # 로고 이미지와 같은 이미지나 js, css 등의 파일이 위치합니다. ├── content # 작성한 글이 들어가는 디렉터리입니다. 차후 해당 디렉터리를 참고해 블로그 페이지를 빌드합니다. ├── data # json이나 csv, yaml과 같은 데이터를 저장할 수 있습니다. ├── hugo.toml # 설정파일입니다. 다양한 설정들이 있으며 차후 별도 포스팅으로 다룰 예정입니다. ├── i18n # 국제화 시 웹사이트 번역 내용이 포함됩니다. 글로벌하게 포스팅을 하려면 번역해서 넣으면 됩니다. ├── layouts # header, body 등 http 템플릿의 레이아웃을 설정할 수 있습니다. ├── public # hugo가 빌드한 파일이 들어가는 경로입니다. 차후 서버가 배포되면 해당 위치의 파일들을 제공합니다. │ ├── categories │ │ └── index.xml │ ├── index.xml │ ├── sitemap.xml │ └── tags │ └── index.xml ├── static # 빌드 시에 그대로 들어가야 하는 정적파일들이 들어갑니다. favicon.ico, robots.txt 등이 있습니다. └── themes # 다운로드한 테마가 들어갑니다. 테마 적용 및 블로그 글 작성 테마 적용방법 hugo사이트에서 기본적으로 제공하는 ananke 테마를 적용해보겠습니다.\n새로 생성한 사이트 루트 디렉토리에서 진행합니다.\n깃으로 테마를 다운로드 하기 위해 다음과 같은 명령어를 사용합니다.\n# 해당 경로에서 git을 생성합니다. $ git init # submodule로 테마를 다운로드 받습니다. $ git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke 해당 테마를 적용하기 위해 설정파일(hugo.toml)에 테마를 명시합니다. # hugo.toml파일을 열고 아래 내용을 적으시면 됩니다. theme = \u0026#39;ananke\u0026#39; 아까 접속한 로컬 서버에 테마가 적용되었는지 확인합니다. 블로그 글 작성 블로그 글을 작성하는 명령어는 다음과 같습니다. # 해당 명령어를 사용하면 /content 내부에 /posts/my-first-post.md 파일이 생성됩니다. $ hugo new content posts/my-first-post.md 해당 파일에 다음과 같은 내용을 작성해봅시다. // 아래 주석의 내부를 /posts/my-first-post.md 파일의 생성된 글 아래에 붙여넣으시면 됩니다. +++ title = \u0026#39;My First Post\u0026#39; date = 2024-03-03T16:49:22+09:00 draft = true +++ /* \u0026lt;!-- -----------여기부터-------------- --\u0026gt; ## Hello World! *안녕하세요!* **첫 블로그 글**입니다. [홈페이지](http://localhost:1313)로도 이동이 가능합니다. \u0026lt;!-- -----------여기까지-------------- --\u0026gt; */ 이제 로컬 서버에서 확인하기 위해 서버가 실행중인 쉘에서 서버를 중지한 후, -D 옵션을 붙여 다시 시작해봅시다. /* 로컬서버 중지 */ $ ctrl + c /* 서버 재시작 */ $ hugo server -D 글이 정상적으로 업로드 되었는지 확인해봅니다. 여기까지 따라오셨다면, 블로그 생성 및 게시글 작성까지 완료입니다!\n결론 지금까지 Hugo의 설치방법과 블로그 생성 및 글 작성방법에 대해 알아보았습니다.\n명령어 몇 줄로 쉽게 홈페이지를 생성할 수 있다니 참 좋은 세상에 살고 있는 것 같습니다. \u0026#x1f604;\n다음 시간에는 Git pages를 활용해서 웹에 블로그를 직접 배포해 보겠습니다.\nReferences \u0026ldquo;Hugo 공식 문서\u0026rdquo;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n쉘에서 사용하는 간단한(?) 명령어를 뜻합니다. 윈도우에서는 cmd나 powershell 등에서 실행이 가능하고, 맥에서는 기본적으로 zsh나 bash로 실행할 수 있습니다. 자세한 설명은 위키백과를 확인하세요.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n다만 다른 설치프로그램들과 함께 관리하기 위해 C:₩Program Files₩ 경로에 hugo 폴더를 생성해서 설치하는 것을 권장합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n이 절차를 거치지 않으면 매번 hugo 명령어를 쓸때마다 c:₩{설치경로}₩hugo₩bin₩hugo를 적어야 하므로 매우 불편합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n윈도우 사용자를 위한 경고문\n제가 직접 PowerShell에서 기본적인 명령어를 쳤을땐 잘 작동했던 것 같은데 어디선가 충돌이 발생하나 봅니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/gitblog/3/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e이전 포스팅 참조 :\n\u003ca href=\"https://leaf-nam.github.io/posts/blog/%EA%B0%9C%EB%B0%9C_%EB%B8%94%EB%A1%9C%EA%B7%B8%EC%9D%98_%EC%A2%85%EB%A5%98%EC%99%80_%EC%84%A0%ED%83%9D_240229/\"\u003e개발 블로그의 종류와 선택\u003c/a\u003e \u0026gt; \u003ca href=\"https://leaf-nam.github.io/posts/blog/ssg%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC_240302/\"\u003eSSG에 대하여\u003c/a\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e어떤 개발 블로그를 사용할지도 정했고, SSG 엔진도 정했으니 이제 진짜로 블로그를 만들어보겠습니다.\u003c/p\u003e\n\u003cp\u003e이 글을 읽는 분들은 처음 Hugo를 접하실테니 간단한 구조와 기본적인 명령어, 테마 적용법 정도만 알아보고 자세한 환경설정은 차후 별도의 포스팅으로 다룰 예정입니다.\u003c/p\u003e\n\u003cp\u003e그 외 환경설정은 Hugo 공식문서\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e에 친절하게 \u003cdel\u003e(영어로)\u003c/del\u003e 정리되어 있으니 해당 페이지를 확인하시면 됩니다.\u003c/p\u003e\n\u003cp\u003e\u003cdel\u003eHugo 관련된 오류들은 한글로 검색해도 잘 안나와서 자동으로 영어공부를 하게 해줍니다 \u0026#x1f622;\u003c/del\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ca href=\"https://git-scm.com/downloads\"\u003e이 포스팅은 Git이 설치되어 있는 것을 전제로 합니다. 혹시 Git이 설치되지 않은 분들은 설치 후 진행해주세요.\u003c/a\u003e\u003c/p\u003e","title":"HUGO 기본 설치 및 사용법"},{"content":"도입 이전 포스팅 참조 : 개발 블로그의 종류와 선택\n이제 깃허브 블로그를 쓰기로 마음먹었으니, 어떤 도구를 사용해서 블로그를 만들지에 대한 선택이 남았습니다. 인생도 그렇지만 개발도 항상 선택의 연속인 것 같습니다.\n이번 포스팅에서는 SSG(Static Site Generator)의 개념, 종류와 특징에 대해 알아보고, SSG Framework 중 하나인 HUGO에 대해서 간단히 소개하겠습니다.\nSSG 저는 최초 블로그를 작성하기 위해 어떻게 홈페이지를 만들어야 하는지 고민이 되었고, 여러 옵션들을 확인하던 중, SSG를 사용해 블로그를 만드는 것이 가장 좋아보여 이를 도입하게 되었습니다.\n사실 SSG를 제대로 이해하기 위해서는 SSR, CSR, SPA 등의 개념에 대해 우선 설명해야 하지만, 이는 생각보다 양이 방대하고 개발의 역사와도 관련이 되는 내용이기에 나중에 별도의 포스팅으로 다루도록 하겠습니다. 지금은 블로그를 만들기 위한 필수개념 정도만 간단히 다루고 넘어가겠습니다.\nSSG(Static Site Generator)는 텍스트 입력 파일(ex. Markdown, reStructuredText, AsciiDoc 및 JSON)을 사용하여 정적 웹 페이지를 생성하는 엔진입니다.1\n즉, 간단한 텍스트 파일을 사용해 정적으로 웹 페이지를 빌드하고 배포하는 방식의 개발 방법입니다.\n이러한 SSG는 다양한 장점을 갖고 있으며 SSG 프레임워크를 다루는 잼스택(JAMStack2) 개발자가 별도로 등장할 정도로 최근 많이 주목받는 최신 트렌드 기술 중 하나입니다.\n장점은 다음과 같습니다.\n확장성 : 이미 생성된 파일을 통해 서비스하므로, 기존에 빌드한 파일에 대해 더이상 추가적인 자원을 사용할 필요가 없습니다3. 이는 기존 서비스에 대해 더이상 신경쓰지 않고 새로운 서비스를 확장할 수 있게 해줍니다. 성능 : 이미 빌드가 되어있기에 생성된 파일을 전송하는 과정 자체도 빠른 응답이 가능하며, CDN4과 같은 캐싱 서버에서 직접 해당 파일만 응답하면 되므로 더욱 빠른 성능을 기대할 수 있습니다. 보안 : CDN을 통해 분리된 지역서버는 DB에 접근하지 않고 서비스가 가능하며, 해커에 의한 공격이 발생했을때 중앙 서버에 영향을 미치기 전에 해당 네트워크를 빠르게 분리하는 것이 가능합니다. 또한, 공격 대상인 서버를 다시 구성하여 CDN을 재구축하면 되기에 빠른 대응 또한 가능합니다. 이렇게 훌륭한 SSG이지만, 잼스택 개발자라는 직군이 따로 분리될 정도로 초기 구현 및 세팅이 복잡하고 아직 많은 레퍼런스가 없기에 처음 공부하거나 도입하기가 어렵다는 단점이 있습니다.\n제가 블로깅을 하면서 확장성이나 성능 등을 걱정할 일은 크게 없겠지만, 애초에 깃허브에서 제공하는 배포 도구를 사용하려면 단일 HTML이 필요했습니다. 매번 블로그에 글을 쓸때마다 HTML을 생성할 바에는 차라리 SSG를 공부해서 도입하는게 더 빠르겠다는 생각에 SSG를 사용하기로 마음먹었습니다.\nSSG의 종류 이러한 SSG에도 종류가 많습니다. SSR(Server Side Rendering)5 프레임워크로 알고있던 Next.js도 SSG를 지원했고, 국내 개발 블로그에서 많이 사용하는 jekyll과 제가 사용하는 Hugo 또한 SSG입니다. 이러한 SSG의 특징과 장단점에 대해 알아보겠습니다.\n(어째 저번 포스팅과 흐름이 똑같네요..)\nNext.js 국내 프론트엔드 시장의 대부분을 점유하고 있는 React를 사용해 SSR을 구현한 프레임워크입니다. Vercel에서 개발하고 관리하고 있습니다.(React를 만든 Meta에서 개발한게 아니었네요!)\n제가 생각한 장점 및 특징은 다음과 같습니다.\n많은 레퍼런스와 개발 생태계\n아래 그래프6에서 보시는 것처럼 국내 프레임워크의 높은 순위를 차지하고 있는 React + Next.js인 만큼 수많은 레퍼런스와 학습자료를 쉽게 찾아볼 수 있습니다. 또한, 개발자가 많다는 것은 그만큼 에러가 발생했을 때 여기저기에 질문을 통해 쉽게 해결할 수 있다는 뜻이기도 합니다. 프로그래머스 통계 : 주로 사용하는 웹 프레임워크 또는 라이브러리는 무엇인가요?\n안정성과 다양한 오픈소스 지원\n우선 메타에서 지원 및 유지보수를 하는 React 기반의 프레임워크인 점에서 매우 안정성이 높다고 할 수 있습니다. 또한, 위에서 본 것처럼 많은 사용자들을 보유하다 보니 관련된 오픈소스 라이브러리도 많습니다. 저도 프론트엔드 라이브러리를 도입하기 위해 오픈소스를 찾아보면 체감상 80% 이상은 React기반으로 생성되어 있는 것 같습니다. (부끄럽지만 아직 Vue.js만 조금 다루는 수준이라 React로 되어있는 오픈소스는 그림의 떡처럼 아쉬운 적도 많았습니다.) Vercel과 연동한 손쉬운 배포\n개발사인 vercel은 클라우드 컴퓨팅을 제공하는 회사입니다. 즉, 기본적인 웹사이트 배포를 위한 인프라가 이미 완성되어 있기에 웹페이지만 완성하면 빌드 및 배포하는 것은 정말 쉽다고 합니다. 이러한 호환성을 무기로 하기 위해 Vercel에서 React를 활용한 Next.js 생태계를 구축하지 않았나 싶습니다. 원래도 React는 한번 공부해보고는 싶었고, React만 알면 쉽게 웹페이지를 제작할 수 있다는 부분은 정말 매력적으로 다가왔습니다.\n하지만 React를 단시간에 배울 수 있을지도 조금 걱정되었고, 너무 큰 생태계인지라 블로그를 작성하는 것보다 프레임워크를 배우는데 더 많은 시간을 쓸까 우려가 되었습니다. 자칫 배보다 배꼽이 커질 수도 있겠다는 생각이 들어 다른 옵션을 찾아보게 되었습니다.\nJekyll 지킬은 Ruby7로 개발된 SSG입니다.\n깃허브의 공동 설립자 Tom Preston-Werner에 의해 개발되었으며, 깃허브가 나온 2008년에 함께 출시되었으니 깃허브와 거의 역사를 함께했습니다.\n위 내용만 봐도 깃허브와 아주 친할 것 같은 느낌이네요. 실제로 국내 대부분의 깃허브 블로그는 Jekyll을 활용하고 있고 같은 언어를 사용하기에 호환성이 좋습니다.\n그럼 특장점을 살펴보겠습니다.\n깃허브 연동성\n애초에 깃허브 블로그를 만들기 위해 나온 SSG이기 때문에, 별다른 설치없이 Ruby만 설치하면 RubyGems8를 통해 쉽게 설치가 가능합니다. 아마 깃허브나 지킬 둘중 하나가 망하지 않는 한 깃허브와의 연동은 별다른 이슈가 없을 것 같습니다. 다양한 레퍼런스와 테마\nNext.js만큼은 아니지만 국내의 많은 블로그가 Jekyll로 만들어져 있기 때문에 참고할 만한 사이트가 많습니다. 테마도 많아서 다양한 테마를 직접 설치하고 커스터마이징 하는 것이 가능합니다.9 가벼움\n처음에는 RubyGems의 라이브러리 중 하나가 아닌가 생각할 정도로 가볍고 간편합니다. 저도 개발 경험이 많지는 않지만 지킬과 같이 가볍고 최소한의 필요한 기능만 제공하는 프레임워크가 금방 손에 익고 편했습니다. 저도 Jekyll을 찾아보면서 Ruby라는 언어가 매우 흥미롭게 다가와서 공부해보고 싶다는 생각도 많이 들었습니다. 그리고 Jekyll은 굳이 Ruby의 내부 동작을 몰라도 간단한 쉘스크립트만 실행하면 되기에 배우는데는 큰 무리가 없어 보였습니다.\n하지만 좀더 찾아보니 Ruby만 설치하면 된다는게 말이 쉽지, 사실 쉽지많은 않다는 이야기도 많았고, (Ruby환경세팅이 악명 높기로 유명하다고 합니다) 테마를 적용하는 과정에서도 RubyGem의 번들링하는 과정이 상대적으로 번거롭다는 글도 확인했습니다.10\n마지막으로 깃허브에 많이 의존하고 있다는 것은 반대로 생각하면 깃허브와 생명주기를 함께한다는 것이고, 나중에 다른 사이트나 클라우드를 통해 배포를 하고 싶을때 걸림돌이 되지 않을까 하는 생각도 들었습니다.\nHugo 드디어 오늘의 주인공 Hugo입니다. 이름에서 알 수 있듯이 go언어로 만들어진 SSG입니다. 공식 웹사이트에 들어가면 자세한 설명을 보실 수 있습니다.11\n해외에는 비교적 인기가 많은 것 같은데, 국내에는 레퍼런스가 많이 없습니다. 저도 블로그 환경세팅을 하면서 많은 애를 먹고 있는데, 그래도 세계공용어인 영어가 있으니 대부분의 문제는 어찌저찌 해결이 되긴 합니다..\n그래도 레퍼런스가 없다는 것을 모두 상쇄할 만큼 좋은 장점들이 많아 결국 Hugo를 선택하게 되었습니다.\n성능\n공식 홈페이지에 나와있는 유튜브만 보더라도 성능 면에서 압도적입니다. 영상을 다 보시기 귀찮은 분들을 위해 요약하자면, 하루에 2개씩 5000개의 포스트를 작성하면 대략 6.8년이 걸리는데, 이정도 되는 양도 Hugo로 생성시 6~7초면 빌드가 완료된다는 영상입니다.\n(반면, Jekyll은 1000개 빌드하는데 4~5분 걸린다고 하네요. 공식 페이지에도 빌드관련 이슈가 상당히 많습니다.12)\nGo\n구글에서 사용하는 Go(Golang) 기반이라는 점도 매력적이라고 생각합니다. 컴파일 언어이지만 정말 빨라서 마치 인터프리터 언어처럼 사용이 가능하다고 하는데, 구글에서 지원하고 개발하는만큼 향후에도 많은 발전이 이루어지지 않을까 기대가 됩니다. 문법도 점점 쉽게 개선되고 있고, 특히 html 템플릿은 이중 중괄호 문법으로 마치 Vue.js나 백엔드에서 익숙한 Mustache처럼 간편하게 사용이 가능합니다. Go의 마스코트인 Go gopher. 이름을 좀 대충 지은 티가 나는 것 같기도\u0026hellip;?\n// 위 그림을 불러오기 위한 템플릿 문법입니다. // 원래는 {{와 \u0026lt;를 붙여야 하지만, 예시를 위해 띄어쓰기 했습니다. {{ \u0026lt;figure src=\u0026#34;gogopher.jpeg\u0026#34; caption=\u0026#34;Go의 마스코트인 Go gopher. 이름을 좀 대충 지은 티가 나는 것 같기도...?\u0026#34;\u0026gt; }} 테마 적용과 확장의 용이성\n지킬과 함께 블로그 SSG의 양대산맥인 가장 큰 이유 중 하나는 바로 방대한 양의 테마입니다. 고르는 게 힘들 정도로 다양한 테마가 있고13, 이러한 테마를 적용하거나 커스터마이징 하는게 매우 간편합니다. 저도 시작한지 얼마 되지 않았지만 확실히 테마를 만들거나 불러오는 기능이 간편하고 적용하기도 쉬웠습니다. # 테마를 다운로드받은 후 이 한줄이면 테마가 변경됩니다. hugo -t \u0026#34;테마이름\u0026#34; Go와 테마도 좋지만, 아무래도 성능이 가장 끌리는 부분인 것 같습니다. 애초에 SSG를 사용하는 부분도 빠르고 간편하기 때문인데, 빌드하는 과정에서 너무 많은 시간이 소요되면 원래의 목적과 멀어질 우려가 듭니다. 백엔드는 역시 성능!\n결론 지금까지 SSG의 특징과 종류, 그리고 제가 Hugo를 선택하게 된 이유에 대해 알아보았습니다. 사실 Hugo 말고 다른 SSG도 정말 훌륭하고, 블로깅 정도로 사용하기에는 전혀 무리가 없다고 생각합니다.\n다들 장단점을 잘 찾아보시고 고민해서 본인의 상황에 맞는 최적의 스택을 고르셨으면 좋겠습니다!\n블로그 경험이 없다보니 분량조절이 힘드네요.. 사실 이번 포스팅에서 hugo 설치 및 적용까지 하려고 했는데 내용이 자꾸 길어져서 해당 포스팅은 다음번에 작성하는 것으로 하겠습니다. 그리고 앞으로는 하루 1시간 정도에 작성할 수 있는 분량으로 포스팅을 이어나가려고 합니다.\nReferences \u0026ldquo;What is a Static Site Generator? How do I find the best one to use?\u0026rdquo;. Netlify. 2020.04.14. Phil Hawksworth.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n\u0026ldquo;What is Jamstack?\u0026rdquo;\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nCSR은 이와 다르게 매번 기존 파일을 빌드합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n지리적으로 분산된 서버들을 연결한 네트워크를 뜻합니다. 예를 들어 구글 서버는 미국에 있지만, 구글 코리아는 한국에 따로 서버를 구성해서 파일이 변경될 때만 미국의 구글서버와 동기화합니다. 이렇게 한국에 있는 구글서버를 통해 더욱 빠른 통신이 가능합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n서버에서 페이지를 렌더링하는 방식입니다. SSG와 유사하지만, SSR은 요청 시마다 페이지를 렌더링해서 전송하는 방식이고 SSG는 서버 빌드 시점에 이미 해당 페이지를 생성합니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nProgrammers Dev Survey 2023.Programmers. 2022.12.05. ~ 2022.12.31.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n마츠모토 유키히로가 1995년 만든 스크립트 언어입니다. Ruby On Rails(ROR)라는 프레임워크를 사용하면 풀스택 개발도 가능하며, 깃허브도 ROR로 만들어져 있습니다.\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nRuby에서 사용하는 패키지 관리자입니다. \u0026lsquo;gem\u0026rsquo;으로 시작하는 쉘스크립트는 해당 패키지 관리자를 호출하는 스크립트입니다. (보석 컨셉이 확실하네요!)\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nJekyll 테마 사이트\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nGitHub Pages를 이용한 기술 블로그 제작 후기.Yogiyo. 2019.01.23. Yogiyo Tech Blog\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nWhat is Hugo\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nBuild Time Performance Issues\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nHugo 테마 사이트\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://leaf-nam.github.io/posts/gitblog/2/","summary":"\u003ch2 id=\"도입\"\u003e도입\u003c/h2\u003e\n\u003cp\u003e이전 포스팅 참조 : \u003ca href=\"https://leaf-nam.github.io/posts/blog/%EA%B0%9C%EB%B0%9C_%EB%B8%94%EB%A1%9C%EA%B7%B8%EC%9D%98_%EC%A2%85%EB%A5%98%EC%99%80_%EC%84%A0%ED%83%9D_240229/\"\u003e개발 블로그의 종류와 선택\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e이제 깃허브 블로그를 쓰기로 마음먹었으니, 어떤 도구를 사용해서 블로그를 만들지에 대한 선택이 남았습니다. 인생도 그렇지만 개발도 항상 선택의 연속인 것 같습니다.\u003c/p\u003e\n\u003cp\u003e이번 포스팅에서는 SSG(Static Site Generator)의 개념, 종류와 특징에 대해 알아보고, SSG Framework 중 하나인 HUGO에 대해서 간단히 소개하겠습니다.\u003c/p\u003e\n\u003ch2 id=\"ssg\"\u003eSSG\u003c/h2\u003e\n\u003cp\u003e저는 최초 블로그를 작성하기 위해 어떻게 홈페이지를 만들어야 하는지 고민이 되었고, 여러 옵션들을 확인하던 중, SSG를 사용해 블로그를 만드는 것이 가장 좋아보여 이를 도입하게 되었습니다.\u003c/p\u003e","title":"SSG에 대하여"}]