<jupyter_start><jupyter_text>DictionaryPython **dictionary** is an unordered collection of items.  A **dictionary** is a set of **key** **value** pairs, with the requirement that the keys are unique (within one dictionary).  Dictionaries are optimized to retrieve values when the key is known.- 4.1 - What is a dictionary?- 4.2 - Methods of Dictionary Object- 4.3 - Word Count- 4.4 - What object can be a "key"?- 4.5 - Sparse Matrix- 4.6 - Using Dictionary as Cache- 4.7 - Dictionary EfficiencyReference: https://docs.python.org/3/tutorial/datastructures.htmldictionaries 4.1 - What is a "dictionary"?Python dictionary (dict) is similar to an English dictionary to find the defnition of a word by the search of the **key**.  We can inquire a **value** from a dictionary by using the **key**, therefore, **key** is essential in dictionary data structure. A pair of braces (curly brackets) reates an empty dictionary: { }.  Placing a comma-separated list of key:value pairs within the braces adds initial key:value pairs to the dictionary; this is also the way dictionary are written on output.  Here are some examples:e.g. {key1: value1, Key2: value2, ...}``` Python Create a dictionary called "ages"ages = {"Mary":13, "John":14, "Tony":13} Retrieve the value from a dictionaryages["Mary"]   return 13ages["John"]   return 14ages   return {"Mary":13, "John":14, "Tony":13}```<jupyter_code># Create a dictionary called "ages"
ages = {"Mary":13, "John":14, "Tony":13}
# Retrieve the value from a dictionary
ages["Mary"]  # return 13
ages["John"]  # return 14
ages  # return {"Mary":13, "John":14, "Tony":13}<jupyter_output><empty_output><jupyter_text>Comparing dictionary and list:If you have not encountered a dictionary data structure before, it would be a good idea to compare it with a list object to get a better understand of it.- Both dictionary and list can store any type of object- List index is a ordered sequence of numbers, which reflects the order of the elements and the position of an element in a list.  - Dictionary is indexed by keys, which can be any immutable type; strings and numbers can always be keys.  We have the following examples to demonstrate the different between dictionary and list.e.g.``` Python Create an empty list x and empty dictionary yx = []y = {} Assign key and value into a dictionaryy[0] = "Hello"y[1] = "Goodbye"y   return {0:"Hello", 1:"Goodbye"}```Note, the above example is not to assigne the strings into position 0 and 1 in a dictionary.  What it does is to create a key 0 and assign a string value "Hello" to the key 0 and same of the key 1.  Note that if we are trying to assign value to an empty list without any defined index space, it will return an error message.``` Pythonx[0] = "Hello"   return error message```Once keys and values are assigned to a dictionary, we can retrieve and use it.``` Pythonprint(y[0])   return "Hello"y[1] + ", Friend."   return "Goodbye, Friend."```<jupyter_code># Create an empty list x and empty dictionary y
x = []
y = {}
# Assigning keys and values to a dictionary
y[0] = "Hello"
y[1] = "Goodbye"
y  
# Cannot assign value to an empty list
x[0] = "Hello"
# Using the value assgined to a dictionary
print(y[0])
y[1] + ", Friend."<jupyter_output><empty_output><jupyter_text>It seems like the use of dictionary and list is pretty similar until now.  Let's try using non-number key and understand how dictionary is different to list.e.g.``` Python Assigning a new value to an existing value with the same keyy["two"] = "two"y["two"] = 2    2 will replace the original string "two" Multiple two values from the same dictionaryy["pi"] = 3.14y["two"] * y["pi"]   return 6.28```As you can see, the above operations cannot be done with a list.  List index has to be number and dictionary provides us more flexibility with possible key in number, string, or other Python objects.  This makes dictionary a more useful data type to use over list in most cases, for instance, using dictionary storing phone list is more reasonable than using a list because we can retrieve the phone number by the name of the person.You can also think of a dictionary is a data structure that connect different objects, which is similar to a dictionary or encyclopedia in our life.  We can demonstrate with the following example:e.g. English to French Dictionary``` Pythonenglish_to_french = {}english_to_french['red'] = 'rouge'english_to_french['blue'] = 'bleu'english_to_french['green'] = 'vert'print("red is", english_to_french['red'])   return "red is rouge"```<jupyter_code># Assigning a new value to an existing value with the same key
y["two"] = "two"
y["two"] = 2 
# Multiple two values from the same dictionary
y["pi"] = 3.14
y["two"] * y["pi"]
# Creating an English to French dictionary
english_to_french = {}
english_to_french['red'] = 'rouge'
english_to_french['blue'] = 'bleu'
english_to_french['green'] = 'vert'
print("red is", english_to_french['red'])
# Try it yourself

