# SAI Season7 S+ Team6 "SuPLeme" 
# 3rd week

# ====================================================================================== #
# Practical Problem

1) 3차원 공간에서 임의의 점 3개를 생성한다. 생성된 좌표들로 삼각형을 만들 수 있는지 판별하시오.

1-1) numpy의 random을 사용하여 3차원 공간에서 [-100, 100] 사이의 임의의 정수를 추출해 3좌표 a, b, c를 만드시오. [1]

import numpy as np

def generate_3dots():
    p0 = np.random.randint(-100, 101, size = 3)
    p1 = np.random.randint(-100, 101, size = 3)
    p2 = np.random.randint(-100, 101, size = 3)
    return p0, p1, p2

a, b, c = generate_3dots()
  
1-2) 위 추출된 3좌표로 삼각형을 만들 수 있는 지 확인하시오. [7]
     불가능할 시 가능할 때까지 좌표를 랜덤하게 추출하시오.
     이 후, 삼각형이 완성가능한 좌표를 생성할 때 출력하시오.
Hint) 3좌표 중 임의의 2점을 이어 선분을 벡터로 a,b로 만들었을 때, a = kb꼴로 나타나진다면, 삼각형을
      이룰 수 없음. (단, k는 임의의 실수)
      a-b, a-c를 계산한 후, x좌표, y좌표, z좌표가 모두 임의의 실수 k배로 꼴로 표현되는지 확인해보세요!

def l2_norm(v):
    return np.sqrt(np.sum(v * v))

def is_triangle(p0, p1, p2):
    v01 = np.subtract(p1, p0)
    v02 = np.subtract(p2, p0) 
    if np.dot(v01, v02) != l2_norm(v01) * l2_norm(v02):
        return True
    else:
        return False

# If there are two dots a and b, the vector which is from a to b is equal to (b - a).

# A: Three dots a, b, and c cannot make a triangle.
# B: Two vectors u and v such that u = b - a and v = c - a are parallel.
# C: The dot product of u and v is equal to the product of norms of them.
# D: The cross product of u and v is equal to zero.
# E: The one is the other's multiple(u = kv or v = ku).
# A ~ E are equivalent.

# We'd used C in the function 'is_triangle' to investigate if three dots can make a triangle.
# The function has a problem: cannot compute the product of the norms exactly because of the "Floating-point error".
# <Example>
# p0 = [0,0,0], p1 = [1,1,1], p2 = [2,2,2]
# v01 = [1,1,1], v02 = [2,2,2]
# np.dot(v01, vo2) = 6
# l2_norm(v01) * l2_norm(v02) = 5.9999999... (by Floating-point error)
# np.dot(v01, v02) != l2_norm(v01) * l2_norm(v02) => False

def is_triangle1(p0, p1, p2):
    v01 = np.subtract(p1, p0)
    v02 = np.subtract(p2, p0)
    if np.array_equal(v01, np.zeros(3)):
        return False
    elif np.array_equal(v02, np.zeros(3)):
        return False
    elif np.prod(v01) != 0:
        ratio = v02 / v01
        if ratio[0] == ratio[1] and ratio[1] == ratio[2]:
            return False
        else:
            return True
    elif np.prod(v02) != 0:
        ratio = v01 / v02
        if ratio[0] == ratio[1] and ratio[1] == ratio[2]:
            return False
        else:
            return True
    else:
        for i in range(3):
            if v01[i] != 0:
                nonzero_idx = i
                break
        ratio = v02[nonzero_idx] / v01[nonzero_idx]
        if all(ratio * v01[i] == v02[i] for i in range(3)):
            return False
        else:
            return True

# E_A: v01 = k * v02 or v02 = k * v01
# E_B: v01[i] / v02[i] or v02[i] / v01[i] is constant for i = 0, 1, 2
# E_A and E_B are equivalent
# So we can know if v01 = k * v02 or v02 = k * v01 by deviding v02[i] by v01[i] for i = 0, 1, 2.
# But we need to check if the denominator is equal to zero before dividing.
# If one of the vectors is equal to zero vector, return False.
# If one of the vectors has no zero, divide the other vector by it.
# If the above two conditions are false, two vectors have both zero element and non-zero element.

