목차
- ☆ 표시가 붙은 부분은 스터디 중 나온 얘기 혹은 제 개인적인 생각이나 제가 이해한 방식을 적어놓은 것으로, 책에서 말하고자 하는 바와 다를 수 있습니다.
- 모든 이미지의 출처는 클린 코드(로버트 C. 마틴 저) 책 입니다.
2장 의미 있는 이름
의도를 분명히 밝혀라
⚈ 의도가 분명하게 이름을 지어라. 좋은 이름을 지으려면 시간이 걸리지만 좋은 이름으로 절약하는 시간이 훨씬 더 많다.
⚈ 변수, 함수, 클래스의 이름은 다음의 질문에 답해야 한다. 따로 주석이 필요하다면 의도를 분명히 드러내지 못했다는 말이다.
- 존재 이유는?
- 수행 기능은?
- 사용 방법은?
ind d; //경과 시간(단위: 날짜)
VS
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;
⚈ 의도가 드러나는 이름을 사용하면 코드 이해와 변경이 쉬워진다.
public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<int[]>();
for (int [] x : theList) { // theList에 무엇이 들었는지 알아야 한다.
if (x[0] == 4) { // 0번째 값이 왜 중요한지, '4'가 무슨 의미인지 알아야 한다.
list1.add(x);
}
}
return list1; // list1은 그냥 int[] 형인데 이걸 받은 쪽에서 어떻게 사용하는지 알아야 한다.
}
VS
public List<Cell> getFlaggedCells() {
List<Cell> flaggedCells = new ArrayList<Cells>();
for (Cell cell : gameBoard) {
if (cell.isFlagged()) {
flaggedCells.add(cell);
}
}
return flaggedCells;
}
그릇된 정보를 피하라
⚈ 프로그래머는 코드에 그릇된 단서를 남겨서는 안 된다.
- 예를 들어 hp는 유닉스 플랫폼을 가리키는 이름이므로 직각삼각형의 빗변(hypotenuse)를 구현할 때 hp를 사용하면 독자에게 그릇된 정보를 제공할 수 있다.
- 배열로 구현해놓고 accountList 라고 명명할 경우 List 자료구조로 보이게 하는 그릇된 정보를 제공하는 셈이다. -> 실제로 List를 썼다고 해도 아예 컨테이너 유형을 이름에 넣지 않는 편이 바람직하다.
⚈ 서로 흡사한 이름을 사용하지 않도록 주의
- XYZControllerForEfficientHandlingOfStrings
- XYZControllerForEfficientStorageOfStrings
⚈ 유사한 개념은 유사한 표기법을 사용한다.
- ☆ getName, getAddress, popAge, getPhoneNumber 이런식이면 곤란함
⚈ 소문자 L이나 대문자 O 변수를 쓰지 말자.
- O = 1l*O*0l*l;
의미 있게 구분하라
⚈ 연속된 숫자를 덧붙이는 방식을 피하자
- public static void copyChars(char a1[], char a2[]) (X)
- public static void copyChars(char source, char destination) (O)
⚈ 불용어(☆ 분석에 의미가 없는 단어)를 추가한 이름은 아무런정보도 제공하지 못한다.
- getActiveAccount();
- getActiveAccounts();
- getActiveAccountInfo();
- zork
- theZork
⚈ 읽는 사람이 차이를 알도록 이름을 지어라.
발음하기 쉬운 이름을 사용하라
⚈ 프로그래밍은 사회 활동이다. 발음하기 쉬운 이름을 선택하자.
- genymdhms (X)
검색하기 쉬운 이름을 사용하라
⚈ 문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 쉽게 눈에 띄지 않는다. 또한 검색도 어렵다.
- MAX_CLASSES_PER_STUDENT (O)
- 7 (X)
⚈ e는 영어에서 가장 많이 쓰이는 문자다. 십중팔구 거의 모든 프로그램, 거의 모든 문장에서 등장한다.
- 이런 관점에서 긴 이름이 짧은 이름보다 좋다.
인코딩을 피하라
⚈ ☆ 정보를 부호화해서 나타내는걸 피하라고 이해하면 될듯함.
⚈ 자바 프로그래머는 변수 이름에 타입을 인코딩할 필요가 없다. IDE는 코드를 컴파일하지 않고도 타입 오류를감지할 정도로 발전했다. 접두어(헝가리식 표기법이나 기타 인코딩 방식)는 옛날에 작성한 구닥다리 코드라는 징표가 되버린다.
- ☆ 예를들어 int nNumber, int g_nTimer 와 같은 것. (n은 정수임을 표현, g_는 전역변수임을 표현)
⚈ 인터페이스 클래스 이름 관련
- [A] 인터페이스 : IShapeFactory, 구현 : ShapeFactory
- [B] 인터페이스 : ShapeFactory, 구현 : ShapeFactoryImp
- [A]는 인터페이스라는 사실을 남에게 알린다. 과도한 정보를 제공하므로, 차라리 구현클래스를 인코딩한 [B]가 더 낫다.
- ☆ 구현 클래스에 Imp도 불용어라 생각함. CircleShapeFactory 처럼 하는게 좋을 것 같다.
자신의 기억력을 자랑하지 마라
⚈ 독자가 코드를 읽으면서 변수 이름을 자신이 아는 이름으로 변환해야 한다면 그 변수 이름은 바람직하지 못하다.
- r이라는 변수가 호스트와 프로토콜을 제외한 소문자 URL이라는 사실을 언제나 기억할 수 있을까?
- 명료함이 최고다.
클래스 이름
⚈ 클래스 이름과 객체 이름은 명사나 명사구가 적합하다.
- Customer, WikiPage, Account, AddressParser
메서드 이름
⚈ 메서드 이름은 동사나 동사구가 적합하다.
- postPayment, deletePage, save
- 접근자 : get, 변경자 : set, 조건자 : is 로 시작
- 생성자 중복정의(overload)할 때는 정적 팩토리 메서드를 사용(☆ 정적 팩토리 메서드 : LocaleTime을 리턴해주는 LocaleTime.of() 같은거)
기발한 이름은 피하라
⚈ 재미난 이름보다 명료한 이름을 선택하라.
- HolyHandGenerade (X)
- DeleteItems (O)
- ☆ 사실 예시로 나온걸 이해 못해서 왜 저게 재밌는 이름인지 이해 못했음..
한 개념에 한 단어를 사용하라
⚈ 이름이 다르면 독자는 당연히 클래스도 다르고 타입도 다르리라 생각한다. 추상적인 개념 하나에 단어 하나를 선택해 이를 고수하자.
- 예를 들어, 똑같은 메서드를 클래스마다 fetch, retrieve, get으로 제각각 부르면 혼란스럽다.
말장난을 하지 마라
⚈ 한 단어를 두 가지 목적으로 사용하지 마라.
- add(); // 더한다
- add(); // 추가한다.
- 이런식으로 쓰지 말란 얘기. 이미 add를 더할 때 쓰고 있었다면, 추가한다는 insert나 append로 하는게 적당함
해법(문제) 영역에서 가져온 이름을 사용하라
⚈ 해법 영역에서 먼저 가져오자 - 코드를 읽을 사람도 프로그래머다. 그러므로 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 들을 사용해도 괜찮다.
- ☆ 예를들어 그래프 형태의 구조에 대해 짜고 있으면서 굳이 간선관계를 association으로 정하는 것 보다는 그냥 edges 정도로 정하면 읽기 훨씬 편함.
⚈ 적절한 '프로그래머 용어'가 없다면 문제 영역에서 이름을 가져온다.
- 그럼 코드를 보수하는 프로그래머가 분야 전문가에게 의미를 물어 파악할 수 있다.
의미 있는 맥락을 추가하라
⚈ 대다수의 이름은 스스로 의미가 분명하지 않다. 그래서 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다. 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다.
- firstName, lastName, street, houseNumber, city, state, zipcode, ... -> 주소라는 사실을 알아챌 수 있다.
- firstName, lastName, state -> 이렇게만 있으면 주소라는 사실을 알아챌 수 없다.
- 따라서 addrFirstName, addrLastName, addrState -> 이렇게 의미 있는 맥락을 접두어를 추가하더라도 넣어줘야 한다.
- 더 좋은 방법은 그냥 Address라는 클래스를 생성하는 것이다.
불필요한 맥락을 없애라
⚈ 고급 휘발유 충전소(Gas Station Deluxe) 어플리케이션을 짠다고 가정하자. 모든 클래스 이름을 GSD로 시작하겠다는건 바람직하지 못하다.
- IDE 자동완성에 G만 쳐도 모든 클래스가 다 뜨게될것이다.
마치면서
⚈ 여느 코드 개선 노력과 마찬가지로 이름 역시 나름대로 바꿨다가는 누군가 질책할지도 모른다. 그렇다고 코드를 개선하려는 노력을 중단해서는 안 된다.
☆ 실습 해봤다.
실무에서 작성하는 코드는 그래도 어느정도 생각하며 짜긴 하는데, 알고리즘 문제 풀 때는 사실 절차지향적으로 푸는 편이고, 변수나 함수명도 줄여서 쓰는 경우가 많다. 그나마 좀 객체지향적으로 짠 코드를 기준으로(안그러면 함수나 클래스가 거의 없으니) 책 2장에서 나온 내용을 적용해 리펙토링보기로 했다.
- 원본 코드 (백준 2162를 푼 코드이다.)
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
class Point implements Comparable<Point> {
int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public int compareTo(Point o) {
if (this.x == o.x)
return this.y-o.y;
return this.x-o.x;
}
}
class Line {
Point a, b;
public Line(Point p1, Point p2) {
a = p1.compareTo(p2)<=0?p1:p2;
b = p1.compareTo(p2)<=0?p2:p1;
}
public Line(int x1, int y1, int x2, int y2) {
this(new Point(x1, y1), new Point(x2, y2));
}
public boolean isIntersection(Line ano) {
int res1 = Ccw.getCcw(this.a, this.b, ano.a) * Ccw.getCcw(this.a, this.b, ano.b);
int res2 = Ccw.getCcw(ano.a, ano.b, this.a) * Ccw.getCcw(ano.a, ano.b, this.b);
if (res1==0&&res2==0) {
return this.a.compareTo(ano.b)<=0 && ano.a.compareTo(this.b)<=0;
}
return res1<=0 && res2<=0;
}
}
class Ccw {
public static int getCcw(Point a, Point b, Point c) {
Point[] arr = {a,b,c,a};
int sum = 0;
for (int i = 0; i < 3; i++) {
sum += arr[i].x*arr[i+1].y-arr[i+1].x*arr[i].y;
}
return sum>0?1:sum<0?-1:0;
}
}
class UnionFind {
int[] parents;
public UnionFind(int num) {
parents = new int[num];
Arrays.fill(parents, -1);
}
public int find(int a) {
if (parents[a] < 0) return a;
return parents[a] = find(parents[a]);
}
public void union(int a, int b) {
a = find(a);
b = find(b);
if (a == b) return;
int h = parents[a]<parents[b]?a:b;
int l = parents[a]<parents[b]?b:a;
parents[h] += parents[l];
parents[l] = h;
}
}
public class Main {
UnionFind uf;
private int ni(StringTokenizer st) { return Integer.parseInt(st.nextToken()); }
private void solution() throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1<<16);
int n = Integer.parseInt(br.readLine());
uf = new UnionFind(n);
Line[] lines = new Line[n];
StringTokenizer st;
for (int i = 0; i < n; i++) {
st = new StringTokenizer(br.readLine());
lines[i] = new Line(ni(st), ni(st), ni(st), ni(st));
}
for (int i = 0; i < n; i++) {
for (int j = i+1; j < n; j++) {
if (lines[i].isIntersection(lines[j]))
uf.union(i, j);
}
}
int cnt = 0;
int min = 0;
for (int i = 0; i < n; i++) {
if (uf.parents[i] < 0) {
cnt++;
if (uf.parents[i] < min)
min = uf.parents[i];
}
}
System.out.printf("%d\n%d\n", cnt, -min);
}
public static void main(String[] args) throws Exception {
new Main().solution();
}
}
- 변경 과정 메모
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
// Ccw에서 상수를 사용하기 위해 추가함
enum DirectionOfVectors {
CLOCKWISE(-1),
COUNTER_CLOCKWISE(1),
PARALLEL(0);
private int direction;
DirectionOfVectors(int direction) {
this.direction = direction;
}
int getValue() {
return this.direction;
}
}
// 버퍼 사이즈를 상수로 사용하기 위해 추가함
enum BufferSize {
BUFFER_SIZE(1<<16);
private int bufferSize;
BufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
int getValue() {
return this.bufferSize;
}
}
class Point implements Comparable<Point> {
int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public int compareTo(Point o) {
if (this.x == o.x)
return this.y-o.y;
return this.x-o.x;
}
}
class Line {
// Point a, b;
// a, b로는 의미가 없으므로 변경! 생성 시 정렬해서 들어갈 것인데, 개념상으로 a쪽이 더 작은 크기의 점이 들어갈거라 start로 둠.
Point startPoint, endPoint;
// public Line(Point p1, Point p2) {
// a = p1.compareTo(p2)<=0?p1:p2;
// b = p1.compareTo(p2)<=0?p2:p1;
// }
// 이건 좀 애매하긴 하다. point1, point2가 맞긴함. 임의의 2개의 점을 넣어주는 부분이라. 책에선 쓰지 말랬지만 이게 맞는거같은데.. 어쨌든 p1, p2대신 point1,2로 변경
public Line(Point point1, Point point2) {
startPoint = point1.compareTo(point2)<=0?point1:point2;
endPoint = point1.compareTo(point2)<=0?point2:point1;
}
// public Line(int x1, int y1, int x2, int y2) {
// this(new Point(x1, y1), new Point(x2, y2));
// }
// 생성자 오버라이드가 필요할 경우 정적 팩토리 메서드를 사용하랬으므로 그렇게 해봄.
public static Line of(int x1, int y1, int x2, int y2) {
return new Line(new Point(x1, y1), new Point(x2, y2));
}
// public boolean isIntersection(Line ano) {
// int res1 = Ccw.getCcw(this.startPoint, this.endPoint, ano.startPoint) * Ccw.getCcw(this.startPoint, this.endPoint, ano.endPoint);
// int res2 = Ccw.getCcw(ano.startPoint, ano.endPoint, this.startPoint) * Ccw.getCcw(ano.startPoint, ano.endPoint, this.endPoint);
// if (res1==0&&res2==0) {
// return this.startPoint.compareTo(ano.endPoint)<=0 && ano.startPoint.compareTo(this.endPoint)<=0;
// }
// return res1<=0 && res2<=0;
// }
// 줄여서 써 놓은 부분들 풀로 작성. result1, 2의 경우 중간 계산식이라 둘이 합쳐져야 의미가 있어서 어떻게 처리해야할지 모르겠다.
// 아래처럼 나눠서 표현해보려고 했는데, 이러면 또 '그릇된 정보를 피하라 - 서로 흡사한 이름을 사용하지 않도록 주의' 부분에 딱 걸린다.
// 이 부분은 좀 생각을 해봐야 할 것 같다.
//
// private int intermediateDirectionFormula(Line baseLine, Line anotherLine) {
// int directionToAnotherLineStartPoint = Ccw.directionOfVectors(baseLine.startPoint, baseLine.endPoint, anotherLine.startPoint);
// int directionToAnotherLineEndPoint = Ccw.directionOfVectors(baseLine.startPoint, baseLine.endPoint, anotherLine.endPoint);
// return directionToAnotherLineStartPoint * directionToAnotherLineEndPoint;
// }
//
// public boolean isIntersection(Line anotherLine) {
// int intermediateDirectionFormulaOfBase = intermediateDirectionFormula(this, anotherLine);
// int intermediateDirectionFormulaOfAnother = intermediateDirectionFormula(anotherLine, this);
// if (intermediateDirectionFormulaOfBase==0 && intermediateDirectionFormulaOfAnother==0) {
// return this.startPoint.compareTo(anotherLine.endPoint)<=0 && anotherLine.startPoint.compareTo(this.endPoint)<=0;
// }
// return intermediateDirectionFormulaOfBase<=0 && intermediateDirectionFormulaOfAnother<=0;
// }
public boolean isIntersection(Line anotherLine) {
int result1 = Ccw.directionOfVectors(this.startPoint, this.endPoint, anotherLine.startPoint) * Ccw.directionOfVectors(this.startPoint, this.endPoint, anotherLine.endPoint);
int result2 = Ccw.directionOfVectors(anotherLine.startPoint, anotherLine.endPoint, this.startPoint) * Ccw.directionOfVectors(anotherLine.startPoint, anotherLine.endPoint, this.endPoint);
if (result1==0&&result2==0) {
return this.startPoint.compareTo(anotherLine.endPoint)<=0 && anotherLine.startPoint.compareTo(this.endPoint)<=0;
}
return result1<=0 && result2<=0;
}
}
class Ccw {
// public static int getCcw(Point a, Point b, Point c) {
// Point[] arr = {a,b,c,a};
// int sum = 0;
// for (int i = 0; i < 3; i++) {
// sum += arr[i].x*arr[i+1].y-arr[i+1].x*arr[i].y;
// }
// return sum>0?1:sum<0?-1:0;
// }
// ccw가 해법의 영역에서 가져온 이름이긴 하지만, ccw 자체의 사용법이 다양하고 여기선 단순히 방향성만 확인하므로 방향성을 더 잘 나타내도록 변경
// 또한 벡터 a->b와 a->c의 방향성을 나타내므로 그에 맞게 a,b,c도 변경. arr은 shoelace formula 사용을 위한 것이므로 변경
// 방향성을 숫자가 아니라 상수로 빼서 작성. 다만 계산식에 쓰일거라서 사용하는쪽에서 어차피 상수로 안쓸꺼라 이 부분은 좀 애매한 것 같다.
public static int directionOfVectors(Point basePoint, Point firstEndPoint, Point secondEndPoint) {
Point[] arrayForShoelaceFormula = {basePoint,firstEndPoint,secondEndPoint,basePoint};
int sum = 0;
for (int i = 0; i < 3; i++) {
sum += arrayForShoelaceFormula[i].x * arrayForShoelaceFormula[i+1].y - arrayForShoelaceFormula[i+1].x * arrayForShoelaceFormula[i].y;
}
if (sum > 0)
return DirectionOfVectors.COUNTER_CLOCKWISE.getValue();
else if (sum < 0)
return DirectionOfVectors.CLOCKWISE.getValue();
return DirectionOfVectors.PARALLEL.getValue();
}
}
class UnionFind {
int[] parents; // 해법의 영역에 해당하는 용어이므로 그대로 둬도 될 듯 하다.
public UnionFind(int num) {
parents = new int[num];
Arrays.fill(parents, -1);
}
// public int find(int a) {
// if (parents[a] < 0) return a;
// return parents[a] = find(parents[a]);
// }
// a 대신 인덱스로 변경. find는 해법의 영역에 해당하므로 그대로 둔다.
public int find(int index) {
if (parents[index] < 0) return index;
return parents[index] = find(parents[index]);
}
// public void union(int a, int b) {
// a = find(a);
// b = find(b);
// if (a == b) return;
// int h = parents[a]<parents[b]?a:b;
// int l = parents[a]<parents[b]?b:a;
// parents[h] += parents[l];
// parents[l] = h;
// }
// a, b에 어떤게 들어와도 되고 그냥 2개만 들어오면 되는 형태라 이것도 좀 애매하다. 둘의 차이가 없으므로 숫자를 붙이는게 맞을 것 같다.
// 현재 내가 구현한 분리 집합 알고리즘이 랭크를 이용한 구현이라 h와 l은 높은 랭크와 낮은 랭크를 의미한다. 이것도 변경했다.
// union은 해법의 영역에 해당하는 용어이므로 그대로 둔다.
public void union(int index1, int index2) {
index1 = find(index1);
index2 = find(index2);
if (index1 == index2) return;
int highRankedIndex = parents[index1]<parents[index2]?index1:index2;
int lowRankedIndex = parents[index1]<parents[index2]?index2:index1;
parents[highRankedIndex] += parents[lowRankedIndex];
parents[lowRankedIndex] = highRankedIndex;
}
}
public class Main {
// UnionFind uf;
// 그냥 풀 네임으로 변경
UnionFind unionFind;
// private int ni(StringTokenizer st) { return Integer.parseInt(st.nextToken()); }
// 풀 네임으로 변경
private int nextInt(StringTokenizer stringTokenizer) { return Integer.parseInt(stringTokenizer.nextToken()); }
private void solution() throws Exception {
// BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1<<16);
// 풀네임으로 변경 및 버퍼 사이즈 지정한 부분(메모리 초과를 피하기 위해)을 상수로 변경
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in), BufferSize.BUFFER_SIZE.getValue());
int n = Integer.parseInt(bufferedReader.readLine());
unionFind = new UnionFind(n);
Line[] lines = new Line[n];
// StringTokenizer st;
// 풀네임으로 변경
StringTokenizer stringTokenizer;
for (int i = 0; i < n; i++) {
stringTokenizer = new StringTokenizer(bufferedReader.readLine());
// lines[i] = new Line(ni(st), ni(st), ni(st), ni(st));
// 변경된 정적 팩토리 메소드를 사용하도록 변경
lines[i] = Line.of(nextInt(stringTokenizer), nextInt(stringTokenizer), nextInt(stringTokenizer), nextInt(stringTokenizer));
}
for (int i = 0; i < n; i++) {
for (int j = i+1; j < n; j++) {
if (lines[i].isIntersection(lines[j]))
unionFind.union(i, j);
}
}
int cnt = 0;
int min = 0;
for (int i = 0; i < n; i++) {
if (unionFind.parents[i] < 0) {
cnt++;
if (unionFind.parents[i] < min)
min = unionFind.parents[i];
}
}
System.out.printf("%d\n%d\n", cnt, -min);
}
public static void main(String[] args) throws Exception {
new Main().solution();
}
}
- 변경 결과
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
enum DirectionOfVectors {
CLOCKWISE(-1),
COUNTER_CLOCKWISE(1),
PARALLEL(0);
private int direction;
DirectionOfVectors(int direction) {
this.direction = direction;
}
int getValue() {
return this.direction;
}
}
enum BufferSize {
BUFFER_SIZE(1<<16);
private int bufferSize;
BufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
int getValue() {
return this.bufferSize;
}
}
class Point implements Comparable<Point> {
int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public int compareTo(Point o) {
if (this.x == o.x)
return this.y-o.y;
return this.x-o.x;
}
}
class Line {
Point startPoint, endPoint;
public Line(Point point1, Point point2) {
startPoint = point1.compareTo(point2)<=0?point1:point2;
endPoint = point1.compareTo(point2)<=0?point2:point1;
}
public static Line of(int x1, int y1, int x2, int y2) {
return new Line(new Point(x1, y1), new Point(x2, y2));
}
public boolean isIntersection(Line anotherLine) {
int result1 = Ccw.directionOfVectors(this.startPoint, this.endPoint, anotherLine.startPoint) * Ccw.directionOfVectors(this.startPoint, this.endPoint, anotherLine.endPoint);
int result2 = Ccw.directionOfVectors(anotherLine.startPoint, anotherLine.endPoint, this.startPoint) * Ccw.directionOfVectors(anotherLine.startPoint, anotherLine.endPoint, this.endPoint);
if (result1==0&&result2==0) {
return this.startPoint.compareTo(anotherLine.endPoint)<=0 && anotherLine.startPoint.compareTo(this.endPoint)<=0;
}
return result1<=0 && result2<=0;
}
}
class Ccw {
public static int directionOfVectors(Point basePoint, Point firstEndPoint, Point secondEndPoint) {
Point[] arrayForShoelaceFormula = {basePoint,firstEndPoint,secondEndPoint,basePoint};
int sum = 0;
for (int i = 0; i < 3; i++) {
sum += arrayForShoelaceFormula[i].x * arrayForShoelaceFormula[i+1].y - arrayForShoelaceFormula[i+1].x * arrayForShoelaceFormula[i].y;
}
if (sum > 0)
return DirectionOfVectors.COUNTER_CLOCKWISE.getValue();
else if (sum < 0)
return DirectionOfVectors.CLOCKWISE.getValue();
return DirectionOfVectors.PARALLEL.getValue();
}
}
class UnionFind {
int[] parents;
public UnionFind(int num) {
parents = new int[num];
Arrays.fill(parents, -1);
}
public int find(int index) {
if (parents[index] < 0) return index;
return parents[index] = find(parents[index]);
}
public void union(int index1, int index2) {
index1 = find(index1);
index2 = find(index2);
if (index1 == index2) return;
int highRankedIndex = parents[index1]<parents[index2]?index1:index2;
int lowRankedIndex = parents[index1]<parents[index2]?index2:index1;
parents[highRankedIndex] += parents[lowRankedIndex];
parents[lowRankedIndex] = highRankedIndex;
}
}
public class Main {
UnionFind unionFind;
private int nextInt(StringTokenizer stringTokenizer) { return Integer.parseInt(stringTokenizer.nextToken()); }
private void solution() throws Exception {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in), BufferSize.BUFFER_SIZE.getValue());
int n = Integer.parseInt(bufferedReader.readLine());
unionFind = new UnionFind(n);
Line[] lines = new Line[n];
StringTokenizer stringTokenizer;
for (int i = 0; i < n; i++) {
stringTokenizer = new StringTokenizer(bufferedReader.readLine());
lines[i] = Line.of(nextInt(stringTokenizer), nextInt(stringTokenizer), nextInt(stringTokenizer), nextInt(stringTokenizer));
}
for (int i = 0; i < n; i++) {
for (int j = i+1; j < n; j++) {
if (lines[i].isIntersection(lines[j]))
unionFind.union(i, j);
}
}
int cnt = 0;
int min = 0;
for (int i = 0; i < n; i++) {
if (unionFind.parents[i] < 0) {
cnt++;
if (unionFind.parents[i] < min)
min = unionFind.parents[i];
}
}
System.out.printf("%d\n%d\n", cnt, -min);
}
public static void main(String[] args) throws Exception {
new Main().solution();
}
}
일단 변경된 코드도 정답으로 체크되는걸보니 잘못 변경한 부분은 없는 것 같다.
솔직히 수식 부분은 더 길어지면서 더 알아먹기 힘들어진 것 같긴하다. 그리고 1,2 부분을 어떻게 처리할지 감이 안온다. 실무 영역에서는 그래도 좀 떨어지는 부분이 많은데 알고리즘은 아무래도 절차지향이 더 어울리다보니 적용하기 힘든 것 같다. 아무래도 실습 예제를 잘 못 고른것 같닼ㅋㅋㅋ. 그래도 한번씩 이렇게 의식적으로 해보면 자연스레 다른 곳에도 적용될테니 도움이 될 것 같다.
'Study > 클린코드' 카테고리의 다른 글
[클린코드] 6장. 객체와 자료 구조 (0) | 2023.01.02 |
---|---|
[클린코드] 5장. 형식 맞추기 (0) | 2023.01.02 |
[클린코드] 4장. 주석 (0) | 2022.12.21 |
[클린코드] 3장. 함수 (0) | 2022.12.20 |
[클린코드] 1장. 깨끗한 코드 (0) | 2022.12.14 |
댓글