<jupyter_start><jupyter_text>Dictionary  Lists Dictionaries  Keys and valuesDictionaries **map keys to values**. They represent **mappings** from one set of values to another. A **key** is something we want to use to **look up** a value later. It can be (pretty much) any value: it could be an integer; or a string; or a sequence of strings. A **value** can be any value at all: a string, an image, a network connection: anything we can store in a Python variable.For example, if I were building a dictionary to represent students in a class, I would use **matric numbers** as **keys** and student names and exam scores as **values**. Obviously, two lists could be used instead: one list of possible seasons and a corresponding list of the colours to lookup could be used. But that can be very inefficient as the table gets larger -- you have to step through each element of the table to find the corresponding lookup.Dictionaries are both a simple **and** very efficient way of doing this.   Literal syntaxA dictionary has a literal syntax with curly braces, and each key mapped to one value with a colon to separate them.<jupyter_code>letter_map = {"a": 1, "b": 2, "c": 3, "d": 4}<jupyter_output><empty_output><jupyter_text>If we wish to store information about every student in the classes exam result we could use lists.1. One method is to have a list of lists.<jupyter_code>exam = [["Ann",15],["Bob",9],["Calum",19],["Dave",2],["Eve",12]]<jupyter_output><empty_output><jupyter_text>To access each student's information, we can iterate over it using a for loop.<jupyter_code>for student in exam:
    print(student)<jupyter_output><empty_output><jupyter_text>Now on each iteration student is a list in the form [name,exam].    To access the student's name we use student[0]    To access the student's exam score we use student[1]<jupyter_code>for student in exam:
    print("%s got %d in the exam" %(student[0],student[1]))<jupyter_output><empty_output><jupyter_text>2. Alternatively we could use 2 lists    names is a list of all the student's names.    marks is a list of all the student's marks.    The ith element of marks refers to the mark of the ith element of names.<jupyter_code>names = ["Ann","Bob","Calum","Dave","Eve"]
marks = [15,9,19,2,12]<jupyter_output><empty_output><jupyter_text>To print the information out together we need to access the position in the list. There are two methods of doing this, either iterate over the indicies or enumerate one of the lists.<jupyter_code>#iterate over indices...
for i in range(len(names)):
    print("%s got %d in the exam" %(names[i],marks[i]))
    
print()
    
#or enumerate one of the lists....
for i,n in enumerate(names):
    print("%s got %d in the exam" %(n,marks[i]))<jupyter_output>Ann got 15 in the exam
Bob got 9 in the exam
Calum got 19 in the exam
Dave got 2 in the exam
Eve got 12 in the exam

Ann got 15 in the exam
Bob got 9 in the exam
Calum got 19 in the exam
Dave got 2 in the exam
Eve got 12 in the exam<jupyter_text>3. Another option is to use a dictionary.<jupyter_code>results = {"Ann":15,"Bob":9,"Calum":19,"Dave":2,"Eve":12}<jupyter_output><empty_output><jupyter_text>Here the keys of the dictionary are the student's names and the values are their exam results.<jupyter_code>#prints out all the keys in the dictionary.
print(list(results.keys()))
#prints out all the values in the dictionary
print(list(results.values()))
#print out all the tuples in the form (key,value)
print(list(results.items()))
for key in results:
    print("%s got %d in the exam" %(key,results[key]))<jupyter_output>Ann got 15 in the exam
Bob got 9 in the exam
Calum got 19 in the exam
Dave got 2 in the exam
Eve got 12 in the exam<jupyter_text>Arbitrary ordering**Dictionaries store keys in arbitrary order**. There is **no** guarantee about what order the keys are in. You should assume it is random. If you need ordering, use a list (or another datatype). A dictionary is an unordered collection: it just maintains the relation between keys and values. It can also have **no duplicate keys**. Keys form a set, and every element of a set must be unique.  SlicesNote that there can be no **slices** since dictionaries have no order.<jupyter_code># these are OK
{5: 5, "yes": "yes", 2.0: 2.0}
# no: lists are mutable and thus cannot be keys
{[1, 2]: [1, 2]}
# note: if keys are repeated, the last assigned value will be the key
d = {"a": 2, "a": 3, "a": 4}
print(d)

