laika222
12/26/2018 - 6:06 PM

Dictionaries

################################
### DICTIONARY DATA TYPE ###
################################

# A dictionary is like a list in that it's a collection of many values. But unlike indexes for lists, indexes for dictionaries can use
# different data types, not just integers. Indexes for dictionaries are called *keys*, and a key with its associated value is 
# called a *key-value* pair. Dictionaries can also use interger values as keys, just like how lists use integers for indexes, but they
# do not have to satrt at 0 and can be any number.

myCat = {'size': 'big', 'color': 'grey', 'disposition': 'bitey', 123: 'value'}

myCat['size'] # result is 'big'

myCat[123] # result is 'value'

# Unlike lists, items in dictionaries are unordered. The first item in a list called myList would be list[0], but there is no 'first' item
# in a dictionary. While the order of times matters for determining whether two lists are the same, it does not matter in which order
# the key-value pairs are typed in a dicitonary.

firstList = ['cats', 'dogs', 'bats']
secondList = ['cats', 'bats', 'dogs']

firstList == secondList # result is False

firstDictionary = {'name': 'Whiskers', 'species': 'cat', 'age': '2'}
secondDictionary = {'species': 'cat', 'name': 'Whiskers', 'age': '2'}

firstDictionary == secondDictionary # result is True

# Since dictionaries are not ordered, they can't be sliced like lists. Also, trying to access a key that does not exist will result
# in a KeyError error message, much like a list's out-of-range IndexError error message.

# The ability to have arbitrary values for the keys allows you to organize data in powerful ways. For example, say you have a program to
# store data on your friends' birthdays. You can use a dictionary with the names as keys adn the birthdays as values. Input can also 

birthdays = {'Alice': 'April 1', 'Sammy': 'March 2', 'Cameren': 'August 16'} # create dictionary

while True: # start infinite loop
    print('Enter a name: (blank to quit)') # prompt user for input
    name = input() # create name value, which will take value input by user
    if name == "": # if empty string is entered, break the loop
        break

    if name in birthdays: # if name is in the birthdays dictionary, print the birthday
        print(birthdays[name] + ' is the birthday of ' + name)
    else: # if it's not in the birthday dictionary
        print('I do not have birthday information for ' + name) # say the dictionary doesn't have the name
        print('What is their birthday?') # ask what the birthday is
        bday = input() # create variable bday which will hold the input from the user
        birthdays[name] = bday # add key to birthdays using value currently held in name variable, assign it a value = to value in bday variable
        print('Birthday database updated.') # print a confirmation message that hte dictionary has been updated. You can now enter the new name that was entered and you'll see that it has been added to the dictionary.

################################
### keys(), values(), and items() METHODS OF DICTIONARIES ###
################################

# There are three dictionary methods that return list-like values of the dictionary's keys, values, or both. These values are not true
# lists in that they cannot be modified and do not have an append() method. However, they can be used in for loops. The data types
# are called dict_keys, dict_values, and dict_items.

birthdays = {'Alice': 'April 1', 'Sammy': 'March 2', 'Cameren': 'August 16'} 

for v in birthdays.keys():
    print(v)

    # Result is:

    # April 1
    # March 2
    # August 16

for v in birthdays.values():
    print(v)

    # Result is:

    # Alice
    # Sammy
    # Cameren

for v in birthdays.items():
    print(v)

    # Result is:

    # ('Alice', 'April 1')
    # ('Sammy', 'March 2')
    # ('Cameren', 'August 16')

# Even though these are dict data types and aren't true lists, you can use the list function to return them as lists.
list(birthdays.items()) # Result is [('Alice', 'April 1'), ('Sammy', 'March 2'), ('Cameren', 'August 16')]

################################
### CHECKING WHETHER A KEY OR VALUE EXISTS IN A DICTIONARY ###
################################

# You can check whether a value or key is in a dictionary, just as you can in a list

birthdays = {'Alice': 'April 1', 'Sammy': 'March 2', 'Cameren': 'August 16'} 

