<jupyter_start><jupyter_text>3. Python Built-in Data Structure  List Python lists are very versatile containers (boxes) that are extremely userful in many circumstances. A list in Python, defined using brackets `[]`, is an ordered collection of objects that can contain arbitrary numbers of Python objects, such as strings, integers, floats, lists or others. All these data points can be accessed using numerical index.  Todos1. Create Python list2. Access, modify, and delete list elements3. Merge lists4. Use the slicing synatx to operate on sublists5. Loop over lists  Create a list<jupyter_code>city = ["Lancaster", "Warwick", "Lancaster", "Bath"]<jupyter_output><empty_output><jupyter_text>Access list elements Python follows C's convention of starting from zero as numerical index. This means, if we have a list of length $n$, the first element of the list is at index $0$ (think of location), the second is at index $1$, and so on and so forth. So be careful that, the last element of the list will be at index $n-1$. To get the **first** element in the above list, we type:<jupyter_code># Python is inspired by C
# follows C's convention that index starts with 0
city[0]<jupyter_output><empty_output><jupyter_text>How about the **last** element?<jupyter_code>city[-1]<jupyter_output><empty_output><jupyter_text>mix of strings and numbers<jupyter_code>mix = [1, [2, 3], 'Python rocks!', True]<jupyter_output><empty_output><jupyter_text>manipulate lists<jupyter_code># add a single element to the end of the list
city.append('Manchester')
print(city)
# insert an element into a specific location
city.insert(0, "Cambridge")
print(city)
# update an element
city[0] = "Oxford"
# add another list
city.extend(['York', 'Edinburgh'])
print(city)
# or just + sign
city = city + ['Liverpool', 'Glasgow']
print(city)
# delete element by index
del city[0]
print(city)
# or by value
city.remove("Glasgow")
print(city)
# sort list
city.sort()
print(city)<jupyter_output><empty_output><jupyter_text>Slice a list<jupyter_code># slice list by using (:)
sq = [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]<jupyter_output><empty_output><jupyter_text>another counterintuitive convention We can select a group of elements in a list starting from the first element indicated and going up to (**but not including**) the last element indicated. For example:<jupyter_code># to get the first three element
sq[0:3] # Remember index starts with 0<jupyter_output><empty_output><jupyter_text>We can select everything in a list<jupyter_code>sq[:]<jupyter_output><empty_output><jupyter_text>We can select everything before or after a certain point<jupyter_code># starting from the eighth element
sq[8:]
# another way to get the first three elements
sq[:3]<jupyter_output><empty_output><jupyter_text>We can also add a third component to slice a list, which is the step size we want to take. So instead of taking every single element, we can take every other element. For instance:<jupyter_code># the max index for the list is 10
# if using 10 as the second component, the last element will be excluded
sq[::2]<jupyter_output><empty_output><jupyter_text>loop through lists We can create an empty list for university names and loop through all the cities in the list `city` and add "University of" to city names:<jupyter_code>uni = []
for i in range(len(city)): # len() returns numbers of elements
    uni.append("University of {}".format(city[i]))

print(uni)<jupyter_output><empty_output><jupyter_text>We can also do mathematical operations on each element in a list<jupyter_code>import math

for value in sq:
    print("square root for {} is {}".format(value, int(math.sqrt(value))))
# even better
for index, value in enumerate(sq):
    print("# {} element ==> {}".format(index+1, value))<jupyter_output><empty_output><jupyter_text>Useful built-in function `range()` to generate lists<jupyter_code>my_range = range(10) # <= a total of 10 numbers

for element in my_range:
    print(element)<jupyter_output><empty_output><jupyter_text>Strings are lists of characters! We already know that strings are basically text. In Python, strings are also indexed much in the same way that lists are, which means that we can do use built-in operations to access, slice, and update strings easily depending on what we need.<jupyter_code>my_string = "I love Python!"
