# 개요

리팩토링 7장, 객체 간의 기능 이동 읽고 간단 정리

각 카탈로그에서 설명하는 내용 중 중요하게 생각하는 부분 작성하고 내 생각도 덧붙임

코드 작성하다가 이 예시가 이거구나 싶을 때는 예시코드도 붙일 예정

 

# 객체 간의 기능 이동

객체 설계에서 가장 중요한 일 중 하나가 '기능을 어디에 넣을지 판단'하는것이다.

  • 20년간 작업해 왔는데도 아직 서툴다고한다. 개 구라같지만 그만큼 어렵다는 뜻이겠지

클래스가 방대해지는 원인은 대체로 기능이 너무 많기 때문이다. 이럴 때는 클래스 추출을 통해 많은 기능을 분리 해야 한다.

  • 확실하진 않지만 OOP 원칙중 SRP에 해당하는 내용인것 같다.
  • 메서드와 마찬가지로 클래스도 작아야한다고 생각한다. 메서드가 점점 길어질 때 메서드를 분리해야하는 것처럼 한 클래스에 기능이 점점 늘어나면 클래스도 분리해야한다.

 

## Move Method (메서드 이동)

한 클래스에 기능이 너무 많거나 클래스가 다른 클래스와 과하게 연동되어 의존성이 지나칠 때는 메서드를 옮기는 것이 좋다.

  • 객체간의 의존도를 줄이는 일은 항상 어렵다. 작은 프로젝트에서도 어려운데 규모가 커지면 더 어렵겠지..
  • 한 클래스는 하나의 책임을 가져야 한다는 말이 아직 잘 와닿진 않지만 클래스 이름에 무언가 맞지 않는 기능을 하는 메서드가 있다면, 그 메서드는 그 클래스에 있어야 할 메서드가 아니라는 생각을 한다.

 

## Move Field (필드 이동)

지금은 합리적이고 올바르다고 판단되는 설계라도 나중에는 그렇지 않을 수 있다. 문제는 그게 아니라 그러한 상황 변화에 아무런 대처도 하지 않는 것이다.

  • 내가 한 대부분의 프로젝트들은 한 번 하고 치워버리는 성향이 강했다. (과제나 그냥 작은 토이프로젝트..) 설계도 간단하고 설계가 변하는 일도 없었다. 과제같은 경우는 오히려 설계는 교수나 조교들이 다 했지 ㅋㅋ..
  • 설계가 항상 변할 수 있다는 생각도 중요하고 어느 상황에나 유연하게 대처할 능력이 필요하다고 생각한다.

 

## Extract Class (클래스 추출)

클래스는 확실하게 추상화 되어야 하며, 두 세가지 명확한 기능을 담담해야 한다.

  • 내가 이해하는 추상화는 그 클래스명, 메서드명만 보고도 이 모듈이 어떤 기능을 하는지 이해할 수 있도록 코드를 작성하는 것이다.
  • 그만큼 메서드명, 클래스명을 잘 짓는것이 중요하고, 클래스에 맞는 메서드들만 가지고 있어야한다고 생각한다. 그러면 내부 구현이 어떤 로직인지 자세히 알 필요없다. 이 모듈이 뭘 하는지 이름만 보고도 아니깐

데이터의 일부분과 메서드의 일부분이 한 덩어리이거나 주로 함께 변화하거나 서로 유난히 의존적인 데이터의 일부분도 클래스로 떼어내기 좋다.

 

## Inline Class (클래스 내용 직접 삽입)

주로 클래스의 기능 대부분을 다른 곳으로 옮기는 리팩토링을 수행하고 남은 기능이 거의 없어졌을 때 나타난다.

 

## Hide Delegate (대리 객체 은폐)

캡슐화란 객체가 시스템의 다른 부분에 대한 정보의 일부분만 알 수 있게 은폐하는 것을 뜻한다. 객체를 캡슐화하면 무언가를 변경할 때 그 변화를 전달해야할 객체가 줄어듦으로 변경하기 쉬워진다.

