#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#%% NUMPY MODULE: most useful to deal with (multidimensional) array and matrices
import numpy as np

# Define a 1D NumPy array (similar to a list, but with more functionalities, like element-wise operations
testarray1D = np.array([1, 5, 41.3, 2.0])
print(f'check array type: {type(testarray1D)}')

# Define a 2D NumPy array -> a matrix
testarray2D = np.array([[4, 9, 8, 3],
                        [2, 6, 3, 2],
                        [1, 1, 7, 4]])

# get dimensions (shape) of the 2D arrat:
dim = testarray2D.shape
print(f'dim: {dim}')
# The shape of the array tells you how many rows and columns are in the array.
# Here: 3 rows and 4 columns (3x4 matrix).

# Why bothering with numpy -> we can access specific elements, by indices 
# Access the 3rd element of the 1D array (index 2, since Python uses 0-based indexing)
third_elem = testarray1D[2] 
print(f'third_elem: {third_elem}')

# Access the element in the 2nd row and 3rd column of the 2D array:
second_third_elem = testarray2D[1, 2]  # 1st index is row, 2nd index is column
print(f'second_third_elem: {second_third_elem}\n')

# You can slice arrays to access a range of values:
# Get all rows but only the 2nd and 3rd columns (slice from column index 1 to 3):
second_to_third_col = testarray2D[:, 1:3]  # [:, 1:3] -> rows: all rows (:); columns: 2nd to 3rd
print(f'second_to_third_col: {second_to_third_col}')

# Access elements by lists of indices (advanced indexing):
# Here, we access the 1st and 3rd elements of the 1D array using a list of indices [0, 2]
first_third_elem = testarray1D[[0, 2]]  
# note different syntax: [[0,2]] to select multiple elements at different positions (non sequential)
print(f'first_third_elem: {first_third_elem}')

# # You can also use Boolean indexing to filter values:
# Create a Boolean list where True means we want the element and False means we skip it. 
first_third_elem2 = testarray1D[[True, False, True, False]]
print(f'first_third_elem2: {first_third_elem2}\n')
# This will return the 1st and 3rd elements, because those correspond to True in the Boolean list.

# Boolean indexing works in 2D arrays too:
k = np.array([[True, False, False, False],
              [False, False, True, False],
              [True, False, True, False]])
# This essentially flattens the matrix and selects only the True values.
elem_by_index = testarray2D[k]  
print(f'elem_by_index: {elem_by_index}')
# The result will be a 1D array containing only the elements at positions where 'k' is True.




