import pprint
import time


############################################################
# lists vs dictionaries
############################################################


name1 = "Viola Davis"
name2 = "Tom Hanks"
name3 = "Meryl Streep"

address1 = "Simmons Hall"
address2 = "MacGregor House"
address3 = "East Campus"

classes1 = ["8.01", "21M.301"]
classes2 = ["7.012", "24.900"]
classes3 = ["6.120", "21W.747"]


def parallel_lists(index):
    names = [name1, name2, name3]
    addresses = [address1, address2, address3]
    classes = [classes1, classes2, classes3]
    return (
        f"{names[index]} lives at {addresses[index]}, "
        f"taking classes {classes[index]}"
    )


def nested_lists(index):
    records = [
        [name1, address1, classes1],
        [name2, address2, classes2],
        [name3, address3, classes3],
    ]
    return (
        f"{records[index][0]} lives at {records[index][1]}, "
        f"taking classes {records[index][2]}"
    )


# print(parallel_lists(index=2))
# print(nested_lists(index=2))


def parallel_dicts(name):
    addresses = {name1: address1, name2: address2, name3: address3}
    classes = {name1: classes1, name2: classes2, name3: classes3}
    return f"{name} lives at {addresses[name]}, taking classes {classes[name]}"
    # note: `name` is used as a key lookup for the associated value
    # note: shares [] notation with list indexing


def nested_dicts(name):
    records = {
        name1: {"address": address1, "classes": classes1},
        name2: {"address": address2, "classes": classes2},
        name3: {"address": address3, "classes": classes3},
    }
    return (
        f"{name} lives at {records[name]["address"]}, "
        f"taking classes {records[name]["classes"]}"
    )


# print(parallel_dicts("Meryl Streep"))
# print(nested_dicts("Meryl Streep"))


############################################################
# dict operations
############################################################


def create_full_dict():
    # empty dict
    addresses = {}
    print(addresses)
    print(len(addresses))
    print()

    # dict from key-value pairs
    # pairs = [[name1, address1], [name2, address2], [name3, address3]]
    # addresses = dict(pairs)
    # print(addresses)
    # print(len(addresses))
    # print()

    # dict from keyword arguments
    # record1 = dict(address=address1, classes=classes1)
    # print(record1)
    # print(len(record1))
    # print()

    # (shallow) copy dicts
    # record1_copy = record1.copy()
    # record1_again = dict(record1)
    # print(record1)
    # print(record1_copy)
    # print(record1_again)
    # print()
    # print(id(record1))
    # print(id(record1_copy))
    # print(id(record1_again))
    # print()
    # print(id(record1["classes"]))
    # print(id(record1_copy["classes"]))
    # print(id(record1_again["classes"]))
    # print()

    # dict key lookup is based on == comparison
    # my_key = "classes"
    # your_key = "class" + "es"
    # print(id(my_key))
    # print(id(your_key))
    # print()
    # print(my_key in record1)
    # print(your_key in record1)
    # print(record1[my_key])
    # print(record1[your_key])
    # print()


# create_full_dict()


def mutate_dict():
    # adding or updating individual key-value pairs
    addresses = {}
    addresses[name1] = address1
    print(addresses)
    # addresses[name2] = address2
    # addresses[name3] = address3
    # print(addresses)
    # addresses[name1] = address3  # overwrites existing mapping for name1
    # print(addresses)
    # print()

    # use .get() to avoid KeyError on missing keys
    # print(addresses["Tom Hanks"])
    # print(addresses["Thom Hanks"])  # KeyError
    # addresses["Thom Hanx"] = "somewhere at Harvard"
    # if "Thom Hanx" in addresses:
    #     print(addresses["Thom Hanx"])  # safe to run
    # print(addresses.get("Thom Hanx"))
    # print(addresses.get("Thom Hanx", "PLACEHOLDER"))
    # print()

    # delete key-value pairs
    # del addresses["Thom Hanx"]
    # addresses["Thom Hanx"] = "somewhere at Harvard"  # undo
    # result = addresses.pop("Thom Hanx")
    # print(addresses)
    # print(result)
    # result = addresses.pop("Thom Hanx")  # KeyError
    # result = addresses.pop("Thom Hanx", "CUPHOLDER")
    # print(addresses)
    # print(result)
    # print()

    # merging with other dictionaries
    # new_assignments = {
    #     "Matt Damon": "Next House",
    #     "Viola Davis": "Simmons Hall",
    #     "Steven Yeun": "Random Hall",
    # }
    # addresses.update(new_assignments)
    # print(addresses)
    # pprint.pp(addresses)
    # print()
    # addresses = {name1: address1, name2: address2, name3: address3}  # reset
    # new_addresses = addresses | new_assignments
    # pprint.pp(new_addresses)
    # pprint.pp(addresses)
    # print()
    # addresses |= new_assignments  # equivalent to .update()
    # pprint.pp(addresses)
    # print()


