{ "cells": [ { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n" ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import sys\n", "sys.path.append('../../../styles/')\n", "import styles\n", "styles.custom()" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "Content under Creative Commons Attribution license CC-BY 4.0, code under BSD 3-Clause License © 2017 L.A. Barba, N.C. Clementi" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Strings and lists in action\n", "\n", "After completing Lesson 1 and Lesson 2 of this course in _\"Engineering Computations_,\"\n", "here we have a full example using all that you've learned.\n", "\n", "You may be wondering why we're dedicating the first lessons of the course to playing around with strings and lists. _\"Engineering computations involve numbers, formulas and equations!\"_, you may be thinking. The reason is that this course assumes no programming experience at all, so we want to get everyone used to Python first, without adding the extra complexity of number-crunching. The idea is to get acquainted with programming constructs first, applying them to situations that involve no mathematics ...for now!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Play with the MAE bulletin\n", "\n", "We are going to play with text from a file containing a copy of the MAE Bulletin for 2017-2018. We'll create different lists to allow us to locate the title, credits and description of a course based on the course code.\n", "Then we'll get all the courses with prerequisites into a structured text format—and you'll learn a few tricks along the way!\n", "\n", "If you made a copy of the full set of lessons, as they are stored on GitHub, the data file for this example should be located in a folder named `data`, one level above the location of this lesson. If you downloaded the [data file](https://raw.githubusercontent.com/engineersCode/EngComp1_offtheground/9b4c76512d55435348c4ff48da37618cb5a9b7d6/data/mae_bulletin.txt) elsewhere, you should edit the full path below. \n", "\n", "If you are following along on your own notebook, you can download the data file to your working directory by adding a code cell and executing:\n", "\n", "```python\n", "from urllib.request import urlretrieve\n", "URL = 'http://go.gwu.edu/engcomp1data3'\n", "urlretreive(URL, 'mae_bulletin.txt')\n", "```\n", "\n", "We'll start by reading the data into the Jupyter notebook, then we'll clean the data a bit, and finally work out ways to play with it. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Read data from a file\n", "\n", "We know that we have a data file, and we'd like to read its contents into the Jupyter notebook. Usually, it's a good idea to first peek into the file, to see what its contents look like. This also gives us a chance to teach you a pretty neat trick.\n", "\n", "Recall that code cells in a Jupyter notebook can handle any valid **IPython** statement. Well, IPython is able to do a bit more than just Python: it can also run any system command. If you know some Unix, this can be very useful—for example, you could list all the files in the working directory (your location in the computer's file system), using the `ls` command.\n", "\n", "To execute a system (a.k.a., shell) command, you prepend `!`—a \"bang.\" The command we need is `head`: it prints out the first few lines of a file. \n", "\n", "Our data folder is found one directory up from the lesson (this Jupyter notebook): in Unix, going _up_ a directory is indicated by two dots; so we need to have `../data/` before the file name,`mae_bulletin.txt`, as part of the path. \n", "\n", "Let's call `head` with a bang:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MAE 1004. Engineering Drawing and Computer Graphics. 0-3 Credits.\n", "\n", "Introduction to technical drawing, including use of instruments, lettering, geometric construction, sketching, orthographic projection, section view, dimensioning, tolerancing, and pictorial drawing. Introduction to computer graphics, including topics covered in manual drawing and computer-aided drafting. (Fall and spring).\n", "\n", "MAE 2117. Engineering Computations. 3 Credits.\n", "\n", "Numerical methods for engineering applications. Round-off errors and discretization errors. Methods for solving systems of linear equations, root finding, curve fitting, numerical Fourier transform, and data approximation. Numerical differentiation and integration and numerical solution of differential equations. Computer applications. Prerequisite: MATH 1232. (Fall, Every Year).\n", "\n", "MAE 2124. Linear Systems Analysis for Robotics. 3 Credits.\n", "\n" ] } ], "source": [ "# modify the path to the data file as needed\n", "!head ../data/mae_bulletin.txt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That looks good! The next step is to _open the file_ and save its contents into a Python variable that we can use. \n", "\n", "The **`open()`** function with the file name as a _string_ argument (note the quotes) returns a Python file object. We have several options next, and you should definitely consult the documentation about reading and writing files.\n", "\n", "Write the file name (with the full path) into a string variable:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "file = '../data/mae_bulletin.txt'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you use the **`read()`** file method, you get a (big) string with all the file's contents. If you use instead the **`readlines()`** method, you get a list of strings, where each string contains one line in the file. Yet another option is to use the **`list()`** function to create a list of lines from the file contents.\n", "\n", "We could now write:\n", "\n", "`text_lines = open(file).readlines()`\n", "\n", "to open the file, and read its contents line-by-line into list. This has a potential downside: if we forget to issue the `file.close()` command later, it is possible that the file will not be properly closed (if there is an error in later code, for example). \n", "\n", "To avoid problems, we can use the `with` keyword: it sets up a context for the code that works with the file. Once the code block is exited, the file is automatically closed (releasing system resources, which matters if the file is big). Let's do that." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "with open(file) as file_obj:\n", " text_lines = file_obj.readlines()" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "list" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(text_lines)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "431" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "len(text_lines)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Cleaning and organizing text data\n", "\n", "When manipulating text data, one of the typical tasks is to get rid of extra white spaces and lines. Here, we'll remove the spaces at the beginning and end of each line. \n", "\n", "Note that there are also some blank lines: we'll skip them. The goal is to get two new lists: one with the course ID line, and another with the course descriptions. \n", "\n", "Study the following block of code:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "courses = []\n", "descriptions = []\n", "\n", "for line in text_lines:\n", " line = line.strip() #Remove white spaces\n", " if line == '': #Skip the empty lines \n", " continue\n", " elif line.startswith('MAE'): \n", " courses.append(line) #Save lines that start with MAE in list\n", " else:\n", " descriptions.append(line) #Save descriptions in other list" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Be sure to also visit the documentation for string methods, to get a glimpse of all the things you can do with Python! Here, we used the **`strip()`** method to get rid of leading and trailing spaces, and we also used **`startswith()`** to identify the course ID lines.\n", "\n", "Let's check what we ended up with by printing a few items in each list:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['MAE 1004. Engineering Drawing and Computer Graphics. 0-3 Credits.', 'MAE 2117. Engineering Computations. 3 Credits.', 'MAE 2124. Linear Systems Analysis for Robotics. 3 Credits.']\n" ] } ], "source": [ "#print first 3 elements of courses\n", "print(courses[0:3])" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['Introduction to technical drawing, including use of instruments, lettering, geometric construction, sketching, orthographic projection, section view, dimensioning, tolerancing, and pictorial drawing. Introduction to computer graphics, including topics covered in manual drawing and computer-aided drafting. (Fall and spring).', 'Numerical methods for engineering applications. Round-off errors and discretization errors. Methods for solving systems of linear equations, root finding, curve fitting, numerical Fourier transform, and data approximation. Numerical differentiation and integration and numerical solution of differential equations. Computer applications. Prerequisite: MATH 1232. (Fall, Every Year).', 'Properties of linear systems. Mathematical modeling of dynamic systems. State space, state variables, and their selection. Linearization of non-linear behavior. Matrix functions. Solution of state equations in the time domain and using transformations. System stability and frequency response.']\n" ] } ], "source": [ "#print first 3 elements of descriptions\n", "print(descriptions[0:3])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We should also check that both lists are the of the same length!" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "108\n", "108\n" ] } ], "source": [ "print(len(courses))\n", "print(len(descriptions))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Separate `courses` list into course id, title, and credits\n", "\n", "We may want to have the information of the course id, title, and credits in separate lists. Here's how we could do that:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "course_id = []\n", "course_title = []\n", "course_credits = []\n", "\n", "for course in courses:\n", " course_info = course.split('. ') \n", " course_id.append(course_info[0])\n", " course_title.append(course_info[1])\n", " course_credits.append(course_info[2])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that we split using the string `'. '` (period + space) to avoid having an extra white space at the beginning of each string in the lists for course title and credits. \n", "\n", "Let's print out the first elements of the new lists." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['MAE 1004', 'MAE 2117', 'MAE 2124', 'MAE 2131', 'MAE 2170']\n" ] } ], "source": [ "print(course_id[0:5])" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['Engineering Drawing and Computer Graphics', 'Engineering Computations', 'Linear Systems Analysis for Robotics', 'Thermodynamics', 'History and Impact of the US Patent System']\n" ] } ], "source": [ "print(course_title[0:5])" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['0-3 Credits.', '3 Credits.', '3 Credits.', '3 Credits.', '3 Credits.']\n" ] } ], "source": [ "print(course_credits[0:5])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Tracking course information\n", "\n", "The lists we have created are aligned: that is each item on the same index location corresponds to the same course. So by finding the location of a course id, we can access all the other information. \n", "\n", "We use the `index()` method to find the index of the course id and track the rest of the info. Try it out!" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "17" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "course_id.index('MAE 3190')" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MAE 3190\n", "Analysis and Synthesis of Mechanisms\n", "3 Credits.\n", "Kinematics and dynamics of mechanisms. Displacements, velocities, and accelerations in linkage, cam, and gear systems by analytical, graphical, and computer methods. Synthesis of linkages to meet prescribed performance requirements. Prerequisite: APSC 2058. (Fall).\n" ] } ], "source": [ "print(course_id[17])\n", "print(course_title[17])\n", "print(course_credits[17])\n", "print(descriptions[17])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How many courses have prerequisites?\n", "\n", "Here's an idea: we could look for all the courses that have prerequisites using a `for` statement. In this case, we'll iterate over the _index_ of the elements in the list `descriptions`. \n", "\n", "It gives us a chance to introduce to you a most helpful Python object: **`range`**: it creates a sequence of numbers in arithmetic progression to iterate over. With a single argument, `range(N)` will create a sequence of length `N` starting at zero: `0, 1, 2, …, N-1`. " ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n", "3\n" ] } ], "source": [ "for i in range(4):\n", " print(i)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The funny thing with `range` is that it's created on-the-fly, when iterating over it. So it's not really a list, although for most practical purposes, it behaves like one. \n", "\n", "A typical way to use it is with an argument that's output by the `len()` function. Study this block of code:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "course_with_pre = []\n", "\n", "for i in range(len(descriptions)):\n", " if 'Prerequisite' in descriptions[i]:\n", " course_with_pre.append(course_id[i])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we have a list named `course_with_pre` that contains the id of all the courses that have prerequisites." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['MAE 2117', 'MAE 2131', 'MAE 3120', 'MAE 3126', 'MAE 3128', 'MAE 3134', 'MAE 3145', 'MAE 3155', 'MAE 3162', 'MAE 3166W', 'MAE 3167W', 'MAE 3187', 'MAE 3190', 'MAE 3191', 'MAE 3192', 'MAE 3193', 'MAE 3195', 'MAE 3197', 'MAE 4129', 'MAE 4149', 'MAE 4157', 'MAE 4163', 'MAE 4168', 'MAE 4172', 'MAE 4182', 'MAE 4193', 'MAE 4194', 'MAE 4198', 'MAE 4199', 'MAE 6201', 'MAE 6207', 'MAE 6220', 'MAE 6221', 'MAE 6222', 'MAE 6223', 'MAE 6224', 'MAE 6225', 'MAE 6226', 'MAE 6227', 'MAE 6228', 'MAE 6229', 'MAE 6230', 'MAE 6231', 'MAE 6232', 'MAE 6233', 'MAE 6234', 'MAE 6237', 'MAE 6238', 'MAE 6239', 'MAE 6240', 'MAE 6241', 'MAE 6243', 'MAE 6244', 'MAE 6245', 'MAE 6246', 'MAE 6247', 'MAE 6249', 'MAE 6251', 'MAE 6252', 'MAE 6253', 'MAE 6254', 'MAE 6255', 'MAE 6257', 'MAE 6258', 'MAE 6260', 'MAE 6261', 'MAE 6262', 'MAE 6270', 'MAE 6271', 'MAE 6274', 'MAE 6276', 'MAE 6280', 'MAE 6281', 'MAE 6282', 'MAE 6283', 'MAE 6284', 'MAE 6286', 'MAE 6287', 'MAE 6288', 'MAE 6290', 'MAE 6291', 'MAE 6292', 'MAE 8350', 'MAE 8351', 'MAE 8352']\n" ] } ], "source": [ "print(course_with_pre)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### _Exercise:_\n", "\n", "1. Save in a new list named `course_with_cor` all the courses that have a corequisite, and print out the list. \n", "2. Using a `for` statement and `if-elif-else` statements, separate the courses that are offered in the Fall semester, those offered in the Spring semester, those offered in both semesters , and those that don't specify a semester. Create 4 lists: `fall_and_spring`, `fall`, `spring` and `not_spec`.\n", "\n", "*Tip:*\n", "\n", "When looking for the courses that are taught both semesters, check on the original file to look for the correct syntax." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To check your answers uncomment the following lines by deleting the # symbol and running the cell. If there is no output you got it right!" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "#course_with_cor_ans = ['MAE 3134', 'MAE 3184', 'MAE 4183', 'MAE 4195', 'MAE 6194', 'MAE 6195']\n", "#fall_and_spring_ans = ['MAE 1004', 'MAE 4199']\n", "#fall_ans = ['MAE 2117', 'MAE 3126', 'MAE 3145', 'MAE 3162', 'MAE 3166W', 'MAE 3190', 'MAE 3191', 'MAE 4157', 'MAE 4163', 'MAE 4193', 'MAE 6210', 'MAE 6275']\n", "#spring_ans = ['MAE 3128', 'MAE 3167W', 'MAE 3193', 'MAE 6194', 'MAE 6195', 'MAE 6229', 'MAE 6235', 'MAE 6242', 'MAE 6247', 'MAE 6249', 'MAE 6258']\n", "#not_spec_ans = ['MAE 2124', 'MAE 2131', 'MAE 2170', 'MAE 3120', 'MAE 3134', 'MAE 3155', 'MAE 3171', 'MAE 3184', 'MAE 3187', 'MAE 3192', 'MAE 3195', 'MAE 3196', 'MAE 3197', 'MAE 4129', 'MAE 4149', 'MAE 4168', 'MAE 4172', 'MAE 4182', 'MAE 4183', 'MAE 4194', 'MAE 4195', 'MAE 4198', 'MAE 6201', 'MAE 6203', 'MAE 6204', 'MAE 6207', 'MAE 6220', 'MAE 6221', 'MAE 6222', 'MAE 6223', 'MAE 6224', 'MAE 6225', 'MAE 6226', 'MAE 6227', 'MAE 6228', 'MAE 6230', 'MAE 6231', 'MAE 6232', 'MAE 6233', 'MAE 6234', 'MAE 6237', 'MAE 6238', 'MAE 6239', 'MAE 6240', 'MAE 6241', 'MAE 6243', 'MAE 6244', 'MAE 6245', 'MAE 6246', 'MAE 6251', 'MAE 6252', 'MAE 6253', 'MAE 6254', 'MAE 6255', 'MAE 6257', 'MAE 6260', 'MAE 6261', 'MAE 6262', 'MAE 6263', 'MAE 6270', 'MAE 6271', 'MAE 6274', 'MAE 6276', 'MAE 6277', 'MAE 6280', 'MAE 6281', 'MAE 6282', 'MAE 6283', 'MAE 6284', 'MAE 6286', 'MAE 6287', 'MAE 6288', 'MAE 6290', 'MAE 6291', 'MAE 6292', 'MAE 6298', 'MAE 6998', 'MAE 6999', 'MAE 8350', 'MAE 8351', 'MAE 8352', 'MAE 8998', 'MAE 8999']\n", "\n", "#assert course_with_cor == course_with_cor_ans\n", "#assert fall_and_spring == fall_and_spring_ans \n", "#assert fall == fall_ans\n", "#assert spring == spring_ans\n", "#assert not_spec == not_spec_ans" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Making a dictionary of courses with their prerequisites\n", "\n", "Having a list of all the courses that have prerequisities seems hardly useful. Normally you want to know _which courses_ are prerequisites for others. To structure our desired result, we will introduce a new Python data type: the _dictionary_.\n", "\n", "A Python dictionary stores values using a unique _key_, rather than an index, as we've used until now. Similar to a real-life dictionary, where you can look up words to get their definitions, a Python dictionary lets you look up keys to find their values. \n", "\n", "For example, you could have:\n", "\n", "`fruits = {'apple': 'red', 'banana': 'yellow', 'raspberry': 'red'}`\n", "\n", "Notice that we use curly brackets as delimiters, `{}`, instead of the square brackets used by lists. Also, the _value_ can be repeated (here, `'red'`), but not the key. You can now get the color of a fruit using: \n", "\n", "`color = fruits['apple']`\n", "\n", "Python dictionaries are very flexible and powerful. Let's see what they can do for us here.\n", "\n", "We notice that two strings are relevant in the descriptions: `'Prerequisite'` and `'Prerequisites'`. Start by creating an empty dictionary, then iterate over the descriptions looking for these strings, and assign what comes after them to the dictionary values. The course IDs will be the keys." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "course_with_pre = {}\n", "\n", "for i in range(len(descriptions)):\n", " if 'Prerequisites:' in descriptions[i]:\n", " key = course_id[i]\n", " temp = descriptions[i].split('Prerequisites: ')\n", " temp = temp[1].split('.')\n", " value = temp[0]\n", " course_with_pre[key] = value\n", " elif 'Prerequisite:' in descriptions[i]:\n", " key = course_id[i]\n", " temp = descriptions[i].split('Prerequisite: ')\n", " temp = temp[1].split('.')\n", " value = temp[0]\n", " course_with_pre[key] = value" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'MATH 1232'" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "course_with_pre['MAE 2117']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Cool. Now you can look up the prerequisite for any course. Perhaps you want to save the result to a file, to have it handy. You know how to open an existing file to read its contents with the `open()` method. This method has a second optional argument: `w` for writing, and `a` for appending to a file (`r` is for reading and is the default value)." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "with open(\"course_with_pre.txt\", 'w') as f: \n", " for key, value in course_with_pre.items(): \n", " f.write('%s:%s\\n' % (key, value))" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MAE 2117:MATH 1232\n", "MAE 2131:PHYS 1021\n", "MAE 3120:MAE 2117\n", "MAE 3126:APSC 2058\n", "MAE 3128:APSC 2057, CE 2220\n", "MAE 3134:APSC 2113; corequisite: APSC 2058\n", "MAE 3145:APSC 2058\n", "MAE 3155:MAE 3126\n", "MAE 3162:APSC 2057 and CE 2220\n", "MAE 3166W:CHEM 1111 and PHYS 1022\n" ] } ], "source": [ "!head course_with_pre.txt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### _Exercise:_\n", "\n", "Repeat the process for the [APSC courses](https://bulletin.gwu.edu/courses/apsc/):\n", "\n", "- create a new text file and copy-paste the website contents, or download the file from the URL `https://go.gwu.edu/engcomp1data3b`\n", "- read the contents into a list of text lines\n", "- iterate through the text lines and:\n", " - skip empty lines\n", " - grab the course ID and the course descriptions into new lists\n", " - separate course ID, title and number of credits into separate lists\n", "- find courses that have Prerequisites and save to a dictionary\n", "- append the dictionary entries to the existing `course_with_pre.txt` file" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What we've learned\n", "\n", "* System commands in a code cell start with a bang (`!`).\n", "* Opening a text file and saving its contents into a string or list variable.\n", "* Cleaning up text data using string methods.\n", "* Manipulating text into lists.\n", "* Using Python dictionaries." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.4" }, "widgets": { "state": {}, "version": "1.1.2" } }, "nbformat": 4, "nbformat_minor": 4 }