{ "cells": [ { "cell_type": "markdown", "id": "5b310e45", "metadata": {}, "source": [ "# Lesson 4: Data Structures" ] }, { "cell_type": "markdown", "id": "7989a963", "metadata": {}, "source": [ "## Objectives\n", "\n", "File processing opens up new opportunities to work with more complex data than the numbers and strings that we've been typing into py scripts. But how do we represent this complex data? **Data structures** such as lists can represent complex data. While lists are quite useful on their own, Python provides several other built-in data structures to make it easier to represent complex data. By the end of this lesson, students will be able to:\n", "\n", "1. Apply list comprehensions to define basic list sequences.\n", "2. Apply set operations to store and retrieve values in a set.\n", "3. Apply dict operations to store and retrieve values in a dictionary.\n", "4. Describe the difference between the various data structures' properties (list, set, dict, tuple).\n", "5. Define type annotations for parameters and returns." ] }, { "cell_type": "markdown", "id": "01cfa426", "metadata": {}, "source": [ "## List Comprehensions\n", "\n", "Recall that we have previously used **lists** to represent indexed sequences of values, which can store values of any type:" ] }, { "cell_type": "code", "execution_count": null, "id": "e7232996", "metadata": {}, "outputs": [], "source": [ "# Create a list\n", "l = [1, 2, 'hello']\n", "\n", "# Print a list and get a value\n", "print(l)\n", "print(l[1]) # Lists, like str, are 0-indexed\n", "\n", "# You can also use slicing on lists, just like str\n", "print(l[1:2]) # [2]\n", "print(l[:2]) # [1, 2]\n", "\n", "# You can use assignment to update values in a list (can't with a str)\n", "l[1] = 'dogs'\n", "print(l)\n", "\n", "# Two ways to loop over a list\n", "for i in range(len(l)):\n", " print(l[i])\n", "\n", "for val in l:\n", " print(val)\n", "\n", "# Build up a list programmatically, fun_numbers from last time\n", "start = 2\n", "stop = 16\n", "result = [] # Empty list\n", "for i in range(start, stop):\n", " if i % 2 == 0 or i % 5 == 0:\n", " result.append(i)\n", "print(result)" ] }, { "cell_type": "markdown", "id": "54b3a93a", "metadata": {}, "source": [ "So far, we've shown how to specify a list by hand, using the following syntax:" ] }, { "cell_type": "code", "execution_count": null, "id": "0435b38e", "metadata": {}, "outputs": [], "source": [ "nums = [1, 2, 3]\n", "print(nums)" ] }, { "cell_type": "markdown", "id": "7355e8ee", "metadata": {}, "source": [ "What if I wanted to make a list of the numbers from 1 to 10? We could do that, but it would take a little bit more typing." ] }, { "cell_type": "code", "execution_count": null, "id": "fe3a0e02", "metadata": {}, "outputs": [], "source": [ "nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n", "print(nums)" ] }, { "cell_type": "markdown", "id": "24b4b58c", "metadata": {}, "source": [ "But, what if we wanted a list of numbers from 1 to 100? That's a ridiculous amount of typing! We could instead build up the list programmatically. Here is an example using the `list` methods we have learned." ] }, { "cell_type": "code", "execution_count": null, "id": "23ac3fa4", "metadata": {}, "outputs": [], "source": [ "nums = []\n", "for i in range(1, 101): \n", " nums.append(i)\n", "print(nums)" ] }, { "cell_type": "markdown", "id": "a74d4166", "metadata": {}, "source": [ "This is handy because `range` defines a sequence of numbers we are interested in, and then we just append them to our list. But these two ways are not the only ways to define a list data structure!" ] }, { "cell_type": "markdown", "id": "9b7aa811", "metadata": {}, "source": [ "### List Comprehension Syntax\n", "\n", "This is a very common pattern in Python, so it provides some nice syntax called a **list comprehension** to help you build up these lists. We first show the code and then explain the parts." ] }, { "cell_type": "code", "execution_count": null, "id": "991a4f36", "metadata": {}, "outputs": [], "source": [ "nums = [i for i in range(1, 101)]\n", "print(nums)" ] }, { "cell_type": "markdown", "id": "14453c2a", "metadata": {}, "source": [ "This is very similar to our first approach, where we spelled out what was inside the list, like in `[1, 2, 3]`. We read list comprehensions from the inside to the outside." ] }, { "cell_type": "code", "execution_count": null, "id": "8c09c6d8", "metadata": {}, "outputs": [], "source": [ "nums = [ # 3) Store the result in a list called nums\n", " i # 2) The value you will put in the list\n", " for i in range(1, 101) # 1) What you are looping over\n", "]\n", "print(nums)" ] }, { "cell_type": "markdown", "id": "bd597088", "metadata": {}, "source": [ "This is just a compact syntax for writing the full loop we showed earlier! It's a bit weird when you first see it that the for comes after the value, but you get used to it with practice. This syntax can be very handy for specifying things really quickly.\n", "\n", "One of the nice things about list comprehensions is they let you pretty easily transform your values before putting them in the list. In the last example, we just put i in the list but that isn't the only option! For example, what if we wanted to put in the squares of all the even numbers between 1 and 10? We could write a list comprehension for that too!" ] }, { "cell_type": "code", "execution_count": null, "id": "9a545715", "metadata": {}, "outputs": [], "source": [ "squares = [i ** 2 for i in range(1, 11)]\n", "print(squares)" ] }, { "cell_type": "markdown", "id": "58a2d5a5", "metadata": {}, "source": [ "Again, you should read it in the order we showed above:\n", "\n", "1. **What are you looping over?** `range(1, 11)` using loop variable `i`\n", "2. **What value are you storing in the result?** `i ** 2` (i.e. $i^2$)\n", "3. **What are we storing the result as?** A `list` named `squares`\n", "\n", "One last thing list comprehensions allow you to do is filter values from the original sequence to just the ones you want! For example, what if we only wanted the squares of numbers divisible by 3 between 1 and 10? Now you could implement this specific task by changing the `range` call to include a step-size, but you could imagine having a more complex condition that you can't simply solve it using that approach.\n", "\n", "**Take a second to think about how we would write this without a list comprehension.**\n", "\n", "It would be similar to our approach above that uses a call to `append`, but would be different in that it has an if-statement to only `append` some times. It would look something like the snippet below." ] }, { "cell_type": "code", "execution_count": null, "id": "f679fc3f", "metadata": {}, "outputs": [], "source": [ "squares = []\n", "for i in range(1, 11):\n", " if i % 3 == 0: # This is the new addition!\n", " squares.append(i ** 2)\n", "print(squares)" ] }, { "cell_type": "markdown", "id": "435b92e5", "metadata": {}, "source": [ "Python provides a syntax for conditionally including a value in a list comprehension using an if statement inside the comprehension. Again, the syntax looks a bit weird at first." ] }, { "cell_type": "code", "execution_count": null, "id": "51e8bc92", "metadata": {}, "outputs": [], "source": [ "squares = [i ** 2 for i in range(1, 11) if i % 3 == 0]\n", "print(squares)" ] }, { "cell_type": "markdown", "id": "12652c08", "metadata": {}, "source": [ "Like before, we will write this out in another way to show what is going on:" ] }, { "cell_type": "code", "execution_count": null, "id": "94657394", "metadata": {}, "outputs": [], "source": [ "squares = [ # 4) Store the result in a list called squares\n", " i ** 2 # 3) What value should be stored in the result?\n", " for i in range(1, 11) # 1) What you are looping over\n", " if i % 3 == 0 # 2) Should we include the value in the result?\n", "]\n", "print(squares)" ] }, { "cell_type": "markdown", "id": "9d542729", "metadata": {}, "source": [ "## Tuples\n", "\n", "Let's focus on two key properties of the **list data structure**.\n", "\n", "* Lists have **integer indices** that order the elements in the list.\n", "* Lists are **mutable**: unlike a string, elements can be added or removed at any index in a list.\n", "\n", "A different data structure in our programming toolbox is the **tuple** (pronounced either like \"two pull\" or to rhyme with \"supple\"). A `tuple` is much like a list in that it has integer indices, but it is different in that it is **immutable**. A `tuple` will have a pre-defined number of values inside of it, and you can't modify them!\n", "\n", "Lists and tuples look very similar but have some key differences.\n", "\n", "* Tuples don't have any meaningful methods like `list` does since you cannot modify them.\n", "* While lists are defined with square brackets (`[]`), tuples are defined with commas alone: `1, 2, 3`. We can also add parentheses around the structure for clarity. In fact, when displaying a tuple, Python will show parentheses to indicate that the tuple is a single unit!\n", "\n", "To access values in a `tuple`, you can index into them just like lists. To prove to you that you can't modify tuples, compare the list output to the tuple output:" ] }, { "cell_type": "code", "execution_count": null, "id": "5fc07ad1", "metadata": {}, "outputs": [], "source": [ "l = [1, 2, 3]\n", "print('l[0] =',l[0])\n", "print('l before', l)\n", "l[1] = 14\n", "print('l after', l)" ] }, { "cell_type": "code", "execution_count": null, "id": "cc059d71", "metadata": {}, "outputs": [], "source": [ "t = (1, 2, 3)\n", "print('t[0] =', t[0])\n", "# Though they display with parentheses for clarity\n", "print('t before', t)\n", "# Tuples cannot be modified\n", "t[1] = 14\n", "print('t after', t)" ] }, { "cell_type": "markdown", "id": "719f9d83", "metadata": {}, "source": [ "**Food for thought:** Why would we want to use a `tuple` instead of a `list`? What does the use of a `tuple` communicate to other programmers who might be using your code or data?" ] }, { "cell_type": "markdown", "id": "51c27738", "metadata": {}, "source": [ "### Unpacking Tuples\n", "\n", "Tuples normally appear as a way to return more than one value from a function (see the `five_number_summary` function in THA 1 for an example!). For example, we can write a function that returns both the first and second letter from a word. This is all done with Python returning and unpacking a tuple." ] }, { "cell_type": "code", "execution_count": null, "id": "5a5e2732", "metadata": {}, "outputs": [], "source": [ "def first_two_letters(word):\n", " return word[0], word[1]\n", "\n", "# Unpack the result into a and b\n", "a, b = first_two_letters('goodbye')\n", "print(a)\n", "print(b)" ] }, { "cell_type": "markdown", "id": "a689d835", "metadata": {}, "source": [ "One nice feature Python allows you to do is to **unpack** a tuple so that you can give a variable name to each component rather than having to specify the values by index (i.e. `t[2]`)." ] }, { "cell_type": "code", "execution_count": null, "id": "5fecdc59", "metadata": {}, "outputs": [], "source": [ "t = (4, 5, 6)\n", "print(t[1] + t[2])\n", "\n", "a, b, c = t # \"Unpacks\" t so that each element gets a variable name\n", "print(b + c)" ] }, { "cell_type": "markdown", "id": "7c8b2a14", "metadata": {}, "source": [ "The number of assignment targets on the left side **must** match with the length of the tuple. The following two snippets show what happens when you try to unpack too many or too few items." ] }, { "cell_type": "code", "execution_count": null, "id": "5d1b7f29", "metadata": {}, "outputs": [], "source": [ "t = (4, 5, 6)\n", "a, b, c, d = t # Try to unpack it into 4 variables\n", "print(b + c)" ] }, { "cell_type": "code", "execution_count": null, "id": "06da644d", "metadata": {}, "outputs": [], "source": [ "t = (4, 5, 6)\n", "a, b = t # Try to only unpack the first 2 values\n", "print(a)" ] }, { "cell_type": "markdown", "id": "e7e1388e", "metadata": {}, "source": [ "If you only want to use a few values, a common technique is to use a name like `_` that indicates it won't be used elsewhere." ] }, { "cell_type": "code", "execution_count": null, "id": "a171499a", "metadata": {}, "outputs": [], "source": [ "t = (4, 5, 6)\n", "a, b, _ = t # Unpack all 3 values but assign the last one to a placeholder\n", "print(a)" ] }, { "cell_type": "markdown", "id": "3e201eba", "metadata": {}, "source": [ "This isn't any special syntax! This is just using the character `_` as the variable name (just like we can use the name `b` for a variable name)! For example, you could theoretically `print(_)` and it would print `6` in the snippet above. It's not conventional to do this though, since using `_` as a variable name indicates you don't care to use that value!" ] }, { "cell_type": "markdown", "id": "b95973cd", "metadata": {}, "source": [ "## Sets\n", "\n", "Let's look at another data structure called a **set** that is designed with the idea of finding a set of unique values in mind. A `set` is like a `list` in the sense that it is a sequence of values, but differs in two major ways:\n", "\n", "* The `set` does not allow duplicate values.\n", "* The `set` does not have a notion of indices. There is no \"index 0\", in a `set`. The mindset you should have a set is an unordered \"bag\" of values. The `set` does have some *internal* ordering in which it stores the values, but you can't access any particular value inside of it.\n", "\n", "`set` is a type defined in Python that implements this behavior. The following cell shows how to create one (using `set()` to make an empty `set`). The `set` class has the following methods (assume stored in a variable called `s`):\n", "\n", "* `s.add(x)` which adds `x` to `s` (ignores duplicates)\n", "* `s.remove(x)` removes `x` from `s`\n", "* `s.clear()` removes all values from `s`" ] }, { "cell_type": "code", "execution_count": null, "id": "43b952ba", "metadata": {}, "outputs": [], "source": [ "nums = set() # Creates an empty set\n", "print(nums)\n", "\n", "nums.add(1)\n", "nums.add(2)\n", "nums.add(3)\n", "nums.add(4)\n", "nums.add(2) # Is a duplicate, so it's ignored\n", "nums.add(-1)\n", "\n", "print(nums)\n", "\n", "print(set([1,1,1,2,3,4,4]))" ] }, { "cell_type": "markdown", "id": "f7ef4fe9", "metadata": {}, "source": [ "Also helpful, `set`s also support the `len` function and allow you to use the `in` keyword to see if they contain a value." ] }, { "cell_type": "code", "execution_count": null, "id": "1eaec3b5", "metadata": {}, "outputs": [], "source": [ "print(len(nums))\n", "\n", "if 5 in nums:\n", " print('Found it')\n", "else:\n", " print('Did not find it')" ] }, { "cell_type": "markdown", "id": "82012d02", "metadata": {}, "source": [ "However, like we said earlier the `set` does not have a notion of an index. That means if you try to run the following cell, you will get an error." ] }, { "cell_type": "code", "execution_count": null, "id": "9af3d810", "metadata": {}, "outputs": [], "source": [ "print(nums[0])" ] }, { "cell_type": "markdown", "id": "38424e31", "metadata": {}, "source": [ "So what's the point of using a `set` over a `list`? It turns out the way it is implemented internally, it does an incredibly fast job at determining whether a particular element is inside of it so as to avoid adding duplicates. By losing the ability to \"index\" into the `set`, we gain speed since it can store the data in its own special way to optimize these \"membership queries\" (i.e. \"is this element in this set?\").\n", "\n", "To show this, let's revisit `count_unique_words` from Lesson 3 with basically all of the same code above, except:\n", "\n", "* Change the `unique_words` to a `set` instead of a `list`.\n", "* Remove the `in` check since the `set` just skips duplicates." ] }, { "cell_type": "code", "execution_count": null, "id": "e405b20e", "metadata": {}, "outputs": [], "source": [ "def count_unique_words_set(file_name):\n", " unique_words = set()\n", " with open(file_name) as f:\n", " for line in f.readlines():\n", " for word in line.split():\n", " unique_words.add(word)\n", " return len(unique_words)" ] }, { "cell_type": "markdown", "id": "1b6dc603", "metadata": {}, "source": [ "## Dictionaries\n", "\n", "Suppose we wanted to perform a slightly more complex analysis. Instead of finding the number of unique words, what if we want to count the occurrences of each word in a file? It's not clear how you could use a `list` or `set` to solve the problem, \"For a given word, how many of them have we seen?\" A `list` seems like it's more on the right track, but unfortunately, the indices have to be numbers! There is no way of using a `list` to say that a word should be an index.\n", "\n", "The last data structure we are going to learn in this lesson is called a **dictionary**, represented in Python as `dict`. A `dict` is a very powerful data structure since it acts, in some sense, as a more generalized list. Essentially a `dict` is much like a `list`, but allows you to store **any data type as the index**, while a `list` only allows integers from `0` to `len - 1`.\n", "\n", "To create a `dict` in Python, you use the syntax in the following snippet. Note that `dict` supports the square-bracket notation for accessing a value, but now you can use any value for the index. In fact, `dict` uses a different term for the index to reduce confusion with lists: we call the \"index\" of an entry in a dict its **key**. We describe a `dict` as a collection of **key/value pairs** that are accessible via the key." ] }, { "cell_type": "code", "execution_count": null, "id": "38c0e902", "metadata": {}, "outputs": [], "source": [ "d = {'a': 1, 'b': 17, 47: 'scurvy'}\n", "print(d)\n", "# This makes a dictionary with the following keys/values:\n", "# The key 'a' is associated with the value 1\n", "# The key 'b' is associated with the value 17\n", "# The key 47 is associated with the value 'scurvy'\n", "\n", "# You can get/set the value for a key using the square-bracket notation\n", "print(d['b'])\n", "\n", "d['dogs'] = 'cute'\n", "print(d)\n", "\n", "# If a key already exists in the dict, it will be overwritten if you set it\n", "d['dogs'] = 'very adorable'\n", "print(d)" ] }, { "cell_type": "markdown", "id": "cda639d7", "metadata": {}, "source": [ "The nice thing is you have a pretty solid understanding of how to use a `dict` already because you know how to use lists! The semantics of accessing/setting a value associated to a key are very similar to accessing/setting a value associated to an index in a `list`.\n", "\n", "If you try to look up a key that is not in the `dict`, you will run into a `KeyError`. As a note, we also show how to make an empty dict with the syntax `{}` (just like an empty list is `[]`)." ] }, { "cell_type": "code", "execution_count": null, "id": "235336cf", "metadata": {}, "outputs": [], "source": [ "d = {}\n", "d['dogs'] = 'very cute'\n", "print(d['cats'])" ] }, { "cell_type": "markdown", "id": "f6d1b6e9", "metadata": {}, "source": [ "To prevent this error, you can use the `in` keyword to see if a key is in a `dict` before trying to access it." ] }, { "cell_type": "code", "execution_count": null, "id": "10ddd641", "metadata": {}, "outputs": [], "source": [ "d = {}\n", "d['dogs'] = 'very cute'\n", "if 'cats' in d:\n", " print(d['cats'])\n", "else:\n", " print('No cats!')" ] }, { "cell_type": "markdown", "id": "9ca58d93", "metadata": {}, "source": [ "### Example: Counting Word Lengths\n", "\n", "Imagine we had a list of strings, and we wanted to find sum of the word lengths that start with each letter. For example, with the list `['cats', 'dogs', 'deers']` we would report the sum of the lengths of strings that start with `'c'` is `4` while the sum of the lengths of strings that start with `'d'` is `9`. We will write a function called `count_lengths` to solve this problem. The function should take a `list` of words (all `str`) and we can assume none of the `str` are the empty string.\n", "\n", "This seems like the task of a `dict` where the *keys* are the first letters of the words, and the *values* are the sum of the lengths. Let's try to write a function to use the things we have seen so far to do this!" ] }, { "cell_type": "code", "execution_count": null, "id": "8fd8174c", "metadata": {}, "outputs": [], "source": [ "def count_lengths(words):\n", " counts = {}\n", " for word in words:\n", " first_letter = word[0]\n", " counts[first_letter] = counts[first_letter] + len(word)\n", " return counts\n", " \n", "print(count_lengths(['cats', 'dogs', 'deers']))" ] }, { "cell_type": "markdown", "id": "36c0d3e1", "metadata": {}, "source": [ "We ran into an error! What happened?\n", "\n", "It turns out we crashed on the first word in the list, `'cats'`. We get the first letter `'c'` and we try to get the value in the dictionary associated to the key `'c'` when we evaluate `counts[first_letter] + len(word)`. Remember though, if a key is not present, we get a `KeyError`, which is exactly what happened in this snippet!\n", "\n", "To fix this, we need to introduce a common pattern when working with `dict`s. If you are ever adding values to a `dict`, you commonly need to think about these cases:\n", "\n", "* This is the first time we have seen the key\n", "* We have seen the key before\n", "\n", "Depending on which case you are in, you need to write different code to handle the fact that the key is not present in the `dict` in the first case. We can easily fix this by introducing a check that uses `in`. All of the added code is inside the loop, and is there to avoid getting this `KeyError`." ] }, { "cell_type": "code", "execution_count": null, "id": "045cddc4", "metadata": {}, "outputs": [], "source": [ "def count_lengths(words):\n", " counts = {}\n", " for word in words:\n", " first_letter = word[0]\n", " if first_letter in counts:\n", " counts[first_letter] = counts[first_letter] + len(word)\n", " else:\n", " counts[first_letter] = len(word)\n", " return counts\n", " \n", "print(count_lengths(['cats', 'dogs', 'deers']))" ] }, { "cell_type": "markdown", "id": "48b7c624", "metadata": {}, "source": [ "**Food for thought:** How would you describe what the `if` check is doing in English?" ] }, { "cell_type": "markdown", "id": "2e149137", "metadata": {}, "source": [ "## Type Annotations\n", "\n", "By now, we have learned a lot of different data types and data structures. Many students in this class come from programming backgrounds in a language where you have to declare the types of your variables (e.g., Java). Python was created with the idea of flexibility, so that any variable can store a value of any type, so you don't need type declarations.\n", "\n", "However, Python has more recently started adding notions of defining types for variables to help spot certain types of bugs earlier. They've added the ability to annotate variables with a type to help indicate what type of values should be stored there. They are still relatively early in development, so these annotations are purely informational. Still, future versions of Python will allow more complex checks to occur based on these type annotations.\n", "\n", "In CSE 163, we will ask you to use these **type annotations** for parameters and returns of a function. We will not expect you to annotate local variables. This matches what many Python developers do, where they add type annotations to function headers to document their methods better but do not do so on their local variables.\n", "\n", "Here is an example method with type annotations. The annotation is read `param_name: type` and for the return type, it is the type that follows the `->`. The syntax says that the parameter `s` is type `str`, the parameter `times` is type `int` and the `return` type of the method is a `str`." ] }, { "cell_type": "code", "execution_count": null, "id": "fbd5b8bb", "metadata": {}, "outputs": [], "source": [ "def repeat(s: str, times: int) -> str:\n", " \"\"\"\n", " Returns a new string that has the contents of s repeated \n", " times times\n", " \"\"\"\n", "\n", " result = \"\"\n", " for i in range(times):\n", " result += s\n", " return result" ] }, { "cell_type": "markdown", "id": "8bf8ff24", "metadata": {}, "source": [ "Adding these annotations makes it clear to someone reading the method exactly what types should be passed in. Adding these does not mean we don't have to document our parameters and returns in our comments. It's just an added note indicating what values should be. Like comments and doc-strings, type annotations do not affect the behavior of your code; it just provides more information to the user what values should be given and what values will be returned." ] }, { "cell_type": "markdown", "id": "a385f626", "metadata": {}, "source": [ "### Annotating Data Structures\n", "\n", "With data structures, there is an extra step with type annotations: we need to indicate what are the types *inside* the structure.\n", "\n", "For example, if you have a method that takes a `list` of strings and prints out the first character of each string, it would be inappropriate to type the parameter as a `list` because we then don't know that the values inside the `list` must be `str`s. So we need to modify our type annotation to say `list[str]` (square brackets important) to indicate that it is a `list` containing `str` elements." ] }, { "cell_type": "code", "execution_count": null, "id": "dbd0e1aa", "metadata": {}, "outputs": [], "source": [ "def print_first_characters(words: list[str]) -> None:\n", " \"\"\"\n", " Takes in a list of words and prints the first\n", " character in each word\n", " \"\"\"\n", " for word in words:\n", " print(word[0])" ] }, { "cell_type": "markdown", "id": "7218ba40", "metadata": {}, "source": [ "Here are some other example data structure type annotations:\n", "\n", "* `list[float]` - A `list` of `float` elements:\n", "* `set[int]` - A `set` of `int` elements\n", "* `dict[str, int]` - A `dict` with `str` keys and `int` values (Note we have to specify the type for the key and the value, separated by a comma)\n", "* `dict[str, int | str]` - A dict with `str` keys and values that are either `int` or `str` (Note that we indicate the possibility of multiple data types with the `|`)\n", "* `tuple[int, str, float]` - A `tuple` of length 3 with the first element an `int`, the second a `str`, and the third a `float`.\n", "\n", "Sometimes, though, you can't come up with a good type annotation for a parameter or a value inside of a data structure. For example, maybe you have a `list` with heterogeneous types (mixed types) where you can't constrain them to a set of known types; maybe it can contain `bool`s or `int`s or `float`s or `str` and a myriad of other types that would be too hard to list out.\n", "\n", "In this case, Python has a fallback where you can say the type of a value (or the type inside of a data structure) is `Any` type. This is generally a bad practice unless necessary, as it defeats the purpose of specifying types in the first place. That being said, here's how you can do it. Note the `import` statement at the top!" ] }, { "cell_type": "code", "execution_count": null, "id": "f6d84995", "metadata": {}, "outputs": [], "source": [ "from typing import Any\n", "\n", "def example(x: list[Any]) -> dict[str, Any]:\n", " ..." ] }, { "cell_type": "markdown", "id": "c9531280", "metadata": {}, "source": [ "**For any non-creative assignments in CSE 163, `Any` will not be an appropriate type annotation!**\n", "\n", "You are **not** responsible for using type annotations on THA 1. However, starting from THA 2, we will expect that your code has the correct type annotations! See more details in the [Code Quality Guide](https://courses.cs.washington.edu/courses/cse163/26wi/code_quality/#type-annotations).\n", "\n", "**Food for thought:** How would you type-annotate the other functions that you've seen so far in this class?\n" ] }, { "cell_type": "markdown", "id": "3be6f273", "metadata": {}, "source": [ "## ⏸️ Pause and 🧠 Think\n", "\n", "Take a moment to review the following concepts and reflect on your own understanding. A good temperature check for your understanding is asking yourself whether you might be able to explain these concepts to a friend outside of this class.\n", "\n", "Here's what we covered in this lesson:\n", "\n", "* List comprehensions\n", "* `tuple`s and tuple unpacking\n", "* `set` and set methods\n", "* `dict` and dictionary methods\n", "* Type annotations\n", "\n", "Here are some other guiding exercises and questions to help you reflect on what you've seen so far:\n", "\n", "1. In your own words, write a few sentences summarizing what you learned in this lesson.\n", "2. What did you find challenging in this lesson? Come up with some questions you might ask your peers or the course staff to help you better understand that concept.\n", "3. What was familiar about what you saw in this lesson? How might you relate it to things you have learned before?\n", "4. Throughout the lesson, there were a few **Food for thought** questions. Try exploring one or more of them and see what you find." ] }, { "cell_type": "markdown", "id": "9e01e828", "metadata": {}, "source": [ "## In-Class\n", "\n", "When you come to class, we will work together on `area_codes.py` and `count_words.py`. Make sure that you have a way of editing and running these files!" ] }, { "cell_type": "markdown", "id": "6c26da6f", "metadata": {}, "source": [ "### `area_codes`\n", "\n", "For this practice problem, refer to `area_codes.py`.\n", "\n", "Write a function `area_codes` that takes a `list` of `str` as input, where each `str` in the `list` is a phone number, and returns the number of unique area codes found in those phone numbers. Each phone number will be of the format `'123-456-7890'` and the area code is the first three characters in the `str`.\n", "\n", "For example, if we were to call\n", "\n", "```python\n", "area_codes([\n", " '123-456-7890',\n", " '206-123-4567',\n", " '123-000-0000',\n", " '425-999-9999'\n", "])\n", "```\n", "\n", "This call would return `3` because there are `3` unique area-codes in these phone numbers `(123, 206, 425)`.\n", "\n", "**Hint:** Try developing a simpler version of this problem that prints all the area codes for the given phone numbers. Then, count each unique area code by selecting the appropriate data structure: `list`, `tuple`, `set`, or `dict`." ] }, { "cell_type": "markdown", "id": "4750fe65", "metadata": {}, "source": [ "### `count_words`\n", "\n", "For this practice problem, refer to `count_words.py`.\n", "\n", "Write a function `count_words` that takes a file name and returns a `dict` that stores the tokens as keys and the number of times a specific token appeared in the file as values. Remember that a token is a sequence of characters separated by spaces.\n", "\n", "Consider a file `popular_techno_song.txt` with the contents:\n", "\n", "```text\n", "123456\n", "dun dun dun dun\n", "dun dun dun dun\n", "err\n", "dun dun dun dun dun dun dun dun\n", "dundundundundundundundundundun\n", "er er er er er er ER ER ER ER ER ER der der der der derrr\n", "```\n", "\n", "`count_words('popular_techno_song.txt')` should return the dict\n", "\n", "```text\n", "{\n", " 'dun': 16,\n", " 'err': 1,\n", " 'dundundundundundundundundundun': 1,\n", " 'er': 6,\n", " 'ER': 6,\n", " 'der': 4,\n", " 'derrr': 1\n", "}\n", "```\n", "\n", "No need to do anything fancy with capitalization or ignoring any punctuation! Just process each token. Here's our recommended process:\n", "\n", "1. Start by writing the function header.\n", "2. Then, process the file word-by-word.\n", "3. Finally, think about how to store data." ] }, { "cell_type": "markdown", "id": "63f78c9d", "metadata": {}, "source": [ "## Canvas Quiz\n", "\n", "All done with the lesson? Complete the [Canvas Quiz linked here!](https://canvas.uw.edu/courses/1860345/quizzes/2330953)" ] } ], "metadata": { "kernelspec": { "display_name": "cse163", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.13.5" } }, "nbformat": 4, "nbformat_minor": 5 }