# mutate_dict()


############################################################
# immutability/hashability of dict keys, tuples
############################################################


def dict_key_hashability():
    lotto_lookup = {}

    # lists cannot be dict keys
    ticket = [41, 61, 19, 37]
    # lotto_lookup[ticket] = "winner!"  # TypeError: unhashable
    # ticket[3] = 89
    # result = lotto_lookup[[41, 61, 19, 37]]  # TypeError: unhashable
    # print(result)

    # tuples are just like lists, but immutable
    # ticket = (41, 61, 19, 37)
    # ticket[3] = 89  # TypeError: can't assign

    # immutable == hashable for built-in types
    # lotto_lookup[ticket] = "winner!"
    # ticket_gen = ()  # empty tuple
    # ticket_gen += (41,)  # single-element tuple
    # ticket_gen += (61,)
    # ticket_gen += (19, 37)
    # result = lotto_lookup[ticket_gen]
    # print(result)


# dict_key_hashability()


def valid_dict_key_types():
    sample = {}

    # built-in scalar (non-compound) types are hashable
    # best to stick with `int`
    sample[10] = [4, 3, 2, 1]
    sample[False] = 12
    print(sample)
    print(sample[0])  # False == 0 and True == 1
    # sample[3.0] = "cheers"
    # num = 0
    # for _ in range(7):
    #     num += 3 / 7
    # print(num)
    # print(num == 3.0)
    # print(sample[num])  # KeyError
    # print()

    # str is hashable
    # sample["6ood p4$Sw0Rd"] = sample[10]
    # print(sample)
    # print()

    # tuple is hashable as long as elements are hashable
    # countdown = tuple(sample[10])
    # sample[countdown] = [sample[False], sample[10]]
    # pprint.pp(sample)
    # sample[(5, countdown)] = 16
    # pprint.pp(sample)
    # pprint.pp(sample[(5, (4, 3, 2, 1))])
    # print()


# valid_dict_key_types()


############################################################
# dict iteration
############################################################


def dict_iteration():
    addresses = {name1: address1, name2: address2, name3: address3}

    # dictionaries iterate over their keys
    for key in addresses:
        print(f"{key:<20} {addresses[key]}")
    print(list(addresses))
    print()

    # iterable dictionary views
    # for key in addresses.keys():
    #     print(key)
    # print()
    # for value in addresses.values():
    #     print(value)
    # print()
    # for pair in addresses.items():
    #     print(pair)
    # print()


# dict_iteration()


def tuple_secrets():
    # multiple assignment through sequence unpacking
    (var1, var2, var3) = ("pitter", "patter", [1, 2, 3])
    print(var1, var2, var3)
    # (var1, var2, [x, y, z]) = ("pitter", "patter", [1, 2, 3])
    # print(var1, var2, x, y, z)
    # (var1, var2, (x, y, z)) = ("pitter", "patter", [1, 2, 3])
    # print(var1, var2, x, y, z)
    # print()

    # common pattern in dict iteration
    # addresses = {name1: address1, name2: address2, name3: address3}
    # for (key, value) in addresses.items():
    #     print(f"{key:<20} {value}")
    # print()

    # parentheses are only for grouping
    # necessary only when otherwise ambiguous
    # sequence = 1, 2, "I", 3, 4, "H", 5, 6, "T"
    # print(type(sequence))
    # print(sequence)
    # print()
    # sequence = (1, 2), "I", (3, 4), "H", (5, 6), "T"
    # print(sequence)
    # print()

    # so more convenient to iterate like this
    # for key, value in addresses.items():
    #     print(f"{key:<20} {value}")
    # print()

    # multiple returns values are actually returning a tuple
    # return "I'm", "finished!"