'Alice' in birthdays.keys() # result is True
'Rhoda' in birthdays.keys() # result is False

'March 2' in birthdays.values() # result is True
'February 1' not in birthdays.values() # result is True

################################
### THE get() METHOD ###
################################

# In the previous section, you had to check whether a key exists before accessing that key's value. The get() method allows you to check
# for the key and retrieve the value at the same time. It's set up like ISNULL() in SQL in that you specificy which key you'd like to retreive
# the value for, and then a value to insert if the key isn't found. Without this backup substitution value, Python would return an error.

shoppingList = {'apples': 5, 'pizzas': 2}

'I will bring ' + str(shoppingList.get('apples', 0)) + ' apples.' # result is 'I will bring 5 apples.'
'I will bring ' + str(shoppingList.get('hot dogs', 0)) + ' hot dogs.' # result is 'I will bring 0 hot dogs.' Notice how since the fallback value was used since 'hot dogs' doesn't exist as a key.

################################
### THE setdefault() METHOD ###
################################

# You'll often need to set a value in the dictionary for a key if the key does not already have a value. The code would look like:
shoppingList = {'apples': 5, 'pizzas': 2}
if 'hot dogs' not in shoppingList:
    shoppingList['hot dogs'] = 4

shoppingList.items() # result is dict_items([('apples', 5), ('pizzas', 2), ('hot dogs', 4)])

# setdefault() is a shortcut around having to do multiple lines of code. The first argument is the key to check for, and the second argument is
# the value to set at that key if the key does not exist. It's a nice shortcut to ensure that a key exists.
shoppingList = {'apples': 5, 'pizzas': 2}

shoppingList.setdefault('hot dogs', 4) # 'hot dogs' does not exist, so it'll be created with a default value of 4
shoppingList.items() # result is dict_items([('apples', 5), ('pizzas', 2), ('hot dogs', 4)])

shoppingList.setdefault('apples', 200) # 'apples' already exists, so nothing will be changed with that keyword or value
shoppingList.items() # result is dict_items([('apples', 5), ('pizzas', 2), ('hot dogs', 4)])

# For a fuller example of how setdefault() can be a good shortcut, look at this program whichcounts the number of letters in the message.
# It creates an empty dictionary, starts a loop that goes through each letter of the message, and counts them. It creates keys in the 
# dictionary for each new character it finds, and on a step if the character already exists, it takes the value and adds 1.

message = 'It was Christmas Eve and all through the house not a creature was stirring, not even a mouse.'

letterCount = {} # create empty dictionary

for character in message: # set a loop with variable character that goes through each character. 
    letterCount.setdefault(character,0) #check if that letter already exists, and if not, create a key with a default value of 0
    letterCount[character] = letterCount[character] + 1 # add to the value of that key by one. If key was just created, it'll iterate from 0 to 1. If key already had value of 6, it'll iterate up to 7.

print(letterCount) # result is {'I': 1, 't': 8, ' ': 17, 'w': 2, 'a': 8, 's': 7, 'C': 1, 'h': 5, 'r': 6, 'i': 3, 'm': 2, 'E': 1, 'v': 2, 'e': 8, 'n': 5, 'd': 1, 'l': 2, 'o': 5, 'u': 4, 'g': 2, 'c': 1, ',': 1}

################################
### PRINTING DICTIONARIES ###
################################

# The pprint module is a good to way to 'pretty print' a dictionary's values.

import pprint

message = 'It was Christmas Eve and all through the house not a creature was stirring, not even a mouse.'

letterCount = {} # create empty dictionary

for character in message: # set a loop with variable character that goes through each character. 
    letterCount.setdefault(character,0) #check if that letter already exists, and if not, create a key with a default value of 0
    letterCount[character] = letterCount[character] + 1 # add to the value of that key by one (values in dictionaries are accessed via square brackets, just like lists. If key was just created, it'll iterate from 0 to 1. If key already had value of 6, it'll iterate up to 7.

print(letterCount)

pprint.pprint(letterCount) # invoke the pprint method of the pprint module to print things nicely