클라이언트가 서버 객체의 필드 중 하나에 정의된 메서드를 호출할 때 클라이언트는 이 대리 객체에 관해 알아야 한다. 대리 객체가 변경될 때 클라이언트도 변경해야 할 가능성이 있기 때문이다. 이런 의존성을 없애려면 대리 객체를 감추는 간단한 위임 메서드를 서버에 두면 된다. 변경은 서버에만 이뤄지고 클라이언트에는 영향을 주지 않는다.

  • 음 쉽게 생각하면 a객체 내에 있는 b객체의 메서드를 쓰기위해서 a.getB().somethingMetodOfB()이런 식으로 열거해서 쓰는것 보다. a 내의somethingMethod()에서 b객체의 메서드를 사용하는것이 좋다는 얘기 같다.

 

## Remove Middle Man (과잉 중개 메서드 제거)

대리 객체 사용을 캡슐화하면 얻는 장점도 있지만 단점도 생긴다. 클라이언트가 대리 객체의 새 기능을 사용해야 할 때마다 서버에 간단한 위임 메서드를 추가해야 한다는 점이다.

은폐의 적절한 정도를 알기란 어렵다. 하지만 리팩토링을 실시할 때는 은폐의 정도를 잘 몰라도 된다. 시스템이 변경되면 은폐의 정도의 기준도 변한다. 전에는 적절했던 캡슐화가 현재 시점에서는 부적절할 수도 있다. 리팩토링은 필요해질 때마다 보수하면 된다.

  • 리팩토링 하고 후회하지 말라고 하는것 같다. 어짜피 다시 롤백하면 되니까
  • 근데 말처럼 안된다 리팩토링하고 "이게 맞나? 전의 코드가 더 낫지않나?" 라는 고민을 할 때가 자주 있다.

 

## 내 생각

메서드 내에서만 해결하면 됐던 리팩토링에서 클래스로 옮겨가면 조금씩 어려워진다. 이게 클래스가 적으면 그러려니 하겠는데 프로젝트에서 클래스가 적을리가..

여기에 이제 디자인 패턴, OOP 원칙 등 더 머리 아파지는게 나오면 머리에 과부하오는데 리팩토링을 이해하기 위해서 선행 학습이 어느정도 필요한것 같다 ㅜ

# 개요

리팩토링 6장, 메서드 정리 부분 읽고 간단 정리

각 카탈로그에서 설명하는 내용 중 중요하게 생각하는 부분 작성하고 내 생각도 덧붙임

코드 작성하다가 이 예시가 이거구나 싶을 때는 예시코드도 붙일 예정

 

# 메서드 정리

리팩토링의 주된 작업은 코드를 포장하는 메서드를 적절히 정리하는 것이다.

  • 매우 동의한다. 메서드가 길어지면 읽기도, 이해하기도 힘들어진다. 메서드는 짧아야하고 그 메서드가 하는 기능이 메서드 이름으로 명확하게 드러나야 한다고 생각한다.
  • 메서드가 점점 길어진다는 것은 메서드가 하나의 기능이 아니라 여러 기능을 하게 된다는 뜻이다. 그러므로 메서드 추출을 통해 추상화하자.

메서드 추출에서 가장 힘든 작업은 지역변수를 처리하는 것인데, 그건 주로 임시변수 때문이다.

  • 나는 잘 와닿지 않는게, 요즘 IDE에서 리팩토링 기능을 많이 지원해준다. 그래서 내가 메서드 추출하고 싶은 부분을 드래그해서 몇번 '딸깍'하면 어느 변수를 매개변수로 보낼지, 어느 변수에 반환값을 대입해줘야 하는지 잘 지원해준다.
  • 물론 변수가 많아지면 IDE가 제공해주는 기능을 쓰기 어려울 때도 있다. 더 좋은 방법이 있을 수 있겠지만, 나는 메서드 내의 지역변수들을 전부 클래스 필드로 올려버린다. 그 이후에 메서드 추출과정에서 지역변수로 보내거나 쿼리 메서드로 바꾸는 작업을 한다.

직관적인 이름의 간결한 메서드가 좋은 이유

  1. 메서드가 적절히 잘게 쪼개져 있으면 다른 메서드에서 쉽게 사용할 수 있다.
  2. 상위 계층의 메서드에서 주석같은 더 많은 정보를 읽어들일 수 있다.
  3. 재정의 하기도 훨씬 수월하다.
  • 1번에 "다른 메서드에서 쉽게 사용할 수 있다" 라는 내용을 보고 생각난 건데, 옛날에는 꼭 다른 여러 메서드에서 사용하기 위해 메서드 추출을 해야한다고 생각했다. 그래서 "하나의 메서드에서만 사용하는데 굳이 메서드 추출할 필요가 있을까?" 라는 생각도 했었는데, 메서드의 호출 횟수에 따라 메서드를 추출 해야한다는 것은 엉터리같은 생각이었다.