# Write a program that require user to enter 3 names and 3 ages, 
# which allow user to search the age with a given name.<jupyter_output><empty_output><jupyter_text>4.2 - Methods of Dictionary ObjectExcept the basic assignment and extraction operations, dictionary also supports many operations.  Here are some examples:e.g. Using len( ) function to check the numbers of key:value pairs``` Python Create a dictionary with 3 key:value pairsenglish_to_french = {"red":"rouge", "blue":"bleu", "green":"vert"} Use len( ) to find out the numbers of key:value pairs in a dictionarylen(english_to_french)   return 3``` keys( ), values( ), and item( ) methodsWe can use the keys( ) method to extract all the keys in a dictionary.e.g. ``` Pythonlist(english_to_french.keys())  return ['green', 'blue', 'red']```We can also use the values( ) method to extract all the values in a dictionary, but it is not as intuitive and commonly used as keys( ).e.g.``` Pythonlist(english_to_french.values())  return ['vert', 'bleu', 'rough']```item( ) will return the key:value pairs in a tuple:e.g.``` Pythonlist(english_to_french.items())   return[('green', 'vert'), ('blue', 'bleu', ('red', 'rouge')]```The above mentioned methods are usually applied in a for loop to extract the contents stored in a dictionary.<jupyter_code># Create a dictionary with 3 key:value pairs
english_to_french = {"red":"rouge", "blue":"bleu", "green":"vert"}

# Use len( ) to find out the numbers of key:value pairs in a dictionary
len(english_to_french)
# Extract all the keys in a dictionary
list(english_to_french.keys()) 
# Extract all the values in a dictionary
list(english_to_french.values())
# extract all the key:value pairs in a dictionary
list(english_to_french.items())<jupyter_output><empty_output><jupyter_text>get( ) and setdefault( ) methodsThe **del** keyword can be used to remove/delete pair object in a dictionary:e.g.``` Python Delete a pair object in a dictionarylist(english_to_french.items())   return[('green', 'vert'), ('blue', 'bleu', ('red', 'rouge')]del english_to_french['green']list(english_to_french.items())   return[('blue', 'bleu', ('red', 'rouge')]```When using a dictionary, we want to avoid calling an non-existing key, which cause an error in our code.  One way to have a quick check is to use the **in** keyword.  e.g. ``` Python Check if a key exist in a dictionary'red' in english_to_french   return True'orange' in english_to_french   return False```An alternative is using the get( ) method.  If the key exists in a dictionary, it returns the key related value, otherwise, it returns a pre-assigned value in the argument.e.g. Using get( ) method``` Pythonprint(english_to_french.get('blue', 'No translation'))   return 'bleu'print(english_to_french.get('pink', 'No translation'))   return 'No translation'```Note: The second argument is not mandatory to put in, but when it is not defined and the key does not exist in the dictionary, it returns "None".setdefault( ) and get( ) are similar method, when the key is not found in the dictionary, it returns the pre-assigned value in the argument:e.g. Using setdefault( ) method``` Pythonprint(english_to_french.setdefault('blue', 'No translation'))   return 'bleu'print(english_to_french.setdefault('pink', 'No translation'))   return 'No translation'```The different between setdefault( ) and get( ) is that when setdefault( ) method fails to find the key in the dictionary, it automatically created a new key and assign the pre-assigned value to the new key.  In the previous example, 'pink' is created in the dictionary as the new key and "No translation" is assigned to the key as value.  get( ) does not create the new key:value pair in contrast.<jupyter_code># Delete a pair object in a dictionary
list(english_to_french.items())  
del english_to_french['green']
list(english_to_french.items())
# Check if a key exist in a dictionary
'red' in english_to_french  
'orange' in english_to_french 
# Using the get( ) method to extract a vlaue
print(english_to_french.get('blue', 'No translation')) 
print(english_to_french.get('pink', 'No translation'))
# Using the setdefault( ) method
print(english_to_french.setdefault('blue', 'No translation')) 
print(english_to_french.setdefault('pink', 'No translation'))<jupyter_output><empty_output><jupyter_text>copy( ) and update( ) methodscopy( ) method returns a shallow copy of a dictionary:e.g. copy( ) method``` Python Copy a dictionaryx = {0:"zero", 1:"one"}y = x.copy()y   return {0:"zero", 1:"one"}```update( ) method updates the dictionary with the elements from another dictionary object or from an iterable of key/value pairs.  If the key is in the dictionary, it updates the key with the new value.  If the key is not in the dictionary, element(s) will be added to the dictionary. In general, we can think of update( ) as joining two dictionaries.e.g. update( ) method``` Python Update dictionary x with elements in dictionary zz = {1:'one', 2:'two'}x = {0:'zero', 1:'first'}x.update(z)x   return {0:'zero', 1:'one', 2, 'two'}```<jupyter_code># Copy a dictionary
x = {0:"zero", 1:"one"}
y = x.copy()
y 
# Update dictionary x with elements in dictionary z
z = {1:'one', 2:'two'}
x = {0:'zero', 1:'first'}
x.update(z)
x<jupyter_output><empty_output><jupyter_text>Summary of dictionary methodsHere is a table summarizes the methods we covered in the previous section for dictionary object.  Reference: https://docs.python.org/3/library/stdtypes.htmldict|  Operation  |  Description  |  Example  || :---:  |  :---:  |  :---:  ||  {}  |  Create an empty dictionary  |  x = {}  ||  len()  |  Return the length of a dictionary  |  len(x)  ||  keys()  |  Return all the keys in a dictionary  |  x.keys()  ||  values()  |  Return all the values in a dictionary  |  x.values()  ||  items()  |  Return all the key:value pairs in a dictionary  |  x.items()  ||  del  |  Remove an item from a dictionary  |  del x[key]  ||  in  |  Check if a key exists in a dictionary  |  'obj' in x  ||  get()  |  Return the corresponding value of a key, if a key does not exist, return the pre-assigned value  |  x.get('obj', None)  ||  setdefault()  |  Return the corresponding value of a key, if a key does not exist, add a new key:value pair based on the key search and pre-assigned value and return the pre-assigned value  |  x.setdefault('key', None)  ||  copy()  |  Shallow copy a dictionary  |  y = x.copy() ||  update()  |  Update a dictionary with elements from another dictionary  |  x.update(z)  |<jupyter_code># Try it yourself!