# Result is:

    # {' ': 17,
    #  ',': 1,
    #  '.': 1,
    #  'C': 1,
    #  'E': 1,
    #  'I': 1,
    #  'a': 8,
    #  'c': 1,
    #  'd': 1,
    #  'e': 8,
    #  'g': 2,
    #  'h': 5,
    #  'i': 3,
    #  'l': 2,
    #  'm': 2,
    #  'n': 5,
    #  'o': 5,
    #  'r': 6,
    #  's': 7,
    #  't': 8,
    #  'u': 4,
    #  'v': 2,
    #  'w': 2}

################################
### EXAMPLE OF TIC-TAC-TOE USING DICTIONARIES ###
################################

# In this example, a tic-tac-toe board can be represented using dictionary keys.

# 'top-L'     -   'top-M'     -   'top-R'
# ---------------------------------------
# 'mid-L'     -   'mid-M'     -   'mid-R'
# ---------------------------------------
# 'low-L'     -   'low-M'     -   'low-R'

# You can use string values to represent what's in each slot on the board: 'X', 'O', ' ' (a space character for nothing there)

# To start, create a dictionary for theBoard, placing a value of ' ' in each space (an empty board)

theBoard = {'top-L': ' ', 'top-M': ' ', 'top-R': ' ',
            'mid-L': ' ', 'mid-M': ' ', 'mid-R': ' ',
            'low-L': ' ', 'low-M': ' ', 'low-R': ' '}

# You can then create a function that prints the current contents of the board so the players can see it.
# This function excepts the tic-tac-toe data structure to be a dictionary with keys for all nine slots, so you need to make sure that
# the dictionary passed to the function has all of the slots named correctly so that the match what the funciton is printing.

def printBoard(board): # define printBoard, with an argument board
    print(board['top-L'] + '|' + board['top-M'] + '|' + board['top-R'])
    print('-+-+-')
    print(board['mid-L'] + '|' + board['mid-M'] + '|' + board['mid-R'])
    print('-+-+-')
    print(board['low-L'] + '|' + board['low-M'] + '|' + board['low-R'])
    print('-+-+-')

printBoard(theBoard) # invoke printBoard, passing in for the argument board the dictionary theBoard

# Result is an empty board:

    #  | | 
    # -+-+-
    #  | | 
    # -+-+-
    #  | | 
    # -+-+-

# If you assign values to any of the dicitonary keys, those will be printed by printBoard
theBoard = {'top-L': 'X', 'top-M': ' ', 'top-R': ' ',
            'mid-L': ' ', 'mid-M': ' ', 'mid-R': ' ',
            'low-L': ' ', 'low-M': ' ', 'low-R': 'O'}

def printBoard(board): 
    print(board['top-L'] + '|' + board['top-M'] + '|' + board['top-R'])
    print('-+-+-')
    print(board['mid-L'] + '|' + board['mid-M'] + '|' + board['mid-R'])
    print('-+-+-')
    print(board['low-L'] + '|' + board['low-M'] + '|' + board['low-R'])
    print('-+-+-')

printBoard(theBoard) 

# Result is a board with X's and O's entered based on the values for the keys:

    # X| | 
    # -+-+-
    #  | | 
    # -+-+-
    #  | |O 
    # -+-+-

# You can then add code that allows the players to enter their moves
theBoard = {'top-L': ' ', 'top-M': ' ', 'top-R': ' ',
            'mid-L': ' ', 'mid-M': ' ', 'mid-R': ' ',
            'low-L': ' ', 'low-M': ' ', 'low-R': ' '}

def printBoard(board): 
    print(board['top-L'] + '|' + board['top-M'] + '|' + board['top-R'])
    print('-+-+-')
    print(board['mid-L'] + '|' + board['mid-M'] + '|' + board['mid-R'])
    print('-+-+-')
    print(board['low-L'] + '|' + board['low-M'] + '|' + board['low-R'])
    print('-+-+-')