# result = tuple_secrets()
# print(result)


############################################################
# example: frequency dictionary of song lyrics
############################################################


def generate_word_freqs(song):
    """
    Given song lyrics as a str, return a dict that maps song words to
    their frequencies in the song.
    """
    # convert to lowercase and separate words by spaces
    song_words = song.lower()
    words_list = song_words.split()

    word_freqs = {}
    for word in words_list:
        if word not in word_freqs:  # create a new dictionary entry
            word_freqs[word] = 1
        else:  # word was previously seen, so increment frequency
            word_freqs[word] += 1
    return word_freqs


def find_frequent_word(word_freqs):
    """
    Given a dict that maps words to their frequencies, find the words
    that occur the most often. (There could be more than one word with
    the highest frequency.)

    Return a tuple of (list, int), where the list contains the words
    that occur with the highest frequency, and the int is that frequency.
    """
    words = []
    highest = max(word_freqs.values())
    for word in word_freqs:
        if word_freqs[word] == highest:
            words.append(word)
    return words, highest


def occurs_often(word_freqs, at_least):
    """
    Given a dict that maps words to their frequencies, find all the
    words with frequencies equal to or greater than a given int at_least.

    Return a list of tuples in order of frequency, where each tuple has
    the form (list of words, frequency).

    Side effect warning: The input dict word_freqs may be modified.
    """
    freq_list = []
    while True:
        # find most common word(s) with frequency >= at_least
        words, frequency = find_frequent_word(word_freqs)
        if frequency < at_least:
            break
        freq_list.append((words, frequency))
        # remove those words, so the next round finds words
        # with the next highest frequency
        for word in words:
            del word_freqs[word]
    return freq_list


########################################
# pick a song by uncommenting your favorite
########################################


song = "I threw a wish in the well Dont ask me Ill never tell I looked to you as it fell And now youre in my way  Id trade my soul for a wish Pennies and dimes for a kiss I wasnt looking for this But now youre in my way  Your stare was holdin Ripped jeans skin was showin Hot night wind was blowin Where do you think youre going baby  Hey I just met you And this is crazy But heres my number So call me maybe  Its hard to look right At you baby But heres my number So call me maybe  Hey I just met you And this is crazy But heres my number So call me maybe  And all the other boys Try to chase me But heres my number So call me maybe  You took your time with the call I took no time with the fall You gave me nothing at all But still youre in my way  I beg and borrow and steal Have foresight and its real I didnt know I would feel it But its in my way  Your stare was holdin Ripped jeans skin was showin Hot night wind was blowin Where you think youre going baby  Hey I just met you And this is crazy But heres my number So call me maybe  Its hard to look right At you baby But heres my number So call me maybe  Hey I just met you And this is crazy But heres my number So call me maybe  And all the other boys Try to chase me But heres my number So call me maybe  Before you came into my life I missed you so bad I missed you so bad I missed you so so bad  Before you came into my life I missed you so bad And you should know that I missed you so so bad bad bad  Its hard to look right At you baby But heres my number So call me maybe  Hey I just met you And this is crazy But heres my number So call me maybe  And all the other boys Try to chase me But heres my number So call me maybe  Before you came into my life I missed you so bad I missed you so bad I missed you so so bad  Before you came into my life I missed you so bad And you should know that  So call me maybe"