중요한 것은 메서드의 길이가 아니라 메서드 이름과 메서드 내용의 의미적 차이라고 할 수 있다.

  • 맞는 표현인지 모르겠는데 추상화 레벨이 같아야한다는 의미같다.
  • 예전에 유튜브에서 백명석의 클린 코더스 영상에서 비슷한 내용을 들은것 같다.. 나중에 참고해서 내용추가하자..

리팩토링의 핵심은 의도한 기능을 하눈ㄴ에 파악할 수 있는 직관적인 메서드명을 사용하는 것과 메서드를 간결하게 만드는 것이다.

 

## Extract Method (메서드 추출)

메서드 추출 기법은 제일 많이 사용된다.

  • 가장 많이 사용되고, 가장 쉽게 할 수 있는 리팩토링 중 하나라고 생각한다.

메서드가 너무 길거나 코드에 주석을 달아야만 의도를 이해할 수 있을 때 별도의 메서드를 만든다.

  • "이 부분이 무슨 기능을 한다" 라고 주석을 달지 말고 메서드로 추출하자.

 

## Inline Method (메서드 대신 내용 직접 삽입)

간혹 메서드명에 모든 기능이 반영될 정도로 메서드 기능이 지나치게 단순할 때가 있다.

  • 나는 보통 조건문 작성할 때 이런 상황이 많이 생기는데, 자주 고민한다. 보통은 메서드로 추출하는 편이긴한데 그러고 또 한참 고민한다.. 너무 어렵..

 

## Inline Temp (임시 변수 내용 직접 삽입) && Replace Temp with Query (임시변수를 메서드 호출로 전환)

임시변수를 메서드 호출로 수정하면 클래스 안의 모든 메서드가 그 정보에 접근할 수 있다. 이렇게 하면 클래스의 코드가 훨씬 깔끔해진다.

  • 코드 작성할 땐 임시변수에 어떤 값이 저장되어 있는지 확인하기 위해 임시변수에 값을 대입해놓는다. 제대로 동작하는 것을 확인 한 후에는 임시변수를 지워버리고 메서드 호출로 대체한다.

 

## Introduce Explaining Variable (직관적 임시변수 사용)

수식이 너무 복잡해져서 이해하기 힘들 수 있다. 이럴 때는 임시변수를 사용하면 더 처리하기 쉽게 쪼갤 수 있다.

 

## Split Temporary Variable (임시변수 분리)

한 임시 변수에 여러 값을 대입하지 마라

  • 이건 너무 당연한 얘기라.. 예를 들어 임시변수 하나 생성해서 둘레, 면적을 같이 대입하면 안되고, 둘레를 저장하는 임시변수와 면적을 저장하는 임시변수 따로 선언해야한다는 얘기이다.

 

## Replace Method with Method Object (메서드를 메서드 객체로 전환)

지역변수 때문에 메서드 추출을 적용할 수 없는 긴 메서드가 있을 때 그 메서드 자체를 클래스로 만들어서 모든 지역변수를 클래스의 필드로 만들고 클래스 내의 여러 메서드로 쪼갠다.

  • IDE 도움을 받기 힘들 때 내가 하는 방법과 비슷한것 같다.

 

## 내 생각

몇몇 기법들은 카탈로그 제목만 보고 "이게 뭐지?" 라는 생각이었는데 예제를 보니까 내가 의식하지 않아도 당연하게 하고 있었던 기법들이었다. 당연한 만큼 간결하고 깔끔한 코드 작성을 위해서 이 챕터의 내용들이 기본이 되어야 한다고 생각한다.

1. 개요

실습에 사용할 계정 생성

SQL Developer 사용

 

2. 계정 생성

먼저 admin 계정으로 접속한다.

비밀번호는 Oracle 처음 설치 했을 때 설정한 비밀번호

다음과 같이 작성한다.

CREATE USER {USERNAME} IDENTIFIED BY {PASSWORD}

 

2-1. 계정 생성 에러 발생했을 때

처음 계정 생성할 때 다음과 같은 에러가 출력된다

이게 뭐임;;

구글 검색해보니까 계정 사용자명은 C##{USER\_NAME}형식을 사용해야한다고 한다. 그대로 따라 주면 되긴 하는데 일단 실습에서는 일반 형식을 사용할 예정인데...