# Suppose you are given two dictionaries:

x = {'a':1, 'b':2, 'c':3, 'd':4}
y = {'a':6, 'e':5, 'f':6}

# What would be the return value from the following code?

del x['d']

z = x.setdefault('g', 7)

x.update(z)<jupyter_output><empty_output><jupyter_text>4.3 - Word CountSuppose you are given a work document contains a single word in each row and you are tyring count the frequency of a specific word in this document.  Using dictionary is going to help you complete this task easily:e.g.``` Python Count the frequency of a wordsample_string = "To be or not to be"occurrences = {}for word in sample_string.split():    occurrences[word] = occurrences.get(word, 0) + 1    for word in occurrences:    print("The word", word, "occurs", occurences[word], \          "times in the string")```In this example, "occurrences" dictionary is used to store the frequency of each word.  This is a great example to demonstrate the advantage of dictionary in programming.<jupyter_code># Count the frequency of a word
sample_string = "To be or not to be"
occurrences = {}
for word in sample_string.split():
    occurrences[word] = occurrences.get(word, 0) + 1
    
for word in occurrences:
    print("The word", word, "occurs", occurences[word], \
          "times in the string")<jupyter_output><empty_output><jupyter_text>4.4 - What object can be a "key"?We have been using integer and string as "key" of a dictionary.  In fact, Python also allows other **immutable** object as "key" of a dictionary. In many cases, we wish to use a list as "key" of a dictionary because it's convenience to have a list of elements as keys, such as human resource data using last name and first name as key or using latitude and longitude as key to find location on a map.  Unfortunately, list is a **mutable** Python object, which means it cannot be used as "key".  A solution to this is to use an **immutable** list object, **tuple**. Once a tuple is created, it cannot be changed or updated.  Therefore, it is a suitable object to be a key of a dictionary.  However, there is one more condition to be qualified as key. A dictionary key has to be hashable.  Any hashable object or element can be interpreted by the __hash__() method.  Even though tuple itself is immutable, but mutable objects can be assigned to a tuple, such as a list.  When an element changes in a tuple, the hash value will also changed. Therefore, this kind of tuple cannot be the key of a dictionary.  The conclusion is that when we are using a tuple as key of a dictionary, we need to make sure all the elements in the tuple are all immutable.Here is a list of Python data types:|  Python Data Type  |  Immutable  |  Hashable  |  Can be key?  || :---:  |  :---:  |  :---:  |  :---:  ||  int  |  Yes  |  Yes  |  Yes  ||  float  |  Yes  |  Yes  |  Yes  ||  boolean  |  Yes  |  Yes  |  Yes  ||  complex  |  Yes  |  Yes  |  Yes  ||  str  |  Yes  |  Yes  |  Yes  ||  bytes  |  Yes  |  Yes  |  Yes  ||  bytearray  |  Yes  |  No  |  No  ||  list  |  No  |  No  |  NO  ||  tuple  |  Yes  |  Sometime  |  Sometime  ||  set  |  No  |  No  |  No  ||  frozenset  |  Yes  |  Yes  |  Yes  ||  dictionary  |  No  |  No  |  No  |<jupyter_code># Try it yourself!