# song = "Because you know Im all about that bass  Bout that bass no treble Im all about that bass Bout that bass no treble Im all about that bass Bout that bass no treble Im all about that bass Bout that bass bass bass bass  Yeah its pretty clear I aint no size two But I can shake it shake it like Im supposed to do Cause I got that boom boom that all the boys chase And all the right junk in all the right places I see the magazine workin that Photoshop We know that stuff aint real come on now make it stop If you got beauty beauty just raise em up Cause every inch of you is perfect from the bottom to the top Yeah my mama she told me dont worry about your size Shoo wop wop shaooh wop wop She says Boys like a little more booty to hold at night That booty uh that booty booty You know I wont be no stick figure silicone Barbie doll So if that what youre into then go head and move along Because you know Im all about that bass Bout that bass no treble Im all about that bass Bout that bass no treble Im all about that bass Bout that bass no treble Im all about that bass Bout that bass Hey Im bringing booty back Go head and tell them skinny girls that No Im just playing I know you think youre fat But Im here to tell you Every inch of you is perfect from the bottom to the top Yeah my mama she told me dont worry about your size Shoo wop wop shaooh wop wop She says Boys like a little more booty to hold at night That booty booty uh that booty booty You know I wont be no stick figure silicone Barbie doll So if thats what youre into then go head and move along Because you know Im all about that bass Bout that bass no treble Im all about that bass Bout that bass no treble Im all about that bass Bout that bass no treble Im all about that bass Bout that bass You know Im all about that bass Bout that bass no treble I said Im all about that bass Bout that bass no treble Im all about that bass Bout that bass no treble Im all about that bass Bout that bass Because you know Im all about that bass Bout that bass no treble Im all about that bass Bout that bass no treble Im all about that bass Bout that bass no treble Im all about that bass Bout that bass Hey Im all about that bass Bout that bass Hey Im all about that bass Bout that bass Hey Yeah yeah ohh You know you like this bass Hey"

# song = "It might seem crazy what Im about to say Sunshine shes here you can take a break Im a hot air balloon that could go to space With the air like I dont care baby by the way  Uh  Because Im happy Clap along if you feel like a room without a roof Because Im happy Clap along if you feel like happiness is the truth Because Im happy Clap along if you know what happiness is to you Because Im happy Clap along if you feel like thats what you wanna do  Here come bad news talking this and that yeah Well give me all you got and dont hold it back yeah Well I should probably warn you Ill be just fine yeah No offense to you dont waste your time Heres why  Because Im happy Clap along if you feel like a room without a roof Because Im happy Clap along if you feel like happiness is the truth Because Im happy Clap along if you know what happiness is to you Because Im happy Clap along if you feel like thats what you wanna do  Hey Go Uh  Happy Bring me down Cant nothing Bring me down My levels too high Bring me down Cant nothing Bring me down I said let me tell you now Bring me down Cant nothing Bring me down My levels too high Bring me down Cant nothing Bring me down I said  Because Im happy Clap along if you feel like a room without a roof Because Im happy Clap along if you feel like happiness is the truth Because Im happy Clap along if you know what happiness is to you Because Im happy Clap along if you feel like thats what you wanna do  Because Im happy Clap along if you feel like a room without a roof Because Im happy Clap along if you feel like happiness is the truth Because Im happy Clap along if you know what happiness is to you Because Im happy Clap along if you feel like thats what you wanna do  Hey Go Uh  Happy repeats Bring me down cant nothing Bring me down my levels too high Bring me down cant nothing Bring me down I said let me tell you now  Because Im happy Clap along if you feel like a room without a roof Because Im happy Clap along if you feel like happiness is the truth Because Im happy Clap along if you know what happiness is to you Because Im happy Clap along if you feel like thats what you wanna do   Because Im happy Clap along if you feel like a room without a roof Because Im happy Clap along if you feel like happiness is the truth Because Im happy Clap along if you know what happiness is to you Because Im happy Clap along if you feel like thats what you wanna do  Hey Cmon"

# song = "Oh  oh oh Oh  Yeah  Im gonna take my horse to the old town road Im gonna ride til I cant no more Im gonna take my horse to the old town road Im gonna ride til I cant no more Kio  Kio   I got the horses in the back Horse tack is attached Hat is matte black Got the boots thats black to match Ridin on a horse  ha You can whip your Porsche I been in the valley You aint been up off that porch  now  Cant nobody tell me nothin You cant tell me nothin Cant nobody tell me nothin You cant tell me nothin  Ridin on a tractor Lean all in my bladder Cheated on my baby You can go and ask her My life is a movie Bull ridin and boobies Cowboy hat from Gucci Wrangler on my booty  Cant nobody tell me nothin You cant tell me nothin Cant nobody tell me nothin You cant tell me nothin  Yeah  Im gonna take my horse to the old town road Im gonna ride til I cant no more Im gonna take my horse to the old town road Im gonna ride til I cant no more   Hat down  cross town  livin like a rock star Spent a lot of money on my brand new guitar Babys got a habit diamond rings and Fendi sports bras Ridin down Rodeo in my Maserati sports car Got no stress  Ive been through all that Im like a Marlboro Man so I kick on back Wish I could roll on back to that old town road I wanna ride til I cant no more   Yeah  Im gonna take my horse to the old town road Im gonna ride til I cant no more Im gonna take my horse to the old town road Im gonna ride til I cant no more"