print(my_string[0]) # "I"
print(my_string[2:6]) # "Love"
print(my_string[::-1]) # reverse it!<jupyter_output><empty_output><jupyter_text>Regular Expression is a powerful engine to deal with text and is widely used in programming and data analysis. We will see an example at the end of today's session.  Tuple and Set While we can do everything we want in a list, a special form of data container, very similar to list, is `tuple`. The only difference is that we cannot modify a tuple once defined as we do to a list. Tuple is defined using `( )`.<jupyter_code>my_tuple = ("I", "want", "my", "lunch")
print(my_tuple)
my_tuple[3] = "dinner" # error if we attempt to update tuple<jupyter_output><empty_output><jupyter_text>A `set` is a collection of unordered but unique elements, defined using curl brackets `{ }`. Note: indexing is not working for `set` but instead we can check if an element is in a set or not.<jupyter_code>uni_set = set(uni)<jupyter_output><empty_output><jupyter_text>Dictionary Another essential data structure in Python is the dictionary. Dictionaries are defined with a combination of curly braces (`{ }`) and colons (`:`). The braces define the beginning and end of a dictionary and the colons indicate key-value pairs. A dictionary is essentially a set of `key-value pairs`. Dictionary is very close to an Excel spreadsheet or table, with `key` as column name and `value` as row value  Todos1. Create Python dictionaries2. Access, modify, and delete dictionary items3. Merge dictionaries4. Loop over dictionary keys, values, and items  Create a dictionary<jupyter_code>capitals = {'China': 'Beijing',
            'United Kingdom': 'London',
            'France': 'Paris',
            'Italy': 'Rome'}<jupyter_output><empty_output><jupyter_text>Accessing dictionaries items Instead of using numerical index as we did in a list, we can use a more intuitive approach in a dictionary.<jupyter_code>capitals['China']<jupyter_output><empty_output><jupyter_text>Add items to dictionary<jupyter_code>capitals['United States'] = 'Washington, DC'
capitals<jupyter_output><empty_output><jupyter_text>if we call a key from the dictionary, but there is no such pair in our container/box/data, then we get an error<jupyter_code>capitals['Germany']<jupyter_output><empty_output><jupyter_text>Check if an item belongs to a dictionary<jupyter_code>'Germany' in capitals # Python knows that you are talking about keys
'United Kingdom' in capitals<jupyter_output><empty_output><jupyter_text>Combine dictionaries<jupyter_code>morecapitals = {'Germany': 'Berlin',
                'Japan': 'Tokyo'}
capitals.update(morecapitals)
capitals<jupyter_output><empty_output><jupyter_text>Delete Key-Value pair<jupyter_code>del capitals['United States']
capitals<jupyter_output><empty_output><jupyter_text>Dictionaries are useful with `loop`<jupyter_code># if we want to know what are the keys we have in the dictionary
# loop through keys
for key in capitals.keys():
    print(key)
# if we want to know what values we stored
# loop through values
for value in capitals.values():
    print(value)
# or loop over pairs
for key, value in capitals.items():
    print(key, value)<jupyter_output><empty_output><jupyter_text>ComprehensionsIn python programming, especially when dealing with data, there are many cases where we want to iterate over or loop through list or dictionary, perform an operation on every element, and then collect the results in a new list or dictionary for future analysis.We can certainly perform this with a traditional `for` loop but Python offers a better approach, comprehensions, which let us write shorter and cleaner codes that acchieves the same effect.  Todos1. rewrite a loop as a comprehensions2. filter items3. create a list or a dictionary from a comprehensions  Create a list using `for` loop<jupyter_code>sq = []
for i in range(10):
    sq.append(i**2)
sq<jupyter_output><empty_output><jupyter_text>Create a list using `comprehension`<jupyter_code># inline for loop
sq = [i**2 for i in range(10)]
sq<jupyter_output><empty_output><jupyter_text>Filter elements in a list using `for` loop<jupyter_code>sq = []
for i in range(10):
    if i < 5:
        sq.append(i**2)
sq<jupyter_output><empty_output><jupyter_text>Filter elements in a list using `comprehension`<jupyter_code># if condition
sq = [i**2 for i in range(10) if i < 5]
sq<jupyter_output><empty_output><jupyter_text>Create a dictionary using `comprehension`<jupyter_code>i_sq = {i: i**2 for i in range(10) if i < 5}
i_sq<jupyter_output><empty_output><jupyter_text>TASK: Can you use a comprehension to transpose key and value of the dictionary `capitals`?  4. Flow Control/Logical Statements  Basic Logic If you recall, a boolean takes one of the two values, `True` or `False` (sometimes 1 or 0). The basic logical statements that we can make are defined using Python built-in **comparison operators**:- `==`: equal to- `!=`: NOT equal to- `<`: less than- `<=`: less than or equal to- `>`: greater than- `>=`: greater than or equal to<jupyter_code>1 == 1
2.0 != 2
3 > 3
3 >= 3<jupyter_output><empty_output><jupyter_text>Boolean Operators We can also put various comparisons together using `Boolean Operators`:- `and`: and- `or`: or- `not`: not<jupyter_code>True and True
True and False
(1 == 0) and (1 < 2)
(1 == 0) or (1 < 2)
not (1 == 0) and (1 < 2)<jupyter_output><empty_output><jupyter_text>TruthinessAs a general rule, containers like strings, tuples, dictionaries, lists, and sets, will return `True` if they contain anything at all and `False` if they contain nothing.<jupyter_code>bool('')
bool('I am not empty')
bool([])<jupyter_output><empty_output><jupyter_text>This feature becomes very handy if we need to test if a variable or any object exists in Python's memory so that we can avoid errors.<jupyter_code>bool(capitals['China'])<jupyter_output><empty_output><jupyter_text>If-statementsWe can create segments of code that only execute if a set of `conditions` is met. We use if-statements in conjunction with logical statements in order to create `branches` in our code.An `if branch` gets executed when the `condition` is considered to be `True`. If condition is evaluated as `False`, the if block will simply be skipped unless there is an `else branch`. Conditions are made using either logical operators or by using the truthiness of values in Python. An if-statement is defined with a colon and a block of indented text:<jupyter_code># Multiple-branch logical statements