# Determine which of the following can be key of a dictionary.

1

'bob'

('tom', [1,2,3])

["filename"]

"filename"

("filename", "extension")<jupyter_output><empty_output><jupyter_text>4.5 - Sparse MatrixIn mathematical world, matrix is defined as two-dimenional array.  In Python, the standard method to represent a matrix is to use two pairs of square brackets. Here is an example:e.g.``` Python Create a matrix using two pairs of square bracketsmatrix = [[3, 0, -2, 11], [0, 9, 0, 0], [0, 7, 0, 0], [0, 0, 0 ,-5]]```The elements of a 2D array are arranged in rows and columns, and we can extract the element(s) using the following syntex:e.g.``` Python Extract element(s) from a matrix matrix[rownum][colnum]element = matrix[1][3]element   return -2```In some applications, a matrix could have thousands of rows and columns, which multiply up to millions of elements.  In such matrix, there could be many elements with value of zero.  For saving the memory, this type of matrix will usually be stored without the zero value and they are called "sparse matrices".Dictionary with tuple index could easily create a sparse matrix.  e.g.``` Python Create a sparse matrixmatrix = {(0,0):3, (0,2):-2, (0,3):11,           (1,1):9, (2,1):7, (3,3):-5} Now we can extract the element from the sparse matrixif (rownum, column) in matrix:    element = matrix[(rownum, colnum)]else:    element = 0```An alternative is to use get( ) method.  If a key is not found in a dictionary, returns 0.e.g.``` Python USing get( ) methodelement = matrix.get((rownum, colnum), 0)```Dictionary can be a convenience option to create a sparse matrix and extracting elements from it.  When dealing with matrix with massive computation involves, it is suggested to use other computation package such as NumPy (www.numpy.org).<jupyter_code># Create a matrix using two pairs of square brackets
matrix = [[3, 0, -2, 11], [0, 9, 0, 0], [0, 7, 0, 0], [0, 0, 0 ,-5]]
# Extract element(s) from a matrix
# matrix[rownum][colnum]
element = matrix[1][3]
element 
# Create a sparse matrix
matrix = {(0,0):3, (0,2):-2, (0,3):11, 
          (1,1):9, (2,1):7, (3,3):-5}

# Now we can extract the element from the sparse matrix
if (rownum, column) in matrix:
    element = matrix[(rownum, colnum)]
else:
    element = 0
# USing get( ) method
element = matrix.get((rownum, colnum), 0)<jupyter_output><empty_output><jupyter_text>4.6 - Using dictionary as cacheIn this section, we would like to introduce how to use a dictionary as **cache**.  A cache is a data structure, which stores the results of an operation for later use, so it avoids the same calculations repeat multiple times.  Memorization allows to optimize a Python function by caching its output based on the parameters supply to it.  Suppose we need a function called "sole", which needs three arguments to return the result:e.g.  sole function``` Pythondef sole(m, n, t):     some calculations with the parameters    retunr(result)```This sole function is time consuming especially when it needs to be called thousands times, the operation is extremely inefficient.  Assume that the sole() function has some predicted combination of parameters and each combination will be called for multiple times, for instance, sole(12, 20, 6) is one of the combinations, but this combination needs to be callled hundreds times.  To speed up the processing, we can use a tuple (12, 20, 6) as the key and stores the result values into a dictionary, so the calculation needs to be store once and it can be extracted from the dictionary for later use.  The code will look like this:e.g.``` Pythonsole_cache = {}def sole(m, n, t):    if (m, n, t) in sole_cache:        return sole_cache[(m, n, t)]    else:         some calculations with the parameters        sole_cache[(m, n, t)] = result        return result```<jupyter_code># Try it yourself!

# Suppose you are writing a program for a table, 
# how would you use dictionary to store the data in this table?
# Write some code to store the values to and extract the value from the dictionary.
# What are the disadvantages for using the dictionary?<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10590176148156032, 'fraction_numerical': 0.011920600287371614, 'mean_word_length': 4.334090263979562, 'pattern_counts': {'":': 18, '<': 45, '<?xml version=': 0, '>': 45, 'https://': 2, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '305105', 'license': 'MIT', 'n_tokens_mistral': 5703, 'n_tokens_neox': 5451, 'n_words': 2572, 'path': 'Dictionary/.ipynb_checkpoints/Dictionary-checkpoint.ipynb', 'repo_name': 'NormanLo4319/Python_Fundamentals', 'size': '18791'}