<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Lecture 3
Step1:  It's easy to determine the name of the variable; in this case, the name is $x$. It can be a bit more complicated to determine the type of the variable, as it depends on the value the variable is storing. In this case, it's storing the number 2. Since there's no decimal point on the number, we call this number an integer, or int for short.
Thus, in this case, the name of the variable is x and the type is int.
Numerical types
What other types of variables are there?
Step2:  In this example, since y is assigned a value of 2.0, it is referred to as a floating-point variable, or float for short. It doesn't matter that the decimal is 0; internally, Python sees the explicit presence of a decimal and treats the variable y as having type float.
Floats do the heavy-lifting of much of the computation in data science. Whenever you're computing probabilities or fractions or normalizations, floats are the types of variables you're using. In general, you tend to use floats for heavy computation, and ints for counting things.
There is an explicit connection between ints and floats. Let's illustrate with an example
Step3:  In this case, we've defined two variables x and y and assigned them integer values, so they are both of type int. However, we've used them both in a division operation and assigned the result to a variable named z. If we were to check the type of z, what type do you think it would be?
z is a float!
Step4:  How does that happen? Shouldn't an operation involving two ints produce an int? In general, yes it does. However, in cases where a decimal number is outputted, Python implicitly "promotes" the variable storing the result. This is known as casting, and it can take two forms
Step5:  Explicit casting, on the other hand, is a little trickier. In this case, it's you the programmer who are making explicit (hence the name) what type you want your variables to be. Python has a couple special built-in functions for performing explicit casting on variables, and they're named what you would expect
Step6:  Whoa! What's going on here?
With explicit casting, you are telling Python to override its default behavior. In doing so, it has to make some decisions as to how to do so in a way that still makes sense. When you cast a float to an int, some information is lost; namely, the decimal. So the way Python handles this is by quite literally discarding the entire decimal portion.
In this way, even if your number was 9.999999999 and you perfomed an explicit cast to int(), Python would hand you back a 9.
Language typing mechanisms
Python as a language is known as dynamically typed. This means you don't have to specify the type of the variable when you define it; rather, Python infers the type based on how you've defined it and how you use it. As we've already seen, Python creates a variable of type int when you assign it an integer number like 5, and it automatically converts the type to a float whenever the operations produce decimals.
Other languages, like C++ and Java, are statically typed, meaning in addition to naming a variable when it is declared, the programmer must also explicitly state the type of the variable.
There are pros and cons to both dynamic and static typing; in particular, some would argue that it's easier to make mistakes in dynamically typed languages, as one isn't always 100% certain of what Python's underlying type representation is without explicitly checking the type of the variable. On the other hand, not having to declare types every time you define a new variable can eliminate a lot of boilerplate code.
In fact, when checking for the type of the variable, Python implements what is known as duck typing
Step7:  Unlike numerical types like ints and floats, you can't really perform arithmetic operations on strings, with one exception
Step8:  The + operator, when applied to strings, is called string concatenation. This means, quite literally, that it glues or concatenates two strings together to create a new string. In this case, we took the string in x, concatenated it to an empty space " ", and concatenated that again to the string in y, storing the whole thing in a final string z.
Other than the + operator, the other arithmetic operations aren't defined for strings, so I wouldn't recommend trying them...
Step9:  Casting, however, is alive and well with strings. In particular, if you know the string you're working with is a string representation of a number, you can cast it from a string to a numeric type
Step10:  And back again
Step11:  Strings also have some useful methods that numeric types don't for doing some basic text processing.
Step12:  A very useful method that will come in handy later in the course when we do some text processing is strip(). Often when you're reading text from a file and splitting it into tokens, you're left with strings that have leading or trailing whitespace
Step13:  Anyone who looked at these three strings would say they're the same, but the whitespace before and after the word python in each of them results in Python treating them each as unique. Thankfully, we can use the strip method
Step14:  You can also delimit strings using either single-quotes or double-quotes. Either is fine and largely depends on your preference.
Step15:  Python also has a built-in method len() that can be used to return the length of a string. The length is simply the number of individual characters (including any whitespace) in the string.
Step16:  Variable comparisons and Boolean types
We can also compare variables! By comparing variables, we can ask whether two things are equal, or greater than or less than some other value. This sort of true-or-false comparison gives rise to yet another type in Python
Step17:  Hooray! The == sign is the equality comparison operator, and it will return True or False depending on whether or not the two values are exactly equal. This works for strings as well
Step18:  We can also ask if variables are less than or greater than each other, using the &lt; and &gt; operators, respectively.
Step19:  In a small twist of relative magnitude comparisons, we can also ask if something is less than or equal to or greater than or equal to some other value. To do this, in addition to the comparison operators &lt; or &gt;, we also add an equal sign
Step20:  Interestingly, these operators also work for strings. Be careful, though
Step21:  Part 2
Step22:  Comments are important to good coding style and should be used often for clarification. However, even more preferable to the liberal use of comments is a good variable naming convention. For instance, instead of naming a variable "x" or "y" or "c", give it a name that describes its purpose.
Step23:  I could've used a comment to explain how this variable was storing the length of the string, but by naming the variable itself in terms of what it was doing, I don't even need such a comment. It's self-evident from the name itself what this variable is doing.
Part 3
<ASSISTANT_TASK:>
Python Code:
x = 2
Explanation: Lecture 3: Python Variables and Syntax
CSCI 1360E: Foundations for Informatics and Analytics
Overview and Objectives
In this lecture, we'll get into more detail on Python variables, as well as language syntax. By the end, you should be able to:
Define variables of string and numerical types, convert between them, and use them in basic operations
Explain the different variants of typing in programming languages, and what "duck-typing" in Python does
Understand how Python uses whitespace in its syntax
Demonstrate how smart variable-naming and proper use of comments can effectively document your code
This week will effectively be a "crash-course" in Python basics; there's a lot of ground to cover!
Part 1: Variables and Types
We saw in the last lecture how to define variables, as well as a few of the basic variable "types" available in Python. It's important to keep in mind that each variable you define has a "type", and this type will dictate much (if not all) of the operations you can perform on and with that variable.
To recap: a variable in Python is a sort of placeholder that stores a value. Critically, a variable has both a name and a type. For example:
End of explanation
y = 2.0
Explanation: It's easy to determine the name of the variable; in this case, the name is $x$. It can be a bit more complicated to determine the type of the variable, as it depends on the value the variable is storing. In this case, it's storing the number 2. Since there's no decimal point on the number, we call this number an integer, or int for short.
Thus, in this case, the name of the variable is x and the type is int.
Numerical types
What other types of variables are there?
End of explanation
x = 2
y = 3
z = x / y
Explanation: In this example, since y is assigned a value of 2.0, it is referred to as a floating-point variable, or float for short. It doesn't matter that the decimal is 0; internally, Python sees the explicit presence of a decimal and treats the variable y as having type float.
Floats do the heavy-lifting of much of the computation in data science. Whenever you're computing probabilities or fractions or normalizations, floats are the types of variables you're using. In general, you tend to use floats for heavy computation, and ints for counting things.
There is an explicit connection between ints and floats. Let's illustrate with an example:
End of explanation
type(z)
Explanation: In this case, we've defined two variables x and y and assigned them integer values, so they are both of type int. However, we've used them both in a division operation and assigned the result to a variable named z. If we were to check the type of z, what type do you think it would be?
z is a float!
End of explanation
x = 2
y = 3
z = x * y
type(z)
x = 2.5
y = 3.5
z = x * y
type(z)
Explanation: How does that happen? Shouldn't an operation involving two ints produce an int? In general, yes it does. However, in cases where a decimal number is outputted, Python implicitly "promotes" the variable storing the result. This is known as casting, and it can take two forms: implicit casting (as we just saw), or explicit casting.
Casting
Implicit casting is done in such a way as to try to abide by "common sense": if you're dividing two numbers, you would all but expect to receive a fraction, or decimal, on the other end. If you're multiplying two numbers, the type of the output depends on the types of the inputs--two floats multiplied will likely produce a float, while two ints multiplied will produce an int.
End of explanation
x = 2.5
y = 3.5
z = x * y
print("Float z:\t{}\nInteger z:\t{}".format(z, int(z)))
Explanation: Explicit casting, on the other hand, is a little trickier. In this case, it's you the programmer who are making explicit (hence the name) what type you want your variables to be. Python has a couple special built-in functions for performing explicit casting on variables, and they're named what you would expect: int() for casting a variable as an int, and float for casting it as a float.
End of explanation
x = "this is a string"
type(x)
Explanation: Whoa! What's going on here?
With explicit casting, you are telling Python to override its default behavior. In doing so, it has to make some decisions as to how to do so in a way that still makes sense. When you cast a float to an int, some information is lost; namely, the decimal. So the way Python handles this is by quite literally discarding the entire decimal portion.
In this way, even if your number was 9.999999999 and you perfomed an explicit cast to int(), Python would hand you back a 9.
Language typing mechanisms
Python as a language is known as dynamically typed. This means you don't have to specify the type of the variable when you define it; rather, Python infers the type based on how you've defined it and how you use it. As we've already seen, Python creates a variable of type int when you assign it an integer number like 5, and it automatically converts the type to a float whenever the operations produce decimals.
Other languages, like C++ and Java, are statically typed, meaning in addition to naming a variable when it is declared, the programmer must also explicitly state the type of the variable.
There are pros and cons to both dynamic and static typing; in particular, some would argue that it's easier to make mistakes in dynamically typed languages, as one isn't always 100% certain of what Python's underlying type representation is without explicitly checking the type of the variable. On the other hand, not having to declare types every time you define a new variable can eliminate a lot of boilerplate code.
In fact, when checking for the type of the variable, Python implements what is known as duck typing: if it walks like a duck and quacks like a duck, it's a duck. As such, Python checks the properties of the variables you've defined and treats each variable as the type it most resembles. 
This brings us to a concept known as type safety. This is an important point, especially in dynamically typed languages where the type is not explicitly set by the programmer: there are countless examples of nefarious hacking that has exploited a lack of type safety in certain applications in order to execute malicious code.
A particularly fun example is known as a roundoff error, or more specifically to our case, a representation error. This occurs when we are attempting to represent a value for which we simply don't have enough precision to accurately store. This gets a little technical, but basically there are a certain number of bits allocated that represent the whole number part of a float, and a certain number that represent the decimal part of a float.
When there are too many decimal values to represent (usually because the number we're trying to store is very, very small), we get an underflow error.
For example, let's say we wanted to build an algorithm that automatically predicts whether an email we received is spam or not. To do this, we have to multiply a lot of probabilities together. Probabilities are floats between 0 and 1. Now imagine multiplying several hundreds to thousands of these values together; we'll very likely end up with tiny, tiny numbers. In the case of an underflow error, Python may very well set our total probability to 0 as it won't have enough bits to represent the full decimal.
When there are too many whole numbers to represent (usually because the number we're trying to store is very, very large), we get an overflow error.
One of the most popular examples of an overflow error was the Y2K bug. In this case, most Windows machines internally stored the year as simply the last two digits. Thus, when the year 2000 rolled around, the two numbers representing the year overflowed and reset to 00. A similar problem is anticipated for 2038, when 32-bit Unix machines will also see their internal date representations overflow to 0.
In these cases, and especially in dynamically typed languages like Python, it is very important to know what types of variables you're working with and what the limitations of those types are.
String types
Strings, as we've also seen previously, are the variable types used in Python to represent text.
End of explanation
x = "some string"
y = "another string"
z = x + " " + y
print(z)
Explanation: Unlike numerical types like ints and floats, you can't really perform arithmetic operations on strings, with one exception:
End of explanation
s = "2"
t = "divisor"
x = s / t
Explanation: The + operator, when applied to strings, is called string concatenation. This means, quite literally, that it glues or concatenates two strings together to create a new string. In this case, we took the string in x, concatenated it to an empty space " ", and concatenated that again to the string in y, storing the whole thing in a final string z.
Other than the + operator, the other arithmetic operations aren't defined for strings, so I wouldn't recommend trying them...
End of explanation
s = "2"
x = int(s)
print("x = {} and has type {}.".format(x, type(x)))
Explanation: Casting, however, is alive and well with strings. In particular, if you know the string you're working with is a string representation of a number, you can cast it from a string to a numeric type:
End of explanation
x = 2
s = str(x)
print("s = {} and has type {}.".format(s, type(s)))
Explanation: And back again:
End of explanation
s = "Some string with WORDS"
print(s.upper())  # make all the letters uppercase
print(s.lower())  # make all the letters lowercase
Explanation: Strings also have some useful methods that numeric types don't for doing some basic text processing.
End of explanation
s1 = " python "
s2 = "   python"
s3 = "python     "
Explanation: A very useful method that will come in handy later in the course when we do some text processing is strip(). Often when you're reading text from a file and splitting it into tokens, you're left with strings that have leading or trailing whitespace:
End of explanation
print("|" + s1.strip() + "|")
print("|" + s2.strip() + "|")
print("|" + s3.strip() + "|")
Explanation: Anyone who looked at these three strings would say they're the same, but the whitespace before and after the word python in each of them results in Python treating them each as unique. Thankfully, we can use the strip method:
End of explanation
s = "some string"
t = 'this also works'
Explanation: You can also delimit strings using either single-quotes or double-quotes. Either is fine and largely depends on your preference.
End of explanation
s = "some string"
len(s)
Explanation: Python also has a built-in method len() that can be used to return the length of a string. The length is simply the number of individual characters (including any whitespace) in the string.
End of explanation
x = 2
y = 2
x == y
Explanation: Variable comparisons and Boolean types
We can also compare variables! By comparing variables, we can ask whether two things are equal, or greater than or less than some other value. This sort of true-or-false comparison gives rise to yet another type in Python: the boolean type. A variable of this type takes only two possible values: True or False.
Let's say we have two numeric variables, x and y, and want to check if they're equal. To do this, we use a variation of the assginment operator:
End of explanation
s1 = "a string"
s2 = "a string"
s1 == s2
s3 = "another string"
s1 == s3
Explanation: Hooray! The == sign is the equality comparison operator, and it will return True or False depending on whether or not the two values are exactly equal. This works for strings as well:
End of explanation
x = 1
y = 2
x < y
x > y
Explanation: We can also ask if variables are less than or greater than each other, using the &lt; and &gt; operators, respectively.
End of explanation
x = 2
y = 3
x <= y
x = 3
x <= y
x = 3.00001
x <= y
Explanation: In a small twist of relative magnitude comparisons, we can also ask if something is less than or equal to or greater than or equal to some other value. To do this, in addition to the comparison operators &lt; or &gt;, we also add an equal sign:
End of explanation
s1 = "some string"
s2 = "another string"
s1 > s2
s1 = "Some string"
s1 > s2
Explanation: Interestingly, these operators also work for strings. Be careful, though: their behavior may be somewhat unexpected until you figure out what actual trick is happening:
End of explanation
# Adds two numbers that are initially strings by converting them to an int and a float,
# then converting the final result to an int and storing it in the variable x.
x = int(int("1345") + float("31.5"))
print(x)
Explanation: Part 2: Variable naming conventions and documentation
There are some rules regarding what can and cannot be used as a variable name. Beyond those rules, there are guidelines.
Variable naming rules
Names can contain only letters, numbers, and underscores.
All the letters a-z (upper and lowercase), the numbers 0-9, and underscores are at your disposal. Anything else is illegal. No special characters like pound signs, dollar signs, or percents are allowed. Hashtag alphanumerics only. 
Variable names can only start with letters or underscores.
Numbers cannot be the first character of a variable name. message_1 is a perfectly valid variable name; however, 1_message is not and will throw an error.
Spaces are not allowed in variable names.
Underscores are how Python programmers tend to "simulate" spaces in variable names, but simply put there's no way to name a variable with multiple words separated by spaces.
Avoid using Python keywords or function names as variables.
This might take some trial-and-error. Basically, if you try to name a variable print or float or str, you'll run into a lot of problems down the road. Technically this isn't outlawed in Python, but it will cause a lot of headaches later in your program.
Variable naming conventions
These are not hard-and-fast rules, but rather suggestions to help "standardize" code and make it easier to read by people who aren't necessarily familiar with the code you've written.
Make variable names short, but descriptive.
I've been giving a lot of examples using variables named x, s, and so forth. This is bad. Don't do it--unless, for example, you're defining x and y to be points in a 2D coordinate axis, or as a counter; one-letter variable names for counters are quite common.
Outside of those narrow use-cases, the variable names should constitute a pithy description that reflects their function in your program. A variable storing a name, for example, could be name or even student_name, but don't go as far as to use the_name_of_the_student.
Be careful with the lowercase l or uppercase O.
This is one of those annoying rules that largely only applies to one-letter variables: stay away from using letters that also bear striking resemblance to numbers. Naming your variable l or O may confuse downstream readers of your code, making them think you're sprinkling 1s and 0s throughout your code.
Variable names should be all lowercase, using underscores for multiple words.
Java programmers may take umbrage with this point: the convention there is to useCamelCase for multi-word variable names. Since Python takes quite a bit from the C language (and its back-end is implemented in C), it also borrows a lot of C conventions, one of which is to use underscores and all lowercase letters in variable names.
The one exception to this rule is when you define variables that are constant; that is, their values don't change. In this case, the variable name is usually in all-caps. For example: PI = 3.14159.
Self-documenting code
The practice of pithy but precise variable naming strategies is known as "self-documenting code."
We've learned before that we can insert comments into our code to explain things that might otherwise be confusing:
End of explanation
str_length = len("some string")
Explanation: Comments are important to good coding style and should be used often for clarification. However, even more preferable to the liberal use of comments is a good variable naming convention. For instance, instead of naming a variable "x" or "y" or "c", give it a name that describes its purpose.
End of explanation
x = 5
  x += 10
Explanation: I could've used a comment to explain how this variable was storing the length of the string, but by naming the variable itself in terms of what it was doing, I don't even need such a comment. It's self-evident from the name itself what this variable is doing.
Part 3: Whitespace in Python
Whitespace (no, not that Whitespace) is important in the Python language. Some languages like C++ and Java use semi-colons to delineate the end of a single statement. Python, however, does not, but still needs some way to identify when we've reached the end of a statement.
In Python, it's the return key that denotes the end of a statement. Returns, tabs, and spaces are all collectively known as "whitespace", and each can drastically change how your Python program runs. Especially when we get into loops, conditionals, and functions, this will become critical and may be the source of many insidious bugs.
For example, the following code won't run:
End of explanation
<END_TASK>