print(type('this is a string'))
print(type(101))
print(type(0.8))

"""
Explanation: Chapter 4 - Boolean Expressions and Conditions
This notebook uses code snippets and explanations from this course.
So far, we have learned how to use Python as a basic calculator and how to store information in variables. Now we will set the first steps to an actual useful program. A lot of programming has to do with executing code if a particular condition holds. This enables a program to act upon its inputs. For example: an app on your phone might give a warning if the battery level is lower than 5%. This means that the program needs to check if the variable battery_level is lower than the value of 5. We can do these checks using so called Boolean expressions. These Boolean expressions are the main element in probably one of the most used things in Python: if statements.
At the end of this topic, you will be able to:

work with and understand boolean expressions
work with and understand if statements
understand what indentation is
understand what nesting is

If you want to learn more about these topics, you might find the following links useful:

Documentation: Built-in Types (boolean expressions)
Video: Python Booleans
Video: Conditionals
Explanation: if elif else

If you have questions about this chapter, please contact us(cltl.python.course@gmail.com)**.
1. Boolean expressions
A Boolean expression (or simply: boolean) is an expression that results in the type bool in Python. Possible values are either True or False. Boolean expressions are the building blocks of programming. Any expression that results in True or False can be considered a Boolean expression. 
So far you've mainly seen:
End of explanation
"""


print(type(False))
print(type(True))

"""
Explanation: Now we're introducing:
End of explanation
"""


print(2 < 5)
print(2 <= 5)
print(3 > 7)
print(3 >= 7)
print(3 == 3)
print("school" == "school")
print("Python" != "SPSS")

"""
Explanation: 1.1 Comparison operators
Here is a list of comparison operators used in Boolean expressions:
(You have already used these operators in the previous chapters, but we are treating them in more detail here.) 
| Operator | Meaning | True | False |
|-----------|--------|--------|--------|
| == |    equal    | 2 == 2 | 2 == 3 | 
| != |    not equal   | 3 != 2 | 2 != 2 |
| &lt; | less than | 2 &lt; 13  | 2 &lt; 2 |
| &lt;= |    less than or equal to   |  2 &lt;= 2 | 3 &lt;= 2  |
| &gt; | greater than     | 13 &gt; 2  | 2 &gt; 13  |
| &gt;= |    greather than or equal to   |  3 &gt;= 3  | 2 &gt;= 3  |
Remember that the single = is reserved for assignment! Boolean expressions look at variables but never change them.
End of explanation
"""


greater = 5 > 2
print(greater, type(greater))
greater = 5 < 2
print(greater, type(greater))

"""
Explanation: The relevant 'logical operators' that we used here are: <, <=, >,>=,==,!=. In Python-speak, we say that such a logical expression gets 'evaluated' when you run the code. The outcome of such an evaluation is a 'binary value' or a so-called 'boolean' that can take only two possible values: True or False. You can assign such a boolean to a variable:
End of explanation
"""


print(5 == 5)

print(5 == 4)

print(10 < 20)

print(10 < 8)

print(10 < 10)

print(10 <= 10)

print(20 >= 21)

print(20 == 20)

print(1 == '1')

print(1 != 2)

boolean_expression = 5 == 4
print(boolean_expression)

"""
Explanation: Let's look at some examples. Try to guess the output based on the information about the operators in the table above. Hence, will the expression result in True or False in the following examples?
End of explanation
"""


print("fun" in "function")
print("pie" in "python")

"""
Explanation: 1.2 Membership operators
Python also has so-called membership operators:
| Operator | function | True | False |
|-----------|--------|--------|--------|
| in | left object is a member of right object | "c" in "cat" | "f" in "cat" |
| not in |left object is NOT a member of right object |  "f"  not in "cat" | "c" not in "cat" |
We have already seen the operator in being used for checking whether a string (single or multiple characters) is a substring of another one:
End of explanation
"""


print(5 in 10)

