class와 벡터연산까지 뒤섞여 있는 문제라 처음엔 엄청 당황했지만 하나씩 하나씩 차근차근 해보자.
- Class 2 - Find the Torsional Angle 문제 보러가기
- 벡터와 내적, 외적 구하는 방법은 따로 공부해서 이 글(Vector, Dot product, Cross Product)에 작성해두었으니 참고할 것
문제 해석
- 3차원 공간의 데카르트 좌표계(Cartesian coordinate system) 위 4개의 점 A, B, C, D
- A, B, C로 만들어진 면(plane)과 B,C,D로 만들어진 면 사이의 각 PHI를 출력하라.
- 여기서 X.Y는 X와 Y의 내적(Dot Product), ABxBC는 벡터 AB와 벡터 BC의 외적(Cross Product)를 의미한다.
Input Format
space로 나뉜 3개의 floating number X,Y,Z가 좌표상의 숫자쌍으로써 주어진다.
Output Format
PHI 각을 소수점 뒤 2개까지 구하라.
문제 풀이
- 이번에도 HackerRank에 이미 주어진 코드를 먼저 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import math
#class 부분은 이따 볼거니까 생략
if __name__ == '__main__':
points = list()
for i in range(4):
a = list(map(float, input().split()))
points.append(a)
a, b, c, d = Points(*points[0]), Points(*points[1]), Points(*points[2]), Points(*points[3])
x = (b - a).cross(c - b)
y = (c - b).cross(d - c)
angle = math.acos(x.dot(y) / (x.absolute() * y.absolute()))
print("%.2f" % math.degrees(angle))
- 코사인 값이나 제곱근을 구하는 메소드 때문인지 처음에 math가 소환된 걸 알 수 있다.
points
라는 리스트는 처음에 빈 리스트였다가, 4번의 input이 입력되어 아래 작업을 수행한 후append
로 들어간다.split()
으로 공백이 제거된 input string은 여전히 string이기에map
함수로 각 글자를 float number로 type casting해주었다.- 위의 결과물은 map 객체이므로
list()
로 type casting후 points라는 리스트 안에 넣어주었다.
- a,b,c,d는 모두 Points라는 클래스로 만들어진 객체들인데, 위에서 만든
points
라는 리스트에 0번째부터 3번째까지의 요소이다. 즉 input이 입력된 후 위의 일련의 작업을 수행한 결과가 Points의 객체화가 된 것. - 그 밑의 x와 y가 들어간 연산들을 보니, 우리가 만들어야 할 메소드로
cross()
와dot()
이 보인다.
문제 해결하기
- 이제 위 코드가 작동할 수 있게끔 Points라는 class를 만들어보자.
- initializer에 들어가는 parameter x,y,z 그대로 instance 변수명으로 지정해주자
- initializer의 인자로 points의 0~3번째 리스트가 각각 들어간다.
- 즉 첫번째 input이 ‘0 4 5’라면
Points(*points[0])
는Points(0,4,5)
가 되는 것이다 (*은 뒤에 오는 group object를 unpacking)이렇게 인풋 시 넣은 숫자들 x,y,z 는 이제 객체 안에서의 변수가 된다.1
2
3
4
5class Points(object):
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
- 다음으로
__sub__
이라는 게 나오는데 빼기(‘-‘) 연산자를 통해 수행할 작업을 지정해주는 것이라고 한다. 빼기 기호 뒤에 나오는 게 필요하니까 no라는 parameter가 온다. (number object의 줄임말이려나?)- 연산을 수행하려면 no 또한 벡터처럼 숫자쌍의 형태를 가져야만 할 것인데 주어진 코드에서 빼기연산을 수행하는 대상들은 다 Points 객체이므로 문제 없을 듯.
- 두 벡터의 뺄셈은 벡터합에 따르면 각 항을 뺀 값을 가진 또다른 벡터일 것이다. 그러므로 아래와 같이
__sub__
을 정의할 수 있다. 결과값을 Points 객체인 벡터로 내야 한다.
1 | def __sub__(self, no): |
이번에는 드디어 내적(dot product)을 계산하도록 dot이라는 메소드를 만들어보자.
- 위의 뺄셈 연산과 비슷하게 뒤에 오는 또다른 벡터 객체 no에 대해 간단한 연산을 하도록 지정해주면 된다.
- 내적은 두 벡터 숫자쌍의 각 항의 곱을 모두 더한 값이다.
1
2def dot(self, no):
return self.x*no.x + self.y*no.y + self.z*no.z
이번에는 외적(cross product)을 계산하는 메소드를 만들어준다.
- 외적은 벡터 값을 결과물로 생산하므로, Points 객체로 return 해줘야 한다.
- 외적의 공식은… 아래 코드에서 직접 확인하자.
1
2def cross(self, no):
return Points(self.y*no.z-self.z*no.y, self.x*no.z-self.z*no.x, self.x*no.y-self.y*no.x)
마지막으로 벡터의 magnitude 절대값을 지정하는 메소드 absolute는 친절하게도 HackerRank에서 이미 지정해주었다.
- 각 숫자쌍을 제곱하여 더한 후 제곱근을 한 값이다.
1
2def absolute(self):
return pow((self.x ** 2 + self.y ** 2 + self.z ** 2), 0.5)
- 각 숫자쌍을 제곱하여 더한 후 제곱근을 한 값이다.
이제 위의 모든 코드들을 합쳐주자.
1 | class Points(object): |
느낀 점
하나 하나씩 차근차근히 하면 못할 것이 없다. (비록 5시간이 걸리지만)