d["a"] = "hello"
print(d)<jupyter_output>{'a': 4}
{'a': 'hello'}<jupyter_text>IndexingIndexing works just like on lists, except the indices don't need to be integers any more:<jupyter_code>test_dict = {
    2: "welcome",
    4.15: "a wild float appears!",
    "marco": "polo!",
    "nine": 9
}
print(test_dict[2])
print(test_dict[4.15])
print(test_dict["marco"])
print(test_dict["nine"])<jupyter_output>welcome
a wild float appears!
polo!
9<jupyter_text>We can also set the keys of a dictionary using assignment. The keys don't need to exist; a new key will be created automatically. Any existing key will be overwritten.<jupyter_code>test_dict["seven"] = 7
test_dict[2] = "unwelcome"
print(test_dict)<jupyter_output>{2: 'unwelcome', 4.15: 'a wild float appears!', 'marco': 'polo!', 'nine': 9, 'seven': 7}<jupyter_text>Indexing with an key that doesn't exist will cause an exception:<jupyter_code>print(test_dict["this key doesn't exist"])
results = {"Ann":15,"Bob":9,"Calum":19,"Dave":2,"Eve":12}<jupyter_output><empty_output><jupyter_text>Now let's look at what we need to do to print out an individual's (Bob) result with each of these methods.1. List of lists.<jupyter_code>for student in exam:
    if student[0] == "Bob":
        print(student[1])<jupyter_output><empty_output><jupyter_text>2. Separate lists.<jupyter_code>for i in range(len(names)):
    if names[i] == "Bob":
        print(marks[i])<jupyter_output><empty_output><jupyter_text>3. Dictionaries<jupyter_code>print(results["Bob"])<jupyter_output><empty_output><jupyter_text>Advantages of Dictionaries over Lists.If we wish to access a key in the dictionary we do not need to search through the whole dictionary to find its value, we can directly acces it by using the key as an index into the dictionary. Disadvantage of Dictionaries over Lists.Dictionaries are not ideal for sorting information. If we wanted to store the students names in order of exam results, it would be easy to rearrange the lists. However there is no means of rearranging the dictionary to have the highest exam results first. Dictionarues should be considered to be unordered. To get the exam mark(value) for Calum(key), we use the key as an index into the dictionary and the value associated with that key is returned.<jupyter_code>print(results["Calum"])<jupyter_output>19<jupyter_text>To change the exam mark(value) of Dave(key), we use the key as an index into the dictionary and assign it the new value. This will replace any old values for this key.<jupyter_code>results["Dave"] = 17<jupyter_output><empty_output><jupyter_text>To create a new entry into the dictionary, we can simply use the new key as an index into the dictionary and assign it a value.<jupyter_code>results["Fred"] = 5.<jupyter_output><empty_output><jupyter_text>Find the average mark of all the results.<jupyter_code>total = 0
for name in results:
    total += results[name]
print("The average mark was %.1f." %(total/len(results)))
# Print out Eve's exam result.

# Change Ann's exam result to 16

# Add Guy to the dictionary. His exam result was 8.

# Update all the exam results to be double their original score.<jupyter_output><empty_output><jupyter_text>Equality and comparisonDictionaries are equal if they have the same keys, and each of the keys maps to the same value.Order comparisons are not defined for dictionaries (although they do evaluate to a result)<jupyter_code>print({"a" : 1, "b" : 2} == {"a" : 1, "b" : 2})
print({"a" : 1, "b" : 2} == {"b" : 2, "a" : 1}) # order does not matter
print({"a" : 1, "b" : 2} == {"a" : 1, "b" : 2, "c" : 3}) # different keys
print({"a" : 1, "b" : 2} == {"a" : 1, "b" : 20}) # different values for the same keys
# cannot compare the ordering of dictionaries!
print({"a" : 1, "b" : 2} > {"a" : 1, "b" : 20})<jupyter_output><empty_output><jupyter_text>Membership testJust like lists, we can use `in` to find out if a key (not a value!) is in a dictionary. This is one of the major uses of dictionaries -- to keep a record of "things that have been seen"**But**, unlike a list, the time taken to find an element does not depend on how many elements are in the dictionary.**This is a massive difference!**  get and default values`get()` lets us retrieve a value from a dictionary, substituting a **default** value if the key requested is not in the dictionary.<jupyter_code>fruit = ["apple","banana","apple","apple","orange","banana"]

fruitCount1 = {}

for f in fruit:
    if f in fruitCount1:
        fruitCount1[f] = fruitCount1[f]+1
    else:
        fruitCount1[f] = 1

print(fruitCount1)

fruitCount2 = {}

for f in fruit:
    fruitCount2[f] = fruitCount2.get(f,0) + 1