"""
Explanation: We can only use membership operators with iterables (i.e. python objects that can be split up into smaller components - e.g. characters of a string). The following will therefore not work, because an integer is not iterable:
End of explanation
"""


letters = ['a','b','c','d']
numbers = [1,2,3,4,5]
mixed = [1,2,3,'a','b','c']

print('a' in letters)

print('g' not in letters)

print('d' in mixed)

print(1 in numbers)

print(3 not in mixed)

print('a' not in 'hello world')

"""
Explanation: However, we can use membership operators with other types of 'containers', such as lists. We will discuss lists in much more detail later on, but they represent ordered sequences of objects like strings, integers or a combination. We can use in and not in to check whether an object is a member of a list:
End of explanation
"""


letters = ['a','b','c','d']
numbers = [1,2,3,4,5]

print('a' in letters and 2 in numbers)

print("z" in letters and 3 in numbers)

print("f" in letters and 0 in numbers)

"""
Explanation: 1.3 And, or, and not
Finally, boolean operations are often performed using the boolean operators and, or and not. Given two boolean expressions, bool1 and bool2, this is how they work:
| operation | function | True | False | 
|-----------|--------|----------|---------|
| bool1 and bool2 | True if both bool1 and bool2 are True, otherwise False | (5 == 5 and 3 &lt; 5) | (5 == 5 and 3 &gt; 5) |
| bool1 or bool2 |    True when at least one of the boolean expressions is True, otherwise False    |  (5 == 5 or 3 &gt; 5) | (5 != 5 or 3 &gt; 5) |
| not bool1 | True if bool1 is False, otherwise False | (not 5 != 5) | (not 5 == 5) |
Here are some examples of and:
End of explanation
"""


letters = ['a','b','c','d']
numbers = [1,2,3,4,5]

print('f' in letters or 2 in numbers)

print('a' in letters or 2 in numbers)

print('f' in letters or 10 in numbers)

"""
Explanation: Here are some examples of or:
End of explanation
"""


a_string = "hello"
letters = ['a','b','c','d']
numbers = [1,2,3,4,5]

print(not a_string.endswith("o"))

print(not a_string.startswith("o"))

print(not 'x' in letters)

print(not 4 == 4)

print(not (4 == 4 and "4" == 4))

"""
Explanation: Here are some example of not:
End of explanation
"""


print(not 'x' in letters)
print('x' not in letters)

print(not 4 == 4)
print(4 != 4)

"""
Explanation: Note that for some of these, there are alternative ways of writing them. For example, 'x not in y' and 'not x in y' are identical, and so are 'not x == y' and 'x != y'. For now, it does not really matter which one you use. If you want to read more about it, have a look here and here.
End of explanation
"""


print("test" != "testing" and 1 == 1 and 2 == 2 or 20 in [1, 20, 3, 4,5])

"""
Explanation: 1.4 EXTRA: all() and any()
Take a look at the following example. Do you think it is clear?
End of explanation
"""


letters = ['a','b','c','d']
numbers = [1,2,3,4,5]

list_bools1 = ['a' in letters, 
                2 in numbers]
print(list_bools1)
boolean_expression1 = all(list_bools1)
print(boolean_expression1)

list_bools2 = ['a' in letters, 
                20 in numbers]
print(list_bools2)
boolean_expression2 = all(list_bools2)
print(boolean_expression2)

"""
Explanation: Not really, right? Luckily, Python has another trick to deal with this type of examples: [all and any]((https://docs.python.org/3/library/functions.html#all). Given a list of boolean expressions, this is how they work:
| operation | function |
|-----------|--------|
| all | True if all boolean expressions are True, otherwise False | 
| any | True if at least one boolean expression is True, otherwise False |
If you don't completely understand all() and any(), don't worry, you will not necessarily need them right now. They are just a nice alternative to make your code more readable and you may appreciate that in the future.
Here are some examples of all():
End of explanation
"""


list_bools3 = ['f' in letters, 
                200 in numbers]
print(list_bools3)
boolean_expression3 = any(list_bools3)
print(boolean_expression3)