turn = 'X' # variable to hold the name of the player whose turn it is
for i in range(9): # create loop that will go through 9 iterations
    printBoard(theBoard) # print the board so the players can see the current state
    print('Turn for ' + turn + '. Move on which space?') # identify whose turn it is and ask the player to make a move
    move = input() # variable to hold the move that the player enteres
    #next, sets the key value based on what the user places into the move variable. If user enters 'Top-M' and turn happens to be 'X', then
    # the code becomes 'theBoard[top-M] = 'X''. This will assign the value to 'X' for the topBoard dictionary key 'top-M'
    theBoard[move] = turn  
    if turn == 'X': # switch the turn (flip the turn variable to the other player's character)
        turn = 'O'
    else:
        turn = 'X'

printBoard(theBoard) # one it completes nine rounds, the loop will stop and the program will display the final result

################################
### NESTED DICTIONARIES AND LISTS ###
################################

# Dictionaries and lists can contain other dicitonaries and lists. As an example, there is a program that has nested dictionaries
# to show customers that each have their own owners. numOrders then totals the orders

# create allCustomers dictionaries, whose keys 'Alice', 'Carol', and 'Chris' each have nested dictionaries
allCustomers = {'Alice': {'apples': 5, 'pretzels': 12},
                'Carol': {'guitar': 12, 'speakers': 2},
                'Chris': {'apples': 2, 'pizzas': 7, 'toaster': 1}}

def totalOrders(customers, item):
    numOrders = 0
    # within funciton, loop iterates over all of the key-value pairs plugged into customers argument
    # in the loop, customer name is assigned to k, and the dictionary of items is assigned to v.
    for k, v in customers.items(): 
        numOrders = numOrders + v.get(item,0) # count the number of whatever item is passed in the item argument for this iteration of the loop
    return numOrders # return the value out of the function

print('Number of orders:')
print(' - Apples '+ str(totalOrders(allCustomers, 'apples'))) # run totalOrders, plugging in dictionary allCustomers, looking for item 'apples', result is 7
print(' - Hot Dogs '+ str(totalOrders(allCustomers, 'hot dogs'))) # run totalOrders, plugging in dictionary allCustomers, looking for item 'apples', result is o
print(' - Pretzels '+ str(totalOrders(allCustomers, 'pretzels')))
print(' - Guitar '+ str(totalOrders(allCustomers, 'guitar')))
print(' - Speakers '+ str(totalOrders(allCustomers, 'speakers')))
print(' - Cat Food '+ str(totalOrders(allCustomers, 'cat food')))
print(' - Pizzas '+ str(totalOrders(allCustomers, 'pizzas')))
print(' - Toasters '+ str(totalOrders(allCustomers, 'toaster')))
print(' - Computers '+ str(totalOrders(allCustomers, 'computer')))

################################
### DICTIONARY COMPREHENSION ###
################################

# comprehension allows you concisely form a new list, set, or dictionary by filtering the elements of a collection
# A dictionary comprehension has a format of [key expr : value expr for value in collection if condition]
animals = ['bat', 'cat', 'really big snake', 'parrot']

# mapping the index position of the list as the key, and the item in the list as a value
myMappedDictionary = {index: value for index, value in enumerate(animals)}
myMappedDictionary # result is {0: 'bat', 1: 'cat', 2: 'really big snake', 3: 'parrot'}

# example using an optional filter
myMappedDictionary = {index: value for index, value in enumerate(animals) if len(value) > 3}
myMappedDictionary # result is {2: 'really big snake', 3: 'parrot'}

################################
### CREATING DICTIONARIES FROM SEQUENCES ###
################################

# it's common to end up with two sequences that you want to place into a dictionary as key-value pairs. 
# Since a dictionary is basically a collection of tuples, you can supply them into a dictionary using zip
sequence1 = ['cat', 'mouse', 'parrot']
sequence2 = ['one', 'two', 'three', 'four']

myDictionary = {}
myDictionary = dict(zip(sequence2, sequence1))
myDictionary # result is {'one': 'cat', 'two': 'mouse', 'three': 'parrot'}