더 검색해보니까 세션을 변경해주면 된다고 한다. 그래서 다음과 같이 작성하고 실행한다.

ALTER SEESION SET "_ORACLE_SCRIPT"=true;

그리고 다시 생성하니 잘 된다

 

3. 계정 비밀번호 변경

오타로 비밀번호를 잘못 설정했다.. 비밀번호 변경하는 법은 다음과 같다.

ALTER USER {USERNAME} IDENTIFIED BY {NEW_PASSWORD}

 

4. Reference

 

[Oracle] Oracle XE에서 사용자 생성 오류, ORA-65096: 공통 사용자 또는 롤 이름이 부적합합니다. invalid c

Oracle에서 개발 및 테스트를 위해 Oracle XE를 무료로 사용할 수 있도록 배포하고 있습니다. 그러나 몇 가지 제약사항들이 있는 데 그중에 하나가 사용자 생성 시 사용자 이름에 대한 제약이 있습니

dev-handbook.tistory.com

 

 

새로운 계정을 만들어 테이블 만들어보기

내용요약 새로운 계정을 만든다. 해석한다. scott.sql을 넣는다. 전제조건 오라클이 설치 되어 있어야 한다. 진행하고 있는 오라클의 버전은 11g이다. XE와 좀 다르다. 환경확인 먼저 서비스(services.ms

yjy0755.tistory.com

 

'Database' 카테고리의 다른 글

[Database] SQL Developer JDK 버전 변경하기  (0) 2024.01.23
[Database] SQL Developer 설치  (1) 2024.01.23
[Database] Oracle 설치  (0) 2024.01.22

1. 개요

실수로 SQL Developer 설치하는 과정에서 JDK 경로를 입력 못해주었다..

OK 눌러야 하는데 실수로 ESC 눌러서 Cancel해버림..

다시 설치하면 되겠다는 생각에 지우고 다시 설치해봤는데 경로 입력 화면이 안나온다;;; Auto K....

첫 설치때 JDK 경로를 설정하지 못했거나 JDK를 재설치하는 등 경로가 변경되었다면 어떻게 해야할까?

 

2. 경로 변경하기

C:\Users\{usernmae}\AppData\Roaming\sqldeveloper\{version} 디렉토리로 가면

product.conf 라는 파일이 있다.

이 파일에 SetJavaHome {JDK 경로}를 입력해준다.

 

3. 한가지 의문점

내가 설치한 SQL Developer는 with JDK 11 included인데, 내 JDK 버전은 17이다. 일단 되는건 다 되는거 같은데 어디서 오류가 날지 모르겠다.

이걸 보면 대부분은 되지만 JavaFX를 사용하는 GUI는 안될거라고 하는데... 아직 사용을 안해봐서 모르겠다.

오류가 난다면 추가로 글 올리는 걸로...

'Database' 카테고리의 다른 글

[Database] Oracle 계정 생성하기  (0) 2024.01.23
[Database] SQL Developer 설치  (1) 2024.01.23
[Database] Oracle 설치  (0) 2024.01.22

1. 개요

자바에 통합 개발 환경(IDE) Eclipse나 IntelliJ가 있듯이, Oracle도 SQL Developer가 있다.

물론 CLI로 해도 되지만 편한것 두고 일부러 어렵게 할 필요는 없지

(근데 할 줄은 아는게 중요한 것 같다. 자바도 CLI로 컴파일, 실행 할 줄 알아야 하듯이)

 

2. Oracle 홈페이지 접속

링크: https://www.oracle.com/database/sqldeveloper/technologies/download/

 

Oracle SQL Developer Downloads

This archive. will work on a 32 or 64 bit Windows OS. The bit level of the JDK you install will determine if it runs as a 32 or 64 bit application. This download does not include the required Oracle Java JDK. You will need to install it if it's not already

www.oracle.com

 

3. 운영체제에 맞게 설치 (윈도우)

나중에 자바와 연동하기 위해 꼭 with JDK included 버전을 설치하

 

4. 압축해제 후 sqldeveloper.exe 실행

 

5. 접속

수동으로 접속 생성이나 우측 상단의 '+' 버튼 클릭
CMD의 sqlpuls로 접속한것처럼 사용자 이름(관리자계정) 비밀번호(설치 때 입력했던 비밀번호)로 접속한다

 

6. JDK 경로

실수로 4번의 sqldeveloper.exe 첫 실행하는 과정에서 jdk 경로를 입력 못했거나,

jdk 경로를 수정하고 싶은 경우에 어떻게 해야할까??

 

 

[Database] SQL Developer JDK 버전 변경하기

1. 개요 실수로 SQL Developer 설치하는 과정에서 JDK 경로를 입력 못해주었다.. 다시 설치하면 되겠다는 생각에 지우고 다시 설치해봤는데 경로 입력 화면이 안나온다;;; Auto K.... 첫 설치때 JDK 경로를

jino-dev-diary.tistory.com

위 글을 참고하자

'Database' 카테고리의 다른 글

[Database] Oracle 계정 생성하기  (0) 2024.01.23
[Database] SQL Developer JDK 버전 변경하기  (0) 2024.01.23
[Database] Oracle 설치  (0) 2024.01.22

1. Oracle 홈페이지 접속

링크: https://www.oracle.com/kr/database/technologies/xe-downloads.html

 

Oracle Database Express Edition (XE) Downloads | Oracle 대한민국

Support Oracle Database Express Edition (XE) is a community supported edition of the Oracle Database family. Please go to the Oracle Database XE Community Support Forum for help, feedback, and enhancement requests. Note: Oracle Support Services only provid

www.oracle.com

 

2. 운영체제에 맞게 설치 (나는 윈도우!)

 

3. 압축 풀고 setup.exe 실행하여 설치

디렉토리 설정 안할거면 그냥 다음
비밀번호 입력후 다음 (비밀번호는 잊지말기! 메모라도 해두자!)

 

4. CMD창 열어서 버전 확인, 접속

sqlplus입력하여 접속

사용자명에 오라클 계정 관리자로 접속, 비밀번호는 설치 때 설정했던 비밀번호를 입력해야 한다. (잊지 말라고 했제)

'Database' 카테고리의 다른 글

[Database] Oracle 계정 생성하기  (0) 2024.01.23
[Database] SQL Developer JDK 버전 변경하기  (0) 2024.01.23
[Database] SQL Developer 설치  (1) 2024.01.23

1. 개요

단순히 Restful API만 리턴하려고 했는데, View 처리도 필요할것 같다. (그냥 공부할겸)

resources/templates로의 경로 설정을 해주어야하는데 예전에 짤막하게 Thymeleaf 라이브러리를 쓰면 알아서 경로설정을 해주는것으로 알고 있었다.

그래서 Thymeleaf Dependency 추가하고 하던대로 했는데, 계속 Error Page가 나왔다.

알고보니 application.properties에 설정값을 작성해야 하더라.

다시 잊을까봐 기록한다.

 

2. 안되던 것

resources/static에 있는 index.htmllocalhost:8080/index.html로 잘만 접속되는데

resources/templates에 있는 home.htmllocalhost:8080/home으로 접속 시도해도 계속 에러 페이지가 나옴

 

3. 해결

알고 보니 resoureces/application.properties에 다음과 같은 설정값을 작성해야 함.

prefix와 suffix만 추가해도 된다.

밑의 cache는 개발할때 수정 내역이 바로 반영되도록 하여 변경하기 편하도록 설정한것이고

check-template-location은 디렉토리에 파일이 있는지 확인하고 없으면 에러 발생시키도록 하는것이다.

 

끝!

1. 문제

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

 

1738번: 골목길

첫째 줄에 골목길들이 교차하는 지점의 개수 n (2 ≤ n ≤ 100)과 골목길의 개수 m (1 ≤ m ≤ 20,000) 이 차례로 주어진다. 이어지는 m개의 행에 각각의 골목길을 나타내는 세 정수 u, v, w가 차례로

www.acmicpc.net

문제 설명

민승이는 놀러가기 위해 집을 나섰다. 민승이네 집에서 코레스코 콘도까지 가기 위해서는 복잡하게 얽혀있는 골목길들을 통과해야 한다.

그런데, 어떤 길에는 깡패가 서식하고 있어, 그 길을 지나게 되면 깡패에게 일정한 양의 금품을 갈취당하게 된다. 그런가하면, 어떤 길에는 지나가던 행인들이 흘리고 간 금품들이 떨어져 있어, 그 길을 지나게 되면 일정한 양의 금품을 획득하게 된다. 한 번 지나간 길을 다시 방문하더라도 금품을 갈취당하거나 획득한다.

골목길의 연결 상태와, 각 골목길을 지날 때 갈취당하거나 획득하게 되는 금품의 양이 주어졌을 때, 민승이가 최대한 유리한 경로를 따라 집에서 코레스코 콘도까지 가기 위해서는 어떻게 해야 하는지 출력하는 프로그램을 작성하시오. 

보유 중인 금품의 양이 음수가 될 수 있다. 최대한 유리한 경로 또는 최적의 경로는 민승이네 집에서 출발하여 코레스코 콘도에 도착하는 경로 중 금품의 양이 최대가 되는 경로이다. 

입력

첫째 줄에 골목길들이 교차하는 지점의 개수 n (2 ≤ n ≤ 100)과 골목길의 개수 m (1 ≤ m ≤ 20,000) 이 차례로 주어진다. 이어지는 m개의 행에 각각의 골목길을 나타내는 세 정수 u, v, w가 차례로 주어진다. 이는 u번 교차점에서 v번 교차점으로 이동할 수 있는 골목길이 나있다는 의미이다. 즉, 주어지는 골목길들은 기본적으로 모두 일방통행로이다. w (0 ≤ |w| ≤ 1,000)는 이 길을 지날 때 갈취당하거나 획득하게 되는 금품의 양이다. 음수는 갈취, 양수는 획득을 뜻한다.

골목길의 교차점 번호는 1이상 n이하의 정수이다. 민승이네 집은 1번 교차점에 있고, 이곳 코레스코 콘도는 n번 교차점에 있다.

출력

최적의 경로를 구할 수 있다면 민승이네 집부터 코레스코 콘도까지 가는 동안 거치게 되는 교차점들의 번호를 공백 하나를 사이에 두고 차례로 출력하면 된다. 그런데, 경우에 따라서는 최적의 경로라는 것이 존재하지 않는 상황이 발생한다. 어떠한 경우에 그런 상황이 발생하는지 생각해 보자. 그러한 경우에는 -1을 출력하도록 한다.

최적의 경로가 여러 개 존재할 때는 아무거나 출력해도 된다.

 

2. 어떻게 풀까

  • 최단 경로 알고리즘을 이용해서 해결 할 수 있다.
  • 이 문제의 경우 최솟값을 구하는게 아니라 최댓값을 구하는 것이므로 음수 사이클을 찾는 것이 아니라 양수 사이클을 찾아야 한다.
  • 사이클이 형성되는지 확인 해야하므로 벨만 포드 알고리즘을 이용하여 해결했다.
    • 사이클이 존재하더라도 사이클이 목적지에 영향을 주지 않는다면 상관없다.
      • 이 점을 간과해서 틀렸다. ㅜ

 

3. 코드 (Java)

package baekjoon.shortest_path.골목길_20220318;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {

  private static final BufferedReader READER = new BufferedReader(new InputStreamReader(System.in));
  private static StringTokenizer tokenizer;

  private static int vertexCount;
  private static int edgeCount;
  private static int[][] edges;
  private static int[] distanceFromSource;
  private static int[] predecessor;

  public static void main(String[] args) throws IOException {
    tokenizer = new StringTokenizer(READER.readLine());
    vertexCount = Integer.parseInt(tokenizer.nextToken());
    edgeCount = Integer.parseInt(tokenizer.nextToken());

    init();
    bellmanFord();

    if (distanceFromSource[vertexCount] == Integer.MIN_VALUE) {
      System.out.println(-1);
      return;
    }

    for (int[] edge : edges) {
      int from = edge[0];
      int to = edge[1];
      int weight = edge[2];
      if (distanceFromSource[from] == Integer.MIN_VALUE) {
        continue;
      }

      if (isCycle(from, to, weight) && isCycleOnPath(to)) {
        System.out.println(-1);
        return;
      }
    }

    printShortestPath();
  }

  private static void init() throws IOException {
    edges = new int[edgeCount][];
    for (int i = 0; i < edgeCount; i++) {
      tokenizer = new StringTokenizer(READER.readLine());
      int from = Integer.parseInt(tokenizer.nextToken());
      int to = Integer.parseInt(tokenizer.nextToken());
      int weight = Integer.parseInt(tokenizer.nextToken());

      edges[i] = new int[]{from, to, weight};
    }

    distanceFromSource = new int[vertexCount + 1];
    predecessor = new int[vertexCount + 1];
    for (int i = 1; i <= vertexCount; i++) {
      distanceFromSource[i] = Integer.MIN_VALUE;
      predecessor[i] = -1;
    }
    distanceFromSource[1] = 0;
  }

  private static void bellmanFord() {
    for (int i = 0; i < vertexCount - 1; i++) {
      for (int[] edge : edges) {
        int from = edge[0];
        int to = edge[1];
        int weight = edge[2];
        if (distanceFromSource[from] == Integer.MIN_VALUE) {
          continue;
        }
        if (distanceFromSource[to] < distanceFromSource[from] + weight) {
          distanceFromSource[to] = distanceFromSource[from] + weight;
          predecessor[to] = from;
        }
      }
    }
  }

  private static boolean isCycle(int from, int to, int weight) {
    return distanceFromSource[to] < distanceFromSource[from] + weight;
  }

  private static boolean isCycleOnPath(int targetVertex) {
    boolean[] visit = new boolean[vertexCount + 1];
    Queue<Integer> queue = new LinkedList<>();
    queue.add(targetVertex);

    while (!queue.isEmpty()) {
      int currentVertex = queue.poll();
      if (visit[currentVertex]) {
        continue;
      }
      visit[currentVertex] = true;

      for (int[] edge : edges) {
        int from = edge[0];
        int to = edge[1];

        if (from == currentVertex && !visit[to]) {
          queue.add(to);
        }
      }
    }

    return visit[vertexCount];
  }

  private static void printShortestPath() {
    StringBuilder sb = new StringBuilder();
    int index = vertexCount;
    sb.append(vertexCount);
    while (predecessor[index] != -1) {
      sb.insert(0, predecessor[index] + " ");
      index = predecessor[index];
    }
    System.out.print(sb.toString());
  }
}
  • bellmanFord() 메소드를 통해 출발 노드부터 목적지 노드까지의 최단 경로를 구했다.
  • 목적지 노드까지의 경로가 없거나, 경로상에 양수 사이클이 존재할 경우 -1를 출력하도록 했다.
  • 경로 상에 양수 사이클이 존재하는지 검사했다.
    • 사이클이 존재하는 지 검증
      • 벨만 포드 알고리즘 이후에도 distanceFromSource가 갱신이 되는지 확인했다.
    • 사이클이 존재하고, 그 사이클이 경로상에 존재하는지 검증
      • 사이클이 존재하는 노드에서 목적지 노드까지의 경로가 있는지 BFS를 이용하여 확인했다.
      • 이 부분을 간과해서 틀렸다.
  • predecessor과 backtracking을 이용해서 최단 경로를 출력했다.
    • 처음에는 StringBuilder에 append()로 하나씩 추가한 후 reverse()로 마무리를 했었는데 계속 실패가 나와서 insert()로 바꾸었다.

 

벨만 포드 알고리즘을 알고 있다면 쉽게 풀 수 있긴 한데, 음수(문제는 양수)사이클이 출발 노드에서 목적지 노드까지의 경로에 포함되는지 검사하는 로직이 반드시 필요하다는것을 간과했다. 

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

[백준] 1944. 복제 로봇 (Java)  (0) 2022.01.20
백준 - 1238 - 파티 (java)  (0) 2021.05.26
백준 - 16398 - 행성 연결 (java)  (0) 2021.05.25
백준 - 14502번 - 연구소 (java)  (0) 2021.05.25

1. 문제

https://programmers.co.kr/learn/courses/30/lessons/43163

 

코딩테스트 연습 - 단어 변환

두 개의 단어 begin, target과 단어의 집합 words가 있습니다. 아래와 같은 규칙을 이용하여 begin에서 target으로 변환하는 가장 짧은 변환 과정을 찾으려고 합니다. 1. 한 번에 한 개의 알파벳만 바꿀 수

programmers.co.kr

문제 설명

제한 사항

입출력 예


2. 어떻게 풀까?

  • 인자로 주어지는 begin에서 시작해서 words에 주어진 단어들과 비교하면서 알파벳 하나만 바꿔서 그 단어가 될 수 있는지 확인한다.
  • words내에 방문한 단어들을 방문 표시하고, 모든 단어들을 방문하거나 target과 같은 단어를 방문하고 난 뒤에는 다시 방문 표시를 지워주어야 한다. 이게 무슨소리냐.. 말로 설명하니 어려우니까 그림으로 대체

visit가 다음과 같이 이루어진다고 가정하자. 방문한 word에 visit을 표시
cog까지 도착했을 때 distance는 6이다. 이제 dog로 돌아갈 때 visit 표시를 한 cog를 다시 unvisit로 표시하지 않으면 → cog로 갈 수가 없다
dog → log → cog로 갈 때보다, → cog로 가는 경로가 더 빠르다. 가장 빠른 길을 찾고 있기 때문에 갱신을 위해서 visit을 다시 unvisit로 바꿔주는 작업이 반드시 필요하다.

  • 아니 그럼 visit을 굳이 둘 필요 없지않냐? 라고 생각할 수도 있는데.... 그러면 무한루프에 hot → dot → hot → dot ... 이런식으로 무한루프에 빠지니까 visit또한 반드시 필요하다.

 

 

 

  • hit에서 hot으로, hot에서 dog로 알파벳 하나를 바꿔서 만들 수 있는 단어라는 것을 우리는 직관적으로 알 수 있지만 이것을 코드로는 어떻게 구현할까?
  • 단어와 단어 한 글자씩 비교하면서 다른 글자라면 count를 증가시키고, 그 count가 1이라면 알파벳 하나를 바꿔 만들 수 있는 단어가 된다고 판단하면 되겠다.

3. 코드 (Java)

package programmers.level3.단어변환_20220125;

import java.util.HashMap;
import java.util.Map;

public class Solution {

  public int result = Integer.MAX_VALUE;
  public Map<String, Boolean> visit;

  public int solution(String begin, String target, String[] words) {
    visit = new HashMap<>();
    for (String word : words) {
      visit.put(word, false);
    }
    dfs(begin, target, words, 0);
    return result == Integer.MAX_VALUE ? 0 : result;
  }

  private void dfs(String currentWord, String targetWord, String[] words, int distance) {

    if (currentWord.equals(targetWord)) {
      result = Math.min(result, distance);
      return;
    }

    for (String word : words) {
      if (!visit.get(word) && getCharDifferenceCount(currentWord, word) == 1) {
        visit.replace(word, true);
        dfs(word, targetWord, words, distance + 1);
        visit.replace(word, false);
      }
    }
  }

  private int getCharDifferenceCount(String a, String b) {
    if (a.length() != b.length()) {
      return -1;
    }

    int result = 0;
    for (int i = 0; i < a.length(); i++) {
      if (a.charAt(i) != b.charAt(i)) {
        result++;
      }
    }
    return result;
  }
}

 

 

 

 

 

 

사실 틀렸었다.

애매하게 테스트3만 틀려서 더 멘붕옴;

이유를 생각해봤는데.... 알파벳을 한 번 바꿔서 일치하는지 체크하는 부분에서 실수를 저질렀다.

    for (String word : words) {
      String erase = currentWord.replaceAll("[" + word + "]", "");
      if (!visit.get(word) && erase.length() == 1) {
        visit.replace(word, true);
        dfs(word, targetWord, words, distance + 1);
        visit.replace(word, false);
      }
    }

내 생각은 이랬다. "hot"에서 정규표현식을 이용하여 "[hit]"에 매치되는 부분을 지우면 "o"만 남게되고 그러면 알파벳 차이가 1개이니까 조건에 부합하겠구나! 나 천잰듯?

천재가 아니라 멍청이었다.

만약 단어가 "hto"였다면 어떨까? "hto" → "hit"가 되려면 알파벳을 2개 바꿔야한다. (t → i, o → t) 근데 저 코드대로라면 erase는 역시 "o"만 남게되고 조건에 맞으니까 if문을 통과하게 된다.

아니.. 개 허술한 코드였는데.. 다 틀려야지 하나만 틀리니까 더 찾기 힘들잖아.....................

그래서 정공법으로 코드 수정한 결과가 getCharDifferenceCount() 메소드를 이용한 것이었다.

그나마 다행인 것은 구글링해서 뭐가 틀렸는지 확인한 것이 아니라 스스로 틀린 부분을 생각해낸 부분이라는점..?

git bash에 git status 명령어를 입력하니 이렇게 나온다.

엉..? 디렉토리에 "공부_및_관련자료"가 나오는 것을 보면 단순히 한글 깨짐 현상은 아닌 것 같은데... 왜 이렇게 나오지?

구글링 결과 간단히 해결 가능하다

git config --global core.quotepath false 를 입력하면 된다.

참 쉽죠?

git config --global core.quotepath false

 

+ Recent posts