list_bools4 = ['a' in letters, 
               20 in numbers,
               2 in numbers]
print(list_bools4)
boolean_expression4 =  any(list_bools4)
print(boolean_expression4)

"""
Explanation: Here are some examples of any():
End of explanation
"""


number = 2 # try changing this value to 6
if number <= 5:
    print(number)

"""
Explanation: 2. Conditions: if statements
You might wonder why we took quite some time explaining boolean expresisons. One of the reasons is that they are the main element in probably one of the most used things in Python: if statements. The following picture explains what happens in an if statement in Python.

Let's look at an example (modify the value of number to understand what is happening here):
End of explanation
"""


number = 5
if number == 5: 
    print("number equals 5")
if number > 4: 
    print("number is greater than 4")
if number >= 5:
    print("number is greater than or equals 5")
if number < 6: 
    print("number is less than 6") 
if number <= 5:
    print("number is less than or equals 5")
if number != 6 :
    print("number does not equal 6")

"""
Explanation: You can use as many if statements as you like:
End of explanation
"""


number = 10 # try changing this value to 2
if number <= 5:
    print(number)
else:
    print('number is higher than 5')

"""
Explanation: 2.1 Two-way decisions
But what if we want to have options for two different scenarios? We could just use a bunch of if statements. However, Python has a more efficient way. Apart from if we also have the else statement for two-way decisions (modify the value of number to understand what is happening here):
End of explanation
"""


age = 21
if age < 12:
    print("You're still a child!")
elif age < 18:
    print("You are a teenager!")
elif age < 30:
    print("You're pretty young!")
else:
    print("Wow, you're old!")

"""
Explanation: Now Python always runs one of the two pieces of code. It's like arriving at a fork in the road and choosing one path to follow.
2.2 Multi-way decisions
But of course we don't have to stop there. If you have multiple options, you can use the elif statement. For every if block, you can have one if statement, multiple elif statements and one else statement. So now we know the entire if-elif-else construct:
End of explanation
"""


age = 21
if age < 12:
    print("You're still a child!")
if age < 18:
    print("You are a teenager!")
if age < 30:
    print("You're pretty young!")
else:
    print("Wow, you're old!")

"""
Explanation: First the if statement will be evaluated. Only if that statement turns out to be False the computer will proceed to evaluate the elif statement. If the elif statements in turn would prove to be False, the machine will proceed and execute the lines of code associated with the else statement. You can think of this coding structure as a decision tree! Remember: if somewhere along the tree, your machine comes across a logical expression which is True, it won't bother anymore to evaluate the remaining options! Note that the statements are evaluated in order of occurence.
Can you identify the difference between the code above and the code below? (Try changing age)
End of explanation
"""


person = "John"
print("hello!")
if person == "Alice":
    print("how are you today?")                  #this is indented
    print("do you want to join me for lunch?")   #this is indented
elif person == "Lisa":
    print("let's talk some other time!")         #this is indented
print("goodbye!")

"""
Explanation: Remember: 
- if-if: your code wil check all the if statements
- if-elif: if one condition results to True, it will not check the other conditions
Unless you need to check all conditions, using if-elif is usually preferred because it's more efficient.
3. Indentation
Let's take another look at the example from above (we've added line numbers):
python
1. if number &lt;= 5:
2.     print(number)
3. else:
4.     print('number is higher than 5')
You might have noticed that line 2 starts with 4 spaces. This is on purpose! The indentation lets Python know when it needs to execute the piece of code. When the boolean expression in line 1 is True, Python executes the code from the next line that starts four spaces or one tab (an indent) to the right. This is called indentation. All statements with the same distance to the right belong to the same 'block' of code.
Unlike other languages, Python does not make use of curly braces to mark the start and end of pieces of code, like if statements. The only delimiter is a colon (:) and the indentation of the code. Both four spaces and tabs can be used for indentation. This indentation must be used consistently throughout your code. The most popular way to indent is four spaces (see stackoverflow). For now, you do not have to worry about this, since a tab is automatically converted to four spaces in notebooks.
Take a look at the code below. We see that the indented block is not executed, but the unindented lines of code are. Now go ahead and change the value of the person variable. The conversation should be a bit longer now!
End of explanation
"""