print(fruitCount2)<jupyter_output>{'apple': 3, 'banana': 2, 'orange': 1}
{'apple': 3, 'banana': 2, 'orange': 1}<jupyter_text>Restrictions on keysBecause dictionaries work by hashing, every **key** must be:* unique* hashable* immutableWhereas a **value** can be any type at all. There are no restrictions at all on **values**. Keys can be any immutable data type.<jupyter_code>test = {1:"one", 2.5:1.66, "two":"double", False:"a very long way away"}
for key in test.keys():
    print(key, test[key])<jupyter_output><empty_output><jupyter_text>But list can not be keys as they are mutable.<jupyter_code>notAllowed = {[2,4]:3}<jupyter_output><empty_output><jupyter_text>But tuples are allowed as they are immutable<jupyter_code>allowed = {(2,4):3}<jupyter_output><empty_output><jupyter_text>One-to-many mappingsSo far we have assumed that our dictionaries map from one key to one value. Multiple keys can map to one value, but each key can only map to one value. One-to-one    {"a":"b", "c":"d", "e":"f"}     Many-to-one:    {"a":1, "c":1, "d":1} Each key can have only one value associated with it. Assigning another value to a key with replace the previous value.<jupyter_code>print(test["two"])
test["two"]= "twice"
print(test["two"])<jupyter_output><empty_output><jupyter_text>ListsThis is very easy to relax; we just make the values be lists (or any other compound data structure). **Now each key can map to any number of values.** One-to-many<jupyter_code>{"a": [1, 2, 3], "b": [2], "c": [1, 3]}<jupyter_output><empty_output><jupyter_text>So now there can be multiple values associated with one key.<jupyter_code>test["two"] = ["double","twice"]
print(test["two"])
test["two"].append("both")
test["two"]+=["second"]
print(test["two"])<jupyter_output><empty_output><jupyter_text>Values can be any data type. They can even be nested data structures.<jupyter_code>studentInfo = {"Ann":{"matrix":12543,"email":"<EMAIL>","exams":[21,34,5,14]},
               "Bob":{"matrix":13524,"email":"<EMAIL>","exams":[32,15,7,22]},
              "Calum":{"matrix":23145,"email":"<EMAIL>","exams":[2,39,65,98]},
              "Dave":{"matrix":23562,"email":"<EMAIL>","exams":[97,78,92,88]},
              "Eve":{"matrix":55223,"email":"<EMAIL>","exams":[9,11,23,16]},
              }<jupyter_output><empty_output><jupyter_text>We can print out all the values of the keys.<jupyter_code>for student in studentInfo:
    print(studentInfo[student])<jupyter_output><empty_output><jupyter_text>Now to print just the matrix numbers of the students.....<jupyter_code>for student in studentInfo:
    print(studentInfo[student]["matrix"])<jupyter_output><empty_output><jupyter_text>Sometimes if the dictionary is deeply nested it is easier to use variables rather than writing all the whole dictionary.<jupyter_code>for student in studentInfo:
    s = studentInfo[student]
    print(s["matrix"])
# print out all the names and their email addresses<jupyter_output><empty_output><jupyter_text>Insert Fred's information into the dictionary.    exams = [54,45,55,44]    matrix = 22121    email = <EMAIL>    Does it matter the order the information is entered?<jupyter_code># find the average of every student's exam results and store the result in the dictionary.

# now save the students names in descending order of their average results 
# (use .sort() - ask a tutor if this is unclear).
# print a congratulations message to the top three students telling them their position in the class.<jupyter_output><empty_output><jupyter_text>Write a english/french dictionary. Ask the user to input a number between 1 - 10 and print out the corresponding word in french. (un, deux, trois, quatre, cinq, six, sept, huit, neuf, dix) If the number input is not in the dictionary (use not in) print out a message otherwise print out the number. You want to have a birthday book to store all your friends' birthdays.Your friends are....    Ann - June 28th    Bob - Dec 30th    Calum - May 2nd    Dave - June 12th    Eve - Jan 1st.    Store this information in an appropriate data structure.<jupyter_code># Print out Calum's birthday.
# You made a mistake, Dave's birthday is June 11th. Update his details.
# Add Fred's birthday to your birthday book (June 3rd)
# Print out everyone who has a birthday in December.
# Print out everyone who has a birthday next month.
# Whose birthday came first in June.<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1154931669637552, 'fraction_numerical': 0.019830659536541888, 'mean_word_length': 5.048966756513926, 'pattern_counts': {'":': 52, '<': 132, '<?xml version=': 0, '>': 133, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '33', 'dir': 'jupyter-structured-clean-dedup', 'id': '630778', 'license': 'CC0-1.0', 'n_tokens_mistral': 4431, 'n_tokens_neox': 4151, 'n_words': 1723, 'path': 'dictionaries.ipynb', 'repo_name': 'jeremysinger/glasgow-summer-school', 'size': '13504'}