# -*- coding: utf-8 -*-
"""
Some things to remember: 
    1) Everything in python is an object/variable 
    2) Everything in python is case-sensitive
    3) Indentation matters
    4) row/observation 1 is indexed '0' -> id[1] refers to second row!
"""

#%%     1. PYTHON BASICS 
s = 'a string variable' # A string variable
i = 2 # An integer
f = 3.256 # A float
b = True # Boolean (logical values)

# To see 'what is inside a variable/object: 
    # a) check the variable explorer on the right (different types, different colors...)
print(s)
print(i)
print(f)
print(b)

list1=[1,2]
print(list1)
list2=[i,f]
print(list2)

#%%     2. PYTHON AS A CALCULATOR 
print((1+2)*5)
# Also in the Console: try writing 16 ** 0.5

result1 = 1 + 1
print('result1: result1') # here python doesn't know we are calling an object

print(f'result1: {result1}') # here it does!
# Alternative syntax: %i calls the object (an i(nteger))
print('result1: %i' % result1)

result2 = 5 * (4 - 1) ** 2
print(f'result2: {result2}')

# Print both -> implies defining a LIST
result3 = [result1, result2]
print(f'result3: {result3}')

#%%     3. MORE OBJECTS  
# More objects 
result1 = 1 + 1
# determine the type:
type_result1 = type(result1)
# print the result:
print(f'type_result1: {type_result1}')
result2 = 2.5
type_result2 = type(result2)
print(f'type_result2: {type_result2}')
result3 = 'To be, or not to be: that is the question'
type_result3 = type(result3)
print(f'type_result3: {type_result3}\n')

#%%     4. MORE LISTS  
example_list = [1, 5, 41.3, 2.0]
print(f'type(example_list): {type(example_list)}\n')

# access first entry by index:
first_entry = example_list[0]
print(f'first_entry: {first_entry}\n')

# access second to fourth entry by index:
range2to4 = example_list[1:4]
print(f'range2to4: {range2to4}\n')

# replace third entry by new value:
example_list[2] = 3
print(f'example_list: {example_list}\n')

# apply a function:
function_output = min(example_list)
print(f'function_output: {function_output}\n')

# apply a method:
example_list.sort()
print(f'example_list: {example_list}')

# delete third element of sorted list:
del example_list[2]
print(f'example_list: {example_list}')


#%%     4. LOGICAL OPERATORS   
# Logical operators: 
# Is equal to:
x=1
y=2
print(x==y) 
# Is different from 
print(x!=y)
print(x or y)

#%%     5. LOGICAL OPERATORS FOR CONDITIONS   
if x==1:
    print('yes')
else:
    print('no')
    
if x==1:
    print('yes')
    
    
# Nearly the same logic applies to Error handling
try:
    result = 10 / 0
except ZeroDivisionError:
    print("You can't divide by zero!")

# A more general error
try:
    result = 10 / 0
except Exception:
    print("Didn't work")
    

#%%     6. LOOPS OVER LIST
list1=[1,3,5,7,2,10,1]
for l in list1:
    print(l)

# Same result, different syntax: the loop goes from 0 to the length of the list (7)    
for i in range(0,len(list1)):
    print(list1[i])

# adding a conditional statement: 
for l in list1:
    if l>5:
        print(f'{l} is greater than 5')

# adding a complete conditional statement:         
for l in list1:
    if l>5:
        print(f'{l} is greater than 5')
    if l<=5:
        print(f'{l} is smaller or equal to 5')

#%%    7. UNORDERED LISTS: DICTIONARIES 
# Dictionaries (dict) are unordered sets of components. You access components by their unique keys.

var1 = ['95985', '30368', '20598'] # A list of student IDs (as strings).
var2 = [16, 18, 30] # A list of grades for each student (as integers).
var3 = [False, True, True] # A list of booleans indicating whether each student has passed (True or False).

# dict1: A dictionary is created using the dict() constructor. It has three key-value pairs:
dict1 = dict(name=var1, grade=var2, passed=var3)
print(f'{dict1}')

# another way to define the dict: -> remember this one, we will use it again later
dict2 = {'id': var1, 'grade': var2, 'passed': var3}
print(f'{dict2}')

# get data type with function type()
print(f'type: {type(dict2)}')

# Why bother in moving from ordered lists to dict? Because we can access values by KEY
# The dictionary key 'grade' returns the list of all grades.
grade_all = dict2['grade']
print(f'These are the grades: {grade_all}')

# but we can also access a specific value: for instance grade of Student 1:
grade_s1 = dict2['grade'][0]
id_s1 = dict2['id'][0]
print(f'Student {id_s1} grade: {grade_s1}')
# REMEMBER THAT index 0 indicates the FIRST, index 1 the SECOND

# We can also change specific values in the dictionary: 
# for instance, imagine we want add 2 to score of student 1 in order to let him pass:
dict2['grade'][0] = dict2['grade'][0] + 2
dict2['passed'][0] = True
print(f'dict2: {dict2}')
#this syntax -> replace this element with something else

# add a NEW variable 'real grade':
dict2['real_grade'] = [16,18,20]
print(f'dict2: {dict2}')