while is_triangle1(a, b, c) == False:
    a, b, c = generate_3dots()

print(np.array([a,b,c]))

# The result of print function above is a matrix, which contains the coordinates of three dots.
# 1st row: coordinates of a
# 2nd row: coordinates of b
# 3rd row: coordinates of c

2) 행렬의 덧셈은 행과 열의 크기가 같은 두 행렬의 같은 행, 같은 열의 값을 서로 더한 결과가 된다.
   최대 10X10 크기인 두 개의 행렬을 랜덤으로 만들고, 그 두개의 행렬 덧셈의 결과를 반환하는 함수,
   solution을 완성하시오.
2-1) 최대 크기가 10X10인 두 행렬을 랜덤으로 생성하시오. [2]

nrow = np.random.randint(1, 11)
ncol = np.random.randint(1, 11)

matA = np.random.randint(-100, 101, size = (nrow, ncol))
matB = np.random.randint(-100, 101, size = (nrow, ncol))

2-2) 두 행렬의 덧셈을 하시오. [2]

def solution(mat0, mat1):
    return np.array([np.add(mat0[i], mat1[i]) for i in range(mat0.shape[0])])

mat_result = solution(matA, matB)

2-3) 행렬의 덧셈 값을 출력하시오. [2]

print(mat_result)

2-4) 행렬의 크기를 출력하시오. [2]

print("{}X{}".format(mat_result.shape[0], mat_result.shape[1]))

# ====================================================================================== #
# Theoretical Problem

1) numpy의 np.append()함수에서 axis를 설정하지 않았을 때, 
   두 배열의 차원이 일치하지 않아도 1차원 배열을 반환한다. [2]
    O -> np.append() 디폴트값이 axis=None이고, 이는 차원과 상관없이 원소를 추가하여 1차원 배열을 return해줌 / numpy.append(arr, values, axis=None)
    #근거 코드 
			arr1 = np.arange(1,13)
			arr2 = np.arange(13,25).reshape(3,4)
			arr3 = np.append(arr1, arr2) #axis가 없으면 1차원 배열로 변형해서 붙임
			print(arr3) #오류없이 배열 붙음
			
2) np.sort() 함수를 사용하면 원본 배열이 직접 변경된다. [2]
	 --> X -> 원본 변경 안됨 / np.sort()함수는 정렬된 배열을 return을 해주는데, return값이 있는 것은 대부분 원본이 변경이 안됨. 즉, return값이 없으면 원본이 변경되니 조심해야함
		#근거 코드
			arr=np.random.randint(10, size=10)
			print(arr)
			#np.sort(배열) default : 오름차순
			print(np.sort(arr))
			print(arr)
			
3) np.reshape 함수를 사용하여 배열의 총 원소 수가 변할 수 있다. [2] 
	 --> X -> reshape은 차원(형태) 변경에 주로 사용됨
		#근거 코드 
			 arr=np.arange(1,13).reshape(3,5)
			 print(arr) # 오류발생
			 
4) np.concatenate() 함수는 기본적으로 배열을 0축(행 방향)으로 병합한다. [2]
	 --> O -> concatenate 함수의 매개변수 디폴트 : numpy.concatenate((a1, a2, ...), axis=0, out=None, dtype=None, casting="same_kind")
	  #근거 코드
			arr1 = np.arange(1,13).reshape(3,4)
			arr2 = np.arange(13,25).reshape(3,4)

			arr_default=np.concatenate([arr1,arr2])
			arr_axis0=np.concatenate([arr1,arr2], axis =0 )

			print(arr1)
			print(arr_default)
			print(arr_axis0)

5) numpy 배열을 수평으로 병합하기 위해 np.hstack() 함수를 사용할 수 없다. [2]
   --> X  -> 수평 병합 : hstack
    #근거 코드 
			arr1 = np.arange(1,13).reshape(3,4)
			arr2 = np.arange(13,25).reshape(3,4)

			arr_hstack = np.hstack((arr1, arr2))
			print(arr_hstack)