Class 2 - Find the Torsional Angle

class와 벡터연산까지 뒤섞여 있는 문제라 처음엔 엄청 당황했지만 하나씩 하나씩 차근차근 해보자.

문제 해석

  • 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개까지 구하라.

문제 풀이

  1. 이번에도 HackerRank에 이미 주어진 코드를 먼저 보자.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import 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)
      1
      2
      3
      4
      5
      class Points(object):
      def __init__(self, x, y, z):
      self.x = x
      self.y = y
      self.z = z
      이렇게 인풋 시 넣은 숫자들 x,y,z 는 이제 객체 안에서의 변수가 된다.
  • 다음으로 __sub__이라는 게 나오는데 빼기(‘-‘) 연산자를 통해 수행할 작업을 지정해주는 것이라고 한다. 빼기 기호 뒤에 나오는 게 필요하니까 no라는 parameter가 온다. (number object의 줄임말이려나?)
    • 연산을 수행하려면 no 또한 벡터처럼 숫자쌍의 형태를 가져야만 할 것인데 주어진 코드에서 빼기연산을 수행하는 대상들은 다 Points 객체이므로 문제 없을 듯.
  • 두 벡터의 뺄셈은 벡터합에 따르면 각 항을 뺀 값을 가진 또다른 벡터일 것이다. 그러므로 아래와 같이 __sub__을 정의할 수 있다. 결과값을 Points 객체인 벡터로 내야 한다.
1
2
def __sub__(self, no):
return Points(self.x - no.x, self.y - no.y, self.z - no.z)
  • 이번에는 드디어 내적(dot product)을 계산하도록 dot이라는 메소드를 만들어보자.

    • 위의 뺄셈 연산과 비슷하게 뒤에 오는 또다른 벡터 객체 no에 대해 간단한 연산을 하도록 지정해주면 된다.
    • 내적은 두 벡터 숫자쌍의 각 항의 곱을 모두 더한 값이다.
      1
      2
      def dot(self, no):
      return self.x*no.x + self.y*no.y + self.z*no.z
  • 이번에는 외적(cross product)을 계산하는 메소드를 만들어준다.

    • 외적은 벡터 값을 결과물로 생산하므로, Points 객체로 return 해줘야 한다.
    • 외적의 공식은… 아래 코드에서 직접 확인하자.
      1
      2
      def 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
      2
      def absolute(self):
      return pow((self.x ** 2 + self.y ** 2 + self.z ** 2), 0.5)

이제 위의 모든 코드들을 합쳐주자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Points(object):
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z

def __sub__(self, no):
return Points(self.x - no.x, self.y - no.y, self.z - no.z)

def dot(self, no):
return self.x*no.x + self.y*no.y + self.z*no.z

def 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)

def absolute(self):
return pow((self.x ** 2 + self.y ** 2 + self.z ** 2), 0.5)

느낀 점

하나 하나씩 차근차근히 하면 못할 것이 없다. (비록 5시간이 걸리지만)