# song = "Hey  I was doing just fine before I met you I drink too much and thats an issue but Im okay Hey  you tell your friends it was nice to meet them But I hope I never see them again I know it breaks your heart Moved to the city in a broke down car And four years  no calls Now youre looking pretty in a hotel bar And I cant stop No  I cant stop So baby pull me closer in the backseat of your Rover That I know you cant afford Bite that tattoo on your shoulder Pull the sheets right off the corner Of the mattress that you stole From your roommate back in Boulder We aint ever getting older We aint ever getting older We aint ever getting older You look as good as the day I met you I forget just why I left you  I was insane Stay and play that Blink 182 song That we beat to death in Tucson  okay I know it breaks your heart Moved to the city in a broke down car And four years  no call Now Im looking pretty in a hotel bar And I cant stop No  I cant stop So baby pull me closer in the backseat of your Rover That I know you cant afford Bite that tattoo on your shoulder Pull the sheets right off the corner Of the mattress that you stole From your roommate back in Boulder We aint ever getting older We aint ever getting older We aint ever getting older So baby pull me closer in the backseat of your Rover That I know you cant afford Bite that tattoo on your shoulder Pull the sheets right off the corner Of the mattress that you stole From your roommate back in Boulder We aint ever getting older We aint ever getting older we aint ever getting older We aint ever getting older we aint ever getting older We aint ever getting older we aint ever getting older We aint ever getting older We aint ever getting older No we aint ever getting older"


# print("***** WORDS IN SONG *****")
# song_words = song.lower()
# print(song_words.split())
# print()

# print("***** WORD FREQUENCIES *****")
# song_dict = generate_word_freqs(song)
# print(song_dict)
# print()

# print("***** MOST COMMON WORD *****")
# print(find_frequent_word(song_dict))
# print()

# print("***** TOP MOST COMMON WORDS *****")
# print(occurs_often(song_dict, 16))
# print()


############################################################
# dict performance, hashing, sets
############################################################


def create_list(size):
    result = []
    for k in range(size):
        result.append(k)
    return result


def create_dict(size):
    result = {}
    for k in range(size):
        result[k] = k
    return result


def time_ops(size, trials, ctype="list"):
    timings = []
    for _ in range(trials):
        start = time.time()
        if ctype == "list":
            collection = create_list(size)
        elif ctype == "dict":
            collection = create_dict(size)
        else:
            assert False, f"bad ctype {ctype!r}"
        stop = time.time()
        timings.append(stop - start)
    print(f"{ctype} creation: {sum(timings) / trials} seconds")

    timings = []
    item = size // 2
    for _ in range(trials):
        start = time.time()
        item in collection  # retrieve list element or dict key
        stop = time.time()
        timings.append(stop - start)
    print(f"{ctype} retrieval: {sum(timings) / trials} seconds")


# time_ops(size=10_000_000, trials=20, ctype="list")
# time_ops(size=10_000_000, trials=20, ctype="dict")


def demo_set_objects():
    not_a_set = {}
    print(type(not_a_set))
    print(not_a_set)
    print()

    some_set = {1, 5, "asdf"}
    print(type(some_set))
    print(some_set)
    print(len(some_set))
    # some_set.add(("Tom", "Hanks"))
    # print(some_set)
    # some_set.add(("Tom", "Hanks"))
    # print(some_set)
    # print()

    # some_set.remove(5)
    # print(some_set)
    # print(some_set | {1, 2, 3})
    # print(some_set)
    # some_set |= {1, 2, 3}
    # print(some_set)
    # print()


# demo_set_objects()