x = float(input("Enter a number: "))
if x >= 0:
    if x == 0:
        print("Zero")
    else:
        print("Positive number")
else:
    print("Negative number")

"""
Explanation: 3.1 Nesting
We have seen that all statements with the same distance to the right belong to the same block of code, i.e. the statements within a block line up vertically. The block ends at a line less indented or the end of the file. 
Blocks can contain blocks as welll; this way, we get a nested block structure. The block that has to be more deeply nested is simply indented further to the right:

There may be a situation when you want to check for another condition after a condition resolves to True. In such a situation, you can use the nested if construct. As you can see if you run the code below, the second if statement is only executed if the first if statement returns True. Try changing the value of x to see what the code does.
End of explanation
"""


print(True and True)

print(False and True)

print(1 == 1 and 2 == 1)

print("test" == "test")

print(1 == 1 or 2 != 1)

print(True and 1 == 1)

print(False and 0 != 0)

print(True or 1 == 1)

print("test" == "testing")

print(1 != 0 and 2 == 1)

print("test" != "testing")

print("test" == 1)

print(not (True and False))

print(not (1 == 1 and 0 != 1))

print(not (10 == 1 or 1000 == 1000))

print(not (1 != 10 or 3 == 4))

print(not ("testing" == "testing" and "Zed" == "Cool Guy"))

print(1 == 1 and (not ("testing" == 1 or 1 == 0)))

print("chunky" == "bacon" and (not (3 == 4 or 3 == 3)))

print(3 == 3 and (not ("testing" == "testing" or "Python" == "Fun")))

print("test" != "testing" and 1 == 1 and 2 == 2 and 20 in [1, 20, 3, 4,5])

"""
Explanation: Exercises
Exercise 1:
It's important to practice a lot with boolean expressions. Here is a list of them, which orginate from learnpythonthehardway. Try to guess the output.
End of explanation
"""


# insert your code here

"""
Explanation: Exercise 2:
Write a small program that defines a variable weight. If the weight is > 50 pounds, print "There is a $25 charge for luggage that heavy." If it is not, print: "Thank you for your business." If the weight is exactly 50, print: "Pfiew! The weight is just right!". Change the value of weight a couple of times to check whether your code works. Make use of the logical operators and the if-elif-else construct!
End of explanation
"""


my_string = "hello"
if my_string == "hello":
print("world")

"""
Explanation: Exercise 3:
What's wrong in the following code? Correct the mistake.
End of explanation
"""


my_string = "hello"
if my_string == "hello":
  print("world")

"""
Explanation: Why is the last line in the following code red? Correct the mistake.
End of explanation
"""


my_string = "hello"
if my_string == "hello"
    print("world")

"""
Explanation: What's wrong in the following code? Correct the mistake.
End of explanation
"""


my_string = "hello"
if my_string = "hello":
    print("world")

"""
Explanation: What's wrong in the following code? Correct the mistake.
End of explanation
"""


x = float(input("Enter a number: "))
if x >= 0:
    if x == 0:
        print("Zero")
    else:
        print("Positive number")
else:
    print("Negative number")

"""
Explanation: Exercise 4:
Can you rewrite the code below without nesting? Hint: use the if-elif-else construct.
End of explanation
"""


orange_quality = "fresh"
orange_price = 1.75
# your code here

"""
Explanation: Exercise 5:
A friend wants your advice on how much oranges he should buy. Write a program that will give the advice to buy 24 oranges if the price is lower than 1.50 EUR per kg, 12 oranges if the price is between 1.50 EUR and 3 EUR, and only 1 orange if the price is higher than 3 EUR. But also tell him that he should only buy them if the oranges are fresh; otherwise, he should not get any. Use nesting and the if-elif-else construct.
End of explanation
"""