if "Condition_1":         # First check Condition_1
    print("1")            # If met, take action and finish
elif not "Condition_2":   # If not met, check Condition_2
    print("not 2")        # If met, take action and finish
else:                     # If not met, check other Conditions
    print("never see me") # take action and finish the operation
capitals
# Test if data exists
if capitals['United States']: # truthiness
    print('Yes!')
# if not, we can then add US
else:
    capitals['United States'] = 'Washington, DC'<jupyter_output><empty_output><jupyter_text>Multiple Conditions:<jupyter_code># name = "Liang"
# age = 5

if name == "Alice":
    print("How are you today, Alice.")
else:
    if age < 12:
        print("You are not Alice, kiddo!")
    elif (age >= 12) and (age <=100):
        print("You are not Alice, stranger!")
    else:
        print("You are not Alice, vampire!")<jupyter_output><empty_output><jupyter_text>`for` loop As we have seen many times, `for` loop is extremely useful if we need to operate each data point in a container such as lists and dictionaries.Or we only want to do something interesting for a certain number of times such a arcade game!  `while` loop We can make a block of code execute over and over again with a `while` statement. The body block (actions) will be executed as long as `while` statement's condition is True.<jupyter_code>spam = 0

while spam < 5:
    print("Hello World")
    spam = spam + 1<jupyter_output><empty_output><jupyter_text>break/continue statements Be ware of `infinite loop` since `while` loop can go on and on...We need statements to jump out loops such as break and continue statements<jupyter_code>while True:
    print("Please type your name.")
    name = raw_input()
    
    if name == 'Alice':
        break
print("Nice to meet you, {}".format(name))
while True:
    print("Please input your username")
    name = raw_input()
    
    if name != 'Huawei':
        continue
    else:
        print("Hello, master, please input your password (It is a phone)")
        password = raw_input()
        
        if password == '<PASSWORD>':
            break
print("Access granted")<jupyter_output><empty_output><jupyter_text>===== Your First Python Toy Game =====<jupyter_code># Guess a number
import random

secretNumber = random.randint(1, 20)
print("I am thinking of a number between 1 and 20")

print("You have 6 changes")
for guessesTaken in range(1,7):
    print("Take a guess")
    guess = int(input())
    
    # check with stored secretNumber
    if guess < secretNumber:
        print("too low, try again!")
    elif guess > secretNumber:
        print("too high, try again!")
    else:
        break

if guess == secretNumber:
    print("Good job! You guessed my number {} in {} guesses".format(secretNumber, guessesTaken))
else:
    print("Sorry, I was thinking of {}".format(secretNumber))<jupyter_output><empty_output><jupyter_text>Functions An enhanced version of the above game is putting the above block codes in a function with arguements we can put in:<jupyter_code>import random

def myFirstGame(x, y, z):
    secretNumber = random.randint(x, y)
    print("I am thinking of a number between {} and {}".format(x, y))

    print("You have {} changes".format(z))
    
    for guessesTaken in range(1,z+1):
        print("Take a guess")
        guess = int(input())
    
    # check with stored secretNumber
        if guess < secretNumber:
            print("too low, try again!")
        elif guess > secretNumber:
            print("too high, try again!")
        else:
            break

    if guess == secretNumber:
        print("Good job! You guessed my number {} in {} guesses".format(secretNumber, guessesTaken))
    else:
        print("Sorry, I was thinking of {}".format(secretNumber))
myFirstGame(1, 100, 2)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09770742358078603, 'fraction_numerical': 0.009484170305676855, 'mean_word_length': 4.804752475247525, 'pattern_counts': {'":': 3, '<': 172, '<?xml version=': 0, '>': 166, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '39', 'dir': 'jupyter-structured-clean-dedup', 'id': '514834', 'license': 'MIT', 'n_tokens_mistral': 4527, 'n_tokens_neox': 4345, 'n_words': 1771, 'path': 'notebooks/Session_1/Episode_2/minipy_python.ipynb', 'repo_name': 'drliangjin/little-python-book', 'size': '14649'}