[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n"
  },
  {
    "path": "LICENSE.md",
    "content": "## creative commons\n\n# Attribution-ShareAlike 4.0 International\n\nCreative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.\n\n### Using Creative Commons Public Licenses\n\nCreative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.\n\n* __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors).\n\n* __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees).\n\n## Creative Commons Attribution-ShareAlike 4.0 International Public License\n\nBy exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-ShareAlike 4.0 International Public License (\"Public License\"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.\n\n### Section 1 – Definitions.\n\na. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.\n\nb. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.\n\nc. __BY-SA Compatible License__ means a license listed at [creativecommons.org/compatiblelicenses](http://creativecommons.org/compatiblelicenses), approved by Creative Commons as essentially the equivalent of this Public License.\n\nd. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.\n\ne. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.\n\nf. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.\n\ng. __License Elements__ means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution and ShareAlike.\n\nh. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License.\n\ni. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.\n\nj. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License.\n\nk. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.\n\nl. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.\n\nm. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning.\n\n### Section 2 – Scope.\n\na. ___License grant.___\n\n    1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:\n\n        A. reproduce and Share the Licensed Material, in whole or in part; and\n\n        B. produce, reproduce, and Share Adapted Material.\n\n    2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.\n\n    3. __Term.__ The term of this Public License is specified in Section 6(a).\n\n    4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.\n\n    5. __Downstream recipients.__\n\n        A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.\n\n        B. __Additional offer from the Licensor – Adapted Material. Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply.\n\n        C. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.\n\n    6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).\n\nb. ___Other rights.___\n\n    1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.\n\n    2. Patent and trademark rights are not licensed under this Public License.\n\n    3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties.\n\n### Section 3 – License Conditions.\n\nYour exercise of the Licensed Rights is expressly made subject to the following conditions.\n\na. ___Attribution.___\n\n    1. If You Share the Licensed Material (including in modified form), You must:\n\n        A. retain the following if it is supplied by the Licensor with the Licensed Material:\n\n            i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);\n\n            ii. a copyright notice;\n\n            iii. a notice that refers to this Public License;\n\n            iv. a notice that refers to the disclaimer of warranties;\n\n            v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;\n\n        B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and\n\n        C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.\n\n    2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.\n\n    3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.\n\nb. ___ShareAlike.___\n\nIn addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply.\n\n1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-SA Compatible License.\n\n2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material.\n\n3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply.\n\n### Section 4 – Sui Generis Database Rights.\n\nWhere the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:\n\na. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database;\n\nb. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and\n\nc. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.\n\nFor the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.\n\n### Section 5 – Disclaimer of Warranties and Limitation of Liability.\n\na. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__\n\nb. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__\n\nc. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.\n\n### Section 6 – Term and Termination.\n\na. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.\n\nb. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:\n\n    1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or\n\n    2. upon express reinstatement by the Licensor.\n\n    For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.\n\nc. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.\n\nd. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.\n\n### Section 7 – Other Terms and Conditions.\n\na. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.\n\nb. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.t stated herein are separate from and independent of the terms and conditions of this Public License.\n\n### Section 8 – Interpretation.\n\na. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.\n\nb. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.\n\nc. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.\n\nd. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.\n\n```\nCreative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.\n\nCreative Commons may be contacted at creativecommons.org\n```\n"
  },
  {
    "path": "Notes/00_Setup.md",
    "content": "# Course Setup and Overview\n\nWelcome to Practical Python Programming!   This page has some important information\nabout course setup and logistics.\n\n## Course Duration and Time Requirements\n\nThis course was originally given as an instructor-led in-person\ntraining that spanned 3 to 4 days.  To complete the course in its\nentirety, you should minimally plan on committing 25-35 hours of work.\nMost participants find the material to be quite challenging without\npeeking at solution code (see below).\n\n## Setup and Python Installation\n\nYou need nothing more than a basic Python 3.6 installation or newer.\nThere is no dependency on any particular operating system, editor,\nIDE, or extra Python-related tooling.  There are no third-party\ndependencies.\n\nThat said, most of this course involves learning how to write scripts\nand small programs that involve data read from files.  Therefore, you\nneed to make sure you're in an environment where you can easily work\nwith files.  This includes using an editor to create Python programs\nand being able to run those programs from the shell/terminal.\n\nYou might be inclined to work on this course using a more interactive\nenvironment such as Jupyter Notebooks. **I DO NOT ADVISE THIS!**\nAlthough notebooks are great for experimentation, many of the\nexercises in this course teach concepts related to program\norganization.  This includes working with functions, modules, import\nstatements, and refactoring of programs whose source code spans\nmultiple files.  In my experience, it is hard to replicate this kind\nof working environment in notebooks.\n\n## Forking/Cloning the Course Repository\n\nTo prepare your environment for the course, I recommend creating your\nown fork of the course GitHub repo at\n[https://github.com/dabeaz-course/practical-python](https://github.com/dabeaz-course/practical-python).\nOnce you are done, you can clone it to your local machine:\n\n```\nbash % git clone https://github.com/yourname/practical-python\nbash % cd practical-python\nbash %\n```\n\nDo all of your work within the `practical-python/` directory.  If you\ncommit your solution code back to your fork of the repository, it will\nkeep all of your code together in one place and you'll have a nice\nhistorical record of your work when you're done.\n\nIf you don't want to create a personal fork or don't have a GitHub account,\nyou can still clone the course directory to your machine:\n\n```\nbash % git clone https://github.com/dabeaz-course/practical-python\nbash % cd practical-python\nbash %\n```\n\nWith this option, you just won't be able to commit code changes except\nto the local copy on your machine.\n\n## Coursework Layout\n\nDo all of your coding work in the `Work/` directory.  Within that\ndirectory, there is a `Data/` directory.  The `Data/` directory\ncontains a variety of datafiles and other scripts used during the\ncourse. You will frequently have to access files located in `Data/`.\nCourse exercises are written with the assumption that you are creating\nprograms in the `Work/` directory.\n\n## Course Order\n\nCourse material should be completed in section order, starting with\nsection 1.  Course exercises in later sections build upon code written in\nearlier sections.  Many of the later exercises involve minor refactoring\nof existing code.\n\n## Solution Code\n\nThe `Solutions/` directory contains full solution code to selected\nexercises.  Feel free to look at this if you need a hint.  To get the\nmost out of the course however, you should try to create your own\nsolutions first.\n\n[Contents](Contents.md) \\| [Next (1 Introduction to Python)](01_Introduction/00_Overview.md)\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Notes/01_Introduction/00_Overview.md",
    "content": "[Contents](../Contents.md) \\| [Next (2 Working With Data)](../02_Working_with_data/00_Overview.md)\n\n## 1. Introduction to Python\n\nThe goal of this first section is to introduce some Python basics from\nthe ground up.  Starting with nothing, you'll learn how to edit, run,\nand debug small programs. Ultimately, you'll write a short script that\nreads a CSV data file and performs a simple calculation.\n\n* [1.1 Introducing Python](01_Python.md)\n* [1.2 A First Program](02_Hello_world.md)\n* [1.3 Numbers](03_Numbers.md)\n* [1.4 Strings](04_Strings.md)\n* [1.5 Lists](05_Lists.md)\n* [1.6 Files](06_Files.md)\n* [1.7 Functions](07_Functions.md)\n\n[Contents](../Contents.md) \\| [Next (2 Working With Data)](../02_Working_with_data/00_Overview.md)\n"
  },
  {
    "path": "Notes/01_Introduction/01_Python.md",
    "content": "[Contents](../Contents.md) \\| [Next (1.2 A First Program)](02_Hello_world.md)\n\n# 1.1 Python\n\n### What is Python?\n\nPython is an interpreted high level programming language.  It is often classified as a\n[\"scripting language\"](https://en.wikipedia.org/wiki/Scripting_language) and\nis considered similar to languages such as Perl, Tcl, or Ruby.  The syntax\nof Python is loosely inspired by elements of C programming.\n\nPython was created by Guido van Rossum around 1990 who named it in honor of Monty Python.\n\n### Where to get Python?\n\n[Python.org](https://www.python.org/) is where you obtain Python.  For the purposes of this course, you\nonly need a basic installation.  I recommend installing Python 3.6 or newer. Python 3.6 is used in the notes\nand solutions.\n\n### Why was Python created?\n\nIn the words of Python's creator:\n\n> My original motivation for creating Python was the perceived need\n> for a higher level language in the Amoeba [Operating Systems]\n> project. I realized that the development of system administration\n> utilities in C was taking too long. Moreover, doing these things in\n> the Bourne shell wouldn't work for a variety of reasons. ... So,\n> there was a need for a language that would bridge the gap between C\n> and the shell.\n>\n> - Guido van Rossum\n\n### Where is Python on my Machine?\n\nAlthough there are many environments in which you might run Python,\nPython is typically installed on your machine as a program that runs\nfrom the terminal or command shell. From the terminal, you should be\nable to type `python` like this:\n\n```\nbash $ python\nPython 3.8.1 (default, Feb 20 2020, 09:29:22)\n[Clang 10.0.0 (clang-1000.10.44.4)] on darwin\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n>>> print(\"hello world\")\nhello world\n>>>\n```\n\nIf you are new to using the shell or a terminal, you should probably\nstop, finish a short tutorial on that first, and then return here.\n\nAlthough there are many non-shell environments where you can code\nPython, you will be a stronger Python programmer if you are able to\nrun, debug, and interact with Python at the terminal.  This is\nPython's native environment.  If you are able to use Python here, you\nwill be able to use it everywhere else.\n\n## Exercises\n\n### Exercise 1.1: Using Python as a Calculator\n\nOn your machine, start Python and use it as a calculator to solve the\nfollowing problem.\n\nLucky Larry bought 75 shares of Google stock at a price of $235.14 per\nshare. Today, shares of Google are priced at $711.25. Using Python’s\ninteractive mode as a calculator, figure out how much profit Larry would\nmake if he sold all of his shares.\n\n```python\n>>> (711.25 - 235.14) * 75\n35708.25\n>>>\n```\n\nPro-tip: Use the underscore (\\_) variable to use the result of the last\ncalculation. For example, how much profit does Larry make after his evil\nbroker takes their 20% cut?\n\n```python\n>>> _ * 0.80\n28566.600000000002\n>>>\n```\n\n### Exercise 1.2: Getting help\n\nUse the `help()` command to get help on the `abs()` function. Then use\n`help()` to get help on the `round()` function. Type `help()` just by\nitself with no value to enter the interactive help viewer.\n\nOne caution with `help()` is that it doesn’t work for basic Python\nstatements such as `for`, `if`, `while`, and so forth (i.e., if you type\n`help(for)` you’ll get a syntax error). You can try putting the help\ntopic in quotes such as `help(\"for\")` instead. If that doesn’t work,\nyou’ll have to turn to an internet search.\n\nFollowup: Go to <http://docs.python.org> and find the documentation for\nthe `abs()` function (hint: it’s found under the library reference\nrelated to built-in functions).\n\n### Exercise 1.3: Cutting and Pasting\n\nThis course is structured as a series of traditional web pages where\nyou are encouraged to try interactive Python code samples **by typing\nthem out by hand.** If you are learning Python for the first time,\nthis \"slow approach\" is encouraged.  You will get a better feel for\nthe language by slowing down, typing things in, and thinking about\nwhat you are doing.\n\nIf you must \"cut and paste\" code samples, select code\nstarting after the `>>>` prompt and going up to, but not any further\nthan the first blank line or the next `>>>` prompt (whichever appears\nfirst). Select \"copy\" from the browser, go to the Python window, and\nselect \"paste\" to copy it into the Python shell. To get the code to\nrun, you may have to hit \"Return\" once after you’ve pasted it in.\n\nUse cut-and-paste to execute the Python statements in this session:\n\n```python\n>>> 12 + 20\n32\n>>> (3 + 4\n         + 5 + 6)\n18\n>>> for i in range(5):\n        print(i)\n\n0\n1\n2\n3\n4\n>>>\n```\n\nWarning: It is never possible to paste more than one Python command\n(statements that appear after `>>>`) to the basic Python shell at a\ntime. You have to paste each command one at a time.\n\nNow that you've done this, just remember that you will get more out of\nthe class by typing in code slowly and thinking about it--not cut and pasting.\n\n### Exercise 1.4: Where is My Bus?\n\nNote: This was a whimsical example that was a real crowd-pleaser when\nI taught this course in my office.  You could query the bus and then\nliterally watch it pass by the window out front.  Sadly, APIs rarely live\nforever and it seems that this one has now ridden off into the sunset. --Dave\n\nUpdate: GitHub user @asett has suggested the following modified code might work,\nbut you'll have to provide your own API key (available [here](https://www.transitchicago.com/developers/bustracker/)).\n\n```python\nimport urllib.request\nu = urllib.request.urlopen('http://www.ctabustracker.com/bustime/api/v2/getpredictions?key=ADD_YOUR_API_KEY_HERE&rt=22&stpid=14791')\nfrom xml.etree.ElementTree import parse\ndoc = parse(u)\nprint(\"Arrival time in minutes:\")\nfor pt in doc.findall('.//prdctdn'):\n        print(pt.text)\n```\n\n(Original exercise example follows below)\n\nTry something more advanced and type these statements to find out how\nlong people waiting on the corner of Clark street and Balmoral in\nChicago will have to wait for the next northbound CTA \\#22 bus:\n\n```python\n>>> import urllib.request\n>>> u = urllib.request.urlopen('http://ctabustracker.com/bustime/map/getStopPredictions.jsp?stop=14791&route=22')\n>>> from xml.etree.ElementTree import parse\n>>> doc = parse(u)\n>>> for pt in doc.findall('.//pt'):\n        print(pt.text)\n\n6 MIN\n18 MIN\n28 MIN\n>>>\n```\n\nYes, you just downloaded a web page, parsed an XML document, and\nextracted some useful information in about 6 lines of code. The data\nyou accessed is actually feeding the website\n<http://ctabustracker.com/bustime/home.jsp>. Try it again and watch\nthe predictions change.\n\nNote: This service only reports arrival times within the next 30 minutes.\nIf you're in a different timezone and it happens to be 3am in Chicago, you\nmight not get any output.  You use the tracker link above to double check.\n\nIf the first import statement `import urllib.request` fails, you’re\nprobably using Python 2. For this course, you need to make sure you’re\nusing Python 3.6 or newer. Go to <https://www.python.org> to download\nit if you need it.\n\nIf your work environment requires the use of an HTTP proxy server, you may need\nto set the `HTTP_PROXY` environment variable to make this part of the\nexercise work. For example:\n\n```python\n>>> import os\n>>> os.environ['HTTP_PROXY'] = 'http://yourproxy.server.com'\n>>>\n```\n\nIf you can't make this work, don't worry about it.  The rest of this course\nhas nothing to do with parsing XML.\n\n[Contents](../Contents.md) \\| [Next (1.2 A First Program)](02_Hello_world.md)\n\n"
  },
  {
    "path": "Notes/01_Introduction/02_Hello_world.md",
    "content": "[Contents](../Contents.md) \\| [Previous (1.1 Python)](01_Python.md) \\| [Next (1.3 Numbers)](03_Numbers.md)\n\n# 1.2 A First Program\n\nThis section discusses the creation of your first program, running the\ninterpreter, and some basic debugging.\n\n### Running Python\n\nPython programs always run inside an interpreter.\n\nThe interpreter is a \"console-based\" application that normally runs\nfrom a command shell.\n\n```bash\npython3\nPython 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04)\n[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n>>>\n```\n\nExpert programmers usually have no problem using the interpreter in\nthis way, but it's not so user-friendly for beginners.  You may be using\nan environment that provides a different interface to Python.  That's fine,\nbut learning how to run Python terminal is still a useful skill to know.\n\n### Interactive Mode\n\nWhen you start Python, you get an *interactive* mode where you can experiment.\n\nIf you start typing statements, they will run immediately. There is no\nedit/compile/run/debug cycle.\n\n```python\n>>> print('hello world')\nhello world\n>>> 37*42\n1554\n>>> for i in range(5):\n...     print(i)\n...\n0\n1\n2\n3\n4\n>>>\n```\n\nThis so-called *read-eval-print-loop* (or REPL) is very useful for debugging and exploration.\n\n**STOP**: If you can't figure out how to interact with Python, stop what you're doing\nand figure out how to do it.  If you're using an IDE, it might be hidden behind a\nmenu option or other window.  Many parts of this course assume that you can\ninteract with the interpreter.\n\nLet's take a closer look at the elements of the REPL:\n\n- `>>>` is the interpreter prompt for starting a new statement.\n- `...` is the interpreter prompt for continuing a statement. Enter a blank line to finish typing and run what you've entered.\n\nThe `...` prompt may or may not be shown depending on your environment. For this course,\nit is shown as blanks to make it easier to cut/paste code samples.\n\nThe underscore `_` holds the last result.\n\n```python\n>>> 37 * 42\n1554\n>>> _ * 2\n3108\n>>> _ + 50\n3158\n>>>\n```\n\n*This is only true in the interactive mode.* You never use `_` in a program.\n\n### Creating programs\n\nPrograms are put in `.py` files.\n\n```python\n# hello.py\nprint('hello world')\n```\n\nYou can create these files with your favorite text editor.\n\n### Running Programs\n\nTo execute a program, run it in the terminal with the `python` command.\nFor example, in command-line Unix:\n\n```bash\nbash % python hello.py\nhello world\nbash %\n```\n\nOr from the Windows shell:\n\n```\nC:\\SomeFolder>hello.py\nhello world\n\nC:\\SomeFolder>c:\\python36\\python hello.py\nhello world\n```\n\nNote: On Windows, you may need to specify a full path to the Python interpreter such as `c:\\python36\\python`.\nHowever, if Python is installed in its usual way, you might be able to just type the name of the program\nsuch as `hello.py`.\n\n### A Sample Program\n\nLet's solve the following problem:\n\n> One morning, you go out and place a dollar bill on the sidewalk by the Sears tower in Chicago.\n> Each day thereafter, you go out double the number of bills.\n> How long does it take for the stack of bills to exceed the height of the tower?\n\nHere's a solution:\n\n```python\n# sears.py\nbill_thickness = 0.11 * 0.001 # Meters (0.11 mm)\nsears_height = 442 # Height (meters)\nnum_bills = 1\nday = 1\n\nwhile num_bills * bill_thickness < sears_height:\n    print(day, num_bills, num_bills * bill_thickness)\n    day = day + 1\n    num_bills = num_bills * 2\n\nprint('Number of days', day)\nprint('Number of bills', num_bills)\nprint('Final height', num_bills * bill_thickness)\n```\n\nWhen you run it, you get the following output:\n\n```bash\nbash % python3 sears.py\n1 1 0.00011\n2 2 0.00022\n3 4 0.00044\n4 8 0.00088\n5 16 0.00176\n6 32 0.00352\n...\n21 1048576 115.34336\n22 2097152 230.68672\nNumber of days 23 \nNumber of bills 4194304 \nFinal height 461.37344\n```\n\nUsing this program as a guide, you can learn a number of important core concepts about Python.\n\n### Statements\n\nA python program is a sequence of statements:\n\n```python\na = 3 + 4\nb = a * 2\nprint(b)\n```\n\nEach statement is terminated by a newline. Statements are executed one after the other until control reaches the end of the file.\n\n### Comments\n\nComments are text that will not be executed.\n\n```python\na = 3 + 4\n# This is a comment\nb = a * 2\nprint(b)\n```\n\nComments are denoted by `#` and extend to the end of the line.\n\n### Variables\n\nA variable is a name for a value. You can use letters (lower and\nupper-case) from a to z. As well as the character underscore `_`.\nNumbers can also be part of the name of a variable, except as the\nfirst character.\n\n```python\nheight = 442 # valid\n_height = 442 # valid\nheight2 = 442 # valid\n2height = 442 # invalid\n```\n\n### Types\n\nVariables do not need to be declared with the type of the value.  The type\nis associated with the value on the right hand side, not name of the variable.\n\n```python\nheight = 442           # An integer\nheight = 442.0         # Floating point\nheight = 'Really tall' # A string\n```\n\nPython is dynamically typed. The perceived \"type\" of a variable might change\nas a program executes depending on the current value assigned to it.\n\n### Case Sensitivity\n\nPython is case sensitive. Upper and lower-case letters are considered different letters.\nThese are all different variables:\n\n```python\nname = 'Jake'\nName = 'Elwood'\nNAME = 'Guido'\n```\n\nLanguage statements are always lower-case.\n\n```python\nwhile x < 0:   # OK\nWHILE x < 0:   # ERROR\n```\n\n### Looping\n\nThe `while` statement executes a loop.\n\n```python\nwhile num_bills * bill_thickness < sears_height:\n    print(day, num_bills, num_bills * bill_thickness)\n    day = day + 1\n    num_bills = num_bills * 2\n\nprint('Number of days', day)\n```\n\nThe statements indented below the `while` will execute as long as the expression after the `while` is `true`.\n\n### Indentation\n\nIndentation is used to denote groups of statements that go together.\nConsider the previous example:\n\n```python\nwhile num_bills * bill_thickness < sears_height:\n    print(day, num_bills, num_bills * bill_thickness)\n    day = day + 1\n    num_bills = num_bills * 2\n\nprint('Number of days', day)\n```\n\nIndentation groups the following statements together as the operations that repeat:\n\n```python\n    print(day, num_bills, num_bills * bill_thickness)\n    day = day + 1\n    num_bills = num_bills * 2\n```\n\nBecause the `print()` statement at the end is not indented, it\ndoes not belong to the loop. The empty line is just for\nreadability. It does not affect the execution.\n\n### Indentation best practices\n\n* Use spaces instead of tabs.\n* Use 4 spaces per level.\n* Use a Python-aware editor.\n\nPython's only requirement is that indentation within the same block\nbe consistent.   For example, this is an error:\n\n```python\nwhile num_bills * bill_thickness < sears_height:\n    print(day, num_bills, num_bills * bill_thickness)\n        day = day + 1 # ERROR\n    num_bills = num_bills * 2\n```\n\n### Conditionals\n\nThe `if` statement is used to execute a conditional:\n\n```python\nif a > b:\n    print('Computer says no')\nelse:\n    print('Computer says yes')\n```\n\nYou can check for multiple conditions by adding extra checks using `elif`.\n\n```python\nif a > b:\n    print('Computer says no')\nelif a == b:\n    print('Computer says yes')\nelse:\n    print('Computer says maybe')\n```\n\n### Printing\n\nThe `print` function produces a single line of text with the values passed.\n\n```python\nprint('Hello world!') # Prints the text 'Hello world!'\n```\n\nYou can use variables. The text printed will be the value of the variable, not the name.\n\n```python\nx = 100\nprint(x) # Prints the text '100'\n```\n\nIf you pass more than one value to `print` they are separated by spaces.\n\n```python\nname = 'Jake'\nprint('My name is', name) # Print the text 'My name is Jake'\n```\n\n`print()` always puts a newline at the end.\n\n```python\nprint('Hello')\nprint('My name is', 'Jake')\n```\n\nThis prints:\n\n```code\nHello\nMy name is Jake\n```\n\nThe extra newline can be suppressed:\n\n```python\nprint('Hello', end=' ')\nprint('My name is', 'Jake')\n```\n\nThis code will now print:\n\n```code\nHello My name is Jake\n```\n\n### User input\n\nTo read a line of typed user input, use the `input()` function:\n\n```python\nname = input('Enter your name:')\nprint('Your name is', name)\n```\n\n`input` prints a prompt to the user and returns their response.\nThis is useful for small programs, learning exercises or simple debugging.\nIt is not widely used for real programs.\n\n### pass statement\n\nSometimes you need to specify an empty code block. The keyword `pass` is used for it.\n\n```python\nif a > b:\n    pass\nelse:\n    print('Computer says false')\n```\n\nThis is also called a \"no-op\" statement. It does nothing. It serves as a placeholder for statements, possibly to be added later.\n\n## Exercises\n\nThis is the first set of exercises where you need to create Python\nfiles and run them.  From this point forward, it is assumed that you\nare editing files in the `practical-python/Work/` directory.  To help\nyou locate the proper place, a number of empty starter files have\nbeen created with the appropriate filenames.  Look for the file\n`Work/bounce.py` that's used in the first exercise.\n\n### Exercise 1.5: The Bouncing Ball\n\nA rubber ball is dropped from a height of 100 meters and each time it\nhits the ground, it bounces back up to 3/5 the height it fell.  Write\na program `bounce.py` that prints a table showing the height of the\nfirst 10 bounces.\n\nYour program should make a table that looks something like this:\n\n```code\n1 60.0\n2 36.0\n3 21.599999999999998\n4 12.959999999999999\n5 7.775999999999999\n6 4.6655999999999995\n7 2.7993599999999996\n8 1.6796159999999998\n9 1.0077695999999998\n10 0.6046617599999998\n```\n\n*Note: You can clean up the output a bit if you use the round() function. Try using it to round the output to 4 digits.*\n\n```code\n1 60.0\n2 36.0\n3 21.6\n4 12.96\n5 7.776\n6 4.6656\n7 2.7994\n8 1.6796\n9 1.0078\n10 0.6047\n```\n\n### Exercise 1.6: Debugging\n\nThe following code fragment contains code from the Sears tower problem.  It also has a bug in it.\n\n```python\n# sears.py\n\nbill_thickness = 0.11 * 0.001    # Meters (0.11 mm)\nsears_height   = 442             # Height (meters)\nnum_bills      = 1\nday            = 1\n\nwhile num_bills * bill_thickness < sears_height:\n    print(day, num_bills, num_bills * bill_thickness)\n    day = days + 1\n    num_bills = num_bills * 2\n\nprint('Number of days', day)\nprint('Number of bills', num_bills)\nprint('Final height', num_bills * bill_thickness)\n```\n\nCopy and paste the code that appears above in a new program called `sears.py`.\nWhen you run the code you will get an error message that causes the\nprogram to crash like this:\n\n```code\nTraceback (most recent call last):\n  File \"sears.py\", line 10, in <module>\n    day = days + 1\nNameError: name 'days' is not defined\n```\n\nReading error messages is an important part of Python code. If your program\ncrashes, the very last line of the traceback message is the actual reason why the\nthe program crashed. Above that, you should see a fragment of source code and then\nan identifying filename and line number.\n\n* Which line is the error?\n* What is the error?\n* Fix the error\n* Run the program successfully\n\n\n[Contents](../Contents.md) \\| [Previous (1.1 Python)](01_Python.md) \\| [Next (1.3 Numbers)](03_Numbers.md)\n"
  },
  {
    "path": "Notes/01_Introduction/03_Numbers.md",
    "content": "[Contents](../Contents.md) \\| [Previous (1.2 A First Program)](02_Hello_world.md) \\| [Next (1.4 Strings)](04_Strings.md)\n\n# 1.3 Numbers\n\nThis section discusses mathematical calculations.\n\n### Types of Numbers\n\nPython has 4 types of numbers:\n\n* Booleans\n* Integers\n* Floating point\n* Complex (imaginary numbers)\n\n### Booleans (bool)\n\nBooleans have two values: `True`, `False`.\n\n```python\na = True\nb = False\n```\n\nNumerically, they're evaluated as integers with value `1`, `0`.\n\n```python\nc = 4 + True # 5\nd = False\nif d == 0:\n    print('d is False')\n```\n\n*But, don't write code like that. It would be odd.*\n\n### Integers (int)\n\nSigned values of arbitrary size and base:\n\n```python\na = 37\nb = -299392993727716627377128481812241231\nc = 0x7fa8      # Hexadecimal\nd = 0o253       # Octal\ne = 0b10001111  # Binary\n```\n\nCommon operations:\n\n```\nx + y      Add\nx - y      Subtract\nx * y      Multiply\nx / y      Divide (produces a float)\nx // y     Floor Divide (produces an integer)\nx % y      Modulo (remainder)\nx ** y     Power\nx << n     Bit shift left\nx >> n     Bit shift right\nx & y      Bit-wise AND\nx | y      Bit-wise OR\nx ^ y      Bit-wise XOR\n~x         Bit-wise NOT\nabs(x)     Absolute value\n```\n\n### Floating point (float)\n\nUse a decimal or exponential notation to specify a floating point value:\n\n```python\na = 37.45\nb = 4e5 # 4 x 10**5 or 400,000\nc = -1.345e-10\n```\n\nFloats are represented as double precision using the native CPU representation [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754).\nThis is the same as the `double` type in the programming language C.\n\n> 17 digits of precision  \n> Exponent from -308 to 308\n\nBe aware that floating point numbers are inexact when representing decimals.\n\n```python\n>>> a = 2.1 + 4.2\n>>> a == 6.3\nFalse\n>>> a\n6.300000000000001\n>>>\n```\n\nThis is **not a Python issue**, but the underlying floating point hardware on the CPU.\n\nCommon Operations:\n\n```\nx + y      Add\nx - y      Subtract\nx * y      Multiply\nx / y      Divide\nx // y     Floor Divide\nx % y      Modulo\nx ** y     Power\nabs(x)     Absolute Value\n```\n\nThese are the same operators as Integers, except for the bit-wise operators.\nAdditional math functions are found in the `math` module.\n\n```python\nimport math\na = math.sqrt(x)\nb = math.sin(x)\nc = math.cos(x)\nd = math.tan(x)\ne = math.log(x)\n```\n\n\n### Comparisons\n\nThe following comparison / relational operators work with numbers:\n\n```\nx < y      Less than\nx <= y     Less than or equal\nx > y      Greater than\nx >= y     Greater than or equal\nx == y     Equal to\nx != y     Not equal to\n```\n\nYou can form more complex boolean expressions using\n\n`and`, `or`, `not`\n\nHere are a few examples:\n\n```python\nif b >= a and b <= c:\n    print('b is between a and c')\n\nif not (b < a or b > c):\n    print('b is still between a and c')\n```\n\n### Converting Numbers\n\nThe type name can be used to convert values:\n\n```python\na = int(x)    # Convert x to integer\nb = float(x)  # Convert x to float\n```\n\nTry it out.\n\n```python\n>>> a = 3.14159\n>>> int(a)\n3\n>>> b = '3.14159' # It also works with strings containing numbers\n>>> float(b)\n3.14159\n>>>\n```\n\n## Exercises\n\nReminder: These exercises assume you are working in the `practical-python/Work` directory. Look\nfor the file `mortgage.py`.\n\n### Exercise 1.7: Dave's mortgage\n\nDave has decided to take out a 30-year fixed rate mortgage of $500,000\nwith Guido’s Mortgage, Stock Investment, and Bitcoin trading\ncorporation.  The interest rate is 5% and the monthly payment is\n$2684.11.\n\nHere is a program that calculates the total amount that Dave will have\nto pay over the life of the mortgage:\n\n```python\n# mortgage.py\n\nprincipal = 500000.0\nrate = 0.05\npayment = 2684.11\ntotal_paid = 0.0\n\nwhile principal > 0:\n    principal = principal * (1+rate/12) - payment\n    total_paid = total_paid + payment\n\nprint('Total paid', total_paid)\n```\n\nEnter this program and run it. You should get an answer of `966,279.6`.\n\n### Exercise 1.8: Extra payments\n\nSuppose Dave pays an extra $1000/month for the first 12 months of the mortgage?\n\nModify the program to incorporate this extra payment and have it print the total amount paid along with the number of months required.\n\nWhen you run the new program, it should report a total payment of `929,965.62` over 342 months.\n\n### Exercise 1.9: Making an Extra Payment Calculator\n\nModify the program so that extra payment information can be more generally handled.\nMake it so that the user can set these variables:\n\n```python\nextra_payment_start_month = 61\nextra_payment_end_month = 108\nextra_payment = 1000\n```\n\nMake the program look at these variables and calculate the total paid appropriately.\n\nHow much will Dave pay if he pays an extra $1000/month for 4 years starting after the first\nfive years have already been paid?\n\n### Exercise 1.10: Making a table\n\nModify the program to print out a table showing the month, total paid so far, and the remaining principal.\nThe output should look something like this:\n\n```bash\n1 2684.11 499399.22\n2 5368.22 498795.94\n3 8052.33 498190.15\n4 10736.44 497581.83\n5 13420.55 496970.98\n...\n308 874705.88 3478.83\n309 877389.99 809.21\n310 880074.1 -1871.53\nTotal paid 880074.1\nMonths 310\n```\n\n### Exercise 1.11: Bonus\n\nWhile you’re at it, fix the program to correct for the overpayment that occurs in the last month.\n\n### Exercise 1.12: A Mystery\n\n`int()` and `float()` can be used to convert numbers.  For example,\n\n```python\n>>> int(\"123\")\n123\n>>> float(\"1.23\")\n1.23\n>>>\n```\n\nWith that in mind, can you explain this behavior?\n\n```python\n>>> bool(\"False\")\nTrue\n>>>\n```\n\n[Contents](../Contents.md) \\| [Previous (1.2 A First Program)](02_Hello_world.md) \\| [Next (1.4 Strings)](04_Strings.md)\n"
  },
  {
    "path": "Notes/01_Introduction/04_Strings.md",
    "content": "[Contents](../Contents.md) \\| [Previous (1.3 Numbers)](03_Numbers.md) \\| [Next (1.5 Lists)](05_Lists.md)\n\n# 1.4 Strings\n\nThis section introduces ways to work with text.\n\n### Representing Literal Text\n\nString literals are written in programs with quotes.\n\n```python\n# Single quote\na = 'Yeah but no but yeah but...'\n\n# Double quote\nb = \"computer says no\"\n\n# Triple quotes\nc = '''\nLook into my eyes, look into my eyes, the eyes, the eyes, the eyes,\nnot around the eyes,\ndon't look around the eyes,\nlook into my eyes, you're under.\n'''\n```\n\nNormally strings may only span a single line. Triple quotes capture all text enclosed across multiple lines\nincluding all formatting.\n\nThere is no difference between using single (') versus double (\")\nquotes. *However, the same type of quote used to start a string must be used to\nterminate it*.\n\n### String escape codes\n\nEscape codes are used to represent control characters and characters that can't be easily typed\ndirectly at the keyboard.  Here are some common escape codes:\n\n```\n'\\n'      Line feed\n'\\r'      Carriage return\n'\\t'      Tab\n'\\''      Literal single quote\n'\\\"'      Literal double quote\n'\\\\'      Literal backslash\n```\n\n### String Representation\n\nEach character in a string is stored internally as a so-called Unicode \"code-point\" which is\nan integer.  You can specify an exact code-point value using the following escape sequences:\n\n```python\na = '\\xf1'          # a = 'ñ'\nb = '\\u2200'        # b = '∀'\nc = '\\U0001D122'    # c = '𝄢'\nd = '\\N{FOR ALL}'   # d = '∀'\n```\n\nThe [Unicode Character Database](https://unicode.org/charts) is a reference for all\navailable character codes.\n\n### String Indexing\n\nStrings work like an array for accessing individual characters. You use an integer index, starting at 0.\nNegative indices specify a position relative to the end of the string.\n\n```python\na = 'Hello world'\nb = a[0]          # 'H'\nc = a[4]          # 'o'\nd = a[-1]         # 'd' (end of string)\n```\n\nYou can also slice or select substrings specifying a range of indices with `:`.\n\n```python\nd = a[:5]     # 'Hello'\ne = a[6:]     # 'world'\nf = a[3:8]    # 'lo wo'\ng = a[-5:]    # 'world'\n```\n\nThe character at the ending index is not included.  Missing indices assume the beginning or ending of the string.\n\n### String operations\n\nConcatenation, length, membership and replication.\n\n```python\n# Concatenation (+)\na = 'Hello' + 'World'   # 'HelloWorld'\nb = 'Say ' + a          # 'Say HelloWorld'\n\n# Length (len)\ns = 'Hello'\nlen(s)                  # 5\n\n# Membership test (`in`, `not in`)\nt = 'e' in s            # True\nf = 'x' in s            # False\ng = 'hi' not in s       # True\n\n# Replication (s * n)\nrep = s * 5             # 'HelloHelloHelloHelloHello'\n```\n\n### String methods\n\nStrings have methods that perform various operations with the string data.\n\nExample: stripping any leading / trailing white space.\n\n```python\ns = '  Hello '\nt = s.strip()     # 'Hello'\n```\n\nExample: Case conversion.\n\n```python\ns = 'Hello'\nl = s.lower()     # 'hello'\nu = s.upper()     # 'HELLO'\n```\n\nExample: Replacing text.\n\n```python\ns = 'Hello world'\nt = s.replace('Hello' , 'Hallo')   # 'Hallo world'\n```\n\n**More string methods:**\n\nStrings have a wide variety of other methods for testing and manipulating the text data.\nThis is a small sample of methods:\n\n```python\ns.endswith(suffix)     # Check if string ends with suffix\ns.find(t)              # First occurrence of t in s\ns.index(t)             # First occurrence of t in s\ns.isalpha()            # Check if characters are alphabetic\ns.isdigit()            # Check if characters are numeric\ns.islower()            # Check if characters are lower-case\ns.isupper()            # Check if characters are upper-case\ns.join(slist)          # Join a list of strings using s as delimiter\ns.lower()              # Convert to lower case\ns.replace(old,new)     # Replace text\ns.rfind(t)             # Search for t from end of string\ns.rindex(t)            # Search for t from end of string\ns.split([delim])       # Split string into list of substrings\ns.startswith(prefix)   # Check if string starts with prefix\ns.strip()              # Strip leading/trailing space\ns.upper()              # Convert to upper case\n```\n\n### String Mutability\n\nStrings are \"immutable\" or read-only.\nOnce created, the value can't be changed.\n\n```python\n>>> s = 'Hello World'\n>>> s[1] = 'a'\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in <module>\nTypeError: 'str' object does not support item assignment\n>>>\n```\n\n**All operations and methods that manipulate string data, always create new strings.**\n\n### String Conversions\n\nUse `str()` to convert any value to a string. The result is a string holding the\nsame text that would have been produced by the `print()` statement.\n\n```python\n>>> x = 42\n>>> str(x)\n'42'\n>>>\n```\n\n### Byte Strings\n\nA string of 8-bit bytes, commonly encountered with low-level I/O, is written as follows:\n\n```python\ndata = b'Hello World\\r\\n'\n```\n\nBy putting a little b before the first quotation, you specify that it is a byte string as opposed to a text string.\n\nMost of the usual string operations work.\n\n```python\nlen(data)                         # 13\ndata[0:5]                         # b'Hello'\ndata.replace(b'Hello', b'Cruel')  # b'Cruel World\\r\\n'\n```\n\nIndexing is a bit different because it returns byte values as integers.\n\n```python\ndata[0]   # 72 (ASCII code for 'H')\n```\n\nConversion to/from text strings.\n\n```python\ntext = data.decode('utf-8') # bytes -> text\ndata = text.encode('utf-8') # text -> bytes\n```\n\nThe `'utf-8'` argument specifies a character encoding.  Other common\nvalues include `'ascii'` and `'latin1'`.\n\n### Raw Strings\n\nRaw strings are string literals with an uninterpreted backslash. They\nare specified by prefixing the initial quote with a lowercase \"r\".\n\n```python\n>>> rs = r'c:\\newdata\\test' # Raw (uninterpreted backslash)\n>>> rs\n'c:\\\\newdata\\\\test'\n```\n\nThe string is the literal text enclosed inside, exactly as typed.\nThis is useful in situations where the backslash has special\nsignificance. Example: filename, regular expressions, etc.\n\n### f-Strings\n\nA string with formatted expression substitution.\n\n```python\n>>> name = 'IBM'\n>>> shares = 100\n>>> price = 91.1\n>>> a = f'{name:>10s} {shares:10d} {price:10.2f}'\n>>> a\n'       IBM        100      91.10'\n>>> b = f'Cost = ${shares*price:0.2f}'\n>>> b\n'Cost = $9110.00'\n>>>\n```\n\n**Note: This requires Python 3.6 or newer.**  The meaning of the format codes\nis covered later.\n\n## Exercises\n\nIn these exercises, you'll experiment with operations on Python's\nstring type.  You should do this at the Python interactive prompt\nwhere you can easily see the results.  Important note:\n\n> In exercises where you are supposed to interact with the interpreter,\n> `>>>` is the interpreter prompt that you get when Python wants\n> you to type a new statement.  Some statements in the exercise span\n> multiple lines--to get these statements to run, you may have to hit\n> 'return' a few times.  Just a reminder that you *DO NOT* type\n> the `>>>` when working these examples.\n\nStart by defining a string containing a series of stock ticker symbols like this:\n\n```python\n>>> symbols = 'AAPL,IBM,MSFT,YHOO,SCO'\n>>>\n```\n\n### Exercise 1.13: Extracting individual characters and substrings\n\nStrings are arrays of characters. Try extracting a few characters:\n\n```python\n>>> symbols[0]\n?\n>>> symbols[1]\n?\n>>> symbols[2]\n?\n>>> symbols[-1]        # Last character\n?\n>>> symbols[-2]        # Negative indices are from end of string\n?\n>>>\n```\n\nIn Python, strings are read-only.\n\nVerify this by trying to change the first character of `symbols` to a lower-case 'a'.\n\n```python\n>>> symbols[0] = 'a'\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: 'str' object does not support item assignment\n>>>\n```\n\n### Exercise 1.14: String concatenation\n\nAlthough string data is read-only, you can always reassign a variable\nto a newly created string.\n\nTry the following statement which concatenates a new symbol \"GOOG\" to\nthe end of `symbols`:\n\n```python\n>>> symbols = symbols + 'GOOG'\n>>> symbols\n'AAPL,IBM,MSFT,YHOO,SCOGOOG'\n>>>\n```\n\nOops!  That's not what you wanted. Fix it so that the `symbols` variable holds the value `'AAPL,IBM,MSFT,YHOO,SCO,GOOG'`.\n\n```python\n>>> symbols = ?\n>>> symbols\n'AAPL,IBM,MSFT,YHOO,SCO,GOOG'\n>>>\n```\n\nAdd `'HPQ'` to the front the string:\n\n```python\n>>> symbols = ?\n>>> symbols\n'HPQ,AAPL,IBM,MSFT,YHOO,SCO,GOOG'\n>>>\n```\n\nIn these examples, it might look like the original string is being\nmodified, in an apparent violation of strings being read only.  Not\nso. Operations on strings create an entirely new string each\ntime. When the variable name `symbols` is reassigned, it points to the\nnewly created string.  Afterwards, the old string is destroyed since\nit's not being used anymore.\n\n### Exercise 1.15: Membership testing (substring testing)\n\nExperiment with the `in` operator to check for substrings.  At the\ninteractive prompt, try these operations:\n\n```python\n>>> 'IBM' in symbols\n?\n>>> 'AA' in symbols\nTrue\n>>> 'CAT' in symbols\n?\n>>>\n```\n\n*Why did the check for `'AA'` return `True`?*\n\n### Exercise 1.16: String Methods\n\nAt the Python interactive prompt, try experimenting with some of the string methods.\n\n```python\n>>> symbols.lower()\n?\n>>> symbols\n?\n>>>\n```\n\nRemember, strings are always read-only.  If you want to save the result of an operation, you need to place it in a variable:\n\n```python\n>>> lowersyms = symbols.lower()\n>>>\n```\n\nTry some more operations:\n\n```python\n>>> symbols.find('MSFT')\n?\n>>> symbols[13:17]\n?\n>>> symbols = symbols.replace('SCO','DOA')\n>>> symbols\n?\n>>> name = '   IBM   \\n'\n>>> name = name.strip()    # Remove surrounding whitespace\n>>> name\n?\n>>>\n```\n\n### Exercise 1.17: f-strings\n\nSometimes you want to create a string and embed the values of\nvariables into it.\n\nTo do that, use an f-string. For example:\n\n```python\n>>> name = 'IBM'\n>>> shares = 100\n>>> price = 91.1\n>>> f'{shares} shares of {name} at ${price:0.2f}'\n'100 shares of IBM at $91.10'\n>>>\n```\n\nModify the `mortgage.py` program from [Exercise 1.10](03_Numbers.md) to create its output using f-strings.\nTry to make it so that output is nicely aligned.\n\n\n### Exercise 1.18: Regular Expressions\n\nOne limitation of the basic string operations is that they don't\nsupport any kind of advanced pattern matching.  For that, you\nneed to turn to Python's `re` module and regular expressions.\nRegular expression handling is a big topic, but here is a short\nexample:\n\n```python\n>>> text = 'Today is 3/27/2018. Tomorrow is 3/28/2018.'\n>>> # Find all occurrences of a date\n>>> import re\n>>> re.findall(r'\\d+/\\d+/\\d+', text)\n['3/27/2018', '3/28/2018']\n>>> # Replace all occurrences of a date with replacement text\n>>> re.sub(r'(\\d+)/(\\d+)/(\\d+)', r'\\3-\\1-\\2', text)\n'Today is 2018-3-27. Tomorrow is 2018-3-28.'\n>>>\n```\n\nFor more information about the `re` module, see the official documentation at\n[https://docs.python.org/library/re.html](https://docs.python.org/3/library/re.html).\n\n\n### Commentary\n\nAs you start to experiment with the interpreter, you often want to\nknow more about the operations supported by different objects.  For\nexample, how do you find out what operations are available on a\nstring?\n\nDepending on your Python environment, you might be able to see a list\nof available methods via tab-completion.  For example, try typing\nthis:\n\n```python\n>>> s = 'hello world'\n>>> s.<tab key>\n>>>\n```\n\nIf hitting tab doesn't do anything, you can fall back to the\nbuiltin-in `dir()` function.  For example:\n\n```python\n>>> s = 'hello'\n>>> dir(s)\n['__add__', '__class__', '__contains__', ..., 'find', 'format',\n'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace',\n'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition',\n'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit',\n'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase',\n'title', 'translate', 'upper', 'zfill']\n>>>\n```\n\n`dir()` produces a list of all operations that can appear after the `(.)`.\nUse the `help()` command to get more information about a specific operation:\n\n```python\n>>> help(s.upper)\nHelp on built-in function upper:\n\nupper(...)\n    S.upper() -> string\n\n    Return a copy of the string S converted to uppercase.\n>>>\n```\n\n[Contents](../Contents.md) \\| [Previous (1.3 Numbers)](03_Numbers.md) \\| [Next (1.5 Lists)](05_Lists.md)\n"
  },
  {
    "path": "Notes/01_Introduction/05_Lists.md",
    "content": "[Contents](../Contents.md) \\| [Previous (1.4 Strings)](04_Strings.md) \\| [Next (1.6 Files)](06_Files.md)\n\n# 1.5 Lists\n\nThis section introduces lists, Python's primary type for holding an ordered collection of values.\n\n### Creating a List\n\nUse square brackets to define a list literal:\n\n```python\nnames = [ 'Elwood', 'Jake', 'Curtis' ]\nnums = [ 39, 38, 42, 65, 111]\n```\n\nSometimes lists are created by other methods.  For example, a string can be split into a\nlist using the `split()` method:\n\n```python\n>>> line = 'GOOG,100,490.10'\n>>> row = line.split(',')\n>>> row\n['GOOG', '100', '490.10']\n>>>\n```\n\n### List operations\n\nLists can hold items of any type. Add a new item using `append()`:\n\n```python\nnames.append('Murphy')    # Adds at end\nnames.insert(2, 'Aretha') # Inserts in middle\n```\n\nUse `+` to concatenate lists:\n\n```python\ns = [1, 2, 3]\nt = ['a', 'b']\ns + t           # [1, 2, 3, 'a', 'b']\n```\n\nLists are indexed by integers. Starting at 0.\n\n```python\nnames = [ 'Elwood', 'Jake', 'Curtis' ]\n\nnames[0]  # 'Elwood'\nnames[1]  # 'Jake'\nnames[2]  # 'Curtis'\n```\n\nNegative indices count from the end.\n\n```python\nnames[-1] # 'Curtis'\n```\n\nYou can change any item in a list.\n\n```python\nnames[1] = 'Joliet Jake'\nnames                     # [ 'Elwood', 'Joliet Jake', 'Curtis' ]\n```\n\nLength of the list.\n\n```python\nnames = ['Elwood','Jake','Curtis']\nlen(names)  # 3\n```\n\nMembership test (`in`, `not in`).\n\n```python\n'Elwood' in names       # True\n'Britney' not in names  # True\n```\n\nReplication (`s * n`).\n\n```python\ns = [1, 2, 3]\ns * 3   # [1, 2, 3, 1, 2, 3, 1, 2, 3]\n```\n\n### List Iteration and Search\n\nUse `for` to iterate over the list contents.\n\n```python\nfor name in names:\n    # use name\n    # e.g. print(name)\n    ...\n```\n\nThis is similar to a `foreach` statement from other programming languages.\n\nTo find the position of something quickly, use `index()`.\n\n```python\nnames = ['Elwood','Jake','Curtis']\nnames.index('Curtis')   # 2\n```\n\nIf the element is present more than once, `index()` will return the index of the first occurrence.\n\nIf the element is not found, it will raise a `ValueError` exception.\n\n### List Removal\n\nYou can remove items either by element value or by index:\n\n```python\n# Using the value\nnames.remove('Curtis')\n\n# Using the index\ndel names[1]\n```\n\nRemoving an item does not create a hole.  Other items will move down\nto fill the space vacated.  If there are more than one occurrence of\nthe element, `remove()` will remove only the first occurrence.\n\n### List Sorting\n\nLists can be sorted \"in-place\".\n\n```python\ns = [10, 1, 7, 3]\ns.sort()                    # [1, 3, 7, 10]\n\n# Reverse order\ns = [10, 1, 7, 3]\ns.sort(reverse=True)        # [10, 7, 3, 1]\n\n# It works with any ordered data\ns = ['foo', 'bar', 'spam']\ns.sort()                    # ['bar', 'foo', 'spam']\n```\n\nUse `sorted()` if you'd like to make a new list instead:\n\n```python\nt = sorted(s)               # s unchanged, t holds sorted values\n```\n\n### Lists and Math\n\n*Caution: Lists were not designed for math operations.*\n\n```python\n>>> nums = [1, 2, 3, 4, 5]\n>>> nums * 2\n[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n>>> nums + [10, 11, 12, 13, 14]\n[1, 2, 3, 4, 5, 10, 11, 12, 13, 14]\n```\n\nSpecifically, lists don't represent vectors/matrices as in MATLAB, Octave, R, etc.\nHowever, there are some packages to help you with that (e.g. [numpy](https://numpy.org)).\n\n## Exercises\n\nIn this exercise, we experiment with Python's list datatype. In the last section,\nyou worked with strings containing stock symbols.\n\n```python\n>>> symbols = 'HPQ,AAPL,IBM,MSFT,YHOO,DOA,GOOG'\n```\n\nSplit it into a list of names using the `split()` operation of strings:\n\n```python\n>>> symlist = symbols.split(',')\n```\n\n### Exercise 1.19: Extracting and reassigning list elements\n\nTry a few lookups:\n\n```python\n>>> symlist[0]\n'HPQ'\n>>> symlist[1]\n'AAPL'\n>>> symlist[-1]\n'GOOG'\n>>> symlist[-2]\n'DOA'\n>>>\n```\n\nTry reassigning one value:\n\n```python\n>>> symlist[2] = 'AIG'\n>>> symlist\n['HPQ', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'DOA', 'GOOG']\n>>>\n```\n\nTake a few slices:\n\n```python\n>>> symlist[0:3]\n['HPQ', 'AAPL', 'AIG']\n>>> symlist[-2:]\n['DOA', 'GOOG']\n>>>\n```\n\nCreate an empty list and append an item to it.\n\n```python\n>>> mysyms = []\n>>> mysyms.append('GOOG')\n>>> mysyms\n['GOOG']\n```\n\nYou can reassign a portion of a list to another list. For example:\n\n```python\n>>> symlist[-2:] = mysyms\n>>> symlist\n['HPQ', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG']\n>>>\n```\n\nWhen you do this, the list on the left-hand-side (`symlist`) will be resized as appropriate to make the right-hand-side (`mysyms`) fit.\nFor instance, in the above example, the last two items of `symlist` got replaced by the single item in the list `mysyms`.\n\n### Exercise 1.20: Looping over list items\n\nThe `for` loop works by looping over data in a sequence such as a list.\nCheck this out by typing the following loop and watching what happens:\n\n```python\n>>> for s in symlist:\n        print('s =', s)\n# Look at the output\n```\n\n### Exercise 1.21: Membership tests\n\nUse the `in` or `not in` operator to check if `'AIG'`,`'AA'`, and `'CAT'` are in the list of symbols.\n\n```python\n>>> # Is 'AIG' IN the `symlist`?\nTrue\n>>> # Is 'AA' IN the `symlist`?\nFalse\n>>> # Is 'CAT' NOT IN the `symlist`?\nTrue\n>>>\n```\n\n### Exercise 1.22: Appending, inserting, and deleting items\n\nUse the `append()` method to add the symbol `'RHT'` to end of `symlist`.\n\n```python\n>>> # append 'RHT'\n>>> symlist\n['HPQ', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG', 'RHT']\n>>>\n```\n\nUse the `insert()` method to insert the symbol `'AA'` as the second item in the list.\n\n```python\n>>> # Insert 'AA' as the second item in the list\n>>> symlist\n['HPQ', 'AA', 'AAPL', 'AIG', 'MSFT', 'YHOO', 'GOOG', 'RHT']\n>>>\n```\n\nUse the `remove()` method to remove `'MSFT'` from the list.\n\n```python\n>>> # Remove 'MSFT'\n>>> symlist\n['HPQ', 'AA', 'AAPL', 'AIG', 'YHOO', 'GOOG', 'RHT']\n>>>\n```\n\nAppend a duplicate entry for `'YHOO'` at the end of the list.\n\n*Note: it is perfectly fine for a list to have duplicate values.*\n\n```python\n>>> # Append 'YHOO'\n>>> symlist\n['HPQ', 'AA', 'AAPL', 'AIG', 'YHOO', 'GOOG', 'RHT', 'YHOO']\n>>>\n```\n\nUse the `index()` method to find the first position of `'YHOO'` in the list.\n\n```python\n>>> # Find the first index of 'YHOO'\n4\n>>> symlist[4]\n'YHOO'\n>>>\n```\n\nCount how many times `'YHOO'` is in the list:\n\n```python\n>>> symlist.count('YHOO')\n2\n>>>\n```\n\nRemove the first occurrence of `'YHOO'`.\n\n```python\n>>> # Remove first occurrence 'YHOO'\n>>> symlist\n['HPQ', 'AA', 'AAPL', 'AIG', 'GOOG', 'RHT', 'YHOO']\n>>>\n```\n\nJust so you know, there is no method to find or remove all occurrences of an item.\nHowever, we'll see an elegant way to do this in section 2.\n\n### Exercise 1.23: Sorting\n\nWant to sort a list?  Use the `sort()` method. Try it out:\n\n```python\n>>> symlist.sort()\n>>> symlist\n['AA', 'AAPL', 'AIG', 'GOOG', 'HPQ', 'RHT', 'YHOO']\n>>>\n```\n\nWant to sort in reverse? Try this:\n\n```python\n>>> symlist.sort(reverse=True)\n>>> symlist\n['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA']\n>>>\n```\n\nNote: Sorting a list modifies its contents 'in-place'.  That is, the elements of the list are shuffled around, but no new list is created as a result.\n\n### Exercise 1.24: Putting it all back together\n\nWant to take a list of strings and join them together into one string?\nUse the `join()` method of strings like this (note: this looks funny at first).\n\n```python\n>>> a = ','.join(symlist)\n>>> a\n'YHOO,RHT,HPQ,GOOG,AIG,AAPL,AA'\n>>> b = ':'.join(symlist)\n>>> b\n'YHOO:RHT:HPQ:GOOG:AIG:AAPL:AA'\n>>> c = ''.join(symlist)\n>>> c\n'YHOORHTHPQGOOGAIGAAPLAA'\n>>>\n```\n\n### Exercise 1.25: Lists of anything\n\nLists can contain any kind of object, including other lists (e.g., nested lists).\nTry this out:\n\n```python\n>>> nums = [101, 102, 103]\n>>> items = ['spam', symlist, nums]\n>>> items\n['spam', ['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA'], [101, 102, 103]]\n```\n\nPay close attention to the above output. `items` is a list with three elements.\nThe first element is a string, but the other two elements are lists.\n\nYou can access items in the nested lists by using multiple indexing operations.\n\n```python\n>>> items[0]\n'spam'\n>>> items[0][0]\n's'\n>>> items[1]\n['YHOO', 'RHT', 'HPQ', 'GOOG', 'AIG', 'AAPL', 'AA']\n>>> items[1][1]\n'RHT'\n>>> items[1][1][2]\n'T'\n>>> items[2]\n[101, 102, 103]\n>>> items[2][1]\n102\n>>>\n```\n\nEven though it is technically possible to make very complicated list\nstructures, as a general rule, you want to keep things simple.\nUsually lists hold items that are all the same kind of value.  For\nexample, a list that consists entirely of numbers or a list of text\nstrings.  Mixing different kinds of data together in the same list is\noften a good way to make your head explode so it's best avoided.\n\n[Contents](../Contents.md) \\| [Previous (1.4 Strings)](04_Strings.md) \\| [Next (1.6 Files)](06_Files.md)\n"
  },
  {
    "path": "Notes/01_Introduction/06_Files.md",
    "content": "[Contents](../Contents.md) \\| [Previous (1.5 Lists)](05_Lists.md) \\| [Next (1.7 Functions)](07_Functions.md)\n\n# 1.6 File Management\n\nMost programs need to read input from somewhere. This section discusses file access.\n\n### File Input and Output\n\nOpen a file.\n\n```python\nf = open('foo.txt', 'rt')     # Open for reading (text)\ng = open('bar.txt', 'wt')     # Open for writing (text)\n```\n\nRead all of the data.\n\n```python\ndata = f.read()\n\n# Read only up to 'maxbytes' bytes\ndata = f.read([maxbytes])\n```\n\nWrite some text.\n\n```python\ng.write('some text')\n```\n\nClose when you are done.\n\n```python\nf.close()\ng.close()\n```\n\nFiles should be properly closed and it's an easy step to forget.\nThus, the preferred approach is to use the `with` statement like this.\n\n```python\nwith open(filename, 'rt') as file:\n    # Use the file `file`\n    ...\n    # No need to close explicitly\n...statements\n```\n\nThis automatically closes the file when control leaves the indented code block.\n\n### Common Idioms for Reading File Data\n\nRead an entire file all at once as a string.\n\n```python\nwith open('foo.txt', 'rt') as file:\n    data = file.read()\n    # `data` is a string with all the text in `foo.txt`\n```\n\nRead a file line-by-line by iterating.\n\n```python\nwith open(filename, 'rt') as file:\n    for line in file:\n        # Process the line\n```\n\n### Common Idioms for Writing to a File\n\nWrite string data.\n\n```python\nwith open('outfile', 'wt') as out:\n    out.write('Hello World\\n')\n    ...\n```\n\nRedirect the print function.\n\n```python\nwith open('outfile', 'wt') as out:\n    print('Hello World', file=out)\n    ...\n```\n\n## Exercises\n\nThese exercises depend on a file `Data/portfolio.csv`.  The file\ncontains a list of lines with information on a portfolio of stocks.\nIt is assumed that you are working in the `practical-python/Work/`\ndirectory.  If you're not sure, you can find out where Python thinks\nit's running by doing this:\n\n```python\n>>> import os\n>>> os.getcwd()\n'/Users/beazley/Desktop/practical-python/Work' # Output vary\n>>>\n```\n\n### Exercise 1.26: File Preliminaries\n\nFirst, try reading the entire file all at once as a big string:\n\n```python\n>>> with open('Data/portfolio.csv', 'rt') as f:\n        data = f.read()\n\n>>> data\n'name,shares,price\\n\"AA\",100,32.20\\n\"IBM\",50,91.10\\n\"CAT\",150,83.44\\n\"MSFT\",200,51.23\\n\"GE\",95,40.37\\n\"MSFT\",50,65.10\\n\"IBM\",100,70.44\\n'\n>>> print(data)\nname,shares,price\n\"AA\",100,32.20\n\"IBM\",50,91.10\n\"CAT\",150,83.44\n\"MSFT\",200,51.23\n\"GE\",95,40.37\n\"MSFT\",50,65.10\n\"IBM\",100,70.44\n>>>\n```\n\nIn the above example, it should be noted that Python has two modes of\noutput.  In the first mode where you type `data` at the prompt, Python\nshows you the raw string representation including quotes and escape\ncodes.  When you type `print(data)`, you get the actual formatted\noutput of the string.\n\nAlthough reading a file all at once is simple, it is often not the\nmost appropriate way to do it—especially if the file happens to be\nhuge or if contains lines of text that you want to handle one at a\ntime.\n\nTo read a file line-by-line, use a for-loop like this:\n\n```python\n>>> with open('Data/portfolio.csv', 'rt') as f:\n        for line in f:\n            print(line, end='')\n\nname,shares,price\n\"AA\",100,32.20\n\"IBM\",50,91.10\n...\n>>>\n```\n\nWhen you use this code as shown, lines are read until the end of the\nfile is reached at which point the loop stops.\n\nOn certain occasions, you might want to manually read or skip a\n*single* line of text (e.g., perhaps you want to skip the first line\nof column headers).\n\n```python\n>>> f = open('Data/portfolio.csv', 'rt')\n>>> headers = next(f)\n>>> headers\n'name,shares,price\\n'\n>>> for line in f:\n    print(line, end='')\n\n\"AA\",100,32.20\n\"IBM\",50,91.10\n...\n>>> f.close()\n>>>\n```\n\n`next()` returns the next line of text in the file. If you were to call it repeatedly, you would get successive lines.\nHowever, just so you know, the `for` loop already uses `next()` to obtain its data.\nThus, you normally wouldn’t call it directly unless you’re trying to explicitly skip or read a single line as shown.\n\nOnce you’re reading lines of a file, you can start to perform more processing such as splitting.\nFor example, try this:\n\n```python\n>>> f = open('Data/portfolio.csv', 'rt')\n>>> headers = next(f).split(',')\n>>> headers\n['name', 'shares', 'price\\n']\n>>> for line in f:\n    row = line.split(',')\n    print(row)\n\n['\"AA\"', '100', '32.20\\n']\n['\"IBM\"', '50', '91.10\\n']\n...\n>>> f.close()\n```\n\n*Note: In these examples, `f.close()` is being called explicitly because the `with` statement isn’t being used.*\n\n### Exercise 1.27: Reading a data file\n\nNow that you know how to read a file, let’s write a program to perform a simple calculation.\n\nThe columns in `portfolio.csv` correspond to the stock name, number of\nshares, and purchase price of a single stock holding.  Write a program called\n`pcost.py` that opens this file, reads all lines, and calculates how\nmuch it cost to purchase all of the shares in the portfolio.\n\n*Hint: to convert a string to an integer, use `int(s)`. To convert a string to a floating point, use `float(s)`.*\n\nYour program should print output such as the following:\n\n```bash\nTotal cost 44671.15\n```\n\n### Exercise 1.28: Other kinds of \"files\"\n\nWhat if you wanted to read a non-text file such as a gzip-compressed\ndatafile?  The builtin `open()` function won’t help you here, but\nPython has a library module `gzip` that can read gzip compressed\nfiles.\n\nTry it:\n\n```python\n>>> import gzip\n>>> with gzip.open('Data/portfolio.csv.gz', 'rt') as f:\n        for line in f:\n            print(line, end='')\n\n... look at the output ...\n>>>\n```\n\nNote: Including the file mode of `'rt'` is critical here.  If you forget that,\nyou'll get byte strings instead of normal text strings.\n\n### Commentary:  Shouldn't we being using Pandas for this?\n\nData scientists are quick to point out that libraries like\n[Pandas](https://pandas.pydata.org) already have a function for\nreading CSV files.  This is true--and it works pretty well.\nHowever, this is not a course on learning Pandas. Reading files\nis a more general problem than the specifics of CSV files.\nThe main reason we're working with a CSV file is that it's a\nfamiliar format to most coders and it's relatively easy to work with\ndirectly--illustrating many Python features in the process.\nSo, by all means use Pandas when you go back to work.  For the\nrest of this course however, we're going to stick with standard\nPython functionality.\n\n[Contents](../Contents.md) \\| [Previous (1.5 Lists)](05_Lists.md) \\| [Next (1.7 Functions)](07_Functions.md)\n"
  },
  {
    "path": "Notes/01_Introduction/07_Functions.md",
    "content": "[Contents](../Contents.md) \\| [Previous (1.6 Files)](06_Files.md) \\| [Next (2.0 Working with Data)](../02_Working_with_data/00_Overview.md)\n\n# 1.7 Functions\n\nAs your programs start to get larger, you'll want to get organized.  This section\nbriefly introduces functions and library modules.  Error handling with exceptions is also introduced.\n\n### Custom Functions\n\nUse functions for code you want to reuse. Here is a function definition:\n\n```python\ndef sumcount(n):\n    '''\n    Returns the sum of the first n integers\n    '''\n    total = 0\n    while n > 0:\n        total += n\n        n -= 1\n    return total\n```\n\nTo call a function.\n\n```python\na = sumcount(100)\n```\n\nA function is a series of statements that perform some task and return a result.\nThe `return` keyword is needed to explicitly specify the return value of the function.\n\n### Library Functions\n\nPython comes with a large standard library.\nLibrary modules are accessed using `import`.\nFor example:\n\n```python\nimport math\nx = math.sqrt(10)\n\nimport urllib.request\nu = urllib.request.urlopen('http://www.python.org/')\ndata = u.read()\n```\n\nWe will cover libraries and modules in more detail later.\n\n### Errors and exceptions\n\nFunctions report errors as exceptions.  An exception causes a function to abort and may\ncause your entire program to stop if unhandled.\n\nTry this in your python REPL.\n\n```python\n>>> int('N/A')\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in <module>\nValueError: invalid literal for int() with base 10: 'N/A'\n>>>\n```\n\nFor debugging purposes, the message describes what happened, where the error occurred,\nand a traceback showing the other function calls that led to the failure.\n\n### Catching and Handling Exceptions\n\nExceptions can be caught and handled.\n\nTo catch, use the `try - except` statement.\n\n```python\nfor line in file:\n    fields = line.split(',')\n    try:\n        shares = int(fields[1])\n    except ValueError:\n        print(\"Couldn't parse\", line)\n    ...\n```\n\nThe name `ValueError` must match the kind of error you are trying to catch.\n\nIt is often difficult to know exactly what kinds of errors might occur\nin advance depending on the operation being performed.  For better or\nfor worse, exception handling often gets added *after* a program has\nunexpectedly crashed (i.e., \"oh, we forgot to catch that error. We\nshould handle that!\").\n\n### Raising Exceptions\n\nTo raise an exception, use the `raise` statement.\n\n```python\nraise RuntimeError('What a kerfuffle')\n```\n\nThis will cause the program to abort with an exception traceback. Unless caught by a `try-except` block.\n\n```bash\n% python3 foo.py\nTraceback (most recent call last):\n  File \"foo.py\", line 21, in <module>\n    raise RuntimeError(\"What a kerfuffle\")\nRuntimeError: What a kerfuffle\n```\n\n## Exercises\n\n### Exercise 1.29: Defining a function\n\nTry defining a simple function:\n\n```python\n>>> def greeting(name):\n        'Issues a greeting'\n        print('Hello', name)\n\n>>> greeting('Guido')\nHello Guido\n>>> greeting('Paula')\nHello Paula\n>>>\n```\n\nIf the first statement of a function is a string, it serves as documentation.\nTry typing a command such as `help(greeting)` to see it displayed.\n\n### Exercise 1.30: Turning a script into a function\n\nTake the code you wrote for the `pcost.py` program in [Exercise 1.27](06_Files.md)\nand turn it into a function `portfolio_cost(filename)`.  This\nfunction takes a filename as input, reads the portfolio data in that\nfile, and returns the total cost of the portfolio as a float.\n\nTo use your function, change your program so that it looks something\nlike this:\n\n```python\ndef portfolio_cost(filename):\n    ...\n    # Your code here\n    ...\n\ncost = portfolio_cost('Data/portfolio.csv')\nprint('Total cost:', cost)\n```\n\nWhen you run your program, you should see the same output as before.\nAfter you’ve run your program, you can also call your function\ninteractively by typing this:\n\n```bash\nbash $ python3 -i pcost.py\n```\n\nThis will allow you to call your function from the interactive mode.\n\n```python\n>>> portfolio_cost('Data/portfolio.csv')\n44671.15\n>>>\n```\n\nBeing able to experiment with your code interactively is useful for\ntesting and debugging.\n\n### Exercise 1.31: Error handling\n\nWhat happens if you try your function on a file with some missing fields?\n\n```python\n>>> portfolio_cost('Data/missing.csv')\nTraceback (most recent call last):\n    File \"<stdin>\", line 1, in <module>\n    File \"pcost.py\", line 11, in portfolio_cost\n    nshares    = int(fields[1])\nValueError: invalid literal for int() with base 10: ''\n>>>\n```\n\nAt this point, you’re faced with a decision. To make the program work\nyou can either sanitize the original input file by eliminating bad\nlines or you can modify your code to handle the bad lines in some\nmanner.\n\nModify the `pcost.py` program to catch the exception, print a warning\nmessage, and continue processing the rest of the file.\n\n### Exercise 1.32: Using a library function\n\nPython comes with a large standard library of useful functions.  One\nlibrary that might be useful here is the `csv` module. You should use\nit whenever you have to work with CSV data files.  Here is an example\nof how it works:\n\n```python\n>>> import csv\n>>> f = open('Data/portfolio.csv')\n>>> rows = csv.reader(f)\n>>> headers = next(rows)\n>>> headers\n['name', 'shares', 'price']\n>>> for row in rows:\n        print(row)\n\n['AA', '100', '32.20']\n['IBM', '50', '91.10']\n['CAT', '150', '83.44']\n['MSFT', '200', '51.23']\n['GE', '95', '40.37']\n['MSFT', '50', '65.10']\n['IBM', '100', '70.44']\n>>> f.close()\n>>>\n```\n\nOne nice thing about the `csv` module is that it deals with a variety\nof low-level details such as quoting and proper comma splitting.  In\nthe above output, you’ll notice that it has stripped the double-quotes\naway from the names in the first column.\n\nModify your `pcost.py` program so that it uses the `csv` module for\nparsing and try running earlier examples.\n\n### Exercise 1.33: Reading from the command line\n\nIn the `pcost.py` program, the name of the input file has been hardwired into the code:\n\n```python\n# pcost.py\n\ndef portfolio_cost(filename):\n    ...\n    # Your code here\n    ...\n\ncost = portfolio_cost('Data/portfolio.csv')\nprint('Total cost:', cost)\n```\n\nThat’s fine for learning and testing, but in a real program you\nprobably wouldn’t do that.\n\nInstead, you might pass the name of the file in as an argument to a\nscript. Try changing the bottom part of the program as follows:\n\n```python\n# pcost.py\nimport sys\n\ndef portfolio_cost(filename):\n    ...\n    # Your code here\n    ...\n\nif len(sys.argv) == 2:\n    filename = sys.argv[1]\nelse:\n    filename = 'Data/portfolio.csv'\n\ncost = portfolio_cost(filename)\nprint('Total cost:', cost)\n```\n\n`sys.argv` is a list that contains passed arguments on the command line (if any).\n\nTo run your program, you’ll need to run Python from the\nterminal.\n\nFor example, from bash on Unix:\n\n```bash\nbash % python3 pcost.py Data/portfolio.csv\nTotal cost: 44671.15\nbash %\n```\n\n[Contents](../Contents.md) \\| [Previous (1.6 Files)](06_Files.md) \\| [Next (2.0 Working with Data)](../02_Working_with_data/00_Overview.md)"
  },
  {
    "path": "Notes/02_Working_with_data/00_Overview.md",
    "content": "[Contents](../Contents.md) \\| [Prev (1 Introduction to Python)](../01_Introduction/00_Overview.md) \\| [Next (3 Program Organization)](../03_Program_organization/00_Overview.md)\n\n# 2. Working With Data\n\nTo write useful programs, you need to be able to work with data.\nThis section introduces Python's core data structures of tuples,\nlists, sets, and dictionaries and discusses common data handling\nidioms.  The last part of this section dives a little deeper\ninto Python's underlying object model.\n\n* [2.1 Datatypes and Data Structures](01_Datatypes.md)\n* [2.2 Containers](02_Containers.md)\n* [2.3 Formatted Output](03_Formatting.md)\n* [2.4 Sequences](04_Sequences.md)\n* [2.5 Collections module](05_Collections.md)\n* [2.6 List comprehensions](06_List_comprehension.md)\n* [2.7 Object model](07_Objects.md)\n\n[Contents](../Contents.md) \\| [Prev (1 Introduction to Python)](../01_Introduction/00_Overview.md) \\| [Next (3 Program Organization)](../03_Program_organization/00_Overview.md)\n"
  },
  {
    "path": "Notes/02_Working_with_data/01_Datatypes.md",
    "content": "[Contents](../Contents.md) \\| [Previous (1.6 Files)](../01_Introduction/06_Files.md) \\| [Next (2.2 Containers)](02_Containers.md)\n\n# 2.1 Datatypes and Data structures\n\nThis section introduces data structures in the form of tuples and dictionaries.\n\n### Primitive Datatypes\n\nPython has a few primitive types of data:\n\n* Integers\n* Floating point numbers\n* Strings (text)\n\nWe learned about these in the introduction.\n\n### None type\n\n```python\nemail_address = None\n```\n\n`None` is often used as a placeholder for optional or missing value.  It\nevaluates as `False` in conditionals.\n\n```python\nif email_address:\n    send_email(email_address, msg)\n```\n\n### Data Structures\n\nReal programs have more complex data. For example information about a stock holding:\n\n```code\n100 shares of GOOG at $490.10\n```\n\nThis is an \"object\" with three parts:\n\n* Name or symbol of the stock (\"GOOG\", a string)\n* Number of shares (100, an integer)\n* Price (490.10 a float)\n\n### Tuples\n\nA tuple is a collection of values grouped together.\n\nExample:\n\n```python\ns = ('GOOG', 100, 490.1)\n```\n\nSometimes the `()` are omitted in the syntax.\n\n```python\ns = 'GOOG', 100, 490.1\n```\n\nSpecial cases (0-tuple, 1-tuple).\n\n```python\nt = ()            # An empty tuple\nw = ('GOOG', )    # A 1-item tuple\n```\n\nTuples are often used to represent *simple* records or structures.\nTypically, it is a single *object* of multiple parts. A good analogy: *A tuple is like a single row in a database table.*\n\nTuple contents are ordered (like an array).\n\n```python\ns = ('GOOG', 100, 490.1)\nname = s[0]                 # 'GOOG'\nshares = s[1]               # 100\nprice = s[2]                # 490.1\n```\n\nHowever, the contents can't be modified.\n\n```python\n>>> s[1] = 75\nTypeError: object does not support item assignment\n```\n\nYou can, however, make a new tuple based on a current tuple.\n\n```python\ns = (s[0], 75, s[2])\n```\n\n### Tuple Packing\n\nTuples are more about packing related items together into a single *entity*.\n\n```python\ns = ('GOOG', 100, 490.1)\n```\n\nThe tuple is then easy to pass around to other parts of a program as a single object.\n\n### Tuple Unpacking\n\nTo use the tuple elsewhere, you can unpack its parts into variables.\n\n```python\nname, shares, price = s\nprint('Cost', shares * price)\n```\n\nThe number of variables on the left must match the tuple structure.\n\n```python\nname, shares = s     # ERROR\nTraceback (most recent call last):\n...\nValueError: too many values to unpack\n```\n\n### Tuples vs. Lists\n\nTuples look like read-only lists. However, tuples are most often used\nfor a *single item* consisting of multiple parts.  Lists are usually a\ncollection of distinct items, usually all of the same type.\n\n```python\nrecord = ('GOOG', 100, 490.1)       # A tuple representing a record in a portfolio\n\nsymbols = [ 'GOOG', 'AAPL', 'IBM' ]  # A List representing three stock symbols\n```\n\n### Dictionaries\n\nA dictionary is mapping of keys to values.  It's also sometimes called a hash table or\nassociative array.  The keys serve as indices for accessing values.\n\n```python\ns = {\n    'name': 'GOOG',\n    'shares': 100,\n    'price': 490.1\n}\n```\n\n### Common operations\n\nTo get values from a dictionary use the key names.\n\n```python\n>>> print(s['name'], s['shares'])\nGOOG 100\n>>> s['price']\n490.10\n>>>\n```\n\nTo add or modify values assign using the key names.\n\n```python\n>>> s['shares'] = 75\n>>> s['date'] = '6/6/2007'\n>>>\n```\n\nTo delete a value use the `del` statement.\n\n```python\n>>> del s['date']\n>>>\n```\n\n### Why dictionaries?\n\nDictionaries are useful when there are *many* different values and those values\nmight be modified or manipulated.  Dictionaries make your code more readable.\n\n```python\ns['price']\n# vs\ns[2]\n```\n\n## Exercises\n\nIn the last few exercises, you wrote a program that read a datafile\n`Data/portfolio.csv`. Using the `csv` module, it is easy to read the\nfile row-by-row.\n\n```python\n>>> import csv\n>>> f = open('Data/portfolio.csv')\n>>> rows = csv.reader(f)\n>>> next(rows)\n['name', 'shares', 'price']\n>>> row = next(rows)\n>>> row\n['AA', '100', '32.20']\n>>>\n```\n\nAlthough reading the file is easy, you often want to do more with the\ndata than read it.  For instance, perhaps you want to store it and\nstart performing some calculations on it.  Unfortunately, a raw \"row\"\nof data doesn’t give you enough to work with. For example, even a\nsimple math calculation doesn’t work:\n\n```python\n>>> row = ['AA', '100', '32.20']\n>>> cost = row[1] * row[2]\nTraceback (most recent call last):\n    File \"<stdin>\", line 1, in <module>\nTypeError: can't multiply sequence by non-int of type 'str'\n>>>\n```\n\nTo do more, you typically want to interpret the raw data in some way\nand turn it into a more useful kind of object so that you can work\nwith it later.  Two simple options are tuples or dictionaries.\n\n### Exercise 2.1: Tuples\n\nAt the interactive prompt, create the following tuple that represents\nthe above row, but with the numeric columns converted to proper\nnumbers:\n\n```python\n>>> t = (row[0], int(row[1]), float(row[2]))\n>>> t\n('AA', 100, 32.2)\n>>>\n```\n\nUsing this, you can now calculate the total cost by multiplying the\nshares and the price:\n\n```python\n>>> cost = t[1] * t[2]\n>>> cost\n3220.0000000000005\n>>>\n```\n\nIs math broken in Python? What’s the deal with the answer of\n3220.0000000000005?\n\nThis is an artifact of the floating point hardware on your computer\nonly being able to accurately represent decimals in Base-2, not\nBase-10.  For even simple calculations involving base-10 decimals,\nsmall errors are introduced. This is normal, although perhaps a bit\nsurprising if you haven’t seen it before.\n\nThis happens in all programming languages that use floating point\ndecimals, but it often gets hidden when printing. For example:\n\n```python\n>>> print(f'{cost:0.2f}')\n3220.00\n>>>\n```\n\nTuples are read-only. Verify this by trying to change the number of\nshares to 75.\n\n```python\n>>> t[1] = 75\nTraceback (most recent call last):\n    File \"<stdin>\", line 1, in <module>\nTypeError: 'tuple' object does not support item assignment\n>>>\n```\n\nAlthough you can’t change tuple contents, you can always create a\ncompletely new tuple that replaces the old one.\n\n```python\n>>> t = (t[0], 75, t[2])\n>>> t\n('AA', 75, 32.2)\n>>>\n```\n\nWhenever you reassign an existing variable name like this, the old\nvalue is discarded.  Although the above assignment might look like you\nare modifying the tuple, you are actually creating a new tuple and\nthrowing the old one away.\n\nTuples are often used to pack and unpack values into variables. Try\nthe following:\n\n```python\n>>> name, shares, price = t\n>>> name\n'AA'\n>>> shares\n75\n>>> price\n32.2\n>>>\n```\n\nTake the above variables and pack them back into a tuple\n\n```python\n>>> t = (name, 2*shares, price)\n>>> t\n('AA', 150, 32.2)\n>>>\n```\n\n### Exercise 2.2: Dictionaries as a data structure\n\nAn alternative to a tuple is to create a dictionary instead.\n\n```python\n>>> d = {\n        'name' : row[0],\n        'shares' : int(row[1]),\n        'price'  : float(row[2])\n    }\n>>> d\n{'name': 'AA', 'shares': 100, 'price': 32.2 }\n>>>\n```\n\nCalculate the total cost of this holding:\n\n```python\n>>> cost = d['shares'] * d['price']\n>>> cost\n3220.0000000000005\n>>>\n```\n\nCompare this example with the same calculation involving tuples\nabove. Change the number of shares to 75.\n\n```python\n>>> d['shares'] = 75\n>>> d\n{'name': 'AA', 'shares': 75, 'price': 32.2 }\n>>>\n```\n\nUnlike tuples, dictionaries can be freely modified. Add some\nattributes:\n\n```python\n>>> d['date'] = (6, 11, 2007)\n>>> d['account'] = 12345\n>>> d\n{'name': 'AA', 'shares': 75, 'price':32.2, 'date': (6, 11, 2007), 'account': 12345}\n>>>\n```\n\n### Exercise 2.3: Some additional dictionary operations\n\nIf you turn a dictionary into a list, you’ll get all of its keys:\n\n```python\n>>> list(d)\n['name', 'shares', 'price', 'date', 'account']\n>>>\n```\n\nSimilarly, if you use the `for` statement to iterate on a dictionary,\nyou will get the keys:\n\n```python\n>>> for k in d:\n        print('k =', k)\n\nk = name\nk = shares\nk = price\nk = date\nk = account\n>>>\n```\n\nTry this variant that performs a lookup at the same time:\n\n```python\n>>> for k in d:\n        print(k, '=', d[k])\n\nname = AA\nshares = 75\nprice = 32.2\ndate = (6, 11, 2007)\naccount = 12345\n>>>\n```\n\nYou can also obtain all of the keys using the `keys()` method:\n\n```python\n>>> keys = d.keys()\n>>> keys\ndict_keys(['name', 'shares', 'price', 'date', 'account'])\n>>>\n```\n\n`keys()` is a bit unusual in that it returns a special `dict_keys` object.\n\nThis is an overlay on the original dictionary that always gives you\nthe current keys—even if the dictionary changes. For example, try\nthis:\n\n```python\n>>> del d['account']\n>>> keys\ndict_keys(['name', 'shares', 'price', 'date'])\n>>>\n```\n\nCarefully notice that the `'account'` disappeared from `keys` even\nthough you didn’t call `d.keys()` again.\n\nA more elegant way to work with keys and values together is to use the\n`items()` method. This gives you `(key, value)` tuples:\n\n```python\n>>> items = d.items()\n>>> items\ndict_items([('name', 'AA'), ('shares', 75), ('price', 32.2), ('date', (6, 11, 2007))])\n>>> for k, v in d.items():\n        print(k, '=', v)\n\nname = AA\nshares = 75\nprice = 32.2\ndate = (6, 11, 2007)\n>>>\n```\n\nIf you have tuples such as `items`, you can create a dictionary using\nthe `dict()` function. Try it:\n\n```python\n>>> items\ndict_items([('name', 'AA'), ('shares', 75), ('price', 32.2), ('date', (6, 11, 2007))])\n>>> d = dict(items)\n>>> d\n{'name': 'AA', 'shares': 75, 'price':32.2, 'date': (6, 11, 2007)}\n>>>\n```\n\n[Contents](../Contents.md) \\| [Previous (1.6 Files)](../01_Introduction/06_Files.md) \\| [Next (2.2 Containers)](02_Containers.md)\n"
  },
  {
    "path": "Notes/02_Working_with_data/02_Containers.md",
    "content": "[Contents](../Contents.md) \\| [Previous (2.1 Datatypes)](01_Datatypes.md) \\| [Next (2.3 Formatting)](03_Formatting.md)\n\n# 2.2 Containers\n\nThis section discusses lists, dictionaries, and sets.\n\n### Overview\n\nPrograms often have to work with many objects.\n\n* A portfolio of stocks\n* A table of stock prices\n\nThere are three main choices to use.\n\n* Lists. Ordered data.\n* Dictionaries. Unordered data.\n* Sets. Unordered collection of unique items.\n\n### Lists as a Container\n\nUse a list when the order of the data matters. Remember that lists can hold any kind of object.\nFor example, a list of tuples.\n\n```python\nportfolio = [\n    ('GOOG', 100, 490.1),\n    ('IBM', 50, 91.3),\n    ('CAT', 150, 83.44)\n]\n\nportfolio[0]            # ('GOOG', 100, 490.1)\nportfolio[2]            # ('CAT', 150, 83.44)\n```\n\n### List construction\n\nBuilding a list from scratch.\n\n```python\nrecords = []  # Initial empty list\n\n# Use .append() to add more items\nrecords.append(('GOOG', 100, 490.10))\nrecords.append(('IBM', 50, 91.3))\n...\n```\n\nAn example when reading records from a file.\n\n```python\nrecords = []  # Initial empty list\n\nwith open('Data/portfolio.csv', 'rt') as f:\n    next(f) # Skip header\n    for line in f:\n        row = line.split(',')\n        records.append((row[0], int(row[1]), float(row[2])))\n```\n\n### Dicts as a Container\n\nDictionaries are useful if you want fast random lookups (by key name).  For\nexample, a dictionary of stock prices:\n\n```python\nprices = {\n   'GOOG': 513.25,\n   'CAT': 87.22,\n   'IBM': 93.37,\n   'MSFT': 44.12\n}\n```\n\nHere are some simple lookups:\n\n```python\n>>> prices['IBM']\n93.37\n>>> prices['GOOG']\n513.25\n>>>\n```\n\n### Dict Construction\n\nExample of building a dict from scratch.\n\n```python\nprices = {} # Initial empty dict\n\n# Insert new items\nprices['GOOG'] = 513.25\nprices['CAT'] = 87.22\nprices['IBM'] = 93.37\n```\n\nAn example populating the dict from the contents of a file.\n\n```python\nprices = {} # Initial empty dict\n\nwith open('Data/prices.csv', 'rt') as f:\n    for line in f:\n        row = line.split(',')\n        prices[row[0]] = float(row[1])\n```\n\nNote: If you try this on the `Data/prices.csv` file, you'll find that\nit almost works--there's a blank line at the end that causes it to\ncrash.  You'll need to figure out some way to modify the code to\naccount for that (see Exercise 2.6).\n\n### Dictionary Lookups\n\nYou can test the existence of a key.\n\n```python\nif key in d:\n    # YES\nelse:\n    # NO\n```\n\nYou can look up a value that might not exist and provide a default value in case it doesn't.\n\n```python\nname = d.get(key, default)\n```\n\nAn example:\n\n```python\n>>> prices.get('IBM', 0.0)\n93.37\n>>> prices.get('SCOX', 0.0)\n0.0\n>>>\n```\n\n### Composite keys\n\nAlmost any type of value can be used as a dictionary key in Python. A dictionary key must be of a type that is immutable.\nFor example, tuples:\n\n```python\nholidays = {\n  (1, 1) : 'New Years',\n  (3, 14) : 'Pi day',\n  (9, 13) : \"Programmer's day\",\n}\n```\n\nThen to access:\n\n```python\n>>> holidays[3, 14]\n'Pi day'\n>>>\n```\n\n*Neither a list, a set, nor another dictionary can serve as a dictionary key, because lists, sets, and dictionaries are mutable.*\n\n### Sets\n\nSets are collection of unordered unique items.\n\n```python\ntech_stocks = { 'IBM','AAPL','MSFT' }\n# Alternative syntax\ntech_stocks = set(['IBM', 'AAPL', 'MSFT'])\n```\n\nSets are useful for membership tests.\n\n```python\n>>> tech_stocks\nset(['AAPL', 'IBM', 'MSFT'])\n>>> 'IBM' in tech_stocks\nTrue\n>>> 'FB' in tech_stocks\nFalse\n>>>\n```\n\nSets are also useful for duplicate elimination.\n\n```python\nnames = ['IBM', 'AAPL', 'GOOG', 'IBM', 'GOOG', 'YHOO']\n\nunique = set(names)\n# unique = set(['IBM', 'AAPL','GOOG','YHOO'])\n```\n\nAdditional set operations:\n\n```python\nunique.add('CAT')        # Add an item\nunique.remove('YHOO')    # Remove an item\n\ns1 = { 'a', 'b', 'c'}\ns2 = { 'c', 'd' }\ns1 | s2                 # Set union { 'a', 'b', 'c', 'd' }\ns1 & s2                 # Set intersection { 'c' }\ns1 - s2                 # Set difference { 'a', 'b' }\n```\n\n## Exercises\n\nIn these exercises, you start building one of the major programs used\nfor the rest of this course.  Do your work in the file `Work/report.py`.\n\n### Exercise 2.4: A list of tuples\n\nThe file `Data/portfolio.csv` contains a list of stocks in a\nportfolio.  In [Exercise 1.30](../01_Introduction/07_Functions.md), you\nwrote a function `portfolio_cost(filename)` that read this file and\nperformed a simple calculation.\n\nYour code should have looked something like this:\n\n```python\n# pcost.py\n\nimport csv\n\ndef portfolio_cost(filename):\n    '''Computes the total cost (shares*price) of a portfolio file'''\n    total_cost = 0.0\n\n    with open(filename, 'rt') as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            nshares = int(row[1])\n            price = float(row[2])\n            total_cost += nshares * price\n    return total_cost\n```\n\nUsing this code as a rough guide, create a new file `report.py`.  In\nthat file, define a function `read_portfolio(filename)` that opens a\ngiven portfolio file and reads it into a list of tuples.  To do this,\nyou’re going to make a few minor modifications to the above code.\n\nFirst, instead of defining `total_cost = 0`, you’ll make a variable\nthat’s initially set to an empty list. For example:\n\n```python\nportfolio = []\n```\n\nNext, instead of totaling up the cost, you’ll turn each row into a\ntuple exactly as you just did in the last exercise and append it to\nthis list. For example:\n\n```python\nfor row in rows:\n    holding = (row[0], int(row[1]), float(row[2]))\n    portfolio.append(holding)\n```\n\nFinally, you’ll return the resulting `portfolio` list.\n\nExperiment with your function interactively (just a reminder that in\norder to do this, you first have to run the `report.py` program in the\ninterpreter):\n\n*Hint: Use `-i` when executing the file in the terminal*\n\n```python\n>>> portfolio = read_portfolio('Data/portfolio.csv')\n>>> portfolio\n[('AA', 100, 32.2), ('IBM', 50, 91.1), ('CAT', 150, 83.44), ('MSFT', 200, 51.23),\n    ('GE', 95, 40.37), ('MSFT', 50, 65.1), ('IBM', 100, 70.44)]\n>>>\n>>> portfolio[0]\n('AA', 100, 32.2)\n>>> portfolio[1]\n('IBM', 50, 91.1)\n>>> portfolio[1][1]\n50\n>>> total = 0.0\n>>> for s in portfolio:\n        total += s[1] * s[2]\n\n>>> print(total)\n44671.15\n>>>\n```\n\nThis list of tuples that you have created is very similar to a 2-D\narray.  For example, you can access a specific column and row using a\nlookup such as `portfolio[row][column]` where `row` and `column` are\nintegers.\n\nThat said, you can also rewrite the last for-loop using a statement like this:\n\n```python\n>>> total = 0.0\n>>> for name, shares, price in portfolio:\n            total += shares*price\n\n>>> print(total)\n44671.15\n>>>\n```\n\n### Exercise 2.5: List of Dictionaries\n\nTake the function you wrote in Exercise 2.4 and modify to represent each\nstock in the portfolio with a dictionary instead of a tuple.  In this\ndictionary use the field names of \"name\", \"shares\", and \"price\" to\nrepresent the different columns in the input file.\n\nExperiment with this new function in the same manner as you did in\nExercise 2.4.\n\n```python\n>>> portfolio = read_portfolio('Data/portfolio.csv')\n>>> portfolio\n[{'name': 'AA', 'shares': 100, 'price': 32.2}, {'name': 'IBM', 'shares': 50, 'price': 91.1},\n    {'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23},\n    {'name': 'GE', 'shares': 95, 'price': 40.37}, {'name': 'MSFT', 'shares': 50, 'price': 65.1},\n    {'name': 'IBM', 'shares': 100, 'price': 70.44}]\n>>> portfolio[0]\n{'name': 'AA', 'shares': 100, 'price': 32.2}\n>>> portfolio[1]\n{'name': 'IBM', 'shares': 50, 'price': 91.1}\n>>> portfolio[1]['shares']\n50\n>>> total = 0.0\n>>> for s in portfolio:\n        total += s['shares']*s['price']\n\n>>> print(total)\n44671.15\n>>>\n```\n\nHere, you will notice that the different fields for each entry are\naccessed by key names instead of numeric column numbers.  This is\noften preferred because the resulting code is easier to read later.\n\nViewing large dictionaries and lists can be messy. To clean up the\noutput for debugging, consider using the `pprint` function.\n\n```python\n>>> from pprint import pprint\n>>> pprint(portfolio)\n[{'name': 'AA', 'price': 32.2, 'shares': 100},\n    {'name': 'IBM', 'price': 91.1, 'shares': 50},\n    {'name': 'CAT', 'price': 83.44, 'shares': 150},\n    {'name': 'MSFT', 'price': 51.23, 'shares': 200},\n    {'name': 'GE', 'price': 40.37, 'shares': 95},\n    {'name': 'MSFT', 'price': 65.1, 'shares': 50},\n    {'name': 'IBM', 'price': 70.44, 'shares': 100}]\n>>>\n```\n\n### Exercise 2.6: Dictionaries as a container\n\nA dictionary is a useful way to keep track of items where you want to\nlook up items using an index other than an integer.  In the Python\nshell, try playing with a dictionary:\n\n```python\n>>> prices = { }\n>>> prices['IBM'] = 92.45\n>>> prices['MSFT'] = 45.12\n>>> prices\n... look at the result ...\n>>> prices['IBM']\n92.45\n>>> prices['AAPL']\n... look at the result ...\n>>> 'AAPL' in prices\nFalse\n>>>\n```\n\nThe file `Data/prices.csv` contains a series of lines with stock prices.\nThe file looks something like this:\n\n```csv\n\"AA\",9.22\n\"AXP\",24.85\n\"BA\",44.85\n\"BAC\",11.27\n\"C\",3.72\n...\n```\n\nWrite a function `read_prices(filename)` that reads a set of prices\nsuch as this into a dictionary where the keys of the dictionary are\nthe stock names and the values in the dictionary are the stock prices.\n\nTo do this, start with an empty dictionary and start inserting values\ninto it just as you did above. However, you are reading the values\nfrom a file now.\n\nWe’ll use this data structure to quickly lookup the price of a given\nstock name.\n\nA few little tips that you’ll need for this part. First, make sure you\nuse the `csv` module just as you did before—there’s no need to\nreinvent the wheel here.\n\n```python\n>>> import csv\n>>> f = open('Data/prices.csv', 'r')\n>>> rows = csv.reader(f)\n>>> for row in rows:\n        print(row)\n\n\n['AA', '9.22']\n['AXP', '24.85']\n...\n[]\n>>>\n```\n\nThe other little complication is that the `Data/prices.csv` file may\nhave some blank lines in it. Notice how the last row of data above is\nan empty list—meaning no data was present on that line.\n\nThere’s a possibility that this could cause your program to die with\nan exception.  Use the `try` and `except` statements to catch this as\nappropriate.  Thought: would it be better to guard against bad data with\nan `if`-statement instead?\n\nOnce you have written your `read_prices()` function, test it\ninteractively to make sure it works:\n\n```python\n>>> prices = read_prices('Data/prices.csv')\n>>> prices['IBM']\n106.28\n>>> prices['MSFT']\n20.89\n>>>\n```\n\n### Exercise 2.7: Finding out if you can retire\n\nTie all of this work together by adding a few additional statements to\nyour `report.py` program that computes gain/loss. These statements\nshould take the list of stocks in Exercise 2.5 and the dictionary of\nprices in Exercise 2.6 and compute the current value of the portfolio\nalong with the gain/loss.\n\n[Contents](../Contents.md) \\| [Previous (2.1 Datatypes)](01_Datatypes.md) \\| [Next (2.3 Formatting)](03_Formatting.md)\n"
  },
  {
    "path": "Notes/02_Working_with_data/03_Formatting.md",
    "content": "[Contents](../Contents.md) \\| [Previous (2.2 Containers)](02_Containers.md) \\| [Next (2.4 Sequences)](04_Sequences.md)\n\n# 2.3 Formatting\n\nThis section is a slight digression, but when you work with data, you\noften want to produce structured output (tables, etc.). For example:\n\n```code\n      Name      Shares        Price\n----------  ----------  -----------\n        AA         100        32.20\n       IBM          50        91.10\n       CAT         150        83.44\n      MSFT         200        51.23\n        GE          95        40.37\n      MSFT          50        65.10\n       IBM         100        70.44\n```\n\n### String Formatting\n\nOne way to format string in Python 3.6+ is with `f-strings`.\n\n```python\n>>> name = 'IBM'\n>>> shares = 100\n>>> price = 91.1\n>>> f'{name:>10s} {shares:>10d} {price:>10.2f}'\n'       IBM        100      91.10'\n>>>\n```\n\nThe part `{expression:format}` is replaced.\n\nIt is commonly used with `print`.\n\n```python\nprint(f'{name:>10s} {shares:>10d} {price:>10.2f}')\n```\n\n### Format codes\n\nFormat codes (after the `:` inside the `{}`) are similar to C `printf()`.  Common codes\ninclude:\n\n```code\nd       Decimal integer\nb       Binary integer\nx       Hexadecimal integer\nf       Float as [-]m.dddddd\ne       Float as [-]m.dddddde+-xx\ng       Float, but selective use of E notation\ns       String\nc       Character (from integer)\n```\n\nCommon modifiers adjust the field width and decimal precision.  This is a partial list:\n\n```code\n:>10d   Integer right aligned in 10-character field\n:<10d   Integer left aligned in 10-character field\n:^10d   Integer centered in 10-character field\n:0.2f   Float with 2 digit precision\n```\n\n### Dictionary Formatting\n\nYou can use the `format_map()` method to apply string formatting to a dictionary of values:\n\n```python\n>>> s = {\n    'name': 'IBM',\n    'shares': 100,\n    'price': 91.1\n}\n>>> '{name:>10s} {shares:10d} {price:10.2f}'.format_map(s)\n'       IBM        100      91.10'\n>>>\n```\n\nIt uses the same codes as `f-strings` but takes the values from the\nsupplied dictionary.\n\n### format() method\n\nThere is a method `format()` that can apply formatting to arguments or\nkeyword arguments.\n\n```python\n>>> '{name:>10s} {shares:10d} {price:10.2f}'.format(name='IBM', shares=100, price=91.1)\n'       IBM        100      91.10'\n>>> '{:>10s} {:10d} {:10.2f}'.format('IBM', 100, 91.1)\n'       IBM        100      91.10'\n>>>\n```\n\nFrankly, `format()` is a bit verbose. I prefer f-strings.\n\n### C-Style Formatting\n\nYou can also use the formatting operator `%`.\n\n```python\n>>> 'The value is %d' % 3\n'The value is 3'\n>>> '%5d %-5d %10d' % (3,4,5)\n'    3 4              5'\n>>> '%0.2f' % (3.1415926,)\n'3.14'\n```\n\nThis requires a single item or a tuple on the right.  Format codes are\nmodeled after the C `printf()` as well.\n\n*Note: This is the only formatting available on byte strings.*\n\n```python\n>>> b'%s has %d messages' % (b'Dave', 37)\nb'Dave has 37 messages'\n>>> b'%b has %d messages' % (b'Dave', 37)  # %b may be used instead of %s\nb'Dave has 37 messages'\n>>>\n```\n\n## Exercises\n\n### Exercise 2.8: How to format numbers\n\nA common problem with printing numbers is specifying the number of\ndecimal places. One way to fix this is to use f-strings. Try these\nexamples:\n\n```python\n>>> value = 42863.1\n>>> print(value)\n42863.1\n>>> print(f'{value:0.4f}')\n42863.1000\n>>> print(f'{value:>16.2f}')\n        42863.10\n>>> print(f'{value:<16.2f}')\n42863.10\n>>> print(f'{value:*>16,.2f}')\n*******42,863.10\n>>>\n```\n\nFull documentation on the formatting codes used f-strings can be found\n[here](https://docs.python.org/3/library/string.html#format-specification-mini-language). Formatting\nis also sometimes performed using the `%` operator of strings.\n\n```python\n>>> print('%0.4f' % value)\n42863.1000\n>>> print('%16.2f' % value)\n        42863.10\n>>>\n```\n\nDocumentation on various codes used with `%` can be found\n[here](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting).\n\nAlthough it’s commonly used with `print`, string formatting is not tied to printing.\nIf you want to save a formatted string. Just assign it to a variable.\n\n```python\n>>> f = '%0.4f' % value\n>>> f\n'42863.1000'\n>>>\n```\n\n### Exercise 2.9: Collecting Data\n\nIn Exercise 2.7, you wrote a program called `report.py` that computed the gain/loss of a\nstock portfolio.  In this exercise, you're going to start modifying it to produce a table like this:\n\n```\n      Name     Shares      Price     Change\n---------- ---------- ---------- ----------\n        AA        100       9.22     -22.98\n       IBM         50     106.28      15.18\n       CAT        150      35.46     -47.98\n      MSFT        200      20.89     -30.34\n        GE         95      13.48     -26.89\n      MSFT         50      20.89     -44.21\n       IBM        100     106.28      35.84\n```\n\nIn this report, \"Price\" is the current share price of the stock and\n\"Change\" is the change in the share price from the initial purchase\nprice.\n\n\nIn order to generate the above report, you’ll first want to collect\nall of the data shown in the table.  Write a function `make_report()`\nthat takes a list of stocks and dictionary of prices as input and\nreturns a list of tuples containing the rows of the above table.\n\nAdd this function to your `report.py` file. Here’s how it should work\nif you try it interactively:\n\n```python\n>>> portfolio = read_portfolio('Data/portfolio.csv')\n>>> prices = read_prices('Data/prices.csv')\n>>> report = make_report(portfolio, prices)\n>>> for r in report:\n        print(r)\n\n('AA', 100, 9.22, -22.980000000000004)\n('IBM', 50, 106.28, 15.180000000000007)\n('CAT', 150, 35.46, -47.98)\n('MSFT', 200, 20.89, -30.339999999999996)\n('GE', 95, 13.48, -26.889999999999997)\n...\n>>>\n```\n\n### Exercise 2.10: Printing a formatted table\n\nRedo the for-loop in Exercise 2.9, but change the print statement to\nformat the tuples.\n\n```python\n>>> for r in report:\n        print('%10s %10d %10.2f %10.2f' % r)\n\n          AA        100       9.22     -22.98\n         IBM         50     106.28      15.18\n         CAT        150      35.46     -47.98\n        MSFT        200      20.89     -30.34\n...\n>>>\n```\n\nYou can also expand the values and use f-strings. For example:\n\n```python\n>>> for name, shares, price, change in report:\n        print(f'{name:>10s} {shares:>10d} {price:>10.2f} {change:>10.2f}')\n\n          AA        100       9.22     -22.98\n         IBM         50     106.28      15.18\n         CAT        150      35.46     -47.98\n        MSFT        200      20.89     -30.34\n...\n>>>\n```\n\nTake the above statements and add them to your `report.py` program.\nHave your program take the output of the `make_report()` function and print a nicely formatted table as shown.\n\n### Exercise 2.11: Adding some headers\n\nSuppose you had a tuple of header names like this:\n\n```python\nheaders = ('Name', 'Shares', 'Price', 'Change')\n```\n\nAdd code to your program that takes the above tuple of headers and\ncreates a string where each header name is right-aligned in a\n10-character wide field and each field is separated by a single space.\n\n```python\n'      Name     Shares      Price      Change'\n```\n\nWrite code that takes the headers and creates the separator string between the headers and data to follow.\nThis string is just a bunch of \"-\" characters under each field name. For example:\n\n```python\n'---------- ---------- ---------- -----------'\n```\n\nWhen you’re done, your program should produce the table shown at the top of this exercise.\n\n```\n      Name     Shares      Price     Change\n---------- ---------- ---------- ----------\n        AA        100       9.22     -22.98\n       IBM         50     106.28      15.18\n       CAT        150      35.46     -47.98\n      MSFT        200      20.89     -30.34\n        GE         95      13.48     -26.89\n      MSFT         50      20.89     -44.21\n       IBM        100     106.28      35.84\n```\n\n### Exercise 2.12: Formatting Challenge\n\nHow would you modify your code so that the price includes the currency symbol ($) and the output looks like this:\n\n```\n      Name     Shares      Price     Change\n---------- ---------- ---------- ----------\n        AA        100      $9.22     -22.98\n       IBM         50    $106.28      15.18\n       CAT        150     $35.46     -47.98\n      MSFT        200     $20.89     -30.34\n        GE         95     $13.48     -26.89\n      MSFT         50     $20.89     -44.21\n       IBM        100    $106.28      35.84\n```\n\n[Contents](../Contents.md) \\| [Previous (2.2 Containers)](02_Containers.md) \\| [Next (2.4 Sequences)](04_Sequences.md)\n"
  },
  {
    "path": "Notes/02_Working_with_data/04_Sequences.md",
    "content": "[Contents](../Contents.md) \\| [Previous (2.3 Formatting)](03_Formatting.md) \\| [Next (2.5 Collections)](05_Collections.md)\n\n# 2.4 Sequences\n\n### Sequence Datatypes\n\nPython has three *sequence* datatypes.\n\n* String: `'Hello'`. A string is a sequence of characters.\n* List: `[1, 4, 5]`.\n* Tuple: `('GOOG', 100, 490.1)`.\n\nAll sequences are ordered, indexed by integers, and have a length.\n\n```python\na = 'Hello'               # String\nb = [1, 4, 5]             # List\nc = ('GOOG', 100, 490.1)  # Tuple\n\n# Indexed order\na[0]                      # 'H'\nb[-1]                     # 5\nc[1]                      # 100\n\n# Length of sequence\nlen(a)                    # 5\nlen(b)                    # 3\nlen(c)                    # 3\n```\n\nSequences can be replicated: `s * n`.\n\n```python\n>>> a = 'Hello'\n>>> a * 3\n'HelloHelloHello'\n>>> b = [1, 2, 3]\n>>> b * 2\n[1, 2, 3, 1, 2, 3]\n>>>\n```\n\nSequences of the same type can be concatenated: `s + t`.\n\n```python\n>>> a = (1, 2, 3)\n>>> b = (4, 5)\n>>> a + b\n(1, 2, 3, 4, 5)\n>>>\n>>> c = [1, 5]\n>>> a + c\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: can only concatenate tuple (not \"list\") to tuple\n```\n\n### Slicing\n\nSlicing means to take a subsequence from a sequence.\nThe syntax is `s[start:end]`. Where `start` and `end` are the indexes of the subsequence you want.\n\n```python\na = [0,1,2,3,4,5,6,7,8]\n\na[2:5]    # [2,3,4]\na[-5:]    # [4,5,6,7,8]\na[:3]     # [0,1,2]\n```\n\n* Indices `start` and `end` must be integers.\n* Slices do *not* include the end value. It is like a half-open interval from math.\n* If indices are omitted, they default to the beginning or end of the list.\n\n### Slice re-assignment\n\nOn lists, slices can be reassigned and deleted.\n\n```python\n# Reassignment\na = [0,1,2,3,4,5,6,7,8]\na[2:4] = [10,11,12]       # [0,1,10,11,12,4,5,6,7,8]\n```\n\n*Note: The reassigned slice doesn't need to have the same length.*\n\n```python\n# Deletion\na = [0,1,2,3,4,5,6,7,8]\ndel a[2:4]                # [0,1,4,5,6,7,8]\n```\n\n### Sequence Reductions\n\nThere are some common functions to reduce a sequence to a single value.\n\n```python\n>>> s = [1, 2, 3, 4]\n>>> sum(s)\n10\n>>> min(s)\n1\n>>> max(s)\n4\n>>> t = ['Hello', 'World']\n>>> max(t)\n'World'\n>>>\n```\n\n### Iteration over a sequence\n\nThe for-loop iterates over the elements in a sequence.\n\n```python\n>>> s = [1, 4, 9, 16]\n>>> for i in s:\n...     print(i)\n...\n1\n4\n9\n16\n>>>\n```\n\nOn each iteration of the loop, you get a new item to work with.\nThis new value is placed into the iteration variable. In this example, the\niteration variable is `x`:\n\n```python\nfor x in s:         # `x` is an iteration variable\n    ...statements\n```\n\nOn each iteration, the previous value of the iteration variable is overwritten (if any).\nAfter the loop finishes, the variable retains the last value.\n\n### break statement\n\nYou can use the `break` statement to break out of a loop early.\n\n```python\nfor name in namelist:\n    if name == 'Jake':\n        break\n    ...\n    ...\nstatements\n```\n\nWhen the `break` statement executes, it exits the loop and moves\non the next `statements`.  The `break` statement only applies to the\ninner-most loop. If this loop is within another loop, it will not\nbreak the outer loop.\n\n### continue statement\n\nTo skip one element and move to the next one, use the `continue` statement.\n\n```python\nfor line in lines:\n    if line == '\\n':    # Skip blank lines\n        continue\n    # More statements\n    ...\n```\n\nThis is useful when the current item is not of interest or needs to be ignored in the processing.\n\n### Looping over integers\n\nIf you need to count, use `range()`.\n\n```python\nfor i in range(100):\n    # i = 0,1,...,99\n```\n\nThe syntax is `range([start,] end [,step])`\n\n```python\nfor i in range(100):\n    # i = 0,1,...,99\nfor j in range(10,20):\n    # j = 10,11,..., 19\nfor k in range(10,50,2):\n    # k = 10,12,...,48\n    # Notice how it counts in steps of 2, not 1.\n```\n\n* The ending value is never included. It mirrors the behavior of slices.\n* `start` is optional. Default `0`.\n* `step` is optional. Default `1`.\n* `range()` computes values as needed. It does not actually store a large range of numbers.\n\n### enumerate() function\n\nThe `enumerate` function adds an extra counter value to iteration.\n\n```python\nnames = ['Elwood', 'Jake', 'Curtis']\nfor i, name in enumerate(names):\n    # Loops with i = 0, name = 'Elwood'\n    # i = 1, name = 'Jake'\n    # i = 2, name = 'Curtis'\n```\n\nThe general form is `enumerate(sequence [, start = 0])`. `start` is optional.\nA good example of using `enumerate()` is tracking line numbers while reading a file:\n\n```python\nwith open(filename) as f:\n    for lineno, line in enumerate(f, start=1):\n        ...\n```\n\nIn the end, `enumerate` is just a nice shortcut for:\n\n```python\ni = 0\nfor x in s:\n    statements\n    i += 1\n```\n\nUsing `enumerate` is less typing and runs slightly faster.\n\n### For and tuples\n\nYou can iterate with multiple iteration variables.\n\n```python\npoints = [\n  (1, 4),(10, 40),(23, 14),(5, 6),(7, 8)\n]\nfor x, y in points:\n    # Loops with x = 1, y = 4\n    #            x = 10, y = 40\n    #            x = 23, y = 14\n    #            ...\n```\n\nWhen using multiple variables, each tuple is *unpacked* into a set of iteration variables.\nThe number of variables must match the number of items in each tuple.\n\n### zip() function\n\nThe `zip` function takes multiple sequences and makes an iterator that combines them.\n\n```python\ncolumns = ['name', 'shares', 'price']\nvalues = ['GOOG', 100, 490.1 ]\npairs = zip(columns, values)\n# ('name','GOOG'), ('shares',100), ('price',490.1)\n```\n\nTo get the result you must iterate. You can use multiple variables to unpack the tuples as shown earlier.\n\n```python\nfor column, value in pairs:\n    ...\n```\n\nA common use of `zip` is to create key/value pairs for constructing dictionaries.\n\n```python\nd = dict(zip(columns, values))\n```\n\n## Exercises\n\n### Exercise 2.13: Counting\n\nTry some basic counting examples:\n\n```python\n>>> for n in range(10):            # Count 0 ... 9\n        print(n, end=' ')\n\n0 1 2 3 4 5 6 7 8 9\n>>> for n in range(10,0,-1):       # Count 10 ... 1\n        print(n, end=' ')\n\n10 9 8 7 6 5 4 3 2 1\n>>> for n in range(0,10,2):        # Count 0, 2, ... 8\n        print(n, end=' ')\n\n0 2 4 6 8\n>>>\n```\n\n### Exercise 2.14: More sequence operations\n\nInteractively experiment with some of the sequence reduction operations.\n\n```python\n>>> data = [4, 9, 1, 25, 16, 100, 49]\n>>> min(data)\n1\n>>> max(data)\n100\n>>> sum(data)\n204\n>>>\n```\n\nTry looping over the data.\n\n```python\n>>> for x in data:\n        print(x)\n\n4\n9\n...\n>>> for n, x in enumerate(data):\n        print(n, x)\n\n0 4\n1 9\n2 1\n...\n>>>\n```\n\nSometimes the `for` statement, `len()`, and `range()` get used by\nnovices in some kind of horrible code fragment that looks like it\nemerged from the depths of a rusty C program.\n\n```python\n>>> for n in range(len(data)):\n        print(data[n])\n\n4\n9\n1\n...\n>>>\n```\n\nDon’t do that! Not only does reading it make everyone’s eyes bleed,\nit’s inefficient with memory and it runs a lot slower.  Just use a\nnormal `for` loop if you want to iterate over data.  Use `enumerate()`\nif you happen to need the index for some reason.\n\n### Exercise 2.15: A practical enumerate() example\n\nRecall that the file `Data/missing.csv` contains data for a stock\nportfolio, but has some rows with missing data.  Using `enumerate()`,\nmodify your `pcost.py` program so that it prints a line number with\nthe warning message when it encounters bad input.\n\n```python\n>>> cost = portfolio_cost('Data/missing.csv')\nRow 4: Couldn't convert: ['MSFT', '', '51.23']\nRow 7: Couldn't convert: ['IBM', '', '70.44']\n>>>\n```\n\nTo do this, you’ll need to change a few parts of your code.\n\n```python\n...\nfor rowno, row in enumerate(rows, start=1):\n    try:\n        ...\n    except ValueError:\n        print(f'Row {rowno}: Bad row: {row}')\n```\n\n### Exercise 2.16: Using the zip() function\n\nIn the file `Data/portfolio.csv`, the first line contains column\nheaders. In all previous code, we’ve been discarding them.\n\n```python\n>>> f = open('Data/portfolio.csv')\n>>> rows = csv.reader(f)\n>>> headers = next(rows)\n>>> headers\n['name', 'shares', 'price']\n>>>\n```\n\nHowever, what if you could use the headers for something useful? This\nis where the `zip()` function enters the picture.  First try this to\npair the file headers with a row of data:\n\n```python\n>>> row = next(rows)\n>>> row\n['AA', '100', '32.20']\n>>> list(zip(headers, row))\n[ ('name', 'AA'), ('shares', '100'), ('price', '32.20') ]\n>>>\n```\n\nNotice how `zip()` paired the column headers with the column values.\nWe’ve used `list()` here to turn the result into a list so that you\ncan see it. Normally, `zip()` creates an iterator that must be\nconsumed by a for-loop.\n\nThis pairing is an intermediate step to building a\ndictionary. Now try this:\n\n```python\n>>> record = dict(zip(headers, row))\n>>> record\n{'price': '32.20', 'name': 'AA', 'shares': '100'}\n>>>\n```\n\nThis transformation is one of the most useful tricks to know about\nwhen processing a lot of data files.  For example, suppose you wanted\nto make the `pcost.py` program work with various input files, but\nwithout regard for the actual column number where the name, shares,\nand price appear.\n\nModify the `portfolio_cost()` function in `pcost.py` so that it looks like this:\n\n```python\n# pcost.py\n\ndef portfolio_cost(filename):\n    ...\n        for rowno, row in enumerate(rows, start=1):\n            record = dict(zip(headers, row))\n            try:\n                nshares = int(record['shares'])\n                price = float(record['price'])\n                total_cost += nshares * price\n            # This catches errors in int() and float() conversions above\n            except ValueError:\n                print(f'Row {rowno}: Bad row: {row}')\n        ...\n```\n\nNow, try your function on a completely different data file\n`Data/portfoliodate.csv` which looks like this:\n\n```csv\nname,date,time,shares,price\n\"AA\",\"6/11/2007\",\"9:50am\",100,32.20\n\"IBM\",\"5/13/2007\",\"4:20pm\",50,91.10\n\"CAT\",\"9/23/2006\",\"1:30pm\",150,83.44\n\"MSFT\",\"5/17/2007\",\"10:30am\",200,51.23\n\"GE\",\"2/1/2006\",\"10:45am\",95,40.37\n\"MSFT\",\"10/31/2006\",\"12:05pm\",50,65.10\n\"IBM\",\"7/9/2006\",\"3:15pm\",100,70.44\n```\n\n```python\n>>> portfolio_cost('Data/portfoliodate.csv')\n44671.15\n>>>\n```\n\nIf you did it right, you’ll find that your program still works even\nthough the data file has a completely different column format than\nbefore. That’s cool!\n\nThe change made here is subtle, but significant.  Instead of\n`portfolio_cost()` being hardcoded to read a single fixed file format,\nthe new version reads any CSV file and picks the values of interest\nout of it.  As long as the file has the required columns, the code will work.\n\nModify the `report.py` program you wrote in Section 2.3 so that it uses\nthe same technique to pick out column headers.\n\nTry running the `report.py` program on the `Data/portfoliodate.csv`\nfile and see that it produces the same answer as before.\n\n### Exercise 2.17: Inverting a dictionary\n\nA dictionary maps keys to values. For example, a dictionary of stock prices.\n\n```python\n>>> prices = {\n        'GOOG' : 490.1,\n        'AA' : 23.45,\n        'IBM' : 91.1,\n        'MSFT' : 34.23\n    }\n>>>\n```\n\nIf you use the `items()` method, you can get `(key,value)` pairs:\n\n```python\n>>> prices.items()\ndict_items([('GOOG', 490.1), ('AA', 23.45), ('IBM', 91.1), ('MSFT', 34.23)])\n>>>\n```\n\nHowever, what if you wanted to get a list of `(value, key)` pairs instead?\n*Hint: use `zip()`.*\n\n```python\n>>> pricelist = list(zip(prices.values(),prices.keys()))\n>>> pricelist\n[(490.1, 'GOOG'), (23.45, 'AA'), (91.1, 'IBM'), (34.23, 'MSFT')]\n>>>\n```\n\nWhy would you do this? For one, it allows you to perform certain kinds\nof data processing on the dictionary data.\n\n```python\n>>> min(pricelist)\n(23.45, 'AA')\n>>> max(pricelist)\n(490.1, 'GOOG')\n>>> sorted(pricelist)\n[(23.45, 'AA'), (34.23, 'MSFT'), (91.1, 'IBM'), (490.1, 'GOOG')]\n>>>\n```\n\nThis also illustrates an important feature of tuples. When used in\ncomparisons, tuples are compared element-by-element starting with the\nfirst item. Similar to how strings are compared\ncharacter-by-character.\n\n`zip()` is often used in situations like this where you need to pair\nup data from different places.  For example, pairing up the column\nnames with column values in order to make a dictionary of named\nvalues.\n\nNote that `zip()` is not limited to pairs. For example, you can use it\nwith any number of input lists:\n\n```python\n>>> a = [1, 2, 3, 4]\n>>> b = ['w', 'x', 'y', 'z']\n>>> c = [0.2, 0.4, 0.6, 0.8]\n>>> list(zip(a, b, c))\n[(1, 'w', 0.2), (2, 'x', 0.4), (3, 'y', 0.6), (4, 'z', 0.8))]\n>>>\n```\n\nAlso, be aware that `zip()` stops once the shortest input sequence is exhausted.\n\n```python\n>>> a = [1, 2, 3, 4, 5, 6]\n>>> b = ['x', 'y', 'z']\n>>> list(zip(a,b))\n[(1, 'x'), (2, 'y'), (3, 'z')]\n>>>\n```\n\n[Contents](../Contents.md) \\| [Previous (2.3 Formatting)](03_Formatting.md) \\| [Next (2.5 Collections)](05_Collections.md)\n"
  },
  {
    "path": "Notes/02_Working_with_data/05_Collections.md",
    "content": "[Contents](../Contents.md) \\| [Previous (2.4 Sequences)](04_Sequences.md) \\| [Next (2.6 List Comprehensions)](06_List_comprehension.md)\n\n# 2.5 collections module\n\nThe `collections` module provides a number of useful objects for data handling.\nThis part briefly introduces some of these features.\n\n### Example: Counting Things\n\nLet's say you want to tabulate the total shares of each stock.\n\n```python\nportfolio = [\n    ('GOOG', 100, 490.1),\n    ('IBM', 50, 91.1),\n    ('CAT', 150, 83.44),\n    ('IBM', 100, 45.23),\n    ('GOOG', 75, 572.45),\n    ('AA', 50, 23.15)\n]\n```\n\nThere are two `IBM` entries and two `GOOG` entries in this list. The shares need to be combined together somehow.\n\n### Counters\n\nSolution: Use a `Counter`.\n\n```python\nfrom collections import Counter\ntotal_shares = Counter()\nfor name, shares, price in portfolio:\n    total_shares[name] += shares\n\ntotal_shares['IBM']     # 150\n```\n\n### Example: One-Many Mappings\n\nProblem: You want to map a key to multiple values.\n\n```python\nportfolio = [\n    ('GOOG', 100, 490.1),\n    ('IBM', 50, 91.1),\n    ('CAT', 150, 83.44),\n    ('IBM', 100, 45.23),\n    ('GOOG', 75, 572.45),\n    ('AA', 50, 23.15)\n]\n```\n\nLike in the previous example, the key `IBM` should have two different tuples instead.\n\nSolution: Use a `defaultdict`.\n\n```python\nfrom collections import defaultdict\nholdings = defaultdict(list)\nfor name, shares, price in portfolio:\n    holdings[name].append((shares, price))\nholdings['IBM'] # [ (50, 91.1), (100, 45.23) ]\n```\n\nThe `defaultdict` ensures that every time you access a key you get a default value.\n\n### Example: Keeping a History\n\nProblem: We want a history of the last N things.\nSolution: Use a `deque`.\n\n```python\nfrom collections import deque\n\nhistory = deque(maxlen=N)\nwith open(filename) as f:\n    for line in f:\n        history.append(line)\n        ...\n```\n\n## Exercises\n\nThe `collections` module might be one of the most useful library\nmodules for dealing with special purpose kinds of data handling\nproblems such as tabulating and indexing.\n\nIn this exercise, we’ll look at a few simple examples.  Start by\nrunning your `report.py` program so that you have the portfolio of\nstocks loaded in the interactive mode.\n\n```bash\nbash % python3 -i report.py\n```\n\n### Exercise 2.18: Tabulating with Counters\n\nSuppose you wanted to tabulate the total number of shares of each stock.\nThis is easy using `Counter` objects. Try it:\n\n```python\n>>> portfolio = read_portfolio('Data/portfolio.csv')\n>>> from collections import Counter\n>>> holdings = Counter()\n>>> for s in portfolio:\n        holdings[s['name']] += s['shares']\n\n>>> holdings\nCounter({'MSFT': 250, 'IBM': 150, 'CAT': 150, 'AA': 100, 'GE': 95})\n>>>\n```\n\nCarefully observe how the multiple entries for `MSFT` and `IBM` in `portfolio` get combined into a single entry here.\n\nYou can use a Counter just like a dictionary to retrieve individual values:\n\n```python\n>>> holdings['IBM']\n150\n>>> holdings['MSFT']\n250\n>>>\n```\n\nIf you want to rank the values, do this:\n\n```python\n>>> # Get three most held stocks\n>>> holdings.most_common(3)\n[('MSFT', 250), ('IBM', 150), ('CAT', 150)]\n>>>\n```\n\nLet’s grab another portfolio of stocks and make a new Counter:\n\n```python\n>>> portfolio2 = read_portfolio('Data/portfolio2.csv')\n>>> holdings2 = Counter()\n>>> for s in portfolio2:\n          holdings2[s['name']] += s['shares']\n\n>>> holdings2\nCounter({'HPQ': 250, 'GE': 125, 'AA': 50, 'MSFT': 25})\n>>>\n```\n\nFinally, let’s combine all of the holdings doing one simple operation:\n\n```python\n>>> holdings\nCounter({'MSFT': 250, 'IBM': 150, 'CAT': 150, 'AA': 100, 'GE': 95})\n>>> holdings2\nCounter({'HPQ': 250, 'GE': 125, 'AA': 50, 'MSFT': 25})\n>>> combined = holdings + holdings2\n>>> combined\nCounter({'MSFT': 275, 'HPQ': 250, 'GE': 220, 'AA': 150, 'IBM': 150, 'CAT': 150})\n>>>\n```\n\nThis is only a small taste of what counters provide. However, if you\never find yourself needing to tabulate values, you should consider\nusing one.\n\n### Commentary: collections module\n\nThe `collections` module is one of the most useful library modules\nin all of Python.  In fact, we could do an extended tutorial on just\nthat.  However, doing so now would also be a distraction.  For now,\nput `collections` on your list of bedtime reading for later.\n\n[Contents](../Contents.md) \\| [Previous (2.4 Sequences)](04_Sequences.md) \\| [Next (2.6 List Comprehensions)](06_List_comprehension.md)"
  },
  {
    "path": "Notes/02_Working_with_data/06_List_comprehension.md",
    "content": "[Contents](../Contents.md) \\| [Previous (2.5 Collections)](05_Collections.md) \\| [Next (2.7 Object Model)](07_Objects.md)\n\n# 2.6 List Comprehensions\n\nA common task is processing items in a list.  This section introduces list comprehensions,\na powerful tool for doing just that.\n\n### Creating new lists\n\nA list comprehension creates a new list by applying an operation to\neach element of a sequence.\n\n```python\n>>> a = [1, 2, 3, 4, 5]\n>>> b = [2*x for x in a ]\n>>> b\n[2, 4, 6, 8, 10]\n>>>\n```\n\nAnother example:\n\n```python\n>>> names = ['Elwood', 'Jake']\n>>> a = [name.lower() for name in names]\n>>> a\n['elwood', 'jake']\n>>>\n```\n\nThe general syntax is: `[ <expression> for <variable_name> in <sequence> ]`.\n\n### Filtering\n\nYou can also filter during the list comprehension.\n\n```python\n>>> a = [1, -5, 4, 2, -2, 10]\n>>> b = [2*x for x in a if x > 0 ]\n>>> b\n[2, 8, 4, 20]\n>>>\n```\n\n### Use cases\n\nList comprehensions are hugely useful.  For example, you can collect values of a specific\ndictionary fields:\n\n```python\nstocknames = [s['name'] for s in stocks]\n```\n\nYou can perform database-like queries on sequences.\n\n```python\na = [s for s in stocks if s['price'] > 100 and s['shares'] > 50 ]\n```\n\nYou can also combine a list comprehension with a sequence reduction:\n\n```python\ncost = sum([s['shares']*s['price'] for s in stocks])\n```\n\n### General Syntax\n\n```code\n[ <expression> for <variable_name> in <sequence> if <condition>]\n```\n\nWhat it means:\n\n```python\nresult = []\nfor variable_name in sequence:\n    if condition:\n        result.append(expression)\n```\n\n### Historical Digression\n\nList comprehensions come from math (set-builder notation).\n\n```code\na = [ x * x for x in s if x > 0 ] # Python\n\na = { x^2 | x ∈ s, x > 0 }         # Math\n```\n\nIt is also implemented in several other languages. Most\ncoders probably aren't thinking about their math class though. So,\nit's fine to view it as a cool list shortcut.\n\n## Exercises\n\nStart by running your `report.py` program so that you have the\nportfolio of stocks loaded in the interactive mode.\n\n```bash\nbash % python3 -i report.py\n```\n\nNow, at the Python interactive prompt, type statements to perform the\noperations described below.  These operations perform various kinds of\ndata reductions, transforms, and queries on the portfolio data.\n\n### Exercise 2.19: List comprehensions\n\nTry a few simple list comprehensions just to become familiar with the syntax.\n\n```python\n>>> nums = [1,2,3,4]\n>>> squares = [ x * x for x in nums ]\n>>> squares\n[1, 4, 9, 16]\n>>> twice = [ 2 * x for x in nums if x > 2 ]\n>>> twice\n[6, 8]\n>>>\n```\n\nNotice how the list comprehensions are creating a new list with the\ndata suitably transformed or filtered.\n\n### Exercise 2.20: Sequence Reductions\n\nCompute the total cost of the portfolio using a single Python statement.\n\n```python\n>>> portfolio = read_portfolio('Data/portfolio.csv')\n>>> cost = sum([ s['shares'] * s['price'] for s in portfolio ])\n>>> cost\n44671.15\n>>>\n```\n\nAfter you have done that, show how you can compute the current value\nof the portfolio using a single statement.\n\n```python\n>>> value = sum([ s['shares'] * prices[s['name']] for s in portfolio ])\n>>> value\n28686.1\n>>>\n```\n\nBoth of the above operations are an example of a map-reduction. The\nlist comprehension is mapping an operation across the list.\n\n```python\n>>> [ s['shares'] * s['price'] for s in portfolio ]\n[3220.0000000000005, 4555.0, 12516.0, 10246.0, 3835.1499999999996, 3254.9999999999995, 7044.0]\n>>>\n```\n\nThe `sum()` function is then performing a reduction across the result:\n\n```python\n>>> sum(_)\n44671.15\n>>>\n```\n\nWith this knowledge, you are now ready to go launch a big-data startup company.\n\n### Exercise 2.21: Data Queries\n\nTry the following examples of various data queries.\n\nFirst, a list of all portfolio holdings with more than 100 shares.\n\n```python\n>>> more100 = [ s for s in portfolio if s['shares'] > 100 ]\n>>> more100\n[{'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}]\n>>>\n```\n\nAll portfolio holdings for MSFT and IBM stocks.\n\n```python\n>>> msftibm = [ s for s in portfolio if s['name'] in {'MSFT','IBM'} ]\n>>> msftibm\n[{'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 51.23, 'name': 'MSFT', 'shares': 200},\n  {'price': 65.1, 'name': 'MSFT', 'shares': 50}, {'price': 70.44, 'name': 'IBM', 'shares': 100}]\n>>>\n```\n\nA list of all portfolio holdings that cost more than $10000.\n\n```python\n>>> cost10k = [ s for s in portfolio if s['shares'] * s['price'] > 10000 ]\n>>> cost10k\n[{'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}]\n>>>\n```\n\n### Exercise 2.22: Data Extraction\n\nShow how you could build a list of tuples `(name, shares)` where `name` and `shares` are taken from `portfolio`.\n\n```python\n>>> name_shares =[ (s['name'], s['shares']) for s in portfolio ]\n>>> name_shares\n[('AA', 100), ('IBM', 50), ('CAT', 150), ('MSFT', 200), ('GE', 95), ('MSFT', 50), ('IBM', 100)]\n>>>\n```\n\nIf you change the square brackets (`[`,`]`) to curly braces (`{`, `}`), you get something known as a set comprehension.\nThis gives you unique or distinct values.\n\nFor example, this determines the set of unique stock names that appear in `portfolio`:\n\n```python\n>>> names = { s['name'] for s in portfolio }\n>>> names\n{ 'AA', 'GE', 'IBM', 'MSFT', 'CAT' }\n>>>\n```\n\nIf you specify `key:value` pairs, you can build a dictionary.\nFor example, make a dictionary that maps the name of a stock to the total number of shares held.\n\n```python\n>>> holdings = { name: 0 for name in names }\n>>> holdings\n{'AA': 0, 'GE': 0, 'IBM': 0, 'MSFT': 0, 'CAT': 0}\n>>>\n```\n\nThis latter feature is known as a **dictionary comprehension**. Let’s tabulate:\n\n```python\n>>> for s in portfolio:\n        holdings[s['name']] += s['shares']\n\n>>> holdings\n{ 'AA': 100, 'GE': 95, 'IBM': 150, 'MSFT':250, 'CAT': 150 }\n>>>\n```\n\nTry this example that filters the `prices` dictionary down to only\nthose names that appear in the portfolio:\n\n```python\n>>> portfolio_prices = { name: prices[name] for name in names }\n>>> portfolio_prices\n{'AA': 9.22, 'GE': 13.48, 'IBM': 106.28, 'MSFT': 20.89, 'CAT': 35.46}\n>>>\n```\n\n### Exercise 2.23: Extracting Data From CSV Files\n\nKnowing how to use various combinations of list, set, and dictionary\ncomprehensions can be useful in various forms of data processing.\nHere’s an example that shows how to extract selected columns from a\nCSV file.\n\nFirst, read a row of header information from a CSV file:\n\n```python\n>>> import csv\n>>> f = open('Data/portfoliodate.csv')\n>>> rows = csv.reader(f)\n>>> headers = next(rows)\n>>> headers\n['name', 'date', 'time', 'shares', 'price']\n>>>\n```\n\nNext, define a variable that lists the columns that you actually care about:\n\n```python\n>>> select = ['name', 'shares', 'price']\n>>>\n```\n\nNow, locate the indices of the above columns in the source CSV file:\n\n```python\n>>> indices = [ headers.index(colname) for colname in select ]\n>>> indices\n[0, 3, 4]\n>>>\n```\n\nFinally, read a row of data and turn it into a dictionary using a\ndictionary comprehension:\n\n```python\n>>> row = next(rows)\n>>> record = { colname: row[index] for colname, index in zip(select, indices) }   # dict-comprehension\n>>> record\n{'price': '32.20', 'name': 'AA', 'shares': '100'}\n>>>\n```\n\nIf you’re feeling comfortable with what just happened, read the rest\nof the file:\n\n```python\n>>> portfolio = [ { colname: row[index] for colname, index in zip(select, indices) } for row in rows ]\n>>> portfolio\n[{'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'},\n  {'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'},\n  {'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}]\n>>>\n```\n\nOh my, you just reduced much of the `read_portfolio()` function to a single statement.\n\n### Commentary\n\nList comprehensions are commonly used in Python as an efficient means\nfor transforming, filtering, or collecting data.  Due to the syntax,\nyou don’t want to go overboard—try to keep each list comprehension as\nsimple as possible.  It’s okay to break things into multiple\nsteps. For example, it’s not clear that you would want to spring that\nlast example on your unsuspecting co-workers.\n\nThat said, knowing how to quickly manipulate data is a skill that’s\nincredibly useful.  There are numerous situations where you might have\nto solve some kind of one-off problem involving data imports, exports,\nextraction, and so forth.  Becoming a guru master of list\ncomprehensions can substantially reduce the time spent devising a\nsolution.  Also, don't forget about the `collections` module.\n\n[Contents](../Contents.md) \\| [Previous (2.5 Collections)](05_Collections.md) \\| [Next (2.7 Object Model)](07_Objects.md)\n"
  },
  {
    "path": "Notes/02_Working_with_data/07_Objects.md",
    "content": "[Contents](../Contents.md) \\| [Previous (2.6 List Comprehensions)](06_List_comprehension.md) \\| [Next (3 Program Organization)](../03_Program_organization/00_Overview.md)\n\n# 2.7 Objects\n\nThis section introduces more details about Python's internal object model and\ndiscusses some matters related to memory management, copying, and type checking.\n\n### Assignment\n\nMany operations in Python are related to *assigning* or *storing* values.\n\n```python\na = value         # Assignment to a variable\ns[n] = value      # Assignment to a list\ns.append(value)   # Appending to a list\nd['key'] = value  # Adding to a dictionary\n```\n\n*A caution: assignment operations **never make a copy** of the value being assigned.*\nAll assignments are merely reference copies (or pointer copies if you prefer).\n\n### Assignment example\n\nConsider this code fragment.\n\n```python\na = [1,2,3]\nb = a\nc = [a,b]\n```\n\nA picture of the underlying memory operations. In this example, there\nis only one list object `[1,2,3]`, but there are four different\nreferences to it.\n\n![References](references.png)\n\nThis means that modifying a value affects *all* references.\n\n```python\n>>> a.append(999)\n>>> a\n[1,2,3,999]\n>>> b\n[1,2,3,999]\n>>> c\n[[1,2,3,999], [1,2,3,999]]\n>>>\n```\n\nNotice how a change in the original list shows up everywhere else\n(yikes!).  This is because no copies were ever made. Everything is\npointing to the same thing.\n\n### Reassigning values\n\nReassigning a value *never* overwrites the memory used by the previous value.\n\n```python\na = [1,2,3]\nb = a\na = [4,5,6]\n\nprint(a)      # [4, 5, 6]\nprint(b)      # [1, 2, 3]    Holds the original value\n```\n\nRemember: **Variables are names, not memory locations.**\n\n### Some Dangers\n\nIf you don't know about this sharing, you will shoot yourself in the\nfoot at some point.  Typical scenario. You modify some data thinking\nthat it's your own private copy and it accidentally corrupts some data\nin some other part of the program.\n\n*Comment: This is one of the reasons why the primitive datatypes (int,\n float, string) are immutable (read-only).*\n\n### Identity and References\n\nUse the `is` operator to check if two values are exactly the same object.\n\n```python\n>>> a = [1,2,3]\n>>> b = a\n>>> a is b\nTrue\n>>>\n```\n\n`is` compares the object identity (an integer).  The identity can be\nobtained using `id()`.\n\n```python\n>>> id(a)\n3588944\n>>> id(b)\n3588944\n>>>\n```\n\nNote: It is almost always better to use `==` for checking objects.  The behavior\nof `is` is often unexpected:\n\n```python\n>>> a = [1,2,3]\n>>> b = a\n>>> c = [1,2,3]\n>>> a is b\nTrue\n>>> a is c\nFalse\n>>> a == c\nTrue\n>>>\n```\n\n### Shallow copies\n\nLists and dicts have methods for copying.\n\n```python\n>>> a = [2,3,[100,101],4]\n>>> b = list(a) # Make a copy\n>>> a is b\nFalse\n```\n\nIt's a new list, but the list items are shared.\n\n```python\n>>> a[2].append(102)\n>>> b[2]\n[100,101,102]\n>>>\n>>> a[2] is b[2]\nTrue\n>>>\n```\n\nFor example, the inner list `[100, 101, 102]` is being shared.\nThis is known as a shallow copy.  Here is a picture.\n\n![Shallow copy](shallow.png)\n\n### Deep copies\n\nSometimes you need to make a copy of an object and all the objects contained within it.\nYou can use the `copy` module for this:\n\n```python\n>>> a = [2,3,[100,101],4]\n>>> import copy\n>>> b = copy.deepcopy(a)\n>>> a[2].append(102)\n>>> b[2]\n[100,101]\n>>> a[2] is b[2]\nFalse\n>>>\n```\n\n### Names, Values, Types\n\nVariable names do not have a *type*. It's only a name.\nHowever, values *do* have an underlying type.\n\n```python\n>>> a = 42\n>>> b = 'Hello World'\n>>> type(a)\n<type 'int'>\n>>> type(b)\n<type 'str'>\n```\n\n`type()` will tell you what it is. The type name is usually used as a function\nthat creates or converts a value to that type.\n\n### Type Checking\n\nHow to tell if an object is a specific type.\n\n```python\nif isinstance(a, list):\n    print('a is a list')\n```\n\nChecking for one of many possible types.\n\n```python\nif isinstance(a, (list,tuple)):\n    print('a is a list or tuple')\n```\n\n*Caution: Don't go overboard with type checking. It can lead to\nexcessive code complexity.  Usually you'd only do it if doing\nso would prevent common mistakes made by others using your code.\n*\n\n### Everything is an object\n\nNumbers, strings, lists, functions, exceptions, classes, instances,\netc. are all objects.  It means that all objects that can be named can\nbe passed around as data, placed in containers, etc., without any\nrestrictions.  There are no *special* kinds of objects.  Sometimes it\nis said that all objects are \"first-class\".\n\nA simple example:\n\n```python\n>>> import math\n>>> items = [abs, math, ValueError ]\n>>> items\n[<built-in function abs>,\n  <module 'math' (builtin)>,\n  <type 'exceptions.ValueError'>]\n>>> items[0](-45)\n45\n>>> items[1].sqrt(2)\n1.4142135623730951\n>>> try:\n        x = int('not a number')\n    except items[2]:\n        print('Failed!')\nFailed!\n>>>\n```\n\nHere, `items` is a list containing a function, a module and an\nexception.  You can directly use the items in the list in place of the\noriginal names:\n\n```python\nitems[0](-45)       # abs\nitems[1].sqrt(2)    # math\nexcept items[2]:    # ValueError\n```\n\nWith great power comes responsibility.  Just because you can do that doesn't mean you should.\n\n## Exercises\n\nIn this set of exercises, we look at some of the power that comes from first-class\nobjects.\n\n### Exercise 2.24: First-class Data\n\nIn the file `Data/portfolio.csv`, we read data organized as columns that look like this:\n\n```csv\nname,shares,price\n\"AA\",100,32.20\n\"IBM\",50,91.10\n...\n```\n\nIn previous code, we used the `csv` module to read the file, but still\nhad to perform manual type conversions. For example:\n\n```python\nfor row in rows:\n    name   = row[0]\n    shares = int(row[1])\n    price  = float(row[2])\n```\n\nThis kind of conversion can also be performed in a more clever manner\nusing some list basic operations.\n\nMake a Python list that contains the names of the conversion functions\nyou would use to convert each column into the appropriate type:\n\n```python\n>>> types = [str, int, float]\n>>>\n```\n\nThe reason you can even create this list is that everything in Python\nis *first-class*.  So, if you want to have a list of functions, that’s\nfine.  The items in the list you created are functions for converting\na value `x` into a given type (e.g., `str(x)`, `int(x)`, `float(x)`).\n\nNow, read a row of data from the above file:\n\n```python\n>>> import csv\n>>> f = open('Data/portfolio.csv')\n>>> rows = csv.reader(f)\n>>> headers = next(rows)\n>>> row = next(rows)\n>>> row\n['AA', '100', '32.20']\n>>>\n```\n\nAs noted, this row isn’t enough to do calculations because the types\nare wrong. For example:\n\n```python\n>>> row[1] * row[2]\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: can't multiply sequence by non-int of type 'str'\n>>>\n```\n\nHowever, maybe the data can be paired up with the types you specified\nin `types`. For example:\n\n```python\n>>> types[1]\n<type 'int'>\n>>> row[1]\n'100'\n>>>\n```\n\nTry converting one of the values:\n\n```python\n>>> types[1](row[1])     # Same as int(row[1])\n100\n>>>\n```\n\nTry converting a different value:\n\n```python\n>>> types[2](row[2])     # Same as float(row[2])\n32.2\n>>>\n```\n\nTry the calculation with converted values:\n\n```python\n>>> types[1](row[1])*types[2](row[2])\n3220.0000000000005\n>>>\n```\n\nZip the column types with the fields and look at the result:\n\n```python\n>>> r = list(zip(types, row))\n>>> r\n[(<type 'str'>, 'AA'), (<type 'int'>, '100'), (<type 'float'>,'32.20')]\n>>>\n```\n\nYou will notice that this has paired a type conversion with a\nvalue. For example, `int` is paired with the value `'100'`.\n\nThe zipped list is useful if you want to perform conversions on all of\nthe values, one after the other. Try this:\n\n```python\n>>> converted = []\n>>> for func, val in zip(types, row):\n          converted.append(func(val))\n...\n>>> converted\n['AA', 100, 32.2]\n>>> converted[1] * converted[2]\n3220.0000000000005\n>>>\n```\n\nMake sure you understand what’s happening in the above code.  In the\nloop, the `func` variable is one of the type conversion functions\n(e.g., `str`, `int`, etc.) and the `val` variable is one of the values\nlike `'AA'`, `'100'`.  The expression `func(val)` is converting a\nvalue (kind of like a type cast).\n\nThe above code can be compressed into a single list comprehension.\n\n```python\n>>> converted = [func(val) for func, val in zip(types, row)]\n>>> converted\n['AA', 100, 32.2]\n>>>\n```\n\n### Exercise 2.25: Making dictionaries\n\nRemember how the `dict()` function can easily make a dictionary if you\nhave a sequence of key names and values?  Let’s make a dictionary from\nthe column headers:\n\n```python\n>>> headers\n['name', 'shares', 'price']\n>>> converted\n['AA', 100, 32.2]\n>>> dict(zip(headers, converted))\n{'price': 32.2, 'name': 'AA', 'shares': 100}\n>>>\n```\n\nOf course, if you’re up on your list-comprehension fu, you can do the\nwhole conversion in a single step using a dict-comprehension:\n\n```python\n>>> { name: func(val) for name, func, val in zip(headers, types, row) }\n{'price': 32.2, 'name': 'AA', 'shares': 100}\n>>>\n```\n\n### Exercise 2.26: The Big Picture\n\nUsing the techniques in this exercise, you could write statements that\neasily convert fields from just about any column-oriented datafile\ninto a Python dictionary.\n\nJust to illustrate, suppose you read data from a different datafile like this:\n\n```python\n>>> f = open('Data/dowstocks.csv')\n>>> rows = csv.reader(f)\n>>> headers = next(rows)\n>>> row = next(rows)\n>>> headers\n['name', 'price', 'date', 'time', 'change', 'open', 'high', 'low', 'volume']\n>>> row\n['AA', '39.48', '6/11/2007', '9:36am', '-0.18', '39.67', '39.69', '39.45', '181800']\n>>>\n```\n\nLet’s convert the fields using a similar trick:\n\n```python\n>>> types = [str, float, str, str, float, float, float, float, int]\n>>> converted = [func(val) for func, val in zip(types, row)]\n>>> record = dict(zip(headers, converted))\n>>> record\n{'volume': 181800, 'name': 'AA', 'price': 39.48, 'high': 39.69,\n'low': 39.45, 'time': '9:36am', 'date': '6/11/2007', 'open': 39.67,\n'change': -0.18}\n>>> record['name']\n'AA'\n>>> record['price']\n39.48\n>>>\n```\n\nBonus: How would you modify this example to additionally parse the\n`date` entry into a tuple such as `(6, 11, 2007)`?\n\nSpend some time to ponder what you’ve done in this exercise. We’ll\nrevisit these ideas a little later.\n\n[Contents](../Contents.md) \\| [Previous (2.6 List Comprehensions)](06_List_comprehension.md) \\| [Next (3 Program Organization)](../03_Program_organization/00_Overview.md)\n"
  },
  {
    "path": "Notes/03_Program_organization/00_Overview.md",
    "content": "[Contents](../Contents.md) \\| [Prev (2 Working With Data)](../02_Working_with_data/00_Overview.md) \\| [Next (4 Classes and Objects)](../04_Classes_objects/00_Overview.md)\n\n# 3. Program Organization\n\nSo far, we've learned some Python basics and have written some short scripts.\nHowever, as you start to write larger programs, you'll want to get organized.\nThis section dives into greater details on writing functions, handling errors,\nand introduces modules.  By the end you should be able to write programs\nthat are subdivided into functions across multiple files. We'll also give\nsome useful code templates for writing more useful scripts.\n\n* [3.1 Functions and Script Writing](01_Script.md)\n* [3.2 More Detail on Functions](02_More_functions.md)\n* [3.3 Exception Handling](03_Error_checking.md)\n* [3.4 Modules](04_Modules.md)\n* [3.5 Main module](05_Main_module.md)\n* [3.6 Design Discussion about Embracing Flexibility](06_Design_discussion.md)\n\n[Contents](../Contents.md) \\| [Prev (2 Working With Data)](../02_Working_with_data/00_Overview.md) \\| [Next (4 Classes and Objects)](../04_Classes_objects/00_Overview.md)\n\n"
  },
  {
    "path": "Notes/03_Program_organization/01_Script.md",
    "content": "[Contents](../Contents.md) \\| [Previous (2.7 Object Model)](../02_Working_with_data/07_Objects.md) \\| [Next (3.2 More on Functions)](02_More_functions.md)\n\n# 3.1 Scripting\n\nIn this part we look more closely at the practice of writing Python\nscripts.\n\n### What is a Script?\n\nA *script* is a program that runs a series of statements and stops.\n\n```python\n# program.py\n\nstatement1\nstatement2\nstatement3\n...\n```\n\nWe have mostly been writing scripts to this point.\n\n### A Problem\n\nIf you write a useful script, it will grow in features and\nfunctionality.  You may want to apply it to other related problems.\nOver time, it might become a critical application.  And if you don't\ntake care, it might turn into a huge tangled mess.  So, let's get\norganized.\n\n### Defining Things\n\nNames must always be defined before they get used later.\n\n```python\ndef square(x):\n    return x*x\n\na = 42\nb = a + 2     # Requires that `a` is defined\n\nz = square(b) # Requires `square` and `b` to be defined\n```\n\n**The order is important.**\nYou almost always put the definitions of variables and functions near the top.\n\n### Defining Functions\n\nIt is a good idea to put all of the code related to a single *task* all in one place.\nUse a function.\n\n```python\ndef read_prices(filename):\n    prices = {}\n    with open(filename) as f:\n        f_csv = csv.reader(f)\n        for row in f_csv:\n            prices[row[0]] = float(row[1])\n    return prices\n```\n\nA function also simplifies repeated operations.\n\n```python\noldprices = read_prices('oldprices.csv')\nnewprices = read_prices('newprices.csv')\n```\n\n### What is a Function?\n\nA function is a named sequence of statements.\n\n```python\ndef funcname(args):\n  statement\n  statement\n  ...\n  return result\n```\n\n*Any* Python statement can be used inside.\n\n```python\ndef foo():\n    import math\n    print(math.sqrt(2))\n    help(math)\n```\n\nThere are no *special* statements in Python (which makes it easy to remember).\n\n### Function Definition\n\nFunctions can be *defined* in any order.\n\n```python\ndef foo(x):\n    bar(x)\n\ndef bar(x):\n    statements\n\n# OR\ndef bar(x):\n    statements\n\ndef foo(x):\n    bar(x)\n```\n\nFunctions must only be defined prior to actually being *used* (or called) during program execution.\n\n```python\nfoo(3)        # foo must be defined already\n```\n\nStylistically, it is probably more common to see functions defined in\na *bottom-up* fashion.\n\n### Bottom-up Style\n\nFunctions are treated as building blocks.\nThe smaller/simpler blocks go first.\n\n```python\n# myprogram.py\ndef foo(x):\n    ...\n\ndef bar(x):\n    ...\n    foo(x)          # Defined above\n    ...\n\ndef spam(x):\n    ...\n    bar(x)          # Defined above\n    ...\n\nspam(42)            # Code that uses the functions appears at the end\n```\n\nLater functions build upon earlier functions.  Again, this is only\na point of style.  The only thing that matters in the above program\nis that the call to `spam(42)` go last.\n\n### Function Design\n\nIdeally, functions should be a *black box*.\nThey should only operate on passed inputs and avoid global variables\nand mysterious side-effects.  Your main goals: *Modularity* and *Predictability*.\n\n### Doc Strings\n\nIt's good practice to include documentation in the form of a\ndoc-string.  Doc-strings are strings written immediately after the\nname of the function. They feed `help()`, IDEs and other tools.\n\n```python\ndef read_prices(filename):\n    '''\n    Read prices from a CSV file of name,price data\n    '''\n    prices = {}\n    with open(filename) as f:\n        f_csv = csv.reader(f)\n        for row in f_csv:\n            prices[row[0]] = float(row[1])\n    return prices\n```\n\nA good practice for doc strings is to write a short one sentence\nsummary of what the function does.  If more information is needed,\ninclude a short example of usage along with a more detailed\ndescription of the arguments.\n\n### Type Annotations\n\nYou can also add optional type hints to function definitions.\n\n```python\ndef read_prices(filename: str) -> dict:\n    '''\n    Read prices from a CSV file of name,price data\n    '''\n    prices = {}\n    with open(filename) as f:\n        f_csv = csv.reader(f)\n        for row in f_csv:\n            prices[row[0]] = float(row[1])\n    return prices\n```\n\nThe hints do nothing operationally. They are purely informational.\nHowever, they may be used by IDEs, code checkers, and other tools\nto do more.\n\n## Exercises\n\nIn section 2, you wrote a program called `report.py` that printed out\na report showing the performance of a stock portfolio.  This program\nconsisted of some functions. For example:\n\n```python\n# report.py\nimport csv\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    portfolio = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n\n        for row in rows:\n            record = dict(zip(headers, row))\n            stock = {\n                'name' : record['name'],\n                'shares' : int(record['shares']),\n                'price' : float(record['price'])\n            }\n            portfolio.append(stock)\n    return portfolio\n...\n```\n\nHowever, there were also portions of the program that just performed a\nseries of scripted calculations.  This code appeared near the end of\nthe program. For example:\n\n```python\n...\n\n# Output the report\n\nheaders = ('Name', 'Shares', 'Price', 'Change')\nprint('%10s %10s %10s %10s'  % headers)\nprint(('-' * 10 + ' ') * len(headers))\nfor row in report:\n    print('%10s %10d %10.2f %10.2f' % row)\n...\n```\n\nIn this exercise, we’re going take this program and organize it a\nlittle more strongly around the use of functions.\n\n### Exercise 3.1: Structuring a program as a collection of functions\n\nModify your `report.py` program so that all major operations,\nincluding calculations and output, are carried out by a collection of\nfunctions. Specifically:\n\n* Create a function `print_report(report)` that prints out the report.\n* Change the last part of the program so that it is nothing more than a series of function calls and no other computation.\n\n### Exercise 3.2: Creating a top-level function for program execution\n\nTake the last part of your program and package it into a single\nfunction `portfolio_report(portfolio_filename, prices_filename)`.\nHave the function work so that the following function call creates the\nreport as before:\n\n```python\nportfolio_report('Data/portfolio.csv', 'Data/prices.csv')\n```\n\nIn this final version, your program will be nothing more than a series\nof function definitions followed by a single function call to\n`portfolio_report()` at the very end (which executes all of the steps\ninvolved in the program).\n\nBy turning your program into a single function, it becomes easy to run\nit on different inputs.  For example, try these statements\ninteractively after running your program:\n\n```python\n>>> portfolio_report('Data/portfolio2.csv', 'Data/prices.csv')\n... look at the output ...\n>>> files = ['Data/portfolio.csv', 'Data/portfolio2.csv']\n>>> for name in files:\n        print(f'{name:-^43s}')\n        portfolio_report(name, 'Data/prices.csv')\n        print()\n\n... look at the output ...\n>>>\n```\n\n### Commentary\n\nPython makes it very easy to write relatively unstructured scripting code\nwhere you just have a file with a sequence of statements in it. In the\nbig picture, it's almost always better to utilize functions whenever\nyou can.  At some point, that script is going to grow and you'll wish\nyou had a bit more organization.  Also, a little known fact is that Python\nruns a bit faster if you use functions.\n\n[Contents](../Contents.md) \\| [Previous (2.7 Object Model)](../02_Working_with_data/07_Objects.md) \\| [Next (3.2 More on Functions)](02_More_functions.md)"
  },
  {
    "path": "Notes/03_Program_organization/02_More_functions.md",
    "content": "[Contents](../Contents.md) \\| [Previous (3.1 Scripting)](01_Script.md) \\| [Next (3.3 Error Checking)](03_Error_checking.md)\n\n# 3.2 More on Functions\n\nAlthough functions were introduced earlier, very few details were provided on how\nthey actually work at a deeper level.  This section aims to fill in some gaps\nand discuss matters such as calling conventions, scoping rules, and more.\n\n### Calling a Function\n\nConsider this function:\n\n```python\ndef read_prices(filename, debug):\n    ...\n```\n\nYou can call the function with positional arguments:\n\n```\nprices = read_prices('prices.csv', True)\n```\n\nOr you can call the function with keyword arguments:\n\n```python\nprices = read_prices(filename='prices.csv', debug=True)\n```\n\n### Default Arguments\n\nSometimes you want an argument to be optional.  If so, assign a default value\nin the function definition.\n\n```python\ndef read_prices(filename, debug=False):\n    ...\n```\n\nIf a default value is assigned, the argument is optional in function calls.\n\n```python\nd = read_prices('prices.csv')\ne = read_prices('prices.dat', True)\n```\n\n*Note: Arguments with defaults must appear at the end of the arguments list (all non-optional arguments go first).*\n\n### Prefer keyword arguments for optional arguments\n\nCompare and contrast these two different calling styles:\n\n```python\nparse_data(data, False, True) # ?????\n\nparse_data(data, ignore_errors=True)\nparse_data(data, debug=True)\nparse_data(data, debug=True, ignore_errors=True)\n```\n\nIn most cases, keyword arguments improve code clarity--especially for arguments that\nserve as flags or which are related to optional features.\n\n### Design Best Practices\n\nAlways give short, but meaningful names to functions arguments.\n\nSomeone using a function may want to use the keyword calling style.\n\n```python\nd = read_prices('prices.csv', debug=True)\n```\n\nPython development tools will show the names in help features and documentation.\n\n### Returning Values\n\nThe `return` statement returns a value\n\n```python\ndef square(x):\n    return x * x\n```\n\nIf no return value is given or `return` is missing, `None` is returned.\n\n```python\ndef bar(x):\n    statements\n    return\n\na = bar(4)      # a = None\n\n# OR\ndef foo(x):\n    statements  # No `return`\n\nb = foo(4)      # b = None\n```\n\n### Multiple Return Values\n\nFunctions can only return one value.  However, a function may return\nmultiple values by returning them in a tuple.\n\n```python\ndef divide(a,b):\n    q = a // b      # Quotient\n    r = a % b       # Remainder\n    return q, r     # Return a tuple\n```\n\nUsage example:\n\n```python\nx, y = divide(37,5) # x = 7, y = 2\n\nx = divide(37, 5)   # x = (7, 2)\n```\n\n### Variable Scope\n\nPrograms assign values to variables.\n\n```python\nx = value # Global variable\n\ndef foo():\n    y = value # Local variable\n```\n\nVariables assignments occur outside and inside function definitions.\nVariables defined outside are \"global\". Variables inside a function\nare \"local\".\n\n### Local Variables\n\nVariables assigned inside functions are private.\n\n```python\ndef read_portfolio(filename):\n    portfolio = []\n    for line in open(filename):\n        fields = line.split(',')\n        s = (fields[0], int(fields[1]), float(fields[2]))\n        portfolio.append(s)\n    return portfolio\n```\n\nIn this example, `filename`, `portfolio`, `line`, `fields` and `s` are local variables.\nThose variables are not retained or accessible after the function call.\n\n```python\n>>> stocks = read_portfolio('portfolio.csv')\n>>> fields\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in ?\nNameError: name 'fields' is not defined\n>>>\n```\n\nLocals also can't conflict with variables found elsewhere.\n\n### Global Variables\n\nFunctions can freely access the values of globals defined in the same\nfile.\n\n```python\nname = 'Dave'\n\ndef greeting():\n    print('Hello', name)  # Using `name` global variable\n```\n\nHowever, functions can't modify globals:\n\n```python\nname = 'Dave'\n\ndef spam():\n  name = 'Guido'\n\nspam()\nprint(name) # prints 'Dave'\n```\n\n**Remember: All assignments in functions are local.**\n\n### Modifying Globals\n\nIf you must modify a global variable you must declare it as such.\n\n```python\nname = 'Dave'\n\ndef spam():\n    global name\n    name = 'Guido' # Changes the global name above\n```\n\nThe global declaration must appear before its use and the corresponding\nvariable must exist in the same file as the function.   Having seen this,\nknow that it is considered poor form.  In fact, try to avoid `global` entirely\nif you can.  If you need a function to modify some kind of state outside\nof the function, it's better to use a class instead (more on this later).\n\n### Argument Passing\n\nWhen you call a function, the argument variables are names that refer\nto the passed values. These values are NOT copies (see [section\n2.7](../02_Working_with_data/07_Objects.md)). If mutable data types are\npassed (e.g. lists, dicts), they can be modified *in-place*.\n\n```python\ndef foo(items):\n    items.append(42)    # Modifies the input object\n\na = [1, 2, 3]\nfoo(a)\nprint(a)                # [1, 2, 3, 42]\n```\n\n**Key point: Functions don't receive a copy of the input arguments.**\n\n### Reassignment vs Modifying\n\nMake sure you understand the subtle difference between modifying a\nvalue and reassigning a variable name.\n\n```python\ndef foo(items):\n    items.append(42)    # Modifies the input object\n\na = [1, 2, 3]\nfoo(a)\nprint(a)                # [1, 2, 3, 42]\n\n# VS\ndef bar(items):\n    items = [4,5,6]    # Changes local `items` variable to point to a different object\n\nb = [1, 2, 3]\nbar(b)\nprint(b)                # [1, 2, 3]\n```\n\n*Reminder: Variable assignment never overwrites memory. The name is merely bound to a new value.*\n\n## Exercises\n\nThis set of exercises have you implement what is, perhaps, the most\npowerful and difficult part of the course.  There are a lot of steps\nand many concepts from past exercises are put together all at once.\nThe final solution is only about 25 lines of code, but take your time\nand make sure you understand each part.\n\nA central part of your `report.py` program focuses on the reading of\nCSV files.  For example, the function `read_portfolio()` reads a file\ncontaining rows of portfolio data and the function `read_prices()`\nreads a file containing rows of price data. In both of those\nfunctions, there are a lot of low-level \"fiddly\" bits and similar\nfeatures.  For example, they both open a file and wrap it with the\n`csv` module and they both convert various fields into new types.\n\nIf you were doing a lot of file parsing for real, you’d probably want\nto clean some of this up and make it more general purpose.  That's\nour goal.\n\nStart this exercise by opening the file called\n`Work/fileparse.py`. This is where we will be doing our work.\n\n### Exercise 3.3: Reading CSV Files\n\nTo start, let’s just focus on the problem of reading a CSV file into a\nlist of dictionaries.  In the file `fileparse.py`, define a\nfunction that looks like this:\n\n```python\n# fileparse.py\nimport csv\n\ndef parse_csv(filename):\n    '''\n    Parse a CSV file into a list of records\n    '''\n    with open(filename) as f:\n        rows = csv.reader(f)\n\n        # Read the file headers\n        headers = next(rows)\n        records = []\n        for row in rows:\n            if not row:    # Skip rows with no data\n                continue\n            record = dict(zip(headers, row))\n            records.append(record)\n\n    return records\n```\n\nThis function reads a CSV file into a list of dictionaries while\nhiding the details of opening the file, wrapping it with the `csv`\nmodule, ignoring blank lines, and so forth.\n\nTry it out:\n\nHint: `python3 -i fileparse.py`.\n\n```python\n>>> portfolio = parse_csv('Data/portfolio.csv')\n>>> portfolio\n[{'price': '32.20', 'name': 'AA', 'shares': '100'}, {'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'}, {'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'}, {'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}]\n>>>\n```\n\nThis is good except that you can’t do any kind of useful calculation\nwith the data because everything is represented as a string.  We’ll\nfix this shortly, but let’s keep building on it.\n\n### Exercise 3.4: Building a Column Selector\n\nIn many cases, you’re only interested in selected columns from a CSV\nfile, not all of the data.  Modify the `parse_csv()` function so that\nit optionally allows user-specified columns to be picked out as\nfollows:\n\n```python\n>>> # Read all of the data\n>>> portfolio = parse_csv('Data/portfolio.csv')\n>>> portfolio\n[{'price': '32.20', 'name': 'AA', 'shares': '100'}, {'price': '91.10', 'name': 'IBM', 'shares': '50'}, {'price': '83.44', 'name': 'CAT', 'shares': '150'}, {'price': '51.23', 'name': 'MSFT', 'shares': '200'}, {'price': '40.37', 'name': 'GE', 'shares': '95'}, {'price': '65.10', 'name': 'MSFT', 'shares': '50'}, {'price': '70.44', 'name': 'IBM', 'shares': '100'}]\n\n>>> # Read only some of the data\n>>> shares_held = parse_csv('Data/portfolio.csv', select=['name','shares'])\n>>> shares_held\n[{'name': 'AA', 'shares': '100'}, {'name': 'IBM', 'shares': '50'}, {'name': 'CAT', 'shares': '150'}, {'name': 'MSFT', 'shares': '200'}, {'name': 'GE', 'shares': '95'}, {'name': 'MSFT', 'shares': '50'}, {'name': 'IBM', 'shares': '100'}]\n>>>\n```\n\nAn example of a column selector was given in [Exercise 2.23](../02_Working_with_data/06_List_comprehension.md).\nHowever, here’s one way to do it:\n\n```python\n# fileparse.py\nimport csv\n\ndef parse_csv(filename, select=None):\n    '''\n    Parse a CSV file into a list of records\n    '''\n    with open(filename) as f:\n        rows = csv.reader(f)\n\n        # Read the file headers\n        headers = next(rows)\n\n        # If a column selector was given, find indices of the specified columns.\n        # Also narrow the set of headers used for resulting dictionaries\n        if select:\n            indices = [headers.index(colname) for colname in select]\n            headers = select\n        else:\n            indices = []\n\n        records = []\n        for row in rows:\n            if not row:    # Skip rows with no data\n                continue\n            # Filter the row if specific columns were selected\n            if indices:\n                row = [ row[index] for index in indices ]\n\n            # Make a dictionary\n            record = dict(zip(headers, row))\n            records.append(record)\n\n    return records\n```\n\nThere are a number of tricky bits to this part. Probably the most\nimportant one is the mapping of the column selections to row indices.\nFor example, suppose the input file had the following headers:\n\n```python\n>>> headers = ['name', 'date', 'time', 'shares', 'price']\n>>>\n```\n\nNow, suppose the selected columns were as follows:\n\n```python\n>>> select = ['name', 'shares']\n>>>\n```\n\nTo perform the proper selection, you have to map the selected column names to column indices in the file.\nThat’s what this step is doing:\n\n```python\n>>> indices = [headers.index(colname) for colname in select ]\n>>> indices\n[0, 3]\n>>>\n```\n\nIn other words, \"name\" is column 0 and \"shares\" is column 3.\nWhen you read a row of data from the file, the indices are used to filter it:\n\n```python\n>>> row = ['AA', '6/11/2007', '9:50am', '100', '32.20' ]\n>>> row = [ row[index] for index in indices ]\n>>> row\n['AA', '100']\n>>>\n```\n\n### Exercise 3.5: Performing Type Conversion\n\nModify the `parse_csv()` function so that it optionally allows\ntype-conversions to be applied to the returned data.  For example:\n\n```python\n>>> portfolio = parse_csv('Data/portfolio.csv', types=[str, int, float])\n>>> portfolio\n[{'price': 32.2, 'name': 'AA', 'shares': 100}, {'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 51.23, 'name': 'MSFT', 'shares': 200}, {'price': 40.37, 'name': 'GE', 'shares': 95}, {'price': 65.1, 'name': 'MSFT', 'shares': 50}, {'price': 70.44, 'name': 'IBM', 'shares': 100}]\n\n>>> shares_held = parse_csv('Data/portfolio.csv', select=['name', 'shares'], types=[str, int])\n>>> shares_held\n[{'name': 'AA', 'shares': 100}, {'name': 'IBM', 'shares': 50}, {'name': 'CAT', 'shares': 150}, {'name': 'MSFT', 'shares': 200}, {'name': 'GE', 'shares': 95}, {'name': 'MSFT', 'shares': 50}, {'name': 'IBM', 'shares': 100}]\n>>>\n```\n\nYou already explored this in [Exercise 2.24](../02_Working_with_data/07_Objects.md).\nYou'll need to insert the following fragment of code into your solution:\n\n```python\n...\nif types:\n    row = [func(val) for func, val in zip(types, row) ]\n...\n```\n\n### Exercise 3.6: Working without Headers\n\nSome CSV files don’t include any header information.\nFor example, the file `prices.csv` looks like this:\n\n```csv\n\"AA\",9.22\n\"AXP\",24.85\n\"BA\",44.85\n\"BAC\",11.27\n...\n```\n\nModify the `parse_csv()` function so that it can work with such files\nby creating a list of tuples instead.  For example:\n\n```python\n>>> prices = parse_csv('Data/prices.csv', types=[str,float], has_headers=False)\n>>> prices\n[('AA', 9.22), ('AXP', 24.85), ('BA', 44.85), ('BAC', 11.27), ('C', 3.72), ('CAT', 35.46), ('CVX', 66.67), ('DD', 28.47), ('DIS', 24.22), ('GE', 13.48), ('GM', 0.75), ('HD', 23.16), ('HPQ', 34.35), ('IBM', 106.28), ('INTC', 15.72), ('JNJ', 55.16), ('JPM', 36.9), ('KFT', 26.11), ('KO', 49.16), ('MCD', 58.99), ('MMM', 57.1), ('MRK', 27.58), ('MSFT', 20.89), ('PFE', 15.19), ('PG', 51.94), ('T', 24.79), ('UTX', 52.61), ('VZ', 29.26), ('WMT', 49.74), ('XOM', 69.35)]\n>>>\n```\n\nTo make this change, you’ll need to modify the code so that the first\nline of data isn’t interpreted as a header line.  Also, you’ll need to\nmake sure you don’t create dictionaries as there are no longer any\ncolumn names to use for keys.\n\n### Exercise 3.7: Picking a different column delimiter\n\nAlthough CSV files are pretty common, it’s also possible that you\ncould encounter a file that uses a different column separator such as\na tab or space.  For example, the file `Data/portfolio.dat` looks like\nthis:\n\n```csv\nname shares price\n\"AA\" 100 32.20\n\"IBM\" 50 91.10\n\"CAT\" 150 83.44\n\"MSFT\" 200 51.23\n\"GE\" 95 40.37\n\"MSFT\" 50 65.10\n\"IBM\" 100 70.44\n```\n\nThe `csv.reader()` function allows a different column delimiter to be given as follows:\n\n```python\nrows = csv.reader(f, delimiter=' ')\n```\n\nModify your `parse_csv()` function so that it also allows the\ndelimiter to be changed.\n\nFor example:\n\n```python\n>>> portfolio = parse_csv('Data/portfolio.dat', types=[str, int, float], delimiter=' ')\n>>> portfolio\n[{'name': 'AA', 'shares': 100, 'price': 32.2}, {'name': 'IBM', 'shares': 50, 'price': 91.1}, {'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23}, {'name': 'GE', 'shares': 95, 'price': 40.37}, {'name': 'MSFT', 'shares': 50, 'price': 65.1}, {'name': 'IBM', 'shares': 100, 'price': 70.44}]\n>>>\n```\n\n### Commentary\n\nIf you’ve made it this far, you’ve created a nice library function\nthat’s genuinely useful.  You can use it to parse arbitrary CSV files,\nselect out columns of interest, perform type conversions, without\nhaving to worry too much about the inner workings of files or the\n`csv` module.\n\n[Contents](../Contents.md) \\| [Previous (3.1 Scripting)](01_Script.md) \\| [Next (3.3 Error Checking)](03_Error_checking.md)\n"
  },
  {
    "path": "Notes/03_Program_organization/03_Error_checking.md",
    "content": "[Contents](../Contents.md) \\| [Previous (3.2 More on Functions)](02_More_functions.md) \\| [Next (3.4 Modules)](04_Modules.md)\n\n# 3.3 Error Checking\n\nAlthough exceptions were introduced earlier, this section fills in some additional\ndetails about error checking and exception handling.\n\n### How programs fail\n\nPython performs no checking or validation of function argument types\nor values.  A function will work on any data that is compatible with\nthe statements in the function.\n\n```python\ndef add(x, y):\n    return x + y\n\nadd(3, 4)               # 7\nadd('Hello', 'World')   # 'HelloWorld'\nadd('3', '4')           # '34'\n```\n\nIf there are errors in a function, they appear at run time (as an exception).\n\n```python\ndef add(x, y):\n    return x + y\n\n>>> add(3, '4')\nTraceback (most recent call last):\n...\nTypeError: unsupported operand type(s) for +:\n'int' and 'str'\n>>>\n```\n\nTo verify code, there is a strong emphasis on testing (covered later).\n\n### Exceptions\n\nExceptions are used to signal errors.\nTo raise an exception yourself, use `raise` statement.\n\n```python\nif name not in authorized:\n    raise RuntimeError(f'{name} not authorized')\n```\n\nTo catch an exception use `try-except`.\n\n```python\ntry:\n    authenticate(username)\nexcept RuntimeError as e:\n    print(e)\n```\n\n### Exception Handling\n\nExceptions propagate to the first matching `except`.\n\n```python\ndef grok():\n    ...\n    raise RuntimeError('Whoa!')   # Exception raised here\n\ndef spam():\n    grok()                        # Call that will raise exception\n\ndef bar():\n    try:\n       spam()\n    except RuntimeError as e:     # Exception caught here\n        ...\n\ndef foo():\n    try:\n         bar()\n    except RuntimeError as e:     # Exception does NOT arrive here\n        ...\n\nfoo()\n```\n\nTo handle the exception, put statements in the `except` block. You can add any\nstatements you want to handle the error.\n\n```python\ndef grok(): ...\n    raise RuntimeError('Whoa!')\n\ndef bar():\n    try:\n      grok()\n    except RuntimeError as e:   # Exception caught here\n        statements              # Use this statements\n        statements\n        ...\n\nbar()\n```\n\nAfter handling, execution resumes with the first statement after the\n`try-except`.\n\n```python\ndef grok(): ...\n    raise RuntimeError('Whoa!')\n\ndef bar():\n    try:\n      grok()\n    except RuntimeError as e:   # Exception caught here\n        statements\n        statements\n        ...\n    statements                  # Resumes execution here\n    statements                  # And continues here\n    ...\n\nbar()\n```\n\n### Built-in Exceptions\n\nThere are about two-dozen built-in exceptions.  Usually the name of\nthe exception is indicative of what's wrong (e.g., a `ValueError` is\nraised because you supplied a bad value). This is not an\nexhaustive list. Check the [documentation](https://docs.python.org/3/library/exceptions.html) for more.\n\n```python\nArithmeticError\nAssertionError\nEnvironmentError\nEOFError\nImportError\nIndexError\nKeyboardInterrupt\nKeyError\nMemoryError\nNameError\nReferenceError\nRuntimeError\nSyntaxError\nSystemError\nTypeError\nValueError\n```\n\n### Exception Values\n\nExceptions have an associated value. It contains more specific\ninformation about what's wrong.\n\n```python\nraise RuntimeError('Invalid user name')\n```\n\nThis value is part of the exception instance that's placed in the variable supplied to `except`.\n\n```python\ntry:\n    ...\nexcept RuntimeError as e:   # `e` holds the exception raised\n    ...\n```\n\n`e` is an instance of the exception type. However, it often looks like a string when\nprinted.\n\n```python\nexcept RuntimeError as e:\n    print('Failed : Reason', e)\n```\n\n### Catching Multiple Errors\n\nYou can catch different kinds of exceptions using multiple `except` blocks.\n\n```python\ntry:\n  ...\nexcept LookupError as e:\n  ...\nexcept RuntimeError as e:\n  ...\nexcept IOError as e:\n  ...\nexcept KeyboardInterrupt as e:\n  ...\n```\n\nAlternatively, if the statements to handle them is the same, you can group them:\n\n```python\ntry:\n  ...\nexcept (IOError,LookupError,RuntimeError) as e:\n  ...\n```\n\n### Catching All Errors\n\nTo catch any exception, use `Exception` like this:\n\n```python\ntry:\n    ...\nexcept Exception:       # DANGER. See below\n    print('An error occurred')\n```\n\nIn general, writing code like that is a bad idea because you'll have\nno idea why it failed.\n\n### Wrong Way to Catch Errors\n\nHere is the wrong way to use exceptions.\n\n```python\ntry:\n    go_do_something()\nexcept Exception:\n    print('Computer says no')\n```\n\nThis catches all possible errors and it may make it impossible to debug\nwhen the code is failing for some reason you didn't expect at all\n(e.g. uninstalled Python module, etc.).\n\n### Somewhat Better Approach\n\nIf you're going to catch all errors, this is a more sane approach.\n\n```python\ntry:\n    go_do_something()\nexcept Exception as e:\n    print('Computer says no. Reason :', e)\n```\n\nIt reports a specific reason for failure.  It is almost always a good\nidea to have some mechanism for viewing/reporting errors when you\nwrite code that catches all possible exceptions.\n\nIn general though, it's better to catch the error as narrowly as is\nreasonable. Only catch the errors you can actually handle. Let\nother errors pass by--maybe some other code can handle them.\n\n### Reraising an Exception\n\nUse `raise` to propagate a caught error.\n\n```python\ntry:\n    go_do_something()\nexcept Exception as e:\n    print('Computer says no. Reason :', e)\n    raise\n```\n\nThis allows you to take action (e.g. logging) and pass the error on to\nthe caller.\n\n### Exception Best Practices\n\nDon't catch exceptions. Fail fast and loud. If it's important, someone\nelse will take care of the problem.  Only catch an exception if you\nare *that* someone.  That is, only catch errors where you can recover\nand sanely keep going.\n\n### `finally` statement\n\nIt specifies code that must run regardless of whether or not an\nexception occurs.\n\n```python\nlock = Lock()\n...\nlock.acquire()\ntry:\n    ...\nfinally:\n    lock.release()  # this will ALWAYS be executed. With and without exception.\n```\n\nCommonly used to safely manage resources (especially locks, files, etc.).\n\n### `with` statement\n\nIn modern code, `try-finally` is often replaced with the `with` statement.\n\n```python\nlock = Lock()\nwith lock:\n    # lock acquired\n    ...\n# lock released\n```\n\nA more familiar example:\n\n```python\nwith open(filename) as f:\n    # Use the file\n    ...\n# File closed\n```\n\n`with` defines a usage *context* for a resource.  When execution\nleaves that context, resources are released. `with` only works with\ncertain objects that have been specifically programmed to support it.\n\n## Exercises\n\n### Exercise 3.8: Raising exceptions\n\nThe `parse_csv()` function you wrote in the last section allows\nuser-specified columns to be selected, but that only works if the\ninput data file has column headers.\n\nModify the code so that an exception gets raised if both the `select`\nand `has_headers=False` arguments are passed.  For example:\n\n```python\n>>> parse_csv('Data/prices.csv', select=['name','price'], has_headers=False)\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"fileparse.py\", line 9, in parse_csv\n    raise RuntimeError(\"select argument requires column headers\")\nRuntimeError: select argument requires column headers\n>>>\n```\n\nHaving added this one check, you might ask if you should be performing\nother kinds of sanity checks in the function.  For example, should you\ncheck that the filename is a string, that types is a list, or anything\nof that nature?\n\nAs a general rule, it’s usually best to skip such tests and to just\nlet the program fail on bad inputs.  The traceback message will point\nat the source of the problem and can assist in debugging.\n\nThe main reason for adding the above check is to avoid running the code\nin a non-sensical mode (e.g., using a feature that requires column\nheaders, but simultaneously specifying that there are no headers).\n\nThis indicates a programming error on the part of the calling code.\nChecking for cases that \"aren't supposed to happen\" is often a good idea.\n\n### Exercise 3.9: Catching exceptions\n\nThe `parse_csv()` function you wrote is used to process the entire\ncontents of a file.  However, in the real-world, it’s possible that\ninput files might have corrupted, missing, or dirty data.  Try this\nexperiment:\n\n```python\n>>> portfolio = parse_csv('Data/missing.csv', types=[str, int, float])\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"fileparse.py\", line 36, in parse_csv\n    row = [func(val) for func, val in zip(types, row)]\nValueError: invalid literal for int() with base 10: ''\n>>>\n```\n\nModify the `parse_csv()` function to catch all `ValueError` exceptions\ngenerated during record creation and print a warning message for rows\nthat can’t be converted.\n\nThe message should include the row number and information about the\nreason why it failed.  To test your function, try reading the file\n`Data/missing.csv` above.  For example:\n\n```python\n>>> portfolio = parse_csv('Data/missing.csv', types=[str, int, float])\nRow 4: Couldn't convert ['MSFT', '', '51.23']\nRow 4: Reason invalid literal for int() with base 10: ''\nRow 7: Couldn't convert ['IBM', '', '70.44']\nRow 7: Reason invalid literal for int() with base 10: ''\n>>>\n>>> portfolio\n[{'price': 32.2, 'name': 'AA', 'shares': 100}, {'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 40.37, 'name': 'GE', 'shares': 95}, {'price': 65.1, 'name': 'MSFT', 'shares': 50}]\n>>>\n```\n\n### Exercise 3.10: Silencing Errors\n\nModify the `parse_csv()` function so that parsing error messages can\nbe silenced if explicitly desired by the user.  For example:\n\n```python\n>>> portfolio = parse_csv('Data/missing.csv', types=[str,int,float], silence_errors=True)\n>>> portfolio\n[{'price': 32.2, 'name': 'AA', 'shares': 100}, {'price': 91.1, 'name': 'IBM', 'shares': 50}, {'price': 83.44, 'name': 'CAT', 'shares': 150}, {'price': 40.37, 'name': 'GE', 'shares': 95}, {'price': 65.1, 'name': 'MSFT', 'shares': 50}]\n>>>\n```\n\nError handling is one of the most difficult things to get right in\nmost programs.  As a general rule, you shouldn’t silently ignore\nerrors.  Instead, it’s better to report problems and to give the user\nan option to the silence the error message if they choose to do so.\n\n[Contents](../Contents.md) \\| [Previous (3.2 More on Functions)](02_More_functions.md) \\| [Next (3.4 Modules)](04_Modules.md)\n"
  },
  {
    "path": "Notes/03_Program_organization/04_Modules.md",
    "content": "[Contents](../Contents.md) \\| [Previous (3.3 Error Checking)](03_Error_checking.md) \\| [Next (3.5 Main Module)](05_Main_module.md)\n\n# 3.4 Modules\n\nThis section introduces the concept of modules and working with functions that span\nmultiple files.\n\n### Modules and import\n\nAny Python source file is a module.\n\n```python\n# foo.py\ndef grok(a):\n    ...\ndef spam(b):\n    ...\n```\n\nThe `import` statement loads and *executes* a module.\n\n```python\n# program.py\nimport foo\n\na = foo.grok(2)\nb = foo.spam('Hello')\n...\n```\n\n### Namespaces\n\nA module is a collection of named values and is sometimes said to be a\n*namespace*.  The names are all of the global variables and functions\ndefined in the source file.  After importing, the module name is used\nas a prefix. Hence the *namespace*.\n\n```python\nimport foo\n\na = foo.grok(2)\nb = foo.spam('Hello')\n...\n```\n\nThe module name is directly tied to the file name (foo -> foo.py).\n\n### Global Definitions\n\nEverything defined in the *global* scope is what populates the module\nnamespace. Consider two modules\nthat define the same variable `x`.\n\n```python\n# foo.py\nx = 42\ndef grok(a):\n    ...\n```\n\n```python\n# bar.py\nx = 37\ndef spam(a):\n    ...\n```\n\nIn this case, the `x` definitions refer to different variables.  One\nis `foo.x` and the other is `bar.x`.  Different modules can use the\nsame names and those names won't conflict with each other.\n\n**Modules are isolated.**\n\n### Modules as Environments\n\nModules form an enclosing environment for all of the code defined inside.\n\n```python\n# foo.py\nx = 42\n\ndef grok(a):\n    print(x)\n```\n\n*Global* variables are always bound to the enclosing module (same file).\nEach source file is its own little universe.\n\n### Module Execution\n\nWhen a module is imported, *all of the statements in the module\nexecute* one after another until the end of the file is reached.  The\ncontents of the module namespace are all of the *global* names that\nare still defined at the end of the execution process.  If there are\nscripting statements that carry out tasks in the global scope\n(printing, creating files, etc.) you will see them run on import.\n\n### `import as` statement\n\nYou can change the name of a module as you import it:\n\n```python\nimport math as m\ndef rectangular(r, theta):\n    x = r * m.cos(theta)\n    y = r * m.sin(theta)\n    return x, y\n```\n\nIt works the same as a normal import. It just renames the module in that one file.\n\n### `from` module import\n\nThis picks selected symbols out of a module and makes them available locally.\n\n```python\nfrom math import sin, cos\n\ndef rectangular(r, theta):\n    x = r * cos(theta)\n    y = r * sin(theta)\n    return x, y\n```\n\nThis allows parts of a module to be used without having to type the module prefix.\nIt's useful for frequently used names.\n\n### Comments on importing\n\nVariations on import do *not* change the way that modules work.\n\n```python\nimport math\n# vs\nimport math as m\n# vs\nfrom math import cos, sin\n...\n```\n\nSpecifically, `import` always executes the *entire* file and modules\nare still isolated environments.\n\nThe `import module as` statement is only changing the name locally.\nThe `from math import cos, sin` statement still loads the entire\nmath module behind the scenes. It's merely copying the `cos` and `sin`\nnames from the module into the local space after it's done.\n\n### Module Loading\n\nEach module loads and executes only *once*.\n*Note: Repeated imports just return a reference to the previously loaded module.*\n\n`sys.modules` is a dict of all loaded modules.\n\n```python\n>>> import sys\n>>> sys.modules.keys()\n['copy_reg', '__main__', 'site', '__builtin__', 'encodings', 'encodings.encodings', 'posixpath', ...]\n>>>\n```\n\n**Caution:** A common confusion arises if you repeat an `import` statement after\nchanging the source code for a module.  Because of the module cache `sys.modules`,\nrepeated imports always return the previously loaded module--even if a change\nwas made.  The safest way to load modified code into Python is to quit and restart\nthe interpreter.\n\n### Locating Modules\n\nPython consults a path list (sys.path) when looking for modules.\n\n```python\n>>> import sys\n>>> sys.path\n[\n  '',\n  '/usr/local/lib/python36/python36.zip',\n  '/usr/local/lib/python36',\n  ...\n]\n```\n\nThe current working directory is usually first.\n\n### Module Search Path\n\nAs noted, `sys.path` contains the search paths.\nYou can manually adjust if you need to.\n\n```python\nimport sys\nsys.path.append('/project/foo/pyfiles')\n```\n\nPaths can also be added via environment variables.\n\n```python\n% env PYTHONPATH=/project/foo/pyfiles python3\nPython 3.6.0 (default, Feb 3 2017, 05:53:21)\n[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.38)]\n>>> import sys\n>>> sys.path\n['','/project/foo/pyfiles', ...]\n```\n\nAs a general rule, it should not be necessary to manually adjust\nthe module search path.  However, it sometimes arises if you're\ntrying to import Python code that's in an unusual location or\nnot readily accessible from the current working directory.\n\n## Exercises\n\nFor this exercise involving modules, it is critically important to\nmake sure you are running Python in a proper environment.  Modules \noften present new programmers with problems related to the current working\ndirectory or with Python's path settings.  For this course, it is\nassumed that you're writing all of your code in the `Work/` directory.\nFor best results, you should make sure you're also in that directory\nwhen you launch the interpreter.  If not, you need to make sure\n`practical-python/Work` is added to `sys.path`.\n\n### Exercise 3.11: Module imports\n\nIn section 3, we created a general purpose function `parse_csv()` for\nparsing the contents of CSV datafiles.\n\nNow, we’re going to see how to use that function in other programs.\nFirst, start in a new shell window.  Navigate to the folder where you\nhave all your files. We are going to import them.\n\nStart Python interactive mode.\n\n```shell\nbash % python3\nPython 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04)\n[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n>>>\n```\n\nOnce you’ve done that, try importing some of the programs you\npreviously wrote.  You should see their output exactly as before.\nJust to emphasize, importing a module runs its code.\n\n```python\n>>> import bounce\n... watch output ...\n>>> import mortgage\n... watch output ...\n>>> import report\n... watch output ...\n>>>\n```\n\nIf none of this works, you’re probably running Python in the wrong directory.\nNow, try importing your `fileparse` module and getting some help on it.\n\n```python\n>>> import fileparse\n>>> help(fileparse)\n... look at the output ...\n>>> dir(fileparse)\n... look at the output ...\n>>>\n```\n\nTry using the module to read some data:\n\n```python\n>>> portfolio = fileparse.parse_csv('Data/portfolio.csv',select=['name','shares','price'], types=[str,int,float])\n>>> portfolio\n... look at the output ...\n>>> pricelist = fileparse.parse_csv('Data/prices.csv',types=[str,float], has_headers=False)\n>>> pricelist\n... look at the output ...\n>>> prices = dict(pricelist)\n>>> prices\n... look at the output ...\n>>> prices['IBM']\n106.11\n>>>\n```\n\nTry importing a function so that you don’t need to include the module name:\n\n```python\n>>> from fileparse import parse_csv\n>>> portfolio = parse_csv('Data/portfolio.csv', select=['name','shares','price'], types=[str,int,float])\n>>> portfolio\n... look at the output ...\n>>>\n```\n\n### Exercise 3.12: Using your library module\n\nIn section 2, you wrote a program `report.py` that produced a stock report like this:\n\n```\n      Name     Shares      Price     Change\n---------- ---------- ---------- ----------\n        AA        100       9.22     -22.98\n       IBM         50     106.28      15.18\n       CAT        150      35.46     -47.98\n      MSFT        200      20.89     -30.34\n        GE         95      13.48     -26.89\n      MSFT         50      20.89     -44.21\n       IBM        100     106.28      35.84\n```\n\nTake that program and modify it so that all of the input file\nprocessing is done using functions in your `fileparse` module.  To do\nthat, import `fileparse` as a module and change the `read_portfolio()`\nand `read_prices()` functions to use the `parse_csv()` function.\n\nUse the interactive example at the start of this exercise as a guide.\nAfterwards, you should get exactly the same output as before.\n\n### Exercise 3.13: Intentionally left blank (skip)\n\n### Exercise 3.14: Using more library imports\n\nIn section 1, you wrote a program `pcost.py` that read a portfolio and computed its cost.\n\n```python\n>>> import pcost\n>>> pcost.portfolio_cost('Data/portfolio.csv')\n44671.15\n>>>\n```\n\nModify the `pcost.py` file so that it uses the `report.read_portfolio()` function.\n\n### Commentary\n\nWhen you are done with this exercise, you should have three\nprograms. `fileparse.py` which contains a general purpose\n`parse_csv()` function.  `report.py` which produces a nice report, but\nalso contains `read_portfolio()` and `read_prices()` functions.  And\nfinally, `pcost.py` which computes the portfolio cost, but makes use\nof the `read_portfolio()` function written for the `report.py` program.\n\n[Contents](../Contents.md) \\| [Previous (3.3 Error Checking)](03_Error_checking.md) \\| [Next (3.5 Main Module)](05_Main_module.md)\n"
  },
  {
    "path": "Notes/03_Program_organization/05_Main_module.md",
    "content": "[Contents](../Contents.md) \\| [Previous (3.4 Modules)](04_Modules.md) \\| [Next (3.6 Design Discussion)](06_Design_discussion.md)\n\n# 3.5 Main Module\n\nThis section introduces the concept of a main program or main module.\n\n### Main Functions\n\nIn many programming languages, there is a concept of a *main* function or method.\n\n```c\n// c / c++\nint main(int argc, char *argv[]) {\n    ...\n}\n```\n\n```java\n// java\nclass myprog {\n    public static void main(String args[]) {\n        ...\n    }\n}\n```\n\nThis is the first function that executes when an application is launched.\n\n### Python Main Module\n\nPython has no *main* function or method.  Instead, there is a *main*\nmodule. The *main module* is the source file that runs first.\n\n```bash\nbash % python3 prog.py\n...\n```\n\nWhatever file you give to the interpreter at startup becomes *main*. It doesn't matter the name.\n\n### `__main__` check\n\nIt is standard practice for modules that run as a main script to use this convention:\n\n```python\n# prog.py\n...\nif __name__ == '__main__':\n    # Running as the main program ...\n    statements\n    ...\n```\n\nStatements enclosed inside the `if` statement become the *main* program.\n\n### Main programs vs. library imports\n\nAny Python file can either run as main or as a library import:\n\n```bash\nbash % python3 prog.py # Running as main\n```\n\n```python\nimport prog   # Running as library import\n```\n\nIn both cases, `__name__` is the name of the module.  However, it will only be set to `__main__` if\nrunning as main.\n\nUsually, you don't want statements that are part of the main program\nto execute on a library import.  So, it's common to have an `if-`check\nin code that might be used either way.\n\n```python\nif __name__ == '__main__':\n    # Does not execute if loaded with import ...\n```\n\n### Program Template\n\nHere is a common program template for writing a Python program:\n\n```python\n# prog.py\n# Import statements (libraries)\nimport modules\n\n# Functions\ndef spam():\n    ...\n\ndef blah():\n    ...\n\n# Main function\ndef main():\n    ...\n\nif __name__ == '__main__':\n    main()\n```\n\n### Command Line Tools\n\nPython is often used for command-line tools\n\n```bash\nbash % python3 report.py portfolio.csv prices.csv\n```\n\nIt means that the scripts are executed from the shell /\nterminal. Common use cases are for automation, background tasks, etc.\n\n### Command Line Args\n\nThe command line is a list of text strings.\n\n```bash\nbash % python3 report.py portfolio.csv prices.csv\n```\n\nThis list of text strings is found in `sys.argv`.\n\n```python\n# In the previous bash command\nsys.argv # ['report.py, 'portfolio.csv', 'prices.csv']\n```\n\nHere is a simple example of processing the arguments:\n\n```python\nimport sys\n\nif len(sys.argv) != 3:\n    raise SystemExit(f'Usage: {sys.argv[0]} ' 'portfile pricefile')\nportfile = sys.argv[1]\npricefile = sys.argv[2]\n...\n```\n\n### Standard I/O\n\nStandard Input / Output (or stdio) are files that work the same as normal files.\n\n```python\nsys.stdout\nsys.stderr\nsys.stdin\n```\n\nBy default, print is directed to `sys.stdout`.  Input is read from\n`sys.stdin`.  Tracebacks and errors are directed to `sys.stderr`.\n\nBe aware that *stdio* could be connected to terminals, files, pipes, etc.\n\n```bash\nbash % python3 prog.py > results.txt\n# or\nbash % cmd1 | python3 prog.py | cmd2\n```\n\n### Environment Variables\n\nEnvironment variables are set in the shell.\n\n```bash\nbash % setenv NAME dave\nbash % setenv RSH ssh\nbash % python3 prog.py\n```\n\n`os.environ` is a dictionary that contains these values.\n\n```python\nimport os\n\nname = os.environ['NAME'] # 'dave'\n```\n\nChanges are reflected in any subprocesses later launched by the program.\n\n### Program Exit\n\nProgram exit is handled through exceptions.\n\n```python\nraise SystemExit\nraise SystemExit(exitcode)\nraise SystemExit('Informative message')\n```\n\nAn alternative.\n\n```python\nimport sys\nsys.exit(exitcode)\n```\n\nA non-zero exit code indicates an error.\n\n### The `#!` line\n\nOn Unix, the `#!` line can launch a script as Python.\nAdd the following to the first line of your script file.\n\n```python\n#!/usr/bin/env python3\n# prog.py\n...\n```\n\nIt requires the executable permission.\n\n```bash\nbash % chmod +x prog.py\n# Then you can execute\nbash % prog.py\n... output ...\n```\n\n*Note: The Python Launcher on Windows also looks for the `#!` line to indicate language version.*\n\n### Script Template\n\nFinally, here is a common code template for Python programs that run\nas command-line scripts:\n\n```python\n#!/usr/bin/env python3\n# prog.py\n\n# Import statements (libraries)\nimport modules\n\n# Functions\ndef spam():\n    ...\n\ndef blah():\n    ...\n\n# Main function\ndef main(argv):\n    # Parse command line args, environment, etc.\n    ...\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n```\n\n## Exercises\n\n### Exercise 3.15: `main()` functions\n\nIn the file `report.py` add a `main()` function that accepts a list of\ncommand line options and produces the same output as before.  You\nshould be able to run it interactively like this:\n\n```python\n>>> import report\n>>> report.main(['report.py', 'Data/portfolio.csv', 'Data/prices.csv'])\n      Name     Shares      Price     Change\n---------- ---------- ---------- ----------\n        AA        100       9.22     -22.98\n       IBM         50     106.28      15.18\n       CAT        150      35.46     -47.98\n      MSFT        200      20.89     -30.34\n        GE         95      13.48     -26.89\n      MSFT         50      20.89     -44.21\n       IBM        100     106.28      35.84\n>>>\n```\n\nModify the `pcost.py` file so that it has a similar `main()` function:\n\n```python\n>>> import pcost\n>>> pcost.main(['pcost.py', 'Data/portfolio.csv'])\nTotal cost: 44671.15\n>>>\n```\n\n### Exercise 3.16: Making Scripts\n\nModify the `report.py` and `pcost.py` programs so that they can\nexecute as a script on the command line:\n\n```bash\nbash $ python3 report.py Data/portfolio.csv Data/prices.csv\n      Name     Shares      Price     Change\n---------- ---------- ---------- ----------\n        AA        100       9.22     -22.98\n       IBM         50     106.28      15.18\n       CAT        150      35.46     -47.98\n      MSFT        200      20.89     -30.34\n        GE         95      13.48     -26.89\n      MSFT         50      20.89     -44.21\n       IBM        100     106.28      35.84\n\nbash $ python3 pcost.py Data/portfolio.csv\nTotal cost: 44671.15\n```\n\n[Contents](../Contents.md) \\| [Previous (3.4 Modules)](04_Modules.md) \\| [Next (3.6 Design Discussion)](06_Design_discussion.md)\n"
  },
  {
    "path": "Notes/03_Program_organization/06_Design_discussion.md",
    "content": "[Contents](../Contents.md) \\| [Previous (3.5 Main module)](05_Main_module.md) \\| [Next (4 Classes)](../04_Classes_objects/00_Overview.md)\n\n# 3.6 Design Discussion\n\nIn this section we reconsider a design decision made earlier.\n\n### Filenames versus Iterables\n\nCompare these two programs that return the same output.\n\n```python\n# Provide a filename\ndef read_data(filename):\n    records = []\n    with open(filename) as f:\n        for line in f:\n            ...\n            records.append(r)\n    return records\n\nd = read_data('file.csv')\n```\n\n```python\n# Provide lines\ndef read_data(lines):\n    records = []\n    for line in lines:\n        ...\n        records.append(r)\n    return records\n\nwith open('file.csv') as f:\n    d = read_data(f)\n```\n\n* Which of these functions do you prefer? Why?\n* Which of these functions is more flexible?\n\n### Deep Idea: \"Duck Typing\"\n\n[Duck Typing](https://en.wikipedia.org/wiki/Duck_typing) is a computer\nprogramming concept to determine whether an object can be used for a\nparticular purpose.  It is an application of the [duck\ntest](https://en.wikipedia.org/wiki/Duck_test).\n\n> If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.\n\nIn the second version of `read_data()` above, the function expects any\niterable object. Not just the lines of a file.\n\n```python\ndef read_data(lines):\n    records = []\n    for line in lines:\n        ...\n        records.append(r)\n    return records\n```\n\nThis means that we can use it with other *lines*.\n\n```python\n# A CSV file\nlines = open('data.csv')\ndata = read_data(lines)\n\n# A zipped file\nlines = gzip.open('data.csv.gz','rt')\ndata = read_data(lines)\n\n# The Standard Input\nlines = sys.stdin\ndata = read_data(lines)\n\n# A list of strings\nlines = ['ACME,50,91.1','IBM,75,123.45', ... ]\ndata = read_data(lines)\n```\n\nThere is considerable flexibility with this design.\n\n*Question: Should we embrace or fight this flexibility?*\n\n### Library Design Best Practices\n\nCode libraries are often better served by embracing flexibility.\nDon't restrict your options.  With great flexibility comes great power.\n\n## Exercise\n\n### Exercise 3.17: From filenames to file-like objects\n\nYou've now created a file `fileparse.py` that contained a\nfunction `parse_csv()`.  The function worked like this:\n\n```python\n>>> import fileparse\n>>> portfolio = fileparse.parse_csv('Data/portfolio.csv', types=[str,int,float])\n>>>\n```\n\nRight now, the function expects to be passed a filename.  However, you\ncan make the code more flexible.  Modify the function so that it works\nwith any file-like/iterable object.  For example:\n\n```\n>>> import fileparse\n>>> import gzip\n>>> with gzip.open('Data/portfolio.csv.gz', 'rt') as file:\n...      port = fileparse.parse_csv(file, types=[str,int,float])\n...\n>>> lines = ['name,shares,price', 'AA,100,34.23', 'IBM,50,91.1', 'HPE,75,45.1']\n>>> port = fileparse.parse_csv(lines, types=[str,int,float])\n>>>\n```\n\nIn this new code, what happens if you pass a filename as before?\n\n```\n>>> port = fileparse.parse_csv('Data/portfolio.csv', types=[str,int,float])\n>>> port\n... look at output (it should be crazy) ...\n>>>\n```\n\nYes, you'll need to be careful.   Could you add a safety check to avoid this?\n\n### Exercise 3.18: Fixing existing functions\n\nFix the `read_portfolio()` and `read_prices()` functions in the\n`report.py` file so that they work with the modified version of\n`parse_csv()`.  This should only involve a minor modification.\nAfterwards, your `report.py` and `pcost.py` programs should work\nthe same way they always did.\n\n[Contents](../Contents.md) \\| [Previous (3.5 Main module)](05_Main_module.md) \\| [Next (4 Classes)](../04_Classes_objects/00_Overview.md)"
  },
  {
    "path": "Notes/04_Classes_objects/00_Overview.md",
    "content": "[Contents](../Contents.md) \\| [Prev (3 Program Organization)](../03_Program_organization/00_Overview.md) \\| [Next (5 Inner Workings of Python Objects)](../05_Object_model/00_Overview.md)\n\n# 4. Classes and Objects\n\nSo far, our programs have only used built-in Python datatypes.  In\nthis section, we introduce the concept of classes and objects.  You'll\nlearn about the `class` statement that allows you to make new objects.\nWe'll also introduce the concept of inheritance, a tool that is commonly\nuse to build extensible programs.  Finally, we'll look at a few other\nfeatures of classes including special methods, dynamic attribute lookup,\nand defining new exceptions.\n\n* [4.1 Introducing Classes](01_Class.md)\n* [4.2 Inheritance](02_Inheritance.md)\n* [4.3 Special Methods](03_Special_methods.md)\n* [4.4 Defining new Exception](04_Defining_exceptions.md)\n\n[Contents](../Contents.md) \\| [Prev (3 Program Organization)](../03_Program_organization/00_Overview.md) \\| [Next (5 Inner Workings of Python Objects)](../05_Object_model/00_Overview.md)\n"
  },
  {
    "path": "Notes/04_Classes_objects/01_Class.md",
    "content": "[Contents](../Contents.md) \\| [Previous (3.6 Design discussion)](../03_Program_organization/06_Design_discussion.md) \\| [Next (4.2 Inheritance)](02_Inheritance.md)\n\n# 4.1 Classes\n\nThis section introduces the class statement and the idea of creating new objects.\n\n### Object Oriented (OO) programming\n\nA Programming technique where code is organized as a collection of\n*objects*.\n\nAn *object* consists of:\n\n* Data. Attributes\n* Behavior. Methods which are functions applied to the object.\n\nYou have already been using some OO during this course.\n\nFor example, manipulating a list.\n\n```python\n>>> nums = [1, 2, 3]\n>>> nums.append(4)      # Method\n>>> nums.insert(1,10)   # Method\n>>> nums\n[1, 10, 2, 3, 4]        # Data\n>>>\n```\n\n`nums` is an *instance* of a list.\n\nMethods (`append()` and `insert()`) are attached to the instance (`nums`).\n\n### The `class` statement\n\nUse the `class` statement to define a new object.\n\n```python\nclass Player:\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n        self.health = 100\n\n    def move(self, dx, dy):\n        self.x += dx\n        self.y += dy\n\n    def damage(self, pts):\n        self.health -= pts\n```\n\nIn a nutshell, a class is a set of functions that carry out various operations on so-called *instances*.\n\n### Instances\n\nInstances are the actual *objects* that you manipulate in your program.\n\nThey are created by calling the class as a function.\n\n```python\n>>> a = Player(2, 3)\n>>> b = Player(10, 20)\n>>>\n```\n\n`a` and `b` are instances of `Player`.\n\n*Emphasize: The class statement is just the definition (it does\n nothing by itself). Similar to a function definition.*\n\n### Instance Data\n\nEach instance has its own local data.\n\n```python\n>>> a.x\n2\n>>> b.x\n10\n```\n\nThis data is initialized by the `__init__()`.\n\n```python\nclass Player:\n    def __init__(self, x, y):\n        # Any value stored on `self` is instance data\n        self.x = x\n        self.y = y\n        self.health = 100\n```\n\nThere are no restrictions on the total number or type of attributes stored.\n\n### Instance Methods\n\nInstance methods are functions applied to instances of an object.\n\n```python\nclass Player:\n    ...\n    # `move` is a method\n    def move(self, dx, dy):\n        self.x += dx\n        self.y += dy\n```\n\nThe object itself is always passed as first argument.\n\n```python\n>>> a.move(1, 2)\n\n# matches `a` to `self`\n# matches `1` to `dx`\n# matches `2` to `dy`\ndef move(self, dx, dy):\n```\n\nBy convention, the instance is called `self`. However, the actual name\nused is unimportant. The object is always passed as the first\nargument. It is merely Python programming style to call this argument\n`self`.\n\n### Class Scoping\n\nClasses do not define a scope of names.\n\n```python\nclass Player:\n    ...\n    def move(self, dx, dy):\n        self.x += dx\n        self.y += dy\n\n    def left(self, amt):\n        move(-amt, 0)       # NO. Calls a global `move` function\n        self.move(-amt, 0)  # YES. Calls method `move` from above.\n```\n\nIf you want to operate on an instance, you always refer to it explicitly (e.g., `self`).\n\n## Exercises\n\nStarting with this set of exercises, we start to make a series of\nchanges to existing code from previous sections.  It is critical that\nyou have a working version of Exercise 3.18 to start.  If you don't\nhave that, please work from the solution code found in the\n`Solutions/3_18` directory.  It's fine to copy it.\n\n### Exercise 4.1: Objects as Data Structures\n\nIn section 2 and 3, we worked with data represented as tuples and\ndictionaries.  For example, a holding of stock could be represented as\na tuple like this:\n\n```python\ns = ('GOOG',100,490.10)\n```\n\nor as a dictionary like this:\n\n```python\ns = { 'name'   : 'GOOG',\n      'shares' : 100,\n      'price'  : 490.10\n}\n```\n\nYou can even write functions for manipulating such data.  For example:\n\n```python\ndef cost(s):\n    return s['shares'] * s['price']\n```\n\nHowever, as your program gets large, you might want to create a better\nsense of organization.  Thus, another approach for representing data\nwould be to define a class.  Create a file called `stock.py` and\ndefine a class `Stock` that represents a single holding of stock.\nHave the instances of `Stock` have `name`, `shares`, and `price`\nattributes.  For example:\n\n```python\n>>> import stock\n>>> a = stock.Stock('GOOG',100,490.10)\n>>> a.name\n'GOOG'\n>>> a.shares\n100\n>>> a.price\n490.1\n>>>\n```\n\nCreate a few more `Stock` objects and manipulate them.  For example:\n\n```python\n>>> b = stock.Stock('AAPL', 50, 122.34)\n>>> c = stock.Stock('IBM', 75, 91.75)\n>>> b.shares * b.price\n6117.0\n>>> c.shares * c.price\n6881.25\n>>> stocks = [a, b, c]\n>>> stocks\n[<stock.Stock object at 0x37d0b0>, <stock.Stock object at 0x37d110>, <stock.Stock object at 0x37d050>]\n>>> for s in stocks:\n     print(f'{s.name:>10s} {s.shares:>10d} {s.price:>10.2f}')\n\n... look at the output ...\n>>>\n```\n\nOne thing to emphasize here is that the class `Stock` acts like a\nfactory for creating instances of objects.  Basically, you call\nit as a function and it creates a new object for you.  Also, it must\nbe emphasized that each object is distinct---they each have their\nown data that is separate from other objects that have been created.\n\nAn object defined by a class is somewhat similar to a dictionary--just\nwith somewhat different syntax.  For example, instead of writing\n`s['name']` or `s['price']`, you now write `s.name` and `s.price`.\n\n### Exercise 4.2: Adding some Methods\n\nWith classes, you can attach functions to your objects.  These are\nknown as methods and are functions that operate on the data\nstored inside an object.  Add a `cost()` and `sell()` method to your\n`Stock` object.  They should work like this:\n\n```python\n>>> import stock\n>>> s = stock.Stock('GOOG', 100, 490.10)\n>>> s.cost()\n49010.0\n>>> s.shares\n100\n>>> s.sell(25)\n>>> s.shares\n75\n>>> s.cost()\n36757.5\n>>>\n```\n\n### Exercise 4.3: Creating a list of instances\n\nTry these steps to make a list of Stock instances from a list of\ndictionaries. Then compute the total cost:\n\n```python\n>>> import fileparse\n>>> with open('Data/portfolio.csv') as lines:\n...     portdicts = fileparse.parse_csv(lines, select=['name','shares','price'], types=[str,int,float])\n...\n>>> portfolio = [ stock.Stock(d['name'], d['shares'], d['price']) for d in portdicts]\n>>> portfolio\n[<stock.Stock object at 0x10c9e2128>, <stock.Stock object at 0x10c9e2048>, <stock.Stock object at 0x10c9e2080>,\n <stock.Stock object at 0x10c9e25f8>, <stock.Stock object at 0x10c9e2630>, <stock.Stock object at 0x10ca6f748>,\n <stock.Stock object at 0x10ca6f7b8>]\n>>> sum([s.cost() for s in portfolio])\n44671.15\n>>>\n```\n\n### Exercise 4.4: Using your class\n\nModify the `read_portfolio()` function in the `report.py` program so\nthat it reads a portfolio into a list of `Stock` instances as just\nshown in Exercise 4.3.  Once you have done that, fix all of the code\nin `report.py` and `pcost.py` so that it works with `Stock` instances\ninstead of dictionaries.\n\nHint: You should not have to make major changes to the code.  You will mainly\nbe changing dictionary access such as `s['shares']` into `s.shares`.\n\nYou should be able to run your functions the same as before:\n\n```python\n>>> import pcost\n>>> pcost.portfolio_cost('Data/portfolio.csv')\n44671.15\n>>> import report\n>>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv')\n      Name     Shares      Price     Change\n---------- ---------- ---------- ----------\n        AA        100       9.22     -22.98\n       IBM         50     106.28      15.18\n       CAT        150      35.46     -47.98\n      MSFT        200      20.89     -30.34\n        GE         95      13.48     -26.89\n      MSFT         50      20.89     -44.21\n       IBM        100     106.28      35.84\n>>>\n```\n\n[Contents](../Contents.md) \\| [Previous (3.6 Design discussion)](../03_Program_organization/06_Design_discussion.md) \\| [Next (4.2 Inheritance)](02_Inheritance.md)\n"
  },
  {
    "path": "Notes/04_Classes_objects/02_Inheritance.md",
    "content": "[Contents](../Contents.md) \\| [Previous (4.1 Classes)](01_Class.md) \\| [Next (4.3 Special methods)](03_Special_methods.md)\n\n# 4.2 Inheritance\n\nInheritance is a commonly used tool for writing extensible programs.\nThis section explores that idea.\n\n### Introduction\n\nInheritance is used to specialize existing objects:\n\n```python\nclass Parent:\n    ...\n\nclass Child(Parent):\n    ...\n```\n\nThe new class `Child` is called a derived class or subclass.  The\n`Parent` class is known as base class or superclass.  `Parent` is\nspecified in `()` after the class name, `class Child(Parent):`.\n\n### Extending\n\nWith inheritance, you are taking an existing class and:\n\n* Adding new methods\n* Redefining some of the existing methods\n* Adding new attributes to instances\n\nIn the end you are **extending existing code**.\n\n### Example\n\nSuppose that this is your starting class:\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n```\n\nYou can change any part of this via inheritance.\n\n### Add a new method\n\n```python\nclass MyStock(Stock):\n    def panic(self):\n        self.sell(self.shares)\n```\n\nUsage example.\n\n```python\n>>> s = MyStock('GOOG', 100, 490.1)\n>>> s.sell(25)\n>>> s.shares\n75\n>>> s.panic()\n>>> s.shares\n0\n>>>\n```\n\n### Redefining an existing method\n\n```python\nclass MyStock(Stock):\n    def cost(self):\n        return 1.25 * self.shares * self.price\n```\n\nUsage example.\n\n```python\n>>> s = MyStock('GOOG', 100, 490.1)\n>>> s.cost()\n61262.5\n>>>\n```\n\nThe new method takes the place of the old one. The other methods are unaffected. It's tremendous.\n\n## Overriding\n\nSometimes a class extends an existing method, but it wants to use the\noriginal implementation inside the redefinition.  For this, use `super()`:\n\n```python\nclass Stock:\n    ...\n    def cost(self):\n        return self.shares * self.price\n    ...\n\nclass MyStock(Stock):\n    def cost(self):\n        # Check the call to `super`\n        actual_cost = super().cost()\n        return 1.25 * actual_cost\n```\n\nUse `super()` to call the previous version.\n\n*Caution: In Python 2, the syntax was more verbose.*\n\n```python\nactual_cost = super(MyStock, self).cost()\n```\n\n### `__init__` and inheritance\n\nIf `__init__` is redefined, it is essential to initialize the parent.\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\nclass MyStock(Stock):\n    def __init__(self, name, shares, price, factor):\n        # Check the call to `super` and `__init__`\n        super().__init__(name, shares, price)\n        self.factor = factor\n\n    def cost(self):\n        return self.factor * super().cost()\n```\n\nYou should call the `__init__()` method on the `super` which is the\nway to call the previous version as shown previously.\n\n### Using Inheritance\n\nInheritance is sometimes used to organize related objects.\n\n```python\nclass Shape:\n    ...\n\nclass Circle(Shape):\n    ...\n\nclass Rectangle(Shape):\n    ...\n```\n\nThink of a logical hierarchy or taxonomy.  However, a more common (and\npractical) usage is related to making reusable or extensible code.\nFor example, a framework might define a base class and instruct you\nto customize it.\n\n```python\nclass CustomHandler(TCPHandler):\n    def handle_request(self):\n        ...\n        # Custom processing\n```\n\nThe base class contains some general purpose code.\nYour class inherits and customized specific parts.\n\n### \"is a\" relationship\n\nInheritance establishes a type relationship.\n\n```python\nclass Shape:\n    ...\n\nclass Circle(Shape):\n    ...\n```\n\nCheck for object instance.\n\n```python\n>>> c = Circle(4.0)\n>>> isinstance(c, Shape)\nTrue\n>>>\n```\n\n*Important: Ideally, any code that worked with instances of the parent\nclass will also work with instances of the child class.*\n\n### `object` base class\n\nIf a class has no parent, you sometimes see `object` used as the base.\n\n```python\nclass Shape(object):\n    ...\n```\n\n`object` is the parent of all objects in Python.\n\n*Note: it's not technically required, but you often see it specified\nas a hold-over from it's required use in Python 2. If omitted, the\nclass still implicitly inherits from `object`.\n\n### Multiple Inheritance\n\nYou can inherit from multiple classes by specifying them in the definition of the class.\n\n```python\nclass Mother:\n    ...\n\nclass Father:\n    ...\n\nclass Child(Mother, Father):\n    ...\n```\n\nThe class `Child` inherits features from both parents.  There are some\nrather tricky details. Don't do it unless you know what you are doing.\nSome further information will be given in the next section, but we're not\ngoing to utilize multiple inheritance further in this course.\n\n## Exercises\n\nA major use of inheritance is in writing code that's meant to be\nextended or customized in various ways--especially in libraries or\nframeworks. To illustrate, consider the `print_report()` function\nin your `report.py` program.  It should look something like this:\n\n```python\ndef print_report(reportdata):\n    '''\n    Print a nicely formatted table from a list of (name, shares, price, change) tuples.\n    '''\n    headers = ('Name','Shares','Price','Change')\n    print('%10s %10s %10s %10s' % headers)\n    print(('-'*10 + ' ')*len(headers))\n    for row in reportdata:\n        print('%10s %10d %10.2f %10.2f' % row)\n```\n\nWhen you run your report program, you should be getting output like this:\n\n```\n>>> import report\n>>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv')\n      Name     Shares      Price     Change\n---------- ---------- ---------- ----------\n        AA        100       9.22     -22.98\n       IBM         50     106.28      15.18\n       CAT        150      35.46     -47.98\n      MSFT        200      20.89     -30.34\n        GE         95      13.48     -26.89\n      MSFT         50      20.89     -44.21\n       IBM        100     106.28      35.84\n```\n\n### Exercise 4.5: An Extensibility Problem\n\nSuppose that you wanted to modify the `print_report()` function to\nsupport a variety of different output formats such as plain-text,\nHTML, CSV, or XML.  To do this, you could try to write one gigantic\nfunction that did everything.  However, doing so would likely lead to\nan unmaintainable mess.  Instead, this is a perfect opportunity to use\ninheritance instead.\n\nTo start, focus on the steps that are involved in a creating a table.\nAt the top of the table is a set of table headers.  After that, rows\nof table data appear.  Let's take those steps and put them into\ntheir own class.  Create a file called `tableformat.py` and define the\nfollowing class:\n\n```python\n# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headings.\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data.\n        '''\n        raise NotImplementedError()\n```\n\nThis class does nothing, but it serves as a kind of design specification for\nadditional classes that will be defined shortly.  A class like this is\nsometimes called an \"abstract base class.\"\n\nModify the `print_report()` function so that it accepts a\n`TableFormatter` object as input and invokes methods on it to produce\nthe output.  For example, like this:\n\n```python\n# report.py\n...\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formatted table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n```\n\nSince you added an argument to print_report(), you're going to need to modify the\n`portfolio_report()` function as well.  Change it so that it creates a `TableFormatter`\nlike this:\n\n```python\n# report.py\n\nimport tableformat\n\n...\ndef portfolio_report(portfoliofile, pricefile):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files\n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.TableFormatter()\n    print_report(report, formatter)\n```\n\nRun this new code:\n\n```python\n>>> ================================ RESTART ================================\n>>> import report\n>>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv')\n... crashes ...\n```\n\nIt should immediately crash with a `NotImplementedError` exception.  That's not\ntoo exciting, but it's exactly what we expected.  Continue to the next part.\n\n### Exercise 4.6: Using Inheritance to Produce Different Output\n\nThe `TableFormatter` class you defined in part (a) is meant to be\nextended via inheritance.  In fact, that's the whole idea.  To\nillustrate, define a class `TextTableFormatter` like this:\n\n```python\n# tableformat.py\n...\nclass TextTableFormatter(TableFormatter):\n    '''\n    Emit a table in plain-text format\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n```\n\nModify the `portfolio_report()` function like this and try it:\n\n```python\n# report.py\n...\ndef portfolio_report(portfoliofile, pricefile):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files\n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.TextTableFormatter()\n    print_report(report, formatter)\n```\n\nThis should produce the same output as before:\n\n```python\n>>> ================================ RESTART ================================\n>>> import report\n>>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv')\n      Name     Shares      Price     Change\n---------- ---------- ---------- ----------\n        AA        100       9.22     -22.98\n       IBM         50     106.28      15.18\n       CAT        150      35.46     -47.98\n      MSFT        200      20.89     -30.34\n        GE         95      13.48     -26.89\n      MSFT         50      20.89     -44.21\n       IBM        100     106.28      35.84\n>>>\n```\n\nHowever, let's change the output to something else.  Define a new\nclass `CSVTableFormatter` that produces output in CSV format:\n\n```python\n# tableformat.py\n...\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output portfolio data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n```\n\nModify your main program as follows:\n\n```python\ndef portfolio_report(portfoliofile, pricefile):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files\n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.CSVTableFormatter()\n    print_report(report, formatter)\n```\n\nYou should now see CSV output like this:\n\n```python\n>>> ================================ RESTART ================================\n>>> import report\n>>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv')\nName,Shares,Price,Change\nAA,100,9.22,-22.98\nIBM,50,106.28,15.18\nCAT,150,35.46,-47.98\nMSFT,200,20.89,-30.34\nGE,95,13.48,-26.89\nMSFT,50,20.89,-44.21\nIBM,100,106.28,35.84\n```\n\nUsing a similar idea, define a class `HTMLTableFormatter`\nthat produces a table with the following output:\n\n```\n<tr><th>Name</th><th>Shares</th><th>Price</th><th>Change</th></tr>\n<tr><td>AA</td><td>100</td><td>9.22</td><td>-22.98</td></tr>\n<tr><td>IBM</td><td>50</td><td>106.28</td><td>15.18</td></tr>\n<tr><td>CAT</td><td>150</td><td>35.46</td><td>-47.98</td></tr>\n<tr><td>MSFT</td><td>200</td><td>20.89</td><td>-30.34</td></tr>\n<tr><td>GE</td><td>95</td><td>13.48</td><td>-26.89</td></tr>\n<tr><td>MSFT</td><td>50</td><td>20.89</td><td>-44.21</td></tr>\n<tr><td>IBM</td><td>100</td><td>106.28</td><td>35.84</td></tr>\n```\n\nTest your code by modifying the main program to create a\n`HTMLTableFormatter` object instead of a\n`CSVTableFormatter` object.\n\n### Exercise 4.7: Polymorphism in Action\n\nA major feature of object-oriented programming is that you can\nplug an object into a program and it will work without having to\nchange any of the existing code.  For example, if you wrote a program\nthat expected to use a `TableFormatter` object, it would work no\nmatter what kind of `TableFormatter` you actually gave it.  This\nbehavior is sometimes referred to as \"polymorphism.\"\n\nOne potential problem is figuring out how to allow a user to pick out\nthe formatter that they want.  Direct use of the class names such as\n`TextTableFormatter` is often annoying.  Thus, you might consider some\nsimplified approach.  Perhaps you embed an `if-`statement into the\ncode like this:\n\n```python\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files\n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    if fmt == 'txt':\n        formatter = tableformat.TextTableFormatter()\n    elif fmt == 'csv':\n        formatter = tableformat.CSVTableFormatter()\n    elif fmt == 'html':\n        formatter = tableformat.HTMLTableFormatter()\n    else:\n        raise RuntimeError(f'Unknown format {fmt}')\n    print_report(report, formatter)\n```\n\nIn this code, the user specifies a simplified name such as `'txt'` or\n`'csv'` to pick a format.  However, is putting a big `if`-statement in\nthe `portfolio_report()` function like that the best idea?  It might\nbe better to move that code to a general purpose function somewhere\nelse.\n\nIn the `tableformat.py` file, add a function `create_formatter(name)`\nthat allows a user to create a formatter given an output name such as\n`'txt'`, `'csv'`, or `'html'`.  Modify `portfolio_report()` so that it\nlooks like this:\n\n```python\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files\n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n```\n\nTry calling the function with different formats to make sure it's working.\n\n### Exercise 4.8: Putting it all together\n\nModify the `report.py` program so that the `portfolio_report()` function takes\nan optional argument specifying the output format. For example:\n\n```python\n>>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv', 'txt')\n      Name     Shares      Price     Change\n---------- ---------- ---------- ----------\n        AA        100       9.22     -22.98\n       IBM         50     106.28      15.18\n       CAT        150      35.46     -47.98\n      MSFT        200      20.89     -30.34\n        GE         95      13.48     -26.89\n      MSFT         50      20.89     -44.21\n       IBM        100     106.28      35.84\n>>>\n```\n\nModify the main program so that a format can be given on the command line:\n\n```bash\nbash $ python3 report.py Data/portfolio.csv Data/prices.csv csv\nName,Shares,Price,Change\nAA,100,9.22,-22.98\nIBM,50,106.28,15.18\nCAT,150,35.46,-47.98\nMSFT,200,20.89,-30.34\nGE,95,13.48,-26.89\nMSFT,50,20.89,-44.21\nIBM,100,106.28,35.84\nbash $\n```\n\n### Discussion\n\nWriting extensible code is one of the most common uses of inheritance\nin libraries and frameworks.  For example, a framework might instruct\nyou to define your own object that inherits from a provided base\nclass.  You're then told to fill in various methods that implement\nvarious bits of functionality.\n\nAnother somewhat deeper concept is the idea of \"owning your\nabstractions.\"  In the exercises, we defined *our own class* for\nformatting a table.  You may look at your code and tell yourself \"I should\njust use a formatting library or something that someone else already\nmade instead!\"  No, you should use BOTH your class and a library.\nUsing your own class promotes loose coupling and is more flexible.\nAs long as your application uses the programming interface of your class,\nyou can change the internal implementation to work in any way that you\nwant.  You can write all-custom code.  You can use someone's third\nparty package.  You swap out one third-party package for a different\npackage when you find a better one.  It doesn't matter--none of\nyour application code will break as long as you preserve the\ninterface.   That's a powerful idea and it's one of the reasons why\nyou might consider inheritance for something like this.\n\nThat said, designing object oriented programs can be extremely\ndifficult.  For more information, you should probably look for books\non the topic of design patterns (although understanding what happened\nin this exercise will take you pretty far in terms of using objects in\na practically useful way).\n\n[Contents](../Contents.md) \\| [Previous (4.1 Classes)](01_Class.md) \\| [Next (4.3 Special methods)](03_Special_methods.md)\n"
  },
  {
    "path": "Notes/04_Classes_objects/03_Special_methods.md",
    "content": "[Contents](../Contents.md) \\| [Previous (4.2 Inheritance)](02_Inheritance.md) \\| [Next (4.4 Exceptions)](04_Defining_exceptions.md)\n\n# 4.3 Special Methods\n\nVarious parts of Python's behavior can be customized via special or so-called \"magic\" methods.\nThis section introduces that idea.  In addition dynamic attribute access and bound methods\nare discussed.\n\n### Introduction\n\nClasses may define special methods. These have special meaning to the\nPython interpreter.  They are always preceded and followed by\n`__`. For example `__init__`.\n\n```python\nclass Stock(object):\n    def __init__(self):\n        ...\n    def __repr__(self):\n        ...\n```\n\nThere are dozens of special methods, but we will only look at a few specific examples.\n\n### Special methods for String Conversions\n\nObjects have two string representations.\n\n```python\n>>> from datetime import date\n>>> d = date(2012, 12, 21)\n>>> print(d)\n2012-12-21\n>>> d\ndatetime.date(2012, 12, 21)\n>>>\n```\n\nThe `str()` function is used to create a nice printable output:\n\n```python\n>>> str(d)\n'2012-12-21'\n>>>\n```\n\nThe `repr()` function is used to create a more detailed representation\nfor programmers.\n\n```python\n>>> repr(d)\n'datetime.date(2012, 12, 21)'\n>>>\n```\n\nThose functions, `str()` and `repr()`, use a pair of special methods\nin the class to produce the string to be displayed.\n\n```python\nclass Date(object):\n    def __init__(self, year, month, day):\n        self.year = year\n        self.month = month\n        self.day = day\n\n    # Used with `str()`\n    def __str__(self):\n        return f'{self.year}-{self.month}-{self.day}'\n\n    # Used with `repr()`\n    def __repr__(self):\n        return f'Date({self.year},{self.month},{self.day})'\n```\n\n*Note: The convention for `__repr__()` is to return a string that,\n when fed to `eval()`, will recreate the underlying object. If this\n is not possible, some kind of easily readable representation is used\n instead.*\n\n### Special Methods for Mathematics\n\nMathematical operators involve calls to the following methods.\n\n```python\na + b       a.__add__(b)\na - b       a.__sub__(b)\na * b       a.__mul__(b)\na / b       a.__truediv__(b)\na // b      a.__floordiv__(b)\na % b       a.__mod__(b)\na << b      a.__lshift__(b)\na >> b      a.__rshift__(b)\na & b       a.__and__(b)\na | b       a.__or__(b)\na ^ b       a.__xor__(b)\na ** b      a.__pow__(b)\n-a          a.__neg__()\n~a          a.__invert__()\nabs(a)      a.__abs__()\n```\n\n### Special Methods for Item Access\n\nThese are the methods to implement containers.\n\n```python\nlen(x)      x.__len__()\nx[a]        x.__getitem__(a)\nx[a] = v    x.__setitem__(a,v)\ndel x[a]    x.__delitem__(a)\n```\n\nYou can use them in your classes.\n\n```python\nclass Sequence:\n    def __len__(self):\n        ...\n    def __getitem__(self,a):\n        ...\n    def __setitem__(self,a,v):\n        ...\n    def __delitem__(self,a):\n        ...\n```\n\n### Method Invocation\n\nInvoking a method is a two-step process.\n\n1. Lookup: The `.` operator\n2. Method call: The `()` operator\n\n```python\n>>> s = Stock('GOOG',100,490.10)\n>>> c = s.cost  # Lookup\n>>> c\n<bound method Stock.cost of <Stock object at 0x590d0>>\n>>> c()         # Method call\n49010.0\n>>>\n```\n\n### Bound Methods\n\nA method that has not yet been invoked by the function call operator `()` is known as a *bound method*.\nIt operates on the instance where it originated.\n\n```python\n>>> s = Stock('GOOG', 100, 490.10)\n>>> s\n<Stock object at 0x590d0>\n>>> c = s.cost\n>>> c\n<bound method Stock.cost of <Stock object at 0x590d0>>\n>>> c()\n49010.0\n>>>\n```\n\nBound methods are often a source of careless non-obvious errors. For example:\n\n```python\n>>> s = Stock('GOOG', 100, 490.10)\n>>> print('Cost : %0.2f' % s.cost)\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: float argument required\n>>>\n```\n\nOr devious behavior that's hard to debug.\n\n```python\nf = open(filename, 'w')\n...\nf.close     # Oops, Didn't do anything at all. `f` still open.\n```\n\nIn both of these cases, the error is cause by forgetting to include the\ntrailing parentheses.  For example, `s.cost()` or `f.close()`.\n\n### Attribute Access\n\nThere is an alternative way to access, manipulate and manage attributes.\n\n```python\ngetattr(obj, 'name')          # Same as obj.name\nsetattr(obj, 'name', value)   # Same as obj.name = value\ndelattr(obj, 'name')          # Same as del obj.name\nhasattr(obj, 'name')          # Tests if attribute exists\n```\n\nExample:\n\n```python\nif hasattr(obj, 'x'):\n    x = getattr(obj, 'x'):\nelse:\n    x = None\n```\n\n*Note: `getattr()` also has a useful default value *arg*.\n\n```python\nx = getattr(obj, 'x', None)\n```\n\n## Exercises\n\n### Exercise 4.9: Better output for printing objects\n\nModify the `Stock` object that you defined in `stock.py`\nso that the `__repr__()` method produces more useful output.  For\nexample:\n\n```python\n>>> goog = Stock('GOOG', 100, 490.1)\n>>> goog\nStock('GOOG', 100, 490.1)\n>>>\n```\n\nSee what happens when you read a portfolio of stocks and view the\nresulting list after you have made these changes.  For example:\n\n```\n>>> import report\n>>> portfolio = report.read_portfolio('Data/portfolio.csv')\n>>> portfolio\n... see what the output is ...\n>>>\n```\n\n### Exercise 4.10: An example of using getattr()\n\n`getattr()` is an alternative mechanism for reading attributes.  It can be used to\nwrite extremely flexible code.  To begin, try this example:\n\n```python\n>>> import stock\n>>> s = stock.Stock('GOOG', 100, 490.1)\n>>> columns = ['name', 'shares']\n>>> for colname in columns:\n        print(colname, '=', getattr(s, colname))\n\nname = GOOG\nshares = 100\n>>>\n```\n\nCarefully observe that the output data is determined entirely by the attribute\nnames listed in the `columns` variable.\n\nIn the file `tableformat.py`, take this idea and expand it into a generalized\nfunction `print_table()` that prints a table showing\nuser-specified attributes of a list of arbitrary objects.  As with the\nearlier `print_report()` function, `print_table()` should also accept\na `TableFormatter` instance to control the output format.  Here's how\nit should work:\n\n```python\n>>> import report\n>>> portfolio = report.read_portfolio('Data/portfolio.csv')\n>>> from tableformat import create_formatter, print_table\n>>> formatter = create_formatter('txt')\n>>> print_table(portfolio, ['name','shares'], formatter)\n      name     shares\n---------- ----------\n        AA        100\n       IBM         50\n       CAT        150\n      MSFT        200\n        GE         95\n      MSFT         50\n       IBM        100\n\n>>> print_table(portfolio, ['name','shares','price'], formatter)\n      name     shares      price\n---------- ---------- ----------\n        AA        100       32.2\n       IBM         50       91.1\n       CAT        150      83.44\n      MSFT        200      51.23\n        GE         95      40.37\n      MSFT         50       65.1\n       IBM        100      70.44\n>>>\n```\n\n[Contents](../Contents.md) \\| [Previous (4.2 Inheritance)](02_Inheritance.md) \\| [Next (4.4 Exceptions)](04_Defining_exceptions.md)\n\n"
  },
  {
    "path": "Notes/04_Classes_objects/04_Defining_exceptions.md",
    "content": "[Contents](../Contents.md) \\| [Previous (4.3 Special methods)](03_Special_methods.md) \\| [Next (5 Object Model)](../05_Object_model/00_Overview.md)\n\n# 4.4 Defining Exceptions\n\nUser defined exceptions are defined by classes.\n\n```python\nclass NetworkError(Exception):\n    pass\n```\n\n**Exceptions always inherit from `Exception`.**\n\nUsually they are empty classes. Use `pass` for the body.\n\nYou can also make a hierarchy of your exceptions.\n\n```python\nclass AuthenticationError(NetworkError):\n     pass\n\nclass ProtocolError(NetworkError):\n    pass\n```\n\n## Exercises\n\n### Exercise 4.11: Defining a custom exception\n\nIt is often good practice for libraries to define their own exceptions.\n\nThis makes it easier to distinguish between Python exceptions raised\nin response to common programming errors versus exceptions\nintentionally raised by a library to a signal a specific usage\nproblem.\n\nModify the `create_formatter()` function from the last exercise so\nthat it raises a custom `FormatError` exception when the user provides\na bad format name.\n\nFor example:\n\n```python\n>>> from tableformat import create_formatter\n>>> formatter = create_formatter('xls')\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\n  File \"tableformat.py\", line 71, in create_formatter\n    raise FormatError('Unknown table format %s' % name)\nFormatError: Unknown table format xls\n>>>\n```\n\n[Contents](../Contents.md) \\| [Previous (4.3 Special methods)](03_Special_methods.md) \\| [Next (5 Object Model)](../05_Object_model/00_Overview.md)\n"
  },
  {
    "path": "Notes/05_Object_model/00_Overview.md",
    "content": "[Contents](../Contents.md) \\| [Prev (4 Classes and Objects)](../04_Classes_objects/00_Overview.md) \\| [Next (6 Generators)](../06_Generators/00_Overview.md)\n\n# 5. Inner Workings of Python Objects\n\nThis section covers some of the inner workings of Python objects.\nProgrammers coming from other programming languages often find\nPython's notion of classes lacking in features.  For example, there is\nno notion of access-control (e.g., private, protected), the whole\n`self` argument feels weird, and frankly, working with objects\nsometimes feel like a \"free for all.\"  Maybe that's true, but we'll\nfind out how it all works as well as some common programming idioms to\nbetter encapsulate the internals of objects.\n\nIt's not necessary to worry about the inner details to be productive.\nHowever, most Python coders have a basic awareness of how classes\nwork.  So, that's why we're covering it.\n\n* [5.1 Dictionaries Revisited (Object Implementation)](01_Dicts_revisited.md)\n* [5.2 Encapsulation Techniques](02_Classes_encapsulation.md)\n\n[Contents](../Contents.md) \\| [Prev (4 Classes and Objects)](../04_Classes_objects/00_Overview.md) \\| [Next (6 Generators)](../06_Generators/00_Overview.md)\n\n"
  },
  {
    "path": "Notes/05_Object_model/01_Dicts_revisited.md",
    "content": "[Contents](../Contents.md) \\| [Previous (4.4 Exceptions)](../04_Classes_objects/04_Defining_exceptions.md) \\| [Next (5.2 Encapsulation)](02_Classes_encapsulation.md)\n\n# 5.1 Dictionaries Revisited\n\nThe Python object system is largely based on an implementation\ninvolving dictionaries.  This section discusses that.\n\n### Dictionaries, Revisited\n\nRemember that a dictionary is a collection of named values.\n\n```python\nstock = {\n    'name' : 'GOOG',\n    'shares' : 100,\n    'price' : 490.1\n}\n```\n\nDictionaries are commonly used for simple data structures.  However,\nthey are used for critical parts of the interpreter and may be the\n*most important type of data in Python*.\n\n### Dicts and Modules\n\nWithin a module, a dictionary holds all of the global variables and\nfunctions.\n\n```python\n# foo.py\n\nx = 42\ndef bar():\n    ...\n\ndef spam():\n    ...\n```\n\nIf you inspect `foo.__dict__` or `globals()`, you'll see the dictionary.\n\n```python\n{\n    'x' : 42,\n    'bar' : <function bar>,\n    'spam' : <function spam>\n}\n```\n\n### Dicts and Objects\n\nUser defined objects also use dictionaries for both instance data and\nclasses.  In fact, the entire object system is mostly an extra layer\nthat's put on top of dictionaries.\n\nA dictionary holds the instance data, `__dict__`.\n\n```python\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.__dict__\n{'name' : 'GOOG', 'shares' : 100, 'price': 490.1 }\n```\n\nYou populate this dict (and instance) when assigning to `self`.\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n```\n\nThe instance data, `self.__dict__`, looks like this:\n\n```python\n{\n    'name': 'GOOG',\n    'shares': 100,\n    'price': 490.1\n}\n```\n\n**Each instance gets its own private dictionary.**\n\n```python\ns = Stock('GOOG', 100, 490.1)     # {'name' : 'GOOG','shares' : 100, 'price': 490.1 }\nt = Stock('AAPL', 50, 123.45)     # {'name' : 'AAPL','shares' : 50, 'price': 123.45 }\n```\n\nIf you created 100 instances of some class, there are 100 dictionaries\nsitting around holding data.\n\n### Class Members\n\nA separate dictionary also holds the methods.\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    def cost(self):\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        self.shares -= nshares\n```\n\nThe dictionary is in `Stock.__dict__`.\n\n```python\n{\n    'cost': <function>,\n    'sell': <function>,\n    '__init__': <function>\n}\n```\n\n### Instances and Classes\n\nInstances and classes are linked together.  The `__class__` attribute\nrefers back to the class.\n\n```python\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.__dict__\n{ 'name': 'GOOG', 'shares': 100, 'price': 490.1 }\n>>> s.__class__\n<class '__main__.Stock'>\n>>>\n```\n\nThe instance dictionary holds data unique to each instance, whereas\nthe class dictionary holds data collectively shared by *all*\ninstances.\n\n### Attribute Access\n\nWhen you work with objects, you access data and methods using the `.` operator.\n\n```python\nx = obj.name          # Getting\nobj.name = value      # Setting\ndel obj.name          # Deleting\n```\n\nThese operations are directly tied to the dictionaries sitting underneath the covers.\n\n### Modifying Instances\n\nOperations that modify an object update the underlying dictionary.\n\n```python\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.__dict__\n{ 'name':'GOOG', 'shares': 100, 'price': 490.1 }\n>>> s.shares = 50       # Setting\n>>> s.date = '6/7/2007' # Setting\n>>> s.__dict__\n{ 'name': 'GOOG', 'shares': 50, 'price': 490.1, 'date': '6/7/2007' }\n>>> del s.shares        # Deleting\n>>> s.__dict__\n{ 'name': 'GOOG', 'price': 490.1, 'date': '6/7/2007' }\n>>>\n```\n\n### Reading Attributes\n\nSuppose you read an attribute on an instance.\n\n```python\nx = obj.name\n```\n\nThe attribute may exist in two places:\n\n* Local instance dictionary.\n* Class dictionary.\n\nBoth dictionaries must be checked.  First, check in local `__dict__`.\nIf not found, look in `__dict__` of class through `__class__`.\n\n```python\n>>> s = Stock(...)\n>>> s.name\n'GOOG'\n>>> s.cost()\n49010.0\n>>>\n```\n\nThis lookup scheme is how the members of a *class* get shared by all instances.\n\n### How inheritance works\n\nClasses may inherit from other classes.\n\n```python\nclass A(B, C):\n    ...\n```\n\nThe base classes are stored in a tuple in each class.\n\n```python\n>>> A.__bases__\n(<class '__main__.B'>, <class '__main__.C'>)\n>>>\n```\n\nThis provides a link to parent classes.\n\n### Reading Attributes with Inheritance\n\nLogically, the process of finding an attribute is as follows. First,\ncheck in local `__dict__`.  If not found, look in `__dict__` of the\nclass.  If not found in class, look in the base classes through\n`__bases__`.   However, there are some subtle aspects of this discussed next.\n\n### Reading Attributes with Single Inheritance\n\nIn inheritance hierarchies, attributes are found by walking up the\ninheritance tree in order.\n\n```python\nclass A: pass\nclass B(A): pass\nclass C(A): pass\nclass D(B): pass\nclass E(D): pass\n```\nWith single inheritance, there is single path to the top.\nYou stop with the first match.\n\n### Method Resolution Order or MRO\n\nPython precomputes an inheritance chain and stores it in the *MRO* attribute on the class.\nYou can view it.\n\n```python\n>>> E.__mro__\n(<class '__main__.E'>, <class '__main__.D'>,\n <class '__main__.B'>, <class '__main__.A'>,\n <type 'object'>)\n>>>\n```\n\nThis chain is called the **Method Resolution Order**.  To find an\nattribute, Python walks the MRO in order. The first match wins.\n\n### MRO in Multiple Inheritance\n\nWith multiple inheritance, there is no single path to the top.\nLet's take a look at an example.\n\n```python\nclass A: pass\nclass B: pass\nclass C(A, B): pass\nclass D(B): pass\nclass E(C, D): pass\n```\n\nWhat happens when you access an attribute?\n\n```python\ne = E()\ne.attr\n```\n\nAn attribute search process is carried out, but what is the order? That's a problem.\n\nPython uses *cooperative multiple inheritance* which obeys some rules\nabout class ordering.\n\n* Children are always checked before parents\n* Parents (if multiple) are always checked in the order listed.\n\nThe MRO is computed by sorting all of the classes in a hierarchy\naccording to those rules.\n\n```python\n>>> E.__mro__\n(\n  <class 'E'>,\n  <class 'C'>,\n  <class 'A'>,\n  <class 'D'>,\n  <class 'B'>,\n  <class 'object'>)\n>>>\n```\n\nThe underlying algorithm is called the \"C3 Linearization Algorithm.\"\nThe precise details aren't important as long as you remember that a\nclass hierarchy obeys the same ordering rules you might follow if your\nhouse was on fire and you had to evacuate--children first, followed by\nparents.\n\n### An Odd Code Reuse (Involving Multiple Inheritance)\n\nConsider two completely unrelated objects:\n\n```python\nclass Dog:\n    def noise(self):\n        return 'Bark'\n\n    def chase(self):\n        return 'Chasing!'\n\nclass LoudDog(Dog):\n    def noise(self):\n        # Code commonality with LoudBike (below)\n        return super().noise().upper()\n```\n\nAnd\n\n```python\nclass Bike:\n    def noise(self):\n        return 'On Your Left'\n\n    def pedal(self):\n        return 'Pedaling!'\n\nclass LoudBike(Bike):\n    def noise(self):\n        # Code commonality with LoudDog (above)\n        return super().noise().upper()\n```\n\nThere is a code commonality in the implementation of `LoudDog.noise()` and\n`LoudBike.noise()`.  In fact, the code is exactly the same.  Naturally,\ncode like that is bound to attract software engineers.\n\n### The \"Mixin\" Pattern\n\nThe *Mixin* pattern is a class with a fragment of code.\n\n```python\nclass Loud:\n    def noise(self):\n        return super().noise().upper()\n```\n\nThis class is not usable in isolation.\nIt mixes with other classes via inheritance.\n\n```python\nclass LoudDog(Loud, Dog):\n    pass\n\nclass LoudBike(Loud, Bike):\n    pass\n```\n\nMiraculously, loudness was now implemented just once and reused\nin two completely unrelated classes.  This sort of trick is one\nof the primary uses of multiple inheritance in Python.\n\n### Why `super()`\n\nAlways use `super()` when overriding methods.\n\n```python\nclass Loud:\n    def noise(self):\n        return super().noise().upper()\n```\n\n`super()` delegates to the *next class* on the MRO.\n\nThe tricky bit is that you don't know what it is.  You especially don't\nknow what it is if multiple inheritance is being used.\n\n### Some Cautions\n\nMultiple inheritance is a powerful tool. Remember that with power\ncomes responsibility.  Frameworks / libraries sometimes use it for\nadvanced features involving composition of components.  Now, forget\nthat you saw that.\n\n## Exercises\n\nIn Section 4, you defined a class `Stock` that represented a holding of stock.\nIn this exercise, we will use that class.  Restart the interpreter and make a\nfew instances:\n\n```python\n>>> ================================ RESTART ================================\n>>> from stock import Stock\n>>> goog = Stock('GOOG',100,490.10)\n>>> ibm  = Stock('IBM',50, 91.23)\n>>>\n```\n\n### Exercise 5.1: Representation of Instances\n\nAt the interactive shell, inspect the underlying dictionaries of the\ntwo instances you created:\n\n```python\n>>> goog.__dict__\n... look at the output ...\n>>> ibm.__dict__\n... look at the output ...\n>>>\n```\n\n### Exercise 5.2: Modification of Instance Data\n\nTry setting a new attribute on one of the above instances:\n\n```python\n>>> goog.date = '6/11/2007'\n>>> goog.__dict__\n... look at output ...\n>>> ibm.__dict__\n... look at output ...\n>>>\n```\n\nIn the above output, you'll notice that the `goog` instance has a\nattribute `date` whereas the `ibm` instance does not.  It is important\nto note that Python really doesn't place any restrictions on\nattributes.  For example, the attributes of an instance are not\nlimited to those set up in the `__init__()` method.\n\nInstead of setting an attribute, try placing a new value directly into\nthe `__dict__` object:\n\n```python\n>>> goog.__dict__['time'] = '9:45am'\n>>> goog.time\n'9:45am'\n>>>\n```\n\nHere, you really notice the fact that an instance is just a layer on\ntop of a dictionary.  Note: it should be emphasized that direct\nmanipulation of the dictionary is uncommon--you should always write\nyour code to use the (.) syntax.\n\n### Exercise 5.3: The role of classes\n\nThe definitions that make up a class definition are shared by all\ninstances of that class.  Notice, that all instances have a link back\nto their associated class:\n\n```python\n>>> goog.__class__\n... look at output ...\n>>> ibm.__class__\n... look at output ...\n>>>\n```\n\nTry calling a method on the instances:\n\n```python\n>>> goog.cost()\n49010.0\n>>> ibm.cost()\n4561.5\n>>>\n```\n\nNotice that the name 'cost' is not defined in either `goog.__dict__`\nor `ibm.__dict__`.  Instead, it is being supplied by the class\ndictionary.  Try this:\n\n```python\n>>> Stock.__dict__['cost']\n... look at output ...\n>>>\n```\n\nTry calling the `cost()` method directly through the dictionary:\n\n```python\n>>> Stock.__dict__['cost'](goog)\n49010.0\n>>> Stock.__dict__['cost'](ibm)\n4561.5\n>>>\n```\n\nNotice how you are calling the function defined in the class\ndefinition and how the `self` argument gets the instance.\n\nTry adding a new attribute to the `Stock` class:\n\n```python\n>>> Stock.foo = 42\n>>>\n```\n\nNotice how this new attribute now shows up on all of the instances:\n\n```python\n>>> goog.foo\n42\n>>> ibm.foo\n42\n>>>\n```\n\nHowever, notice that it is not part of the instance dictionary:\n\n```python\n>>> goog.__dict__\n... look at output and notice there is no 'foo' attribute ...\n>>>\n```\n\nThe reason you can access the `foo` attribute on instances is that\nPython always checks the class dictionary if it can't find something\non the instance itself.\n\nNote: This part of the exercise illustrates something known as a class\nvariable.  Suppose, for instance, you have a class like this:\n\n```python\nclass Foo(object):\n     a = 13                  # Class variable\n     def __init__(self,b):\n         self.b = b          # Instance variable\n```\n\nIn this class, the variable `a`, assigned in the body of the\nclass itself, is a \"class variable.\"  It is shared by all of the\ninstances that get created.  For example:\n\n```python\n>>> f = Foo(10)\n>>> g = Foo(20)\n>>> f.a          # Inspect the class variable (same for both instances)\n13\n>>> g.a\n13\n>>> f.b          # Inspect the instance variable (differs)\n10\n>>> g.b\n20\n>>> Foo.a = 42   # Change the value of the class variable\n>>> f.a\n42\n>>> g.a\n42\n>>>\n```\n\n### Exercise 5.4: Bound methods\n\nA subtle feature of Python is that invoking a method actually involves\ntwo steps and something known as a bound method.   For example:\n\n```python\n>>> s = goog.sell\n>>> s\n<bound method Stock.sell of Stock('GOOG', 100, 490.1)>\n>>> s(25)\n>>> goog.shares\n75\n>>>\n```\n\nBound methods actually contain all of the pieces needed to call a\nmethod.  For instance, they keep a record of the function implementing\nthe method:\n\n```python\n>>> s.__func__\n<function sell at 0x10049af50>\n>>>\n```\n\nThis is the same value as found in the `Stock` dictionary.\n\n```python\n>>> Stock.__dict__['sell']\n<function sell at 0x10049af50>\n>>>\n```\n\nBound methods also record the instance, which is the `self`\nargument.\n\n```python\n>>> s.__self__\nStock('GOOG',75,490.1)\n>>>\n```\n\nWhen you invoke the function using `()` all of the pieces come\ntogether.  For example, calling `s(25)` actually does this:\n\n```python\n>>> s.__func__(s.__self__, 25)    # Same as s(25)\n>>> goog.shares\n50\n>>>\n```\n\n### Exercise 5.5: Inheritance\n\nMake a new class that inherits from `Stock`.\n\n```\n>>> class NewStock(Stock):\n        def yow(self):\n            print('Yow!')\n\n>>> n = NewStock('ACME', 50, 123.45)\n>>> n.cost()\n6172.50\n>>> n.yow()\nYow!\n>>>\n```\n\nInheritance is implemented by extending the search process for attributes.\nThe `__bases__` attribute has a tuple of the immediate parents:\n\n```python\n>>> NewStock.__bases__\n(<class 'stock.Stock'>,)\n>>>\n```\n\nThe `__mro__` attribute has a tuple of all parents, in the order that\nthey will be searched for attributes.\n\n```python\n>>> NewStock.__mro__\n(<class '__main__.NewStock'>, <class 'stock.Stock'>, <class 'object'>)\n>>>\n```\n\nHere's how the `cost()` method of instance `n` above would be found:\n\n```python\n>>> for cls in n.__class__.__mro__:\n        if 'cost' in cls.__dict__:\n            break\n\n>>> cls\n<class '__main__.Stock'>\n>>> cls.__dict__['cost']\n<function cost at 0x101aed598>\n>>>\n```\n\n[Contents](../Contents.md) \\| [Previous (4.4 Exceptions)](../04_Classes_objects/04_Defining_exceptions.md) \\| [Next (5.2 Encapsulation)](02_Classes_encapsulation.md)\n"
  },
  {
    "path": "Notes/05_Object_model/02_Classes_encapsulation.md",
    "content": "[Contents](../Contents.md) \\| [Previous (5.1 Dictionaries Revisited)](01_Dicts_revisited.md) \\| [Next (6 Generators)](../06_Generators/00_Overview.md)\n\n# 5.2 Classes and Encapsulation\n\nWhen writing classes, it is common to try and encapsulate internal details.\nThis section introduces a few Python programming idioms for this including\nprivate variables and properties.\n\n### Public vs Private.\n\nOne of the primary roles of a class is to encapsulate data and internal\nimplementation details of an object.  However, a class also defines a\n*public* interface that the outside world is supposed to use to\nmanipulate the object.  This distinction between implementation\ndetails and the public interface is important.\n\n### A Problem\n\nIn Python, almost everything about classes and objects is *open*.\n\n* You can easily inspect object internals.\n* You can change things at will.\n* There is no strong notion of access-control (i.e., private class members)\n\nThat is an issue when you are trying to isolate details of the *internal implementation*.\n\n### Python Encapsulation\n\nPython relies on programming conventions to indicate the intended use\nof something.  These conventions are based on naming.  There is a\ngeneral attitude that it is up to the programmer to observe the rules\nas opposed to having the language enforce them.\n\n### Private Attributes\n\nAny attribute name with leading `_` is considered to be *private*.\n\n```python\nclass Person(object):\n    def __init__(self, name):\n        self._name = 0\n```\n\nAs mentioned earlier, this is only a programming style. You can still\naccess and change it.\n\n```python\n>>> p = Person('Guido')\n>>> p._name\n'Guido'\n>>> p._name = 'Dave'\n>>>\n```\n\nAs a general rule, any name with a leading `_` is considered internal implementation\nwhether it's a variable, a function, or a module name.  If you find yourself using such\nnames directly, you're probably doing something wrong. Look for higher level functionality.\n\n### Simple Attributes\n\nConsider the following class.\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n```\n\nA surprising feature is that you can set the attributes\nto any value at all:\n\n```python\n>>> s = Stock('IBM', 50, 91.1)\n>>> s.shares = 100\n>>> s.shares = \"hundred\"\n>>> s.shares = [1, 0, 0]\n>>>\n```\n\nYou might look at that and think you want some extra checks.\n\n```python\ns.shares = '50'     # Raise a TypeError, this is a string\n```\n\nHow would you do it?\n\n### Managed Attributes\n\nOne approach: introduce accessor methods.\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.set_shares(shares)\n        self.price = price\n\n    # Function that layers the \"get\" operation\n    def get_shares(self):\n        return self._shares\n\n    # Function that layers the \"set\" operation\n    def set_shares(self, value):\n        if not isinstance(value, int):\n            raise TypeError('Expected an int')\n        self._shares = value\n```\n\nToo bad that this breaks all of our existing code. `s.shares = 50`\nbecomes `s.set_shares(50)`\n\n### Properties\n\nThere is an alternative approach to the previous pattern.\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    @property\n    def shares(self):\n        return self._shares\n\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, int):\n            raise TypeError('Expected int')\n        self._shares = value\n```\n\nNormal attribute access now triggers the getter and setter methods\nunder `@property` and `@shares.setter`.\n\n```python\n>>> s = Stock('IBM', 50, 91.1)\n>>> s.shares         # Triggers @property\n50\n>>> s.shares = 75    # Triggers @shares.setter\n>>>\n```\n\nWith this pattern, there are *no changes* needed to the source code.\nThe new *setter* is also called when there is an assignment within the class,\nincluding inside the `__init__()` method.\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        ...\n        # This assignment calls the setter below\n        self.shares = shares\n        ...\n\n    ...\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, int):\n            raise TypeError('Expected int')\n        self._shares = value\n```\n\nThere is often a confusion between a property and the use of private names.\nAlthough a property internally uses a private name like `_shares`, the rest\nof the class (not the property) can continue to use a name like `shares`.\n\nProperties are also useful for computed data attributes.\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n\n    @property\n    def cost(self):\n        return self.shares * self.price\n    ...\n```\n\nThis allows you to drop the extra parentheses, hiding the fact that it's actually a method:\n\n```python\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.shares # Instance variable\n100\n>>> s.cost   # Computed Value\n49010.0\n>>>\n```\n\n### Uniform access\n\nThe last example shows how to put a more uniform interface on an object.\nIf you don't do this, an object might be confusing to use:\n\n```python\n>>> s = Stock('GOOG', 100, 490.1)\n>>> a = s.cost() # Method\n49010.0\n>>> b = s.shares # Data attribute\n100\n>>>\n```\n\nWhy is the `()` required for the cost, but not for the shares?  A property\ncan fix this.\n\n### Decorator Syntax\n\nThe `@` syntax is known as \"decoration\".  It specifies a modifier\nthat's applied to the function definition that immediately follows.\n\n```python\n...\n@property\ndef cost(self):\n    return self.shares * self.price\n```\n\nMore details are given in [Section 7](../07_Advanced_Topics/00_Overview).\n\n### `__slots__` Attribute\n\nYou can restrict the set of attributes names.\n\n```python\nclass Stock:\n    __slots__ = ('name','_shares','price')\n    def __init__(self, name, shares, price):\n        self.name = name\n        ...\n```\n\nIt will raise an error for other attributes.\n\n```python\n>>> s.price = 385.15\n>>> s.prices = 410.2\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in ?\nAttributeError: 'Stock' object has no attribute 'prices'\n```\n\nAlthough this prevents errors and restricts usage of objects, it's actually used for performance and\nmakes Python use memory more efficiently.\n\n### Final Comments on Encapsulation\n\nDon't go overboard with private attributes, properties, slots,\netc. They serve a specific purpose and you may see them when reading\nother Python code.  However, they are not necessary for most\nday-to-day coding.\n\n## Exercises\n\n### Exercise 5.6: Simple Properties\n\nProperties are a useful way to add \"computed attributes\" to an object.\nIn `stock.py`, you created an object `Stock`.  Notice that on your\nobject there is a slight inconsistency in how different kinds of data\nare extracted:\n\n```python\n>>> from stock import Stock\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.shares\n100\n>>> s.price\n490.1\n>>> s.cost()\n49010.0\n>>>\n```\n\nSpecifically, notice how you have to add the extra () to `cost` because it is a method.\n\nYou can get rid of the extra () on `cost()` if you turn it into a property.\nTake your `Stock` class and modify it so that the cost calculation works like this:\n\n```python\n>>> ================================ RESTART ================================\n>>> from stock import Stock\n>>> s = Stock('GOOG', 100, 490.1)\n>>> s.cost\n49010.0\n>>>\n```\n\nTry calling `s.cost()` as a function and observe that it\ndoesn't work now that `cost` has been defined as a property.\n\n```python\n>>> s.cost()\n... fails ...\n>>>\n```\n\nMaking this change will likely break your earlier `pcost.py` program.\nYou might need to go back and get rid of the `()` on the `cost()` method.\n\n### Exercise 5.7: Properties and Setters\n\nModify the `shares` attribute so that the value is stored in a\nprivate attribute and that a pair of property functions are used to ensure\nthat it is always set to an integer value.  Here is an example of the expected\nbehavior:\n\n```python\n>>> ================================ RESTART ================================\n>>> from stock import Stock\n>>> s = Stock('GOOG',100,490.10)\n>>> s.shares = 50\n>>> s.shares = 'a lot'\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: expected an integer\n>>>\n```\n\n### Exercise 5.8: Adding slots\n\nModify the `Stock` class so that it has a `__slots__` attribute.  Then,\nverify that new attributes can't be added:\n\n```python\n>>> ================================ RESTART ================================\n>>> from stock import Stock\n>>> s = Stock('GOOG', 100, 490.10)\n>>> s.name\n'GOOG'\n>>> s.blah = 42\n... see what happens ...\n>>>\n```\n\nWhen you use `__slots__`, Python uses a more efficient\ninternal representation of objects.   What happens if you try to\ninspect the underlying dictionary of `s` above?\n\n```python\n>>> s.__dict__\n... see what happens ...\n>>>\n```\n\nIt should be noted that `__slots__` is most commonly used as an\noptimization on classes that serve as data structures.  Using slots\nwill make such programs use far-less memory and run a bit faster.\nYou should probably avoid `__slots__` on most other classes however.\n\n[Contents](../Contents.md) \\| [Previous (5.1 Dictionaries Revisited)](01_Dicts_revisited.md) \\| [Next (6 Generators)](../06_Generators/00_Overview.md)\n"
  },
  {
    "path": "Notes/06_Generators/00_Overview.md",
    "content": "[Contents](../Contents.md) \\| [Prev (5 Inner Workings of Python Objects)](../05_Object_model/00_Overview.md) \\| [Next (7 Advanced Topics)](../07_Advanced_Topics/00_Overview.md)\n\n# 6. Generators\n\nIteration (the `for`-loop) is one of the most common programming\npatterns in Python.  Programs do a lot of iteration to process lists,\nread files, query databases, and more.  One of the most powerful\nfeatures of Python is the ability to customize and redefine iteration\nin the form of a so-called \"generator function.\"  This section\nintroduces this topic.  By the end, you'll write some programs that\nprocess some real-time streaming data in an interesting way.\n\n* [6.1 Iteration Protocol](01_Iteration_protocol.md)\n* [6.2 Customizing Iteration with Generators](02_Customizing_iteration.md)\n* [6.3 Producer/Consumer Problems and Workflows](03_Producers_consumers.md)\n* [6.4 Generator Expressions](04_More_generators.md)\n\n[Contents](../Contents.md) \\| [Prev (5 Inner Workings of Python Objects)](../05_Object_model/00_Overview.md) \\| [Next (7 Advanced Topics)](../07_Advanced_Topics/00_Overview.md)\n"
  },
  {
    "path": "Notes/06_Generators/01_Iteration_protocol.md",
    "content": "[Contents](../Contents.md) \\| [Previous (5.2 Encapsulation)](../05_Object_model/02_Classes_encapsulation.md) \\| [Next (6.2 Customizing Iteration)](02_Customizing_iteration.md)\n\n# 6.1 Iteration Protocol\n\nThis section looks at the underlying process of iteration.\n\n### Iteration Everywhere\n\nMany different objects support iteration.\n\n```python\na = 'hello'\nfor c in a: # Loop over characters in a\n    ...\n\nb = { 'name': 'Dave', 'password':'foo'}\nfor k in b: # Loop over keys in dictionary\n    ...\n\nc = [1,2,3,4]\nfor i in c: # Loop over items in a list/tuple\n    ...\n\nf = open('foo.txt')\nfor x in f: # Loop over lines in a file\n    ...\n```\n\n### Iteration: Protocol\n\nConsider the `for`-statement.\n\n```python\nfor x in obj:\n    # statements\n```\n\nWhat happens under the hood?\n\n```python\n_iter = obj.__iter__()        # Get iterator object\nwhile True:\n    try:\n        x = _iter.__next__()  # Get next item\n        # statements ...\n    except StopIteration:     # No more items\n        break\n```\n\nAll the objects that work with the `for-loop` implement this low-level\niteration protocol.\n\nExample: Manual iteration over a list.\n\n```python\n>>> x = [1,2,3]\n>>> it = x.__iter__()\n>>> it\n<listiterator object at 0x590b0>\n>>> it.__next__()\n1\n>>> it.__next__()\n2\n>>> it.__next__()\n3\n>>> it.__next__()\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in ? StopIteration\n>>>\n```\n\n### Supporting Iteration\n\nKnowing about iteration is useful if you want to add it to your own objects.\nFor example, making a custom container.\n\n```python\nclass Portfolio:\n    def __init__(self):\n        self.holdings = []\n\n    def __iter__(self):\n        return self.holdings.__iter__()\n    ...\n\nport = Portfolio()\nfor s in port:\n    ...\n```\n\n## Exercises\n\n### Exercise 6.1: Iteration Illustrated\n\nCreate the following list:\n\n```python\na = [1,9,4,25,16]\n```\n\nManually iterate over this list.  Call `__iter__()` to get an iterator and\ncall the `__next__()` method to obtain successive elements.\n\n```python\n>>> i = a.__iter__()\n>>> i\n<listiterator object at 0x64c10>\n>>> i.__next__()\n1\n>>> i.__next__()\n9\n>>> i.__next__()\n4\n>>> i.__next__()\n25\n>>> i.__next__()\n16\n>>> i.__next__()\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nStopIteration\n>>>\n```\n\nThe `next()` built-in function is a shortcut for calling\nthe `__next__()` method of an iterator. Try using it on a file:\n\n```python\n>>> f = open('Data/portfolio.csv')\n>>> f.__iter__()    # Note: This returns the file itself\n<_io.TextIOWrapper name='Data/portfolio.csv' mode='r' encoding='UTF-8'>\n>>> next(f)\n'name,shares,price\\n'\n>>> next(f)\n'\"AA\",100,32.20\\n'\n>>> next(f)\n'\"IBM\",50,91.10\\n'\n>>>\n```\n\nKeep calling `next(f)` until you reach the end of the\nfile. Watch what happens.\n\n### Exercise 6.2: Supporting Iteration\n\nOn occasion, you might want to make one of your own objects support\niteration--especially if your object wraps around an existing\nlist or other iterable.  In a new file `portfolio.py`, define the\nfollowing class:\n\n```python\n# portfolio.py\n\nclass Portfolio:\n\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    @property\n    def total_cost(self):\n        return sum([s.cost for s in self._holdings])\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n```\n\nThis class is meant to be a layer around a list, but with some\nextra methods such as the `total_cost` property.  Modify the `read_portfolio()`\nfunction in `report.py` so that it creates a `Portfolio` instance like this:\n\n```\n# report.py\n...\n\nimport fileparse\nfrom stock import Stock\nfrom portfolio import Portfolio\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as file:\n        portdicts = fileparse.parse_csv(file,\n                                        select=['name','shares','price'],\n                                        types=[str,int,float])\n\n    portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]\n    return Portfolio(portfolio)\n...\n```\n\nTry running the `report.py` program. You will find that it fails spectacularly due to the fact\nthat `Portfolio` instances aren't iterable.\n\n```python\n>>> import report\n>>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv')\n... crashes ...\n```\n\nFix this by modifying the `Portfolio` class to support iteration:\n\n```python\nclass Portfolio:\n\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(self):\n        return self._holdings.__iter__()\n\n    @property\n    def total_cost(self):\n        return sum([s.shares*s.price for s in self._holdings])\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n```\n\nAfter you've made this change, your `report.py` program should work again.   While you're\nat it, fix up your `pcost.py` program to use the new `Portfolio` object. Like this:\n\n```python\n# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return portfolio.total_cost\n...\n```\n\nTest it to make sure it works:\n\n```python\n>>> import pcost\n>>> pcost.portfolio_cost('Data/portfolio.csv')\n44671.15\n>>>\n```\n\n### Exercise 6.3: Making a more proper container\n\nIf making a container class, you often want to do more than just\niteration. Modify the `Portfolio` class so that it has some other\nspecial methods like this:\n\n```python\nclass Portfolio:\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(self):\n        return self._holdings.__iter__()\n\n    def __len__(self):\n        return len(self._holdings)\n\n    def __getitem__(self, index):\n        return self._holdings[index]\n\n    def __contains__(self, name):\n        return any([s.name == name for s in self._holdings])\n\n    @property\n    def total_cost(self):\n        return sum([s.shares*s.price for s in self._holdings])\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n```\n\nNow, try some experiments using this new class:\n\n```\n>>> import report\n>>> portfolio = report.read_portfolio('Data/portfolio.csv')\n>>> len(portfolio)\n7\n>>> portfolio[0]\nStock('AA', 100, 32.2)\n>>> portfolio[1]\nStock('IBM', 50, 91.1)\n>>> portfolio[0:3]\n[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44)]\n>>> 'IBM' in portfolio\nTrue\n>>> 'AAPL' in portfolio\nFalse\n>>>\n```\n\nOne important observation about this--generally code is considered\n\"Pythonic\" if it speaks the common vocabulary of how other parts of\nPython normally work.  For container objects, supporting iteration,\nindexing, containment, and other kinds of operators is an important\npart of this.\n\n[Contents](../Contents.md) \\| [Previous (5.2 Encapsulation)](../05_Object_model/02_Classes_encapsulation.md) \\| [Next (6.2 Customizing Iteration)](02_Customizing_iteration.md)"
  },
  {
    "path": "Notes/06_Generators/02_Customizing_iteration.md",
    "content": "[Contents](../Contents.md) \\| [Previous (6.1 Iteration Protocol)](01_Iteration_protocol.md) \\| [Next (6.3 Producer/Consumer)](03_Producers_consumers.md)\n\n# 6.2 Customizing Iteration\n\nThis section looks at how you can customize iteration using a generator function.\n\n### A problem\n\nSuppose you wanted to create your own custom iteration pattern.\n\nFor example, a countdown.\n\n```python\n>>> for x in countdown(10):\n...   print(x, end=' ')\n...\n10 9 8 7 6 5 4 3 2 1\n>>>\n```\n\nThere is an easy way to do this.\n\n### Generators\n\nA generator is a function that defines iteration.\n\n```python\ndef countdown(n):\n    while n > 0:\n        yield n\n        n -= 1\n```\n\nFor example:\n\n```python\n>>> for x in countdown(10):\n...   print(x, end=' ')\n...\n10 9 8 7 6 5 4 3 2 1\n>>>\n```\n\nA generator is any function that uses the `yield` statement.\n\nThe behavior of generators is different than a normal function.\nCalling a generator function creates a generator object. It does not\nimmediately execute the function.\n\n```python\ndef countdown(n):\n    # Added a print statement\n    print('Counting down from', n)\n    while n > 0:\n        yield n\n        n -= 1\n```\n\n```python\n>>> x = countdown(10)\n# There is NO PRINT STATEMENT\n>>> x\n# x is a generator object\n<generator object at 0x58490>\n>>>\n```\n\nThe function only executes on `__next__()` call.\n\n```python\n>>> x = countdown(10)\n>>> x\n<generator object at 0x58490>\n>>> x.__next__()\nCounting down from 10\n10\n>>>\n```\n\n`yield` produces a value, but suspends the function execution.\nThe function resumes on next call to `__next__()`.\n\n```python\n>>> x.__next__()\n9\n>>> x.__next__()\n8\n```\n\nWhen the generator finally returns, the iteration raises an error.\n\n```python\n>>> x.__next__()\n1\n>>> x.__next__()\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in ? StopIteration\n>>>\n```\n\n*Observation: A generator function implements the same low-level\n protocol that the for statements uses on lists, tuples, dicts, files,\n etc.*\n\n## Exercises\n\n### Exercise 6.4: A Simple Generator\n\nIf you ever find yourself wanting to customize iteration, you should\nalways think generator functions.  They're easy to write---make\na function that carries out the desired iteration logic and use `yield`\nto emit values.\n\nFor example, try this generator that searches a file for lines containing\na matching substring:\n\n```python\n>>> def filematch(filename, substr):\n        with open(filename, 'r') as f:\n            for line in f:\n                if substr in line:\n                    yield line\n\n>>> for line in open('Data/portfolio.csv'):\n        print(line, end='')\n\nname,shares,price\n\"AA\",100,32.20\n\"IBM\",50,91.10\n\"CAT\",150,83.44\n\"MSFT\",200,51.23\n\"GE\",95,40.37\n\"MSFT\",50,65.10\n\"IBM\",100,70.44\n>>> for line in filematch('Data/portfolio.csv', 'IBM'):\n        print(line, end='')\n\n\"IBM\",50,91.10\n\"IBM\",100,70.44\n>>>\n```\n\nThis is kind of interesting--the idea that you can hide a bunch of\ncustom processing in a function and use it to feed a for-loop.\nThe next example looks at a more unusual case.\n\n### Exercise 6.5: Monitoring a streaming data source\n\nGenerators can be an interesting way to monitor real-time data sources\nsuch as log files or stock market feeds.  In this part, we'll\nexplore this idea.  To start, follow the next instructions carefully.\n\nThe program `Data/stocksim.py` is a program that\nsimulates stock market data.  As output, the program constantly writes\nreal-time data to a file `Data/stocklog.csv`.  In a\nseparate command window go into the `Data/` directory and run this program:\n\n```bash\nbash % python3 stocksim.py\n```\n\nIf you are on Windows, just locate the `stocksim.py` program and\ndouble-click on it to run it.  Now, forget about this program (just\nlet it run).  Using another window, look at the file\n`Data/stocklog.csv` being written by the simulator.  You should see\nnew lines of text being added to the file every few seconds.  Again,\njust let this program run in the background---it will run for several\nhours (you shouldn't need to worry about it).\n\nOnce the above program is running, let's write a little program to\nopen the file, seek to the end, and watch for new output.  Create a\nfile `follow.py` and put this code in it:\n\n```python\n# follow.py\nimport os\nimport time\n\nf = open('Data/stocklog.csv')\nf.seek(0, os.SEEK_END)   # Move file pointer 0 bytes from end of file\n\nwhile True:\n    line = f.readline()\n    if line == '':\n        time.sleep(0.1)   # Sleep briefly and retry\n        continue\n    fields = line.split(',')\n    name = fields[0].strip('\"')\n    price = float(fields[1])\n    change = float(fields[4])\n    if change < 0:\n        print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')\n```\n\nIf you run the program, you'll see a real-time stock ticker.  Under the hood,\nthis code is kind of like the Unix `tail -f` command that's used to watch a log file.\n\nNote: The use of the `readline()` method in this example is\nsomewhat unusual in that it is not the usual way of reading lines from\na file (normally you would just use a `for`-loop).  However, in\nthis case, we are using it to repeatedly probe the end of the file to\nsee if more data has been added (`readline()` will either\nreturn new data or an empty string).\n\n### Exercise 6.6: Using a generator to produce data\n\nIf you look at the code in Exercise 6.5, the first part of the code is producing\nlines of data whereas the statements at the end of the `while` loop are consuming\nthe data.  A major feature of generator functions is that you can move all\nof the data production code into a reusable function.\n\nModify the code in Exercise 6.5  so that the file-reading is performed by\na generator function `follow(filename)`.   Make it so the following code\nworks:\n\n```python\n>>> for line in follow('Data/stocklog.csv'):\n          print(line, end='')\n\n... Should see lines of output produced here ...\n```\n\nModify the stock ticker code so that it looks like this:\n\n\n```python\nif __name__ == '__main__':\n    for line in follow('Data/stocklog.csv'):\n        fields = line.split(',')\n        name = fields[0].strip('\"')\n        price = float(fields[1])\n        change = float(fields[4])\n        if change < 0:\n            print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')\n```\n\n### Exercise 6.7: Watching your portfolio\n\nModify the `follow.py` program so that it watches the stream of stock\ndata and prints a ticker showing information for only those stocks\nin a portfolio.  For example:\n\n```python\nif __name__ == '__main__':\n    import report\n\n    portfolio = report.read_portfolio('Data/portfolio.csv')\n\n    for line in follow('Data/stocklog.csv'):\n        fields = line.split(',')\n        name = fields[0].strip('\"')\n        price = float(fields[1])\n        change = float(fields[4])\n        if name in portfolio:\n            print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')\n```\n\nNote: For this to work, your `Portfolio` class must support the `in`\noperator.  See [Exercise 6.3](01_Iteration_protocol) and make sure you\nimplement the `__contains__()` operator.\n\n### Discussion\n\nSomething very powerful just happened here.  You moved an interesting iteration pattern\n(reading lines at the end of a file) into its own little function.   The `follow()` function\nis now this completely general purpose utility that you can use in any program.  For\nexample, you could use it to watch server logs, debugging logs, and other similar data sources.\nThat's kind of cool.\n\n[Contents](../Contents.md) \\| [Previous (6.1 Iteration Protocol)](01_Iteration_protocol.md) \\| [Next (6.3 Producer/Consumer)](03_Producers_consumers.md)"
  },
  {
    "path": "Notes/06_Generators/03_Producers_consumers.md",
    "content": "[Contents](../Contents.md) \\| [Previous (6.2 Customizing Iteration)](02_Customizing_iteration.md) \\| [Next (6.4 Generator Expressions)](04_More_generators.md)\n\n# 6.3 Producers, Consumers and Pipelines\n\nGenerators are a useful tool for setting various kinds of\nproducer/consumer problems and dataflow pipelines.  This section\ndiscusses that.\n\n### Producer-Consumer Problems\n\nGenerators are closely related to various forms of *producer-consumer* problems.\n\n```python\n# Producer\ndef follow(f):\n    ...\n    while True:\n        ...\n        yield line        # Produces value in `line` below\n        ...\n\n# Consumer\nfor line in follow(f):    # Consumes value from `yield` above\n    ...\n```\n\n`yield` produces values that `for` consumes.\n\n### Generator Pipelines\n\nYou can use this aspect of generators to set up processing pipelines (like Unix pipes).\n\n*producer* &rarr; *processing* &rarr; *processing* &rarr; *consumer*\n\nProcessing pipes have an initial data producer, some set of intermediate processing stages and a final consumer.\n\n**producer** &rarr; *processing* &rarr; *processing* &rarr; *consumer*\n\n```python\ndef producer():\n    ...\n    yield item\n    ...\n```\n\nThe producer is typically a generator. Although it could also be a list of some other sequence.\n`yield` feeds data into the pipeline.\n\n*producer* &rarr; *processing* &rarr; *processing* &rarr; **consumer**\n\n```python\ndef consumer(s):\n    for item in s:\n        ...\n```\n\nConsumer is a for-loop. It gets items and does something with them.\n\n*producer* &rarr; **processing** &rarr; **processing** &rarr; *consumer*\n\n```python\ndef processing(s):\n    for item in s:\n        ...\n        yield newitem\n        ...\n```\n\nIntermediate processing stages simultaneously consume and produce items.\nThey might modify the data stream.\nThey can also filter (discarding items).\n\n*producer* &rarr; *processing* &rarr; *processing* &rarr; *consumer*\n\n```python\ndef producer():\n    ...\n    yield item          # yields the item that is received by the `processing`\n    ...\n\ndef processing(s):\n    for item in s:      # Comes from the `producer`\n        ...\n        yield newitem   # yields a new item\n        ...\n\ndef consumer(s):\n    for item in s:      # Comes from the `processing`\n        ...\n```\n\nCode to setup the pipeline\n\n```python\na = producer()\nb = processing(a)\nc = consumer(b)\n```\n\nYou will notice that data incrementally flows through the different functions.\n\n## Exercises\n\nFor this exercise the `stocksim.py` program should still be running in the background.\nYou’re going to use the `follow()` function you wrote in the previous exercise.\n\n### Exercise 6.8: Setting up a simple pipeline\n\nLet's see the pipelining idea in action.  Write the following\nfunction:\n\n```python\n>>> def filematch(lines, substr):\n        for line in lines:\n            if substr in line:\n                yield line\n\n>>>\n```\n\nThis function is almost exactly the same as the first generator\nexample in the previous exercise except that it's no longer\nopening a file--it merely operates on a sequence of lines given\nto it as an argument.  Now, try this:\n\n```\n>>> from follow import follow\n>>> lines = follow('Data/stocklog.csv')\n>>> ibm = filematch(lines, 'IBM')\n>>> for line in ibm:\n        print(line)\n\n... wait for output ...\n```\n\nIt might take awhile for output to appear, but eventually you\nshould see some lines containing data for IBM.\n\n### Exercise 6.9: Setting up a more complex pipeline\n\nTake the pipelining idea a few steps further by performing\nmore actions.\n\n```\n>>> from follow import follow\n>>> import csv\n>>> lines = follow('Data/stocklog.csv')\n>>> rows = csv.reader(lines)\n>>> for row in rows:\n        print(row)\n\n['BA', '98.35', '6/11/2007', '09:41.07', '0.16', '98.25', '98.35', '98.31', '158148']\n['AA', '39.63', '6/11/2007', '09:41.07', '-0.03', '39.67', '39.63', '39.31', '270224']\n['XOM', '82.45', '6/11/2007', '09:41.07', '-0.23', '82.68', '82.64', '82.41', '748062']\n['PG', '62.95', '6/11/2007', '09:41.08', '-0.12', '62.80', '62.97', '62.61', '454327']\n...\n```\n\nWell, that's interesting.  What you're seeing here is that the output of the\n`follow()` function has been piped into the `csv.reader()` function and we're\nnow getting a sequence of split rows.\n\n### Exercise 6.10: Making more pipeline components\n\nLet's extend the whole idea into a larger pipeline.  In a separate file `ticker.py`,\nstart by creating a function that reads a CSV file as you did above:\n\n```python\n# ticker.py\n\nfrom follow import follow\nimport csv\n\ndef parse_stock_data(lines):\n    rows = csv.reader(lines)\n    return rows\n\nif __name__ == '__main__':\n    lines = follow('Data/stocklog.csv')\n    rows = parse_stock_data(lines)\n    for row in rows:\n        print(row)\n```\n\nWrite a new function that selects specific columns:\n\n```\n# ticker.py\n...\ndef select_columns(rows, indices):\n    for row in rows:\n        yield [row[index] for index in indices]\n...\ndef parse_stock_data(lines):\n    rows = csv.reader(lines)\n    rows = select_columns(rows, [0, 1, 4])\n    return rows\n```\n\nRun your program again.  You should see output narrowed down like this:\n\n```\n['BA', '98.35', '0.16']\n['AA', '39.63', '-0.03']\n['XOM', '82.45','-0.23']\n['PG', '62.95', '-0.12']\n...\n```\n\nWrite generator functions that convert data types and build dictionaries.\nFor example:\n\n```python\n# ticker.py\n...\n\ndef convert_types(rows, types):\n    for row in rows:\n        yield [func(val) for func, val in zip(types, row)]\n\ndef make_dicts(rows, headers):\n    for row in rows:\n        yield dict(zip(headers, row))\n...\ndef parse_stock_data(lines):\n    rows = csv.reader(lines)\n    rows = select_columns(rows, [0, 1, 4])\n    rows = convert_types(rows, [str, float, float])\n    rows = make_dicts(rows, ['name', 'price', 'change'])\n    return rows\n...\n```\n\nRun your program again.  You should now a stream of dictionaries like this:\n\n```\n{ 'name':'BA', 'price':98.35, 'change':0.16 }\n{ 'name':'AA', 'price':39.63, 'change':-0.03 }\n{ 'name':'XOM', 'price':82.45, 'change': -0.23 }\n{ 'name':'PG', 'price':62.95, 'change':-0.12 }\n...\n```\n\n### Exercise 6.11: Filtering data\n\nWrite a function that filters data.  For example:\n\n```python\n# ticker.py\n...\n\ndef filter_symbols(rows, names):\n    for row in rows:\n        if row['name'] in names:\n            yield row\n```\n\nUse this to filter stocks to just those in your portfolio:\n\n```python\nimport report\nportfolio = report.read_portfolio('Data/portfolio.csv')\nrows = parse_stock_data(follow('Data/stocklog.csv'))\nrows = filter_symbols(rows, portfolio)\nfor row in rows:\n    print(row)\n```\n\n### Exercise 6.12: Putting it all together\n\nIn the `ticker.py` program, write a function `ticker(portfile, logfile, fmt)`\nthat creates a real-time stock ticker from a given portfolio, logfile,\nand table format.  For example::\n\n```python\n>>> from ticker import ticker\n>>> ticker('Data/portfolio.csv', 'Data/stocklog.csv', 'txt')\n      Name      Price     Change\n---------- ---------- ----------\n        GE      37.14      -0.18\n      MSFT      29.96      -0.09\n       CAT      78.03      -0.49\n        AA      39.34      -0.32\n...\n\n>>> ticker('Data/portfolio.csv', 'Data/stocklog.csv', 'csv')\nName,Price,Change\nIBM,102.79,-0.28\nCAT,78.04,-0.48\nAA,39.35,-0.31\nCAT,78.05,-0.47\n...\n```\n\n### Discussion\n\nSome lessons learned: You can create various generator functions and\nchain them together to perform processing involving data-flow\npipelines.  In addition, you can create functions that package a\nseries of pipeline stages into a single function call (for example,\nthe `parse_stock_data()` function).\n\n[Contents](../Contents.md) \\| [Previous (6.2 Customizing Iteration)](02_Customizing_iteration.md) \\| [Next (6.4 Generator Expressions)](04_More_generators.md)\n"
  },
  {
    "path": "Notes/06_Generators/04_More_generators.md",
    "content": "[Contents](../Contents.md) \\| [Previous (6.3 Producer/Consumer)](03_Producers_consumers.md) \\| [Next (7 Advanced Topics)](../07_Advanced_Topics/00_Overview.md)\n\n# 6.4 More Generators\n\nThis section introduces a few additional generator related topics\nincluding generator expressions and the itertools module.\n\n### Generator Expressions\n\nA generator version of a list comprehension.\n\n```python\n>>> a = [1,2,3,4]\n>>> b = (2*x for x in a)\n>>> b\n<generator object at 0x58760>\n>>> for i in b:\n...   print(i, end=' ')\n...\n2 4 6 8\n>>>\n```\n\nDifferences with List Comprehensions.\n\n* Does not construct a list.\n* Only useful purpose is iteration.\n* Once consumed, can't be reused.\n\nGeneral syntax.\n\n```python\n(<expression> for i in s if <conditional>)\n```\n\nIt can also serve as a function argument.\n\n```python\nsum(x*x for x in a)\n```\n\nIt can be applied to any iterable.\n\n```python\n>>> a = [1,2,3,4]\n>>> b = (x*x for x in a)\n>>> c = (-x for x in b)\n>>> for i in c:\n...   print(i, end=' ')\n...\n-1 -4 -9 -16\n>>>\n```\n\nThe main use of generator expressions is in code that performs some\ncalculation on a sequence, but only uses the result once.  For\nexample, strip all comments from a file.\n\n```python\nf = open('somefile.txt')\nlines = (line for line in f if not line.startswith('#'))\nfor line in lines:\n    ...\nf.close()\n```\n\nWith generators, the code runs faster and uses little memory. It's\nlike a filter applied to a stream.\n\n### Why Generators\n\n* Many problems are much more clearly expressed in terms of iteration.\n  * Looping over a collection of items and performing some kind of operation (searching, replacing, modifying, etc.).\n  * Processing pipelines can be applied to a wide range of data processing problems.\n* Better memory efficiency.\n  * Only produce values when needed.\n  * Contrast to constructing giant lists.\n  * Can operate on streaming data\n* Generators encourage code reuse\n  * Separates the *iteration* from code that uses the iteration\n  * You can build a toolbox of interesting iteration functions and *mix-n-match*.\n\n### `itertools` module\n\nThe `itertools` is a library module with various functions designed to help with iterators/generators.\n\n```python\nitertools.chain(s1,s2)\nitertools.count(n)\nitertools.cycle(s)\nitertools.dropwhile(predicate, s)\nitertools.groupby(s)\nitertools.ifilter(predicate, s)\nitertools.imap(function, s1, ... sN)\nitertools.repeat(s, n)\nitertools.tee(s, ncopies)\nitertools.izip(s1, ... , sN)\n```\n\nAll functions process data iteratively.\nThey implement various kinds of iteration patterns.\n\nMore information at [Generator Tricks for Systems Programmers](http://www.dabeaz.com/generators/) tutorial from PyCon '08.\n\n## Exercises\n\nIn the previous exercises, you wrote some code that followed lines being written to a log file and parsed them into a sequence of rows.\nThis exercise continues to build upon that.  Make sure the `Data/stocksim.py` is still running.\n\n### Exercise 6.13: Generator Expressions\n\nGenerator expressions are a generator version of a list comprehension.\nFor example:\n\n```python\n>>> nums = [1, 2, 3, 4, 5]\n>>> squares = (x*x for x in nums)\n>>> squares\n<generator object <genexpr> at 0x109207e60>\n>>> for n in squares:\n...     print(n)\n...\n1\n4\n9\n16\n25\n```\n\nUnlike a list a comprehension, a generator expression can only be used once.\nThus, if you try another for-loop, you get nothing:\n\n```python\n>>> for n in squares:\n...     print(n)\n...\n>>>\n```\n\n### Exercise 6.14: Generator Expressions in Function Arguments\n\nGenerator expressions are sometimes placed into function arguments.\nIt looks a little weird at first, but try this experiment:\n\n```python\n>>> nums = [1,2,3,4,5]\n>>> sum([x*x for x in nums])    # A list comprehension\n55\n>>> sum(x*x for x in nums)      # A generator expression\n55\n>>>\n```\nIn the above example, the second version using generators would\nuse significantly less memory if a large list was being manipulated.\n\nIn your `portfolio.py` file, you performed a few calculations\ninvolving list comprehensions.  Try replacing these with\ngenerator expressions.\n\n### Exercise 6.15: Code simplification\n\nGenerators expressions are often a useful replacement for\nsmall generator functions.  For example, instead of writing a\nfunction like this:\n\n```python\ndef filter_symbols(rows, names):\n    for row in rows:\n        if row['name'] in names:\n            yield row\n```\n\nYou could write something like this:\n\n```python\nrows = (row for row in rows if row['name'] in names)\n```\n\nModify the `ticker.py` program to use generator expressions\nas appropriate.\n\n\n[Contents](../Contents.md) \\| [Previous (6.3 Producer/Consumer)](03_Producers_consumers.md) \\| [Next (7 Advanced Topics)](../07_Advanced_Topics/00_Overview.md)\n"
  },
  {
    "path": "Notes/07_Advanced_Topics/00_Overview.md",
    "content": "[Contents](../Contents.md) \\| [Prev (6 Generators)](../06_Generators/00_Overview.md) \\| [Next (8 Testing and Debugging)](../08_Testing_debugging/00_Overview.md)\n\n# 7. Advanced Topics\n\nIn this section, we look at a small set of somewhat more advanced\nPython features that you might encounter in your day-to-day coding.\nMany of these topics could have been covered in earlier course\nsections, but weren't in order to spare you further head-explosion at\nthe time.\n\nIt should be emphasized that the topics in this section are only meant\nto serve as a very basic introduction to these ideas.  You will need\nto seek more advanced material to fill out details.\n\n* [7.1 Variable argument functions](01_Variable_arguments.md)\n* [7.2 Anonymous functions and lambda](02_Anonymous_function.md)\n* [7.3 Returning function and closures](03_Returning_functions.md)\n* [7.4 Function decorators](04_Function_decorators.md)\n* [7.5 Static and class methods](05_Decorated_methods.md)\n\n[Contents](../Contents.md) \\| [Prev (6 Generators)](../06_Generators/00_Overview.md) \\| [Next (8 Testing and Debugging)](../08_Testing_debugging/00_Overview.md)\n"
  },
  {
    "path": "Notes/07_Advanced_Topics/01_Variable_arguments.md",
    "content": "\n[Contents](../Contents.md) \\| [Previous (6.4 Generator Expressions)](../06_Generators/04_More_generators.md) \\| [Next (7.2 Anonymous Functions)](02_Anonymous_function.md)\n\n# 7.1 Variable Arguments\n\nThis section covers variadic function arguments, sometimes described as\n`*args` and `**kwargs`.\n\n### Positional variable arguments (*args)\n\nA function that accepts *any number* of arguments is said to use variable arguments.\nFor example:\n\n```python\ndef f(x, *args):\n    ...\n```\n\nFunction call.\n\n```python\nf(1,2,3,4,5)\n```\n\nThe extra arguments get passed as a tuple.\n\n```python\ndef f(x, *args):\n    # x -> 1\n    # args -> (2,3,4,5)\n```\n\n### Keyword variable arguments (**kwargs)\n\nA function can also accept any number of keyword arguments.\nFor example:\n\n```python\ndef f(x, y, **kwargs):\n    ...\n```\n\nFunction call.\n\n```python\nf(2, 3, flag=True, mode='fast', header='debug')\n```\n\nThe extra keywords are passed in a dictionary.\n\n```python\ndef f(x, y, **kwargs):\n    # x -> 2\n    # y -> 3\n    # kwargs -> { 'flag': True, 'mode': 'fast', 'header': 'debug' }\n```\n\n### Combining both\n\nA function can also accept any number of variable keyword and non-keyword arguments.\n\n```python\ndef f(*args, **kwargs):\n    ...\n```\n\nFunction call.\n\n```python\nf(2, 3, flag=True, mode='fast', header='debug')\n```\n\nThe arguments are separated into positional and keyword components\n\n```python\ndef f(*args, **kwargs):\n    # args = (2, 3)\n    # kwargs -> { 'flag': True, 'mode': 'fast', 'header': 'debug' }\n    ...\n```\n\nThis function takes any combination of positional or keyword\narguments.  It is sometimes used when writing wrappers or when you\nwant to pass arguments through to another function.\n\n### Passing Tuples and Dicts\n\nTuples can be expanded into variable arguments.\n\n```python\nnumbers = (2,3,4)\nf(1, *numbers)      # Same as f(1,2,3,4)\n```\n\nDictionaries can also be expanded into keyword arguments.\n\n```python\noptions = {\n    'color' : 'red',\n    'delimiter' : ',',\n    'width' : 400\n}\nf(data, **options)\n# Same as f(data, color='red', delimiter=',', width=400)\n```\n\n## Exercises\n\n### Exercise 7.1: A simple example of variable arguments\n\nTry defining the following function:\n\n```python\n>>> def avg(x,*more):\n        return float(x+sum(more))/(1+len(more))\n\n>>> avg(10,11)\n10.5\n>>> avg(3,4,5)\n4.0\n>>> avg(1,2,3,4,5,6)\n3.5\n>>>\n```\n\nNotice how the parameter `*more` collects all of the extra arguments.\n\n### Exercise 7.2: Passing tuple and dicts as arguments\n\nSuppose you read some data from a file and obtained a tuple such as\nthis:\n\n```\n>>> data = ('GOOG', 100, 490.1)\n>>>\n```\n\nNow, suppose you wanted to create a `Stock` object from this\ndata.  If you try to pass `data` directly, it doesn't work:\n\n```\n>>> from stock import Stock\n>>> s = Stock(data)\nTraceback (most recent call last):\n  File \"<stdin>\", line 1, in <module>\nTypeError: __init__() takes exactly 4 arguments (2 given)\n>>>\n```\n\nThis is easily fixed using `*data` instead.  Try this:\n\n```python\n>>> s = Stock(*data)\n>>> s\nStock('GOOG', 100, 490.1)\n>>>\n```\n\nIf you have a dictionary, you can use `**` instead. For example:\n\n```python\n>>> data = { 'name': 'GOOG', 'shares': 100, 'price': 490.1 }\n>>> s = Stock(**data)\nStock('GOOG', 100, 490.1)\n>>>\n```\n\n### Exercise 7.3: Creating a list of instances\n\nIn your `report.py` program, you created a list of instances\nusing code like this:\n\n```python\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        portdicts = fileparse.parse_csv(lines,\n                               select=['name','shares','price'],\n                               types=[str,int,float])\n\n    portfolio = [ Stock(d['name'], d['shares'], d['price'])\n                  for d in portdicts ]\n    return Portfolio(portfolio)\n```\n\nYou can simplify that code using `Stock(**d)` instead.  Make that change.\n\n### Exercise 7.4: Argument pass-through\n\nThe `fileparse.parse_csv()` function has some options for changing the\nfile delimiter and for error reporting.  Maybe you'd like to expose those\noptions to the `read_portfolio()` function above.   Make this change:\n\n```\ndef read_portfolio(filename, **opts):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        portdicts = fileparse.parse_csv(lines,\n                                        select=['name','shares','price'],\n                                        types=[str,int,float],\n                                        **opts)\n\n    portfolio = [ Stock(**d) for d in portdicts ]\n    return Portfolio(portfolio)\n```\n\nOnce you've made the change, trying reading a file with some errors:\n\n```python\n>>> import report\n>>> port = report.read_portfolio('Data/missing.csv')\nRow 4: Couldn't convert ['MSFT', '', '51.23']\nRow 4: Reason invalid literal for int() with base 10: ''\nRow 7: Couldn't convert ['IBM', '', '70.44']\nRow 7: Reason invalid literal for int() with base 10: ''\n>>>\n```\n\nNow, try silencing the errors:\n\n```python\n>>> import report\n>>> port = report.read_portfolio('Data/missing.csv', silence_errors=True)\n>>>\n```\n\n[Contents](../Contents.md) \\| [Previous (6.4 Generator Expressions)](../06_Generators/04_More_generators.md) \\| [Next (7.2 Anonymous Functions)](02_Anonymous_function.md)\n"
  },
  {
    "path": "Notes/07_Advanced_Topics/02_Anonymous_function.md",
    "content": "[Contents](../Contents.md) \\| [Previous (7.1 Variable Arguments)](01_Variable_arguments.md) \\| [Next (7.3 Returning Functions)](03_Returning_functions.md)\n\n# 7.2 Anonymous Functions and Lambda\n\n### List Sorting Revisited\n\nLists can be sorted *in-place*. Using the `sort` method.\n\n```python\ns = [10,1,7,3]\ns.sort() # s = [1,3,7,10]\n```\n\nYou can sort in reverse order.\n\n```python\ns = [10,1,7,3]\ns.sort(reverse=True) # s = [10,7,3,1]\n```\n\nIt seems simple enough. However, how do we sort a list of dicts?\n\n```python\n[{'name': 'AA', 'price': 32.2, 'shares': 100},\n{'name': 'IBM', 'price': 91.1, 'shares': 50},\n{'name': 'CAT', 'price': 83.44, 'shares': 150},\n{'name': 'MSFT', 'price': 51.23, 'shares': 200},\n{'name': 'GE', 'price': 40.37, 'shares': 95},\n{'name': 'MSFT', 'price': 65.1, 'shares': 50},\n{'name': 'IBM', 'price': 70.44, 'shares': 100}]\n```\n\nBy what criteria?\n\nYou can guide the sorting by using a *key function*. The *key\nfunction* is a function that receives the dictionary and returns the\nvalue of interest for sorting.\n\n```python\ndef stock_name(s):\n    return s['name']\n\nportfolio.sort(key=stock_name)\n```\n\nHere's the result.\n\n```python\n# Check how the dictionaries are sorted by the `name` key\n[\n  {'name': 'AA', 'price': 32.2, 'shares': 100},\n  {'name': 'CAT', 'price': 83.44, 'shares': 150},\n  {'name': 'GE', 'price': 40.37, 'shares': 95},\n  {'name': 'IBM', 'price': 91.1, 'shares': 50},\n  {'name': 'IBM', 'price': 70.44, 'shares': 100},\n  {'name': 'MSFT', 'price': 51.23, 'shares': 200},\n  {'name': 'MSFT', 'price': 65.1, 'shares': 50}\n]\n```\n\n### Callback Functions\n\nIn the above example, the key function is an example of a callback\nfunction. The `sort()` method \"calls back\" to a function you supply.\nCallback functions are often short one-line functions that are only\nused for that one operation.  Programmers often ask for a short-cut\nfor specifying this extra processing.\n\n### Lambda: Anonymous Functions\n\nUse a lambda instead of creating the function.  In our previous\nsorting example.\n\n```python\nportfolio.sort(key=lambda s: s['name'])\n```\n\nThis creates an *unnamed* function that evaluates a *single* expression.\nThe above code is much shorter than the initial code.\n\n```python\ndef stock_name(s):\n    return s['name']\n\nportfolio.sort(key=stock_name)\n\n# vs lambda\nportfolio.sort(key=lambda s: s['name'])\n```\n\n### Using lambda\n\n* lambda is highly restricted.\n* Only a single expression is allowed.\n* No statements like `if`, `while`, etc.\n* Most common use is with functions like `sort()`.\n\n## Exercises\n\nRead some stock portfolio data and convert it into a list:\n\n```python\n>>> import report\n>>> portfolio = list(report.read_portfolio('Data/portfolio.csv'))\n>>> for s in portfolio:\n        print(s)\n\nStock('AA', 100, 32.2)\nStock('IBM', 50, 91.1)\nStock('CAT', 150, 83.44)\nStock('MSFT', 200, 51.23)\nStock('GE', 95, 40.37)\nStock('MSFT', 50, 65.1)\nStock('IBM', 100, 70.44)\n>>>\n```\n\n### Exercise 7.5: Sorting on a field\n\nTry the following statements which sort the portfolio data\nalphabetically by stock name.\n\n```python\n>>> def stock_name(s):\n       return s.name\n\n>>> portfolio.sort(key=stock_name)\n>>> for s in portfolio:\n           print(s)\n\n... inspect the result ...\n>>>\n```\n\nIn this part, the `stock_name()` function extracts the name of a stock from\na single entry in the `portfolio` list.   `sort()` uses the result of\nthis function to do the comparison.\n\n### Exercise 7.6: Sorting on a field with lambda\n\nTry sorting the portfolio according the number of shares using a\n`lambda` expression:\n\n```python\n>>> portfolio.sort(key=lambda s: s.shares)\n>>> for s in portfolio:\n        print(s)\n\n... inspect the result ...\n>>>\n```\n\nTry sorting the portfolio according to the price of each stock\n\n```python\n>>> portfolio.sort(key=lambda s: s.price)\n>>> for s in portfolio:\n        print(s)\n\n... inspect the result ...\n>>>\n```\n\nNote: `lambda` is a useful shortcut because it allows you to\ndefine a special processing function directly in the call to `sort()` as\nopposed to having to define a separate function first.\n\n[Contents](../Contents.md) \\| [Previous (7.1 Variable Arguments)](01_Variable_arguments.md) \\| [Next (7.3 Returning Functions)](03_Returning_functions.md)\n"
  },
  {
    "path": "Notes/07_Advanced_Topics/03_Returning_functions.md",
    "content": "[Contents](../Contents.md) \\| [Previous (7.2 Anonymous Functions)](02_Anonymous_function.md) \\| [Next (7.4 Decorators)](04_Function_decorators.md)\n\n# 7.3 Returning Functions\n\nThis section introduces the idea of using functions to create other functions.\n\n### Introduction\n\nConsider the following function.\n\n```python\ndef add(x, y):\n    def do_add():\n        print('Adding', x, y)\n        return x + y\n    return do_add\n```\n\nThis is a function that returns another function.\n\n```python\n>>> a = add(3,4)\n>>> a\n<function do_add at 0x6a670>\n>>> a()\nAdding 3 4\n7\n```\n\n### Local Variables\n\nObserve how the inner function refers to variables defined by the outer\nfunction.\n\n```python\ndef add(x, y):\n    def do_add():\n        # `x` and `y` are defined above `add(x, y)`\n        print('Adding', x, y)\n        return x + y\n    return do_add\n```\n\nFurther observe that those variables are somehow kept alive after\n`add()` has finished.\n\n```python\n>>> a = add(3,4)\n>>> a\n<function do_add at 0x6a670>\n>>> a()\nAdding 3 4      # Where are these values coming from?\n7\n```\n\n### Closures\n\nWhen an inner function is returned as a result, that inner function is known as a *closure*.\n\n```python\ndef add(x, y):\n    # `do_add` is a closure\n    def do_add():\n        print('Adding', x, y)\n        return x + y\n    return do_add\n```\n\n*Essential feature: A closure retains the values of all variables\n needed for the function to run properly later on.*   Think of a\nclosure as a function plus an extra environment that holds the values\nof variables that it depends on.\n\n### Using Closures\n\nClosure are an essential feature of Python. However, their use if often subtle.\nCommon applications:\n\n* Use in callback functions.\n* Delayed evaluation.\n* Decorator functions (later).\n\n### Delayed Evaluation\n\nConsider a function like this:\n\n```python\ndef after(seconds, func):\n    import time\n    time.sleep(seconds)\n    func()\n```\n\nUsage example:\n\n```python\ndef greeting():\n    print('Hello Guido')\n\nafter(30, greeting)\n```\n\n`after` executes the supplied function... later.\n\nClosures carry extra information around.\n\n```python\ndef add(x, y):\n    def do_add():\n        print(f'Adding {x} + {y} -> {x+y}')\n    return do_add\n\ndef after(seconds, func):\n    import time\n    time.sleep(seconds)\n    func()\n\nafter(30, add(2, 3))\n# `do_add` has the references x -> 2 and y -> 3\n```\n\n### Code Repetition\n\nClosures can also be used as technique for avoiding excessive code repetition.\nYou can write functions that make code.\n\n## Exercises\n\n### Exercise 7.7: Using Closures to Avoid Repetition\n\nOne of the more powerful features of closures is their use in\ngenerating repetitive code.  If you refer back to [Exercise\n5.7](../05_Object_model/02_Classes_encapsulation), recall the code for\ndefining a property with type checking.\n\n```python\nclass Stock:\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n    ...\n    @property\n    def shares(self):\n        return self._shares\n\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value, int):\n            raise TypeError('Expected int')\n        self._shares = value\n    ...\n```\n\nInstead of repeatedly typing that code over and over again, you can\nautomatically create it using a closure.\n\nMake a file `typedproperty.py` and put the following code in\nit:\n\n```python\n# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n    @property\n    def prop(self):\n        return getattr(self, private_name)\n\n    @prop.setter\n    def prop(self, value):\n        if not isinstance(value, expected_type):\n            raise TypeError(f'Expected {expected_type}')\n        setattr(self, private_name, value)\n\n    return prop\n```\n\nNow, try it out by defining a class like this:\n\n```python\nfrom typedproperty import typedproperty\n\nclass Stock:\n    name = typedproperty('name', str)\n    shares = typedproperty('shares', int)\n    price = typedproperty('price', float)\n\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n```\n\nTry creating an instance and verifying that type-checking works.\n\n```python\n>>> s = Stock('IBM', 50, 91.1)\n>>> s.name\n'IBM'\n>>> s.shares = '100'\n... should get a TypeError ...\n>>>\n```\n\n### Exercise 7.8: Simplifying Function Calls\n\nIn the above example, users might find calls such as\n`typedproperty('shares', int)` a bit verbose to type--especially if\nthey're repeated a lot.  Add the following definitions to the\n`typedproperty.py` file:\n\n```python\nString = lambda name: typedproperty(name, str)\nInteger = lambda name: typedproperty(name, int)\nFloat = lambda name: typedproperty(name, float)\n```\n\nNow, rewrite the `Stock` class to use these functions instead:\n\n```python\nclass Stock:\n    name = String('name')\n    shares = Integer('shares')\n    price = Float('price')\n\n    def __init__(self, name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price = price\n```\n\nAh, that's a bit better.   The main takeaway here is that closures and `lambda`\ncan often be used to simplify code and eliminate annoying repetition.  This\nis often good.\n\n### Exercise 7.9: Putting it into practice\n\nRewrite the `Stock` class in the file `stock.py` so that it uses typed properties\nas shown.\n\n[Contents](../Contents.md) \\| [Previous (7.2 Anonymous Functions)](02_Anonymous_function.md) \\| [Next (7.4 Decorators)](04_Function_decorators.md)\n"
  },
  {
    "path": "Notes/07_Advanced_Topics/04_Function_decorators.md",
    "content": "[Contents](../Contents.md) \\| [Previous (7.3 Returning Functions)](03_Returning_functions.md) \\| [Next (7.5 Decorated Methods)](05_Decorated_methods.md)\n\n# 7.4 Function Decorators\n\nThis section introduces the concept of a decorator.  This is an advanced\ntopic for which we only scratch the surface.\n\n### Logging Example\n\nConsider a function.\n\n```python\ndef add(x, y):\n    return x + y\n```\n\nNow, consider the function with some logging added to it.\n\n```python\ndef add(x, y):\n    print('Calling add')\n    return x + y\n```\n\nNow a second function also with some logging.\n\n```python\ndef sub(x, y):\n    print('Calling sub')\n    return x - y\n```\n\n### Observation\n\n*Observation: It's kind of repetitive.*\n\nWriting programs where there is a lot of code replication is often\nreally annoying.  They are tedious to write and hard to maintain.\nEspecially if you decide that you want to change how it works (i.e., a\ndifferent kind of logging perhaps).\n\n### Code that makes logging\n\nPerhaps you can make a function that makes functions with logging\nadded to them. A wrapper.\n\n```python\ndef logged(func):\n    def wrapper(*args, **kwargs):\n        print('Calling', func.__name__)\n        return func(*args, **kwargs)\n    return wrapper\n```\n\nNow use it.\n\n```python\ndef add(x, y):\n    return x + y\n\nlogged_add = logged(add)\n```\n\nWhat happens when you call the function returned by `logged`?\n\n```python\nlogged_add(3, 4)      # You see the logging message appear\n```\n\nThis example illustrates the process of creating a so-called *wrapper function*.\n\nA wrapper is a function that wraps around another function with some\nextra bits of processing, but otherwise works in the exact same way\nas the original function.\n\n```python\n>>> logged_add(3, 4)\nCalling add   # Extra output. Added by the wrapper\n7\n>>>\n```\n\n*Note: The `logged()` function creates the wrapper and returns it as a result.*\n\n## Decorators\n\nPutting wrappers around functions is extremely common in Python.\nSo common, there is a special syntax for it.\n\n```python\ndef add(x, y):\n    return x + y\nadd = logged(add)\n\n# Special syntax\n@logged\ndef add(x, y):\n    return x + y\n```\n\nThe special syntax performs the same exact steps as shown above. A decorator is just new syntax.\nIt is said to *decorate* the function.\n\n### Commentary\n\nThere are many more subtle details to decorators than what has been presented here.\nFor example, using them in classes. Or using multiple decorators with a function.\nHowever, the previous example is a good illustration of how their use tends to arise.\nUsually, it's in response to repetitive code appearing across a wide range of\nfunction definitions.  A decorator can move that code to a central definition.\n\n## Exercises\n\n### Exercise 7.10: A decorator for timing\n\nIf you define a function, its name and module are stored in the\n`__name__` and `__module__` attributes. For example:\n\n```python\n>>> def add(x,y):\n        return x+y\n\n>>> add.__name__\n'add'\n>>> add.__module__\n'__main__'\n>>>\n```\n\nIn a file `timethis.py`, write a decorator function `timethis(func)`\nthat wraps a function with an extra layer of logic that prints out how\nlong it takes for a function to execute.  To do this, you'll surround\nthe function with timing calls like this:\n\n```python\nstart = time.time()\nr = func(*args,**kwargs)\nend = time.time()\nprint('%s.%s: %f' % (func.__module__, func.__name__, end-start))\n```\n\nHere is an example of how your decorator should work:\n\n```python\n>>> from timethis import timethis\n>>> @timethis\ndef countdown(n):\n    while n > 0:\n        n -= 1\n\t\n>>> countdown(10000000)\n__main__.countdown : 0.076562\n>>>\n```\n\nDiscussion:  This `@timethis` decorator can be placed in front of any\nfunction definition.   Thus, you might use it as a diagnostic tool for\nperformance tuning.\n\n[Contents](../Contents.md) \\| [Previous (7.3 Returning Functions)](03_Returning_functions.md) \\| [Next (7.5 Decorated Methods)](05_Decorated_methods.md)\n"
  },
  {
    "path": "Notes/07_Advanced_Topics/05_Decorated_methods.md",
    "content": "[Contents](../Contents.md) \\| [Previous (7.4 Decorators)](04_Function_decorators.md) \\| [Next (8 Testing and Debugging)](../08_Testing_debugging/00_Overview.md)\n\n# 7.5 Decorated Methods\n\nThis section discusses a few built-in decorators that are used in\ncombination with method definitions.\n\n### Predefined Decorators\n\nThere are predefined decorators used to specify special kinds of methods in class definitions.\n\n```python\nclass Foo:\n    def bar(self,a):\n        ...\n\n    @staticmethod\n    def spam(a):\n        ...\n\n    @classmethod\n    def grok(cls,a):\n        ...\n\n    @property\n    def name(self):\n        ...\n```\n\nLet's go one by one.\n\n### Static Methods\n\n`@staticmethod` is used to define a so-called *static* class methods\n(from C++/Java).  A static method is a function that is part of the\nclass, but which does *not* operate on instances.\n\n```python\nclass Foo(object):\n    @staticmethod\n    def bar(x):\n        print('x =', x)\n\n>>> Foo.bar(2) x=2\n>>>\n```\n\nStatic methods are sometimes used to implement internal supporting\ncode for a class.  For example, code to help manage created instances\n(memory management, system resources, persistence, locking, etc).\nThey're also used by certain design patterns (not discussed here).\n\n### Class Methods\n\n`@classmethod` is used to define class methods.  A class method is a\nmethod that receives the *class* object as the first parameter instead\nof the instance.\n\n```python\nclass Foo:\n    def bar(self):\n        print(self)\n\n    @classmethod\n    def spam(cls):\n        print(cls)\n\n>>> f = Foo()\n>>> f.bar()\n<__main__.Foo object at 0x971690>   # The instance `f`\n>>> Foo.spam()\n<class '__main__.Foo'>              # The class `Foo`\n>>>\n```\n\nClass methods are most often used as a tool for defining alternate constructors.\n\n```python\nclass Date:\n    def __init__(self,year,month,day):\n        self.year = year\n        self.month = month\n        self.day = day\n\n    @classmethod\n    def today(cls):\n        # Notice how the class is passed as an argument\n        tm = time.localtime()\n        # And used to create a new instance\n        return cls(tm.tm_year, tm.tm_mon, tm.tm_mday)\n\nd = Date.today()\n```\n\nClass methods solve some tricky problems with features like inheritance.\n\n```python\nclass Date:\n    ...\n    @classmethod\n    def today(cls):\n        # Gets the correct class (e.g. `NewDate`)\n        tm = time.localtime()\n        return cls(tm.tm_year, tm.tm_mon, tm.tm_mday)\n\nclass NewDate(Date):\n    ...\n\nd = NewDate.today()\n```\n\n## Exercises\n\n### Exercise 7.11: Class Methods in Practice\n\nIn your `report.py` and `portfolio.py` files, the creation of a `Portfolio`\nobject is a bit muddled.  For example, the `report.py` program has code like this:\n\n```python\ndef read_portfolio(filename, **opts):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        portdicts = fileparse.parse_csv(lines,\n                                        select=['name','shares','price'],\n                                        types=[str,int,float],\n                                        **opts)\n\n    portfolio = [ Stock(**d) for d in portdicts ]\n    return Portfolio(portfolio)\n```\n\nand the `portfolio.py` file defines `Portfolio()` with an odd initializer\nlike this:\n\n```python\nclass Portfolio:\n    def __init__(self, holdings):\n        self.holdings = holdings\n    ...\n```\n\nFrankly, the chain of responsibility is all a bit confusing because the\ncode is scattered.    If a `Portfolio` class is supposed to contain\na list of `Stock` instances, maybe you should change the class to be a bit more clear.\nLike this:\n\n```python\n# portfolio.py\n\nimport stock\n\nclass Portfolio:\n    def __init__(self):\n        self.holdings = []\n\n    def append(self, holding):\n        if not isinstance(holding, stock.Stock):\n            raise TypeError('Expected a Stock instance')\n        self.holdings.append(holding)\n    ...\n```\n\nIf you want to read a portfolio from a CSV file, maybe you should make a\nclass method for it:\n\n```python\n# portfolio.py\n\nimport fileparse\nimport stock\n\nclass Portfolio:\n    def __init__(self):\n        self.holdings = []\n\n    def append(self, holding):\n        if not isinstance(holding, stock.Stock):\n            raise TypeError('Expected a Stock instance')\n        self.holdings.append(holding)\n\n    @classmethod\n    def from_csv(cls, lines, **opts):\n        self = cls()\n        portdicts = fileparse.parse_csv(lines,\n                                        select=['name','shares','price'],\n                                        types=[str,int,float],\n                                        **opts)\n\n        for d in portdicts:\n            self.append(stock.Stock(**d))\n\n        return self\n```\n\nTo use this new Portfolio class, you can now write code like this:\n\n```\n>>> from portfolio import Portfolio\n>>> with open('Data/portfolio.csv') as lines:\n...     port = Portfolio.from_csv(lines)\n...\n>>>\n```\n\nMake these changes to the `Portfolio` class and modify the `report.py`\ncode to use the class method.\n\n[Contents](../Contents.md) \\| [Previous (7.4 Decorators)](04_Function_decorators.md) \\| [Next (8 Testing and Debugging)](../08_Testing_debugging/00_Overview.md)\n"
  },
  {
    "path": "Notes/08_Testing_debugging/00_Overview.md",
    "content": "[Contents](../Contents.md) \\| [Prev (7 Advanced Topics)](../07_Advanced_Topics/00_Overview.md) \\| [Next (9 Packages)](../09_Packages/00_Overview.md)\n\n# 8. Testing and debugging\n\nThis section introduces a few basic topics related to testing,\nlogging, and debugging.\n\n* [8.1 Testing](01_Testing.md)\n* [8.2 Logging, error handling and diagnostics](02_Logging.md)\n* [8.3 Debugging](03_Debugging.md)\n\n[Contents](../Contents.md) \\| [Prev (7 Advanced Topics)](../07_Advanced_Topics/00_Overview.md) \\| [Next (9 Packages)](../09_Packages/00_Overview.md)\n"
  },
  {
    "path": "Notes/08_Testing_debugging/01_Testing.md",
    "content": "[Contents](../Contents.md) \\| [Previous (7.5 Decorated Methods)](../07_Advanced_Topics/05_Decorated_methods.md) \\| [Next (8.2 Logging)](02_Logging.md)\n\n# 8.1 Testing\n\n## Testing Rocks, Debugging Sucks\n\nThe dynamic nature of Python makes testing critically important to\nmost applications.  There is no compiler to find your bugs. The only\nway to find bugs is to run the code and make sure you try out all of\nits features.\n\n## Assertions\n\nThe `assert` statement is an internal check for the program.  If an\nexpression is not true, it raises a `AssertionError` exception.\n\n`assert` statement syntax.\n\n```python\nassert <expression> [, 'Diagnostic message']\n```\n\nFor example.\n\n```python\nassert isinstance(10, int), 'Expected int'\n```\n\nIt shouldn't be used to check the user-input (i.e., data entered\non a web form or something).  It's purpose is more for internal\nchecks and invariants (conditions that should always be true).\n\n### Contract Programming\n\nAlso known as Design By Contract, liberal use of assertions is an\napproach for designing software. It prescribes that software designers\nshould define precise interface specifications for the components of\nthe software.\n\nFor example, you might put assertions on all inputs of a function.\n\n```python\ndef add(x, y):\n    assert isinstance(x, int), 'Expected int'\n    assert isinstance(y, int), 'Expected int'\n    return x + y\n```\n\nChecking inputs will immediately catch callers who aren't using\nappropriate arguments.\n\n```python\n>>> add(2, 3)\n5\n>>> add('2', '3')\nTraceback (most recent call last):\n...\nAssertionError: Expected int\n>>>\n```\n\n### Inline Tests\n\nAssertions can also be used for simple tests.\n\n```python\ndef add(x, y):\n    return x + y\n\nassert add(2,2) == 4\n```\n\nThis way you are including the test in the same module as your code.\n\n*Benefit: If the code is obviously broken, attempts to import the\n module will crash.*\n\nThis is not recommended for exhaustive testing. It's more of a\nbasic \"smoke test\".  Does the function work on any example at all?\nIf not, then something is definitely broken.\n\n### `unittest` Module\n\nSuppose you have some code.\n\n```python\n# simple.py\n\ndef add(x, y):\n    return x + y\n```\n\nNow, suppose you want to test it.  Create a separate testing file like this.\n\n```python\n# test_simple.py\n\nimport simple\nimport unittest\n```\n\nThen define a testing class.\n\n```python\n# test_simple.py\n\nimport simple\nimport unittest\n\n# Notice that it inherits from unittest.TestCase\nclass TestAdd(unittest.TestCase):\n    ...\n```\n\nThe testing class must inherit from `unittest.TestCase`.\n\nIn the testing class, you define the testing methods.\n\n```python\n# test_simple.py\n\nimport simple\nimport unittest\n\n# Notice that it inherits from unittest.TestCase\nclass TestAdd(unittest.TestCase):\n    def test_simple(self):\n        # Test with simple integer arguments\n        r = simple.add(2, 2)\n        self.assertEqual(r, 5)\n    def test_str(self):\n        # Test with strings\n        r = simple.add('hello', 'world')\n        self.assertEqual(r, 'helloworld')\n```\n\n*Important: Each method must start with `test`.\n\n### Using `unittest`\n\nThere are several built in assertions that come with `unittest`. Each of them asserts a different thing.\n\n```python\n# Assert that expr is True\nself.assertTrue(expr)\n\n# Assert that x == y\nself.assertEqual(x,y)\n\n# Assert that x != y\nself.assertNotEqual(x,y)\n\n# Assert that x is near y\nself.assertAlmostEqual(x,y,places)\n\n# Assert that callable(arg1,arg2,...) raises exc\nself.assertRaises(exc, callable, arg1, arg2, ...)\n```\n\nThis is not an exhaustive list. There are other assertions in the\nmodule.\n\n### Running `unittest`\n\nTo run the tests, turn the code into a script.\n\n```python\n# test_simple.py\n\n...\n\nif __name__ == '__main__':\n    unittest.main()\n```\n\nThen run Python on the test file.\n\n```bash\nbash % python3 test_simple.py\nF.\n========================================================\nFAIL: test_simple (__main__.TestAdd)\n--------------------------------------------------------\nTraceback (most recent call last):\n  File \"testsimple.py\", line 8, in test_simple\n    self.assertEqual(r, 5)\nAssertionError: 4 != 5\n--------------------------------------------------------\nRan 2 tests in 0.000s\nFAILED (failures=1)\n```\n\n### Commentary\n\nEffective unit testing is an art and it can grow to be quite\ncomplicated for large applications.\n\nThe `unittest` module has a huge number of options related to test\nrunners, collection of results and other aspects of testing. Consult\nthe documentation for details.\n\n### Third Party Test Tools\n\nThe built-in `unittest` module has the advantage of being available everywhere--it's\npart of Python.  However, many programmers also find it to be quite verbose.\nA popular alternative is [pytest](https://docs.pytest.org/en/latest/).   With pytest,\nyour testing file simplifies to something like the following:\n\n```python\n# test_simple.py\nimport simple\n\ndef test_simple():\n    assert simple.add(2,2) == 4\n\ndef test_str():\n    assert simple.add('hello','world') == 'helloworld'\n```\n\nTo run the tests, you simply type a command such as `python -m pytest`.  It will\ndiscover all of the tests and run them.\n\nThere's a lot more to `pytest` than this example, but it's usually pretty easy to\nget started should you decide to try it out.\n\n## Exercises\n\nIn this exercise, you will explore the basic mechanics of using\nPython's `unittest` module.\n\nIn earlier exercises, you wrote a file `stock.py` that contained a\n`Stock` class.  For this exercise, it assumed that you're using the\ncode written for [Exercise\n7.9](../07_Advanced_Topics/03_Returning_functions) involving\ntyped-properties.  If, for some reason, that's not working, you might\nwant to copy the solution from `Solutions/7_9` to your working\ndirectory.\n\n### Exercise 8.1: Writing Unit Tests\n\nIn a separate file `test_stock.py`, write a set a unit tests\nfor the `Stock` class.   To get you started, here is a small\nfragment of code that tests instance creation:\n\n\n```python\n# test_stock.py\n\nimport unittest\nimport stock\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\nif __name__ == '__main__':\n    unittest.main()\n```\n\nRun your unit tests.   You should get some output that looks like this:\n\n```\n.\n----------------------------------------------------------------------\nRan 1 tests in 0.000s\n\nOK\n```\n\nOnce you're satisfied that it works, write additional unit tests that\ncheck for the following:\n\n- Make sure the `s.cost` property returns the correct value (49010.0)\n- Make sure the `s.sell()` method works correctly.  It should\n  decrement the value of `s.shares` accordingly.\n- Make sure that the `s.shares` attribute can't be set to a non-integer value.\n\nFor the last part, you're going to need to check that an exception is raised.\nAn easy way to do that is with code like this:\n\n```python\nclass TestStock(unittest.TestCase):\n    ...\n    def test_bad_shares(self):\n         s = stock.Stock('GOOG', 100, 490.1)\n         with self.assertRaises(TypeError):\n             s.shares = '100'\n```\n\n[Contents](../Contents.md) \\| [Previous (7.5 Decorated Methods)](../07_Advanced_Topics/05_Decorated_methods.md) \\| [Next (8.2 Logging)](02_Logging.md)\n"
  },
  {
    "path": "Notes/08_Testing_debugging/02_Logging.md",
    "content": "[Contents](../Contents.md) \\| [Previous (8.1 Testing)](01_Testing.md) \\| [Next (8.3 Debugging)](03_Debugging.md)\n\n# 8.2 Logging\n\nThis section briefly introduces the logging module.\n\n### logging Module\n\nThe `logging` module is a standard library module for recording\ndiagnostic information.  It's also a very large module with a lot of\nsophisticated functionality.  We will show a simple example to\nillustrate its usefulness.\n\n### Exceptions Revisited\n\nIn the exercises, we wrote a function `parse()` that looked something\nlike this:\n\n```python\n# fileparse.py\ndef parse(f, types=None, names=None, delimiter=None):\n    records = []\n    for line in f:\n        line = line.strip()\n        if not line: continue\n        try:\n            records.append(split(line,types,names,delimiter))\n        except ValueError as e:\n            print(\"Couldn't parse :\", line)\n            print(\"Reason :\", e)\n    return records\n```\n\nFocus on the `try-except` statement. What should you do in the `except` block?\n\nShould you print a warning message?\n\n```python\ntry:\n    records.append(split(line,types,names,delimiter))\nexcept ValueError as e:\n    print(\"Couldn't parse :\", line)\n    print(\"Reason :\", e)\n```\n\nOr do you silently ignore it?\n\n```python\ntry:\n    records.append(split(line,types,names,delimiter))\nexcept ValueError as e:\n    pass\n```\n\nNeither solution is satisfactory because you often want *both* behaviors (user selectable).\n\n### Using logging\n\nThe `logging` module can address this.\n\n```python\n# fileparse.py\nimport logging\nlog = logging.getLogger(__name__)\n\ndef parse(f,types=None,names=None,delimiter=None):\n    ...\n    try:\n        records.append(split(line,types,names,delimiter))\n    except ValueError as e:\n        log.warning(\"Couldn't parse : %s\", line)\n        log.debug(\"Reason : %s\", e)\n```\n\nThe code is modified to issue warning messages or a special `Logger`\nobject. The one created with `logging.getLogger(__name__)`.\n\n### Logging Basics\n\nCreate a logger object.\n\n```python\nlog = logging.getLogger(name)   # name is a string\n```\n\nIssuing log messages.\n\n```python\nlog.critical(message [, args])\nlog.error(message [, args])\nlog.warning(message [, args])\nlog.info(message [, args])\nlog.debug(message [, args])\n```\n\n*Each method represents a different level of severity.*\n\nAll of them create a formatted log message. `args` is used with the `%` operator to create the message.\n\n```python\nlogmsg = message % args # Written to the log\n```\n\n### Logging Configuration\n\nThe logging behavior is configured separately.\n\n```python\n# main.py\n\n...\n\nif __name__ == '__main__':\n    import logging\n    logging.basicConfig(\n        filename  = 'app.log',      # Log output file\n        level     = logging.INFO,   # Output level\n    )\n```\n\nTypically, this is a one-time configuration at program startup.  The\nconfiguration is separate from the code that makes the logging calls.\n\n### Comments\n\nLogging is highly configurable.  You can adjust every aspect of it:\noutput files, levels, message formats, etc.  However, the code that\nuses logging doesn't have to worry about that.\n\n## Exercises\n\n### Exercise 8.2: Adding logging to a module\n\nIn `fileparse.py`, there is some error handling related to\nexceptions caused by bad input. It looks like this:\n\n```python\n# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    print(f\"Row {rowno}: Couldn't convert {row}\")\n                    print(f\"Row {rowno}: Reason {e}\")\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n```\n\nNotice the print statements that issue diagnostic messages.  Replacing those\nprints with logging operations is relatively simple.  Change the code like this:\n\n```python\n# fileparse.py\nimport csv\nimport logging\nlog = logging.getLogger(__name__)\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    log.warning(\"Row %d: Couldn't convert %s\", rowno, row)\n                    log.debug(\"Row %d: Reason %s\", rowno, e)\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n```\n\nNow that you've made these changes, try using some of your code on\nbad data.\n\n```python\n>>> import report\n>>> a = report.read_portfolio('Data/missing.csv')\nRow 4: Bad row: ['MSFT', '', '51.23']\nRow 7: Bad row: ['IBM', '', '70.44']\n>>>\n```\n\nIf you do nothing, you'll only get logging messages for the `WARNING`\nlevel and above.  The output will look like simple print statements.\nHowever, if you configure the logging module, you'll get additional\ninformation about the logging levels, module, and more.  Type these\nsteps to see that:\n\n```python\n>>> import logging\n>>> logging.basicConfig()\n>>> a = report.read_portfolio('Data/missing.csv')\nWARNING:fileparse:Row 4: Bad row: ['MSFT', '', '51.23']\nWARNING:fileparse:Row 7: Bad row: ['IBM', '', '70.44']\n>>>\n```\n\nYou will notice that you don't see the output from the `log.debug()`\noperation. Type this to change the level.\n\n```\n>>> logging.getLogger('fileparse').setLevel(logging.DEBUG)\n>>> a = report.read_portfolio('Data/missing.csv')\nWARNING:fileparse:Row 4: Bad row: ['MSFT', '', '51.23']\nDEBUG:fileparse:Row 4: Reason: invalid literal for int() with base 10: ''\nWARNING:fileparse:Row 7: Bad row: ['IBM', '', '70.44']\nDEBUG:fileparse:Row 7: Reason: invalid literal for int() with base 10: ''\n>>>\n```\n\nTurn off all, but the most critical logging messages:\n\n```\n>>> logging.getLogger('fileparse').setLevel(logging.CRITICAL)\n>>> a = report.read_portfolio('Data/missing.csv')\n>>>\n```\n\n### Exercise 8.3: Adding Logging to a Program\n\nTo add logging to an application, you need to have some mechanism to\ninitialize the logging module in the main module.  One way to\ndo this is to include some setup code that looks like this:\n\n```\n# This file sets up basic configuration of the logging module.\n# Change settings here to adjust logging output as needed.\nimport logging\nlogging.basicConfig(\n    filename = 'app.log',            # Name of the log file (omit to use stderr)\n    filemode = 'w',                  # File mode (use 'a' to append)\n    level    = logging.WARNING,      # Logging level (DEBUG, INFO, WARNING, ERROR, or CRITICAL)\n)\n```\n\nAgain, you'd need to put this someplace in the startup steps of your\nprogram.  For example, where would you put this in your `report.py` program?\n\n[Contents](../Contents.md) \\| [Previous (8.1 Testing)](01_Testing.md) \\| [Next (8.3 Debugging)](03_Debugging.md)\n"
  },
  {
    "path": "Notes/08_Testing_debugging/03_Debugging.md",
    "content": "[Contents](../Contents.md) \\| [Previous (8.2 Logging)](02_Logging.md) \\| [Next (9 Packages)](../09_Packages/00_Overview.md)\n\n# 8.3 Debugging\n\n### Debugging Tips\n\nSo, your program has crashed...\n\n```bash\nbash % python3 blah.py\nTraceback (most recent call last):\n  File \"blah.py\", line 13, in ?\n    foo()\n  File \"blah.py\", line 10, in foo\n    bar()\n  File \"blah.py\", line 7, in bar\n    spam()\n  File \"blah.py\", 4, in spam\n    line x.append(3)\nAttributeError: 'int' object has no attribute 'append'\n```\n\nNow what?!\n\n### Reading Tracebacks\n\nThe last line is the specific cause of the crash.\n\n```bash\nbash % python3 blah.py\nTraceback (most recent call last):\n  File \"blah.py\", line 13, in ?\n    foo()\n  File \"blah.py\", line 10, in foo\n    bar()\n  File \"blah.py\", line 7, in bar\n    spam()\n  File \"blah.py\", 4, in spam\n    line x.append(3)\n# Cause of the crash\nAttributeError: 'int' object has no attribute 'append'\n```\n\nHowever, it's not always easy to read or understand.\n\n*PRO TIP: Paste the whole traceback into Google.*\n\n### Using the REPL\n\nUse the option `-i` to keep Python alive when executing a script.\n\n```bash\nbash % python3 -i blah.py\nTraceback (most recent call last):\n  File \"blah.py\", line 13, in ?\n    foo()\n  File \"blah.py\", line 10, in foo\n    bar()\n  File \"blah.py\", line 7, in bar\n    spam()\n  File \"blah.py\", 4, in spam\n    line x.append(3)\nAttributeError: 'int' object has no attribute 'append'\n>>>\n```\n\nIt preserves the interpreter state. That means that you can go poking\naround after the crash. Checking variable values and other state.\n\n### Debugging with Print\n\n`print()` debugging is quite common.\n\n*Tip: Make sure you use `repr()`*\n\n```python\ndef spam(x):\n    print('DEBUG:', repr(x))\n    ...\n```\n\n`repr()` shows you an accurate representation of a value. Not the *nice* printing output.\n\n```python\n>>> from decimal import Decimal\n>>> x = Decimal('3.4')\n# NO `repr`\n>>> print(x)\n3.4\n# WITH `repr`\n>>> print(repr(x))\nDecimal('3.4')\n>>>\n```\n\n### The Python Debugger\n\nYou can manually launch the debugger inside a program.\n\n```python\ndef some_function():\n    ...\n    breakpoint()      # Enter the debugger (Python 3.7+)\n    ...\n```\n\nThis starts the debugger at the `breakpoint()` call.\n\nIn earlier Python versions, you did this.  You'll sometimes see this\nmentioned in other debugging guides.\n\n```python\nimport pdb\n...\npdb.set_trace()       # Instead of `breakpoint()`\n...\n```\n\n### Run under debugger\n\nYou can also run an entire program under debugger.\n\n```bash\nbash % python3 -m pdb someprogram.py\n```\n\nIt will automatically enter the debugger before the first\nstatement. Allowing you to set breakpoints and change the\nconfiguration.\n\nCommon debugger commands:\n\n```code\n(Pdb) help            # Get help\n(Pdb) w(here)         # Print stack trace\n(Pdb) d(own)          # Move down one stack level\n(Pdb) u(p)            # Move up one stack level\n(Pdb) b(reak) loc     # Set a breakpoint\n(Pdb) s(tep)          # Execute one instruction\n(Pdb) c(ontinue)      # Continue execution\n(Pdb) l(ist)          # List source code\n(Pdb) a(rgs)          # Print args of current function\n(Pdb) !statement      # Execute statement\n```\n\nFor breakpoints location is one of the following.\n\n```code\n(Pdb) b 45            # Line 45 in current file\n(Pdb) b file.py:45    # Line 45 in file.py\n(Pdb) b foo           # Function foo() in current file\n(Pdb) b module.foo    # Function foo() in a module\n```\n\n## Exercises\n\n### Exercise 8.4:  Bugs? What Bugs?\n\nIt runs. Ship it!\n\n[Contents](../Contents.md) \\| [Previous (8.2 Logging)](02_Logging.md) \\| [Next (9 Packages)](../09_Packages/00_Overview.md)\n"
  },
  {
    "path": "Notes/09_Packages/00_Overview.md",
    "content": "[Contents](../Contents.md) \\| [Prev (8 Testing and Debugging)](../08_Testing_debugging/00_Overview.md)\n\n# 9 Packages\n\nWe conclude the course with a few details on how to organize your code\ninto a package structure.  We'll also discuss the installation of\nthird party packages and preparing to give your own code away to others.\n\nThe subject of packaging is an ever-evolving, overly complex part of\nPython development.  Rather than focus on specific tools, the main\nfocus of this section is on some general code organization principles\nthat will prove useful no matter what tools you later use to give code\naway or manage dependencies.\n\n* [9.1 Packages](01_Packages.md)\n* [9.2 Third Party Modules](02_Third_party.md)\n* [9.3 Giving your code to others](03_Distribution.md)\n\n[Contents](../Contents.md) \\| [Prev (8 Testing and Debugging)](../08_Testing_debugging/00_Overview.md)\n"
  },
  {
    "path": "Notes/09_Packages/01_Packages.md",
    "content": "[Contents](../Contents.md) \\| [Previous (8.3 Debugging)](../08_Testing_debugging/03_Debugging.md) \\| [Next (9.2 Third Party Packages)](02_Third_party.md)\n\n# 9.1 Packages\n\nIf writing a larger program, you don't really want to organize it as a\nlarge of collection of standalone files at the top level.  This\nsection introduces the concept of a package.\n\n### Modules\n\nAny Python source file is a module.\n\n```python\n# foo.py\ndef grok(a):\n    ...\ndef spam(b):\n    ...\n```\n\nAn `import` statement loads and *executes* a module.\n\n```python\n# program.py\nimport foo\n\na = foo.grok(2)\nb = foo.spam('Hello')\n...\n```\n\n### Packages vs Modules\n\nFor larger collections of code, it is common to organize modules into\na package.\n\n```code\n# From this\npcost.py\nreport.py\nfileparse.py\n\n# To this\nporty/\n    __init__.py\n    pcost.py\n    report.py\n    fileparse.py\n```\n\nYou pick a name and make a top-level directory. `porty` in the example\nabove (clearly picking this name is the most important first step).\n\nAdd an `__init__.py` file to the directory. It may be empty.\n\nPut your source files into the directory.\n\n### Using a Package\n\nA package serves as a namespace for imports.\n\nThis means that there are now multilevel imports.\n\n```python\nimport porty.report\nport = porty.report.read_portfolio('port.csv')\n```\n\nThere are other variations of import statements.\n\n```python\nfrom porty import report\nport = report.read_portfolio('portfolio.csv')\n\nfrom porty.report import read_portfolio\nport = read_portfolio('portfolio.csv')\n```\n\n### Two problems\n\nThere are two main problems with this approach.\n\n* imports between files in the same package break.\n* Main scripts placed inside the package break.\n\nSo, basically everything breaks. But, other than that, it works.\n\n### Problem: Imports\n\nImports between files in the same package *must now include the\npackage name in the import*.  Remember the structure.\n\n```code\nporty/\n    __init__.py\n    pcost.py\n    report.py\n    fileparse.py\n```\n\nModified import example.\n\n```python\n# report.py\nfrom porty import fileparse\n\ndef read_portfolio(filename):\n    return fileparse.parse_csv(...)\n```\n\nAll imports are *absolute*, not relative.\n\n```python\n# report.py\nimport fileparse    # BREAKS. fileparse not found\n\n...\n```\n\n### Relative Imports\n\nInstead of directly using the package name,\nyou can use `.` to refer to the current package.\n\n```python\n# report.py\nfrom . import fileparse\n\ndef read_portfolio(filename):\n    return fileparse.parse_csv(...)\n```\n\nSyntax:\n\n```python\nfrom . import modname\n```\n\nThis makes it easy to rename the package.\n\n### Problem: Main Scripts\n\nRunning a package submodule as a main script breaks.\n\n```bash\nbash $ python porty/pcost.py # BREAKS\n...\n```\n\n*Reason: You are running Python on a single file and Python doesn't\n see the rest of the package structure correctly (`sys.path` is\n wrong).*\n\nAll imports break.   To fix this, you need to run your program in\na different way, using the `-m` option.\n\n```bash\nbash $ python -m porty.pcost # WORKS\n...\n```\n\n### `__init__.py` files\n\nThe primary purpose of these files is to stitch modules together.\n\nExample: consolidating functions\n\n```python\n# porty/__init__.py\nfrom .pcost import portfolio_cost\nfrom .report import portfolio_report\n```\n\nThis makes names appear at the *top-level* when importing.\n\n```python\nfrom porty import portfolio_cost\nportfolio_cost('portfolio.csv')\n```\n\nInstead of using the multilevel imports.\n\n```python\nfrom porty import pcost\npcost.portfolio_cost('portfolio.csv')\n```\n\n### Another solution for scripts\n\nAs noted, you now need to use `-m package.module` to\nrun scripts within your package.\n\n```bash\nbash % python3 -m porty.pcost portfolio.csv\n```\n\nThere is another alternative: Write a new top-level script.\n\n```python\n#!/usr/bin/env python3\n# pcost.py\nimport porty.pcost\nimport sys\nporty.pcost.main(sys.argv)\n```\n\nThis script lives *outside* the package.  For example, looking at the directory\nstructure:\n\n```\npcost.py       # top-level-script\nporty/         # package directory\n    __init__.py\n    pcost.py\n    ...\n```\n\n### Application Structure\n\nCode organization and file structure is key to the maintainability of\nan application.\n\nThere is no \"one-size fits all\" approach for Python.  However, one\nstructure that works for a lot of problems is something like this.\n\n```code\nporty-app/\n  README.txt\n  script.py         # SCRIPT\n  porty/\n    # LIBRARY CODE\n    __init__.py\n    pcost.py\n    report.py\n    fileparse.py\n```\n\nThe top-level `porty-app` is a container for everything else--documentation,\ntop-level scripts, examples, etc.\n\nAgain, top-level scripts (if any) need to exist outside the code\npackage. One level up.\n\n```python\n#!/usr/bin/env python3\n# porty-app/script.py\nimport sys\nimport porty\n\nporty.report.main(sys.argv)\n```\n\n## Exercises\n\nAt this point, you have a directory with several programs:\n\n```\npcost.py          # computes portfolio cost\nreport.py         # Makes a report\nticker.py         # Produce a real-time stock ticker\n```\n\nThere are a variety of supporting modules with other functionality:\n\n```\nstock.py          # Stock class\nportfolio.py      # Portfolio class\nfileparse.py      # CSV parsing\ntableformat.py    # Formatted tables\nfollow.py         # Follow a log file\ntypedproperty.py  # Typed class properties\n```\n\nIn this exercise, we're going to clean up the code and put it into\na common package.\n\n### Exercise 9.1: Making a simple package\n\nMake a directory called `porty/` and put all of the above Python\nfiles into it.  Additionally create an empty `__init__.py` file and\nput it in the directory.  You should have a directory of files\nlike this:\n\n```\nporty/\n    __init__.py\n    fileparse.py\n    follow.py\n    pcost.py\n    portfolio.py\n    report.py\n    stock.py\n    tableformat.py\n    ticker.py\n    typedproperty.py\n```\n\nRemove the file `__pycache__` that's sitting in your directory.  This\ncontains pre-compiled Python modules from before.  We want to start\nfresh.\n\nTry importing some of package modules:\n\n```python\n>>> import porty.report\n>>> import porty.pcost\n>>> import porty.ticker\n```\n\nIf these imports fail, go into the appropriate file and fix the\nmodule imports to include a package-relative import.   For example,\na statement such as `import fileparse` might change to the\nfollowing:\n\n```\n# report.py\nfrom . import fileparse\n...\n```\n\nIf you have a statement such as `from fileparse import parse_csv`, change\nthe code to the following:\n\n```\n# report.py\nfrom .fileparse import parse_csv\n...\n```\n\n### Exercise 9.2: Making an application directory\n\nPutting all of your code into a \"package\" isn't often enough for an\napplication. Sometimes there are supporting files, documentation,\nscripts, and other things.  These files need to exist OUTSIDE of the\n`porty/` directory you made above.\n\nCreate a new directory called `porty-app`.  Move the `porty` directory\nyou created in Exercise 9.1 into that directory.  Copy the\n`Data/portfolio.csv` and `Data/prices.csv` test files into this\ndirectory.  Additionally create a `README.txt` file with some\ninformation about yourself.  Your code should now be organized as\nfollows:\n\n```\nporty-app/\n    portfolio.csv\n    prices.csv\n    README.txt\n    porty/\n        __init__.py\n        fileparse.py\n        follow.py\n        pcost.py\n        portfolio.py\n        report.py\n        stock.py\n        tableformat.py\n        ticker.py\n        typedproperty.py\n```\n\nTo run your code, you need to make sure you are working in the top-level `porty-app/`\ndirectory.  For example, from the terminal:\n\n```python\nshell % cd porty-app\nshell % python3\n>>> import porty.report\n>>>\n```\n\nTry running some of your prior scripts as a main program:\n\n```python\nshell % cd porty-app\nshell % python3 -m porty.report portfolio.csv prices.csv txt\n      Name     Shares      Price     Change\n---------- ---------- ---------- ----------\n        AA        100       9.22     -22.98\n       IBM         50     106.28      15.18\n       CAT        150      35.46     -47.98\n      MSFT        200      20.89     -30.34\n        GE         95      13.48     -26.89\n      MSFT         50      20.89     -44.21\n       IBM        100     106.28      35.84\n\nshell %\n```\n\n### Exercise 9.3: Top-level Scripts\n\nUsing the `python -m` command is often a bit weird.  You may want to\nwrite a top level script that simply deals with the oddities of packages.\nCreate a script `print-report.py` that produces the above report:\n\n```python\n#!/usr/bin/env python3\n# print-report.py\nimport sys\nfrom porty.report import main\nmain(sys.argv)\n```\n\nPut this script in the top-level `porty-app/` directory.  Make sure you\ncan run it in that location:\n\n```\nshell % cd porty-app\nshell % python3 print-report.py portfolio.csv prices.csv txt\n      Name     Shares      Price     Change\n---------- ---------- ---------- ----------\n        AA        100       9.22     -22.98\n       IBM         50     106.28      15.18\n       CAT        150      35.46     -47.98\n      MSFT        200      20.89     -30.34\n        GE         95      13.48     -26.89\n      MSFT         50      20.89     -44.21\n       IBM        100     106.28      35.84\n\nshell %\n```\n\nYour final code should now be structured something like this:\n\n```\nporty-app/\n    portfolio.csv\n    prices.csv\n    print-report.py\n    README.txt\n    porty/\n        __init__.py\n        fileparse.py\n        follow.py\n        pcost.py\n        portfolio.py\n        report.py\n        stock.py\n        tableformat.py\n        ticker.py\n        typedproperty.py\n```\n\n[Contents](../Contents.md) \\| [Previous (8.3 Debugging)](../08_Testing_debugging/03_Debugging.md) \\| [Next (9.2 Third Party Packages)](02_Third_party.md)\n"
  },
  {
    "path": "Notes/09_Packages/02_Third_party.md",
    "content": "[Contents](../Contents.md) \\| [Previous (9.1 Packages)](01_Packages.md) \\| [Next (9.3 Distribution)](03_Distribution.md)\n\n# 9.2 Third Party Modules\n\nPython has a large library of built-in modules (*batteries included*).\n\nThere are even more third party modules. Check them in the [Python Package Index](https://pypi.org/) or PyPi.\nOr just do a Google search for a specific topic.\n\nHow to handle third-party dependencies is an ever-evolving topic with\nPython.  This section merely covers the basics to help you wrap\nyour brain around how it works.\n\n### The Module Search Path\n\n`sys.path` is a directory that contains the list of all directories\nchecked by the `import` statement. Look at it:\n\n```python\n>>> import sys\n>>> sys.path\n... look at the result ...\n>>>\n```\n\nIf you import something and it's not located in one of those\ndirectories, you will get an `ImportError` exception.\n\n### Standard Library Modules\n\nModules from Python's standard library usually come from a location\nsuch as `/usr/local/lib/python3.6'.  You can find out for certain\nby trying a short test:\n\n```python\n>>> import re\n>>> re\n<module 're' from '/usr/local/lib/python3.6/re.py'>\n>>>\n```\n\nSimply looking at a module in the REPL is a good debugging tip\nto know about.  It will show you the location of the file.\n\n### Third-party Modules\n\nThird party modules are usually located in a dedicated\n`site-packages` directory.   You'll see it if you perform\nthe same steps as above:\n\n```python\n>>> import numpy\n>>> numpy\n<module 'numpy' from '/usr/local/lib/python3.6/site-packages/numpy/__init__.py'>\n>>>\n```\n\nAgain, looking at a module is a good debugging tip if you're\ntrying to figure out why something related to `import` isn't working\nas expected.\n\n### Installing Modules\n\nThe most common technique for installing a third-party module is to use\n`pip`.  For example:\n\n```bash\nbash % python3 -m pip install packagename\n```\n\nThis command will download the package and install it in the `site-packages`\ndirectory.\n\n### Problems\n\n* You may be using an installation of Python that you don't directly control.\n  * A corporate approved installation\n  * You're using the Python version that comes with the OS.\n* You might not have permission to install global packages in the computer.\n* There might be other dependencies.\n\n### Virtual Environments\n\nA common solution to package installation issues is to create a\nso-called \"virtual environment\" for yourself.  Naturally, there is no\n\"one way\" to do this--in fact, there are several competing tools and\ntechniques.  However, if you are using a standard Python installation,\nyou can try typing this:\n\n```bash\nbash % python -m venv mypython\nbash %\n```\n\nAfter a few moments of waiting, you will have a new directory\n`mypython` that's your own little Python install.  Within that\ndirectory you'll find a `bin/` directory (Unix) or a `Scripts/`\ndirectory (Windows).  If you run the `activate` script found there, it\nwill \"activate\" this version of Python, making it the default `python`\ncommand for the shell.  For example:\n\n```bash\nbash % source mypython/bin/activate\n(mypython) bash %\n```\n\nFrom here, you can now start installing Python packages for yourself.\nFor example:\n\n```\n(mypython) bash % python -m pip install pandas\n...\n```\n\nFor the purposes of experimenting and trying out different\npackages, a virtual environment will usually work fine.  If,\non the other hand, you're creating an application and it\nhas specific package dependencies, that is a slightly\ndifferent problem.\n\n### Handling Third-Party Dependencies in Your Application\n\nIf you have written an application and it has specific third-party\ndependencies, one challenge concerns the creation and preservation of\nthe environment that includes your code and the dependencies.  Sadly,\nthis has been an area of great confusion and frequent change over\nPython's lifetime.  It continues to evolve even now.\n\nRather than provide information that's bound to be out of date soon,\nI refer you to the [Python Packaging User Guide](https://packaging.python.org).\n\n## Exercises\n\n### Exercise 9.4 : Creating a Virtual Environment\n\nSee if you can recreate the steps of making a virtual environment and installing\npandas into it as shown above.\n\n[Contents](../Contents.md) \\| [Previous (9.1 Packages)](01_Packages.md) \\| [Next (9.3 Distribution)](03_Distribution.md)\n\n\n\n\n\n\n"
  },
  {
    "path": "Notes/09_Packages/03_Distribution.md",
    "content": "[Contents](../Contents.md) \\| [Previous (9.2 Third Party Packages)](02_Third_party.md) \\| [Next (The End)](TheEnd.md)\n\n# 9.3 Distribution\n\nAt some point you might want to give your code to someone else, possibly just a co-worker.\nThis section gives the most basic technique of doing that.   For more detailed\ninformation, you'll need to consult the [Python Packaging User Guide](https://packaging.python.org).\n\n### Creating a setup.py file\n\nAdd a `setup.py` file to the top-level of your project directory.\n\n```python\n# setup.py\nimport setuptools\n\nsetuptools.setup(\n    name=\"porty\",\n    version=\"0.0.1\",\n    author=\"Your Name\",\n    author_email=\"you@example.com\",\n    description=\"Practical Python Code\",\n    packages=setuptools.find_packages(),\n)\n```\n\n### Creating MANIFEST.in\n\nIf there are additional files associated with your project, specify them with a `MANIFEST.in` file.\nFor example:\n\n```\n# MANIFEST.in\ninclude *.csv\n```\n\nPut the `MANIFEST.in` file in the same directory as `setup.py`.\n\n### Creating a source distribution\n\nTo create a distribution of your code, use the `setup.py` file.  For example:\n\n```\nbash % python setup.py sdist\n```\n\nThis will create a `.tar.gz` or `.zip` file in the directory `dist/`.  That file is something\nthat you can now give away to others.\n\n### Installing your code\n\nOthers can install your Python code using `pip` in the same way that they do for other\npackages.  They simply need to supply the file created in the previous step.\nFor example:\n\n```\nbash % python -m pip install porty-0.0.1.tar.gz\n```\n\n### Commentary\n\nThe steps above describe the absolute most minimal basics of creating\na package of Python code that you can give to another person.  In\nreality, it can be much more complicated depending on third-party\ndependencies, whether or not your application includes foreign code\n(i.e., C/C++), and so forth.  Covering that is outside the scope of\nthis course.  We've only taken a tiny first step.\n\n## Exercises\n\n### Exercise 9.5:  Make a package\n\nTake the `porty-app/` code you created for Exercise 9.3 and see if you\ncan recreate the steps described here.  Specifically, add a `setup.py`\nfile and a `MANIFEST.in` file to the top-level directory.\nCreate a source distribution file by running `python setup.py sdist`.\n\nAs a final step, see if you can install your package into a Python\nvirtual environment.\n\n[Contents](../Contents.md) \\| [Previous (9.2 Third Party Packages)](02_Third_party.md) \\| [Next (The End)](TheEnd.md)\n\n\n\n\n\n\n"
  },
  {
    "path": "Notes/09_Packages/TheEnd.md",
    "content": "# The End!\n\nYou've made it to the end of the course.  Thanks for your time and your attention.\nMay your future Python hacking be fun and productive!\n\nI'm always happy to get feedback.  You can find me at [https://dabeaz.com](https://dabeaz.com)\nor on Twitter at [@dabeaz](https://twitter.com/dabeaz). - David Beazley.\n\n[Contents](../Contents.md) \\| [Home](../..)\n\n"
  },
  {
    "path": "Notes/Contents.md",
    "content": "# Practical Python Programming\n\n## Table of Contents\n\n* [0. Course Setup (READ FIRST!)](00_Setup.md)\n* [1. Introduction to Python](01_Introduction/00_Overview.md)\n* [2. Working with Data](02_Working_with_data/00_Overview.md)\n* [3. Program Organization](03_Program_organization/00_Overview.md)\n* [4. Classes and Objects](04_Classes_objects/00_Overview.md)\n* [5. The Inner Workings of Python Objects](05_Object_model/00_Overview.md)\n* [6. Generators](06_Generators/00_Overview.md)\n* [7. A Few Advanced Topics](07_Advanced_Topics/00_Overview.md)\n* [8. Testing, Logging, and Debugging](08_Testing_debugging/00_Overview.md)\n* [9. Packages](09_Packages/00_Overview.md)\n\nPlease see the [Instructor Notes](InstructorNotes.md) if you plan on\nteaching the course.\n\n[Home](../README.md)\n\n\n\n\n\n\n"
  },
  {
    "path": "Notes/InstructorNotes.md",
    "content": "# Practical Python Programming - Instructor Notes\n\nAuthor: David Beazley\n\n## Overview\n\nThis document provides some general notes and advice on teaching the\ncontent of my “Practical Python” course including objectives, target\naudience, tricky bits, etc.   \n\nThese instructions were given to people teaching the course in\na typical three-day corporate training environment.  They might\ngive you some insight about teaching your own course.\n\n## Target Audience and General Approach\n\nThis course is intended to be an “Introduction to Python” course for\npeople who already have some programming experience.  This is\ndefinitely not a course designed to teach people “programming 101.”\n\nHaving said that, I have observed that the typical student in a Python\ncourse is also not likely to be a hard-core software engineer or\nprogrammer.  Instead, you are probably going to get a mix of\nengineers, scientists, web programmers, and more inexperienced\ndevelopers.  Student background varies widely.  You might have some\nstudents with a lot of C,C++, Java experience, others might know PHP\nand HTML, others may be coming from tools like MATLAB, and others\nstill might have almost no traditional “programming” experience at all\ndespite my best attempts to make the prerequisites clear.\n\nWith this in mind, the course aims to teach Python through the general\nproblem of manipulating data (stock market data in particular).  This\ndomain has been chosen because it’s simple and something everyone\nshould know about it regardless of their background.  Just as an example,\nstudents with weak programming skills are still likely to know about\ncommon things like using a spreadsheet (e.g., Excel).  So, if they’re\nreally stuck, you can tell them things like “well, this list of tuples\nis kind of like rows of data in a spreadsheet” or “a list\ncomprehension is the same idea as applying an operation to a\nspreadsheet column and putting the result in a different column.”  The\nkey idea is to stay grounded in a real-world setting as opposed to\ngetting sidetracked into esoteric “computer science” problems (e.g.,\n“let’s go compute fibonacci numbers.”).\n\nThis problem domain also works well for introducing other programming\ntopics.  For example, scientists/engineers might want to know about\ndata analysis or plotting.  So, you can show them how to make a plot\nusing matplotlib. Web programmers might want to know how to present\nstock market data on a web-page.  So, you can talk about template\nengines.  Sysadmins might want to do something with log files.  So, you\ncan point them at a log file of real-time streaming stock data.\nSoftware engineers might want to know about design.  So, you can have\nthem look at ways to encapsulate stock data inside an object or making\na program extensible (e.g., how would make this program produce output\nin 10 different table formats).  You get the idea.\n\n## Presentation Guidelines\n\nThe presentation slides (notes) are there to provide a narrative\nstructure to the course and for reference by students when they work\non exercises.  Do not laboriously go over every bullet point on every\nslide--assume that students can read and that they will have time to\ngo back when coding.  I tend to go through the slides at a pretty\nbrisk pace, showing short examples interactively as I go.  I often\nskip slides entirely in favor of live demos.  For example, you don't\nreally need to do a bunch of slides on lists.  Just go to the\ninterpreter and do some list examples live instead. Rule of thumb: No\nmore than 1 minute per slide unless it’s something unusually tricky.\nHonestly, you could probably skip most of the slides and simply\nlecture using live demos if you feel that it works for you.  I often\ndo this.\n\n## Course Exercises\n\nThe course has about 130 hands-on exercises.  If you do every single\nexercise and give students time to think and code, it will likely take\nthem about 10-12 hours. In practice, you will probably find that students\nrequire more time on certain exercises.  I have some notes about this\nbelow.\n\nYou should repeatedly emphasize to students that solution code is\navailable and that it is okay to look at it and copy it--especially\ndue to time requirements.\n\nPrior to teaching the course, I would strongly advise that you go\nthrough and work every single course exercise so that there are no\nsurprises.\n\nDuring course delivery, I usually work every single exercise from\nscratch, without looking at the solution, on my computer while the\nstudents also work.  For this, I strongly advise you to have a printed\ncopy of the exercises on hand that you can look at without having to\npull it up on the computer screen (which is being projected).  Near\nthe end of the exercise time period, I will start discussing my\nsolution code, emphasizes different bits on the screen and talking\nabout them.  If there are any potential problems with the solution\n(including design considerations), I’ll also talk about it.  Emphasize\nto students that they may want to look at/copy solution code before\ngoing forward.\n\n## Section 1:  Introduction\n\nThe major goal of this section is to get people started with the\nenvironment.  This includes using the interactive shell and\nediting/run short programs.  By the end of the section, students\nshould be able to write short scripts that read data files and perform\nsmall calculations.  They will know about numbers, strings, lists, and\nfiles.  There will also be some exposure to functions, exceptions, and\nmodules, but a lot of details will be missing.\n\nThe first part of this course is often the longest because students\nare new to the tools and may have various problems getting things to\nwork.  It is absolutely critical that you go around the room and make\nsure that everyone can edit, run, and debug simple programs.  Make\nsure Python is installed correctly.  Make sure they have the course\nexercises downloaded.  Make sure the internet works.  Fix anything\nelse that comes up.\n\nTiming: I aim to finish section 1 around lunch on the first day. \n\n## Section 2 : Working with Data\n\nThis section is probably the most important in the course.  It covers\nthe basics of data representation and manipulation including tuples,\nlists, dicts, and sets.\n\nSection 2.2 the most important.  Give students as much time as\nthey need to get exercises working within reason.  Depending on audience,\nthe exercises might last 45 minutes.  In the middle of this exercise,\nI will often move forward to Section 2.3 (formatted printing) and\ngive students more time to keep working.  Together, Sections 2.2/2.3\nmight take an hour or more.\n\nSection 2.4 has people explore the use of enumerate(), and zip().  I\nconsider these functions essential so don’t skimp on it.\n\nSection 2.5 introduces the collections module.  There is a LOT that\ncould be said about collections, but it won't be fully appreciated by\nstudents at this time.  Approach this more from the standpoint of\n\"here's this cool module you should look at later. Here are a few cool\nexamples.\"\n\nSection 2.6 introduces list comprehensions which are an important\nfeature for processing list data.  Emphasize to students that list\ncomprehensions are very similar to things like SQL database queries.\nAt the end of this exercise, I often do an interactive demo involving\nsomething more advanced.  Maybe do a list comprehension and plot some\ndata with matplotlib.  Also an opportunity to introduce Jupyter if\nyou're so inclined.\n\nSection 2.7 is the most sophisticated exercise.  It relates to the use\nof first-class data in Python and the fact that data structures like\nlists can hold any kind of object that you want.  The exercises are\nrelated to parsing columns of data in CSV files and concepts are later reused in\nSection 3.2.\n\nTiming: Ideally, you want to be done with section 2 on the first day.\nHowever, it is common to finish with section 2.5 or 2.6.  So, don't\npanic if you feel that you're a bit behind.\n\n## 3. Program Organization\n\nThe main goal of this section is to introduce more details about\nfunctions and to encourage students to use them.  The section builds\nfrom functions into modules and script writing.\n\nSection 3.1 is about going from simple “scripting” to functions.\nStudents should be discouraged from writing disorganized “scripts.”\nInstead, code should at least be modularized into functions.  It makes\nthe code easier to understand, it makes it easier to make changes\nlater, and it actually runs a little bit faster.  Functions are good.\n\nSection 3.2 is probably the most advanced set of exercises in the\nwhole course.  It has students write a general purpose utility\nfunction for parsing column-oriented data.  However, it makes heavy\nuse of list comprehensions as well as lists of functions (e.g.,\nfunctions as first-class objects).  You will probably need to guide\npeople through every single step of this code, showing how it works in\ngreat detail.  The payoff is huge however---you can show people a\nshort general purpose function that does something amazingly powerful\nand which would be virtually impossible to write in C, C++, or Java\nwithout having a *LOT* of very complicated code.  There are a lot of\npossible design/discussion avenues for this code.  Use your\nimagination.\n\nSection 3.3 adds error handling to the function created in Section 3.2\nThis is a good time to talk about exception handling generally.\nDefinitely talk about the dangers of catching all exceptions.  This\nmight be a good time to talk about the “Errors should never pass\nsilently” item on the “Zen of Python.”\n\n*Note: Before Exercise 3.4, make sure students get fully working versions of report.py, pcost.py, and fileparse.py.   Copy from Solutions folder if needed *\n\nSection 3.4 Introduces module imports.  The file written in Section\n3.2-3.3 is used to simplify code in Section 3.1.  Be aware that you\nmay need to help students fix issues with IDLE, sys.path, and other\nassorted settings related to import.\n\nSection 3.5 talks about `__main__` and script writing.  There's a bit\nabout command line arguments.  You might be inclined to discuss a\nmodule like argparse.  However, be warned that doing so opens up\na quagmire. It's usually better to just mention it and move on.\n\nSection 3.6 opens up a discussion about design more generally in Python.\nIs it better to write code that's more flexible vs code that's\nhardwired to only work with filenames?  This is the first place\nwhere you make a code change and have to refactor existing code.\n\nGoing forward from here, most of the exercises make small changes\nto code that's already been written.\n\n## 4. Classes and Objects\n\nThis section is about very basic object oriented programming.  In\ngeneral, it is not safe to assume that people have much background in\nOO.  So, before starting this, I usually generally describe the OO\n“style” and how it's data and methods bundled together.  Do some\nexamples with strings and lists to illustrate that they are “objects”\nand that the methods (invoked via .) do things with the object.\nEmphasize how the methods are attached to the object itself.  For\nexample, you do items.append(x), you don’t call a separate function\nappend(items, x).\n\nSection 4.1 introduces the class statement and shows people how to\nmake a basic object.  Really, this just introduces classes as another\nway to define a simple data structure--relating back to using tuples\nand dicts for this purpose in section 2.\n\nSection 4.2 is about inheritance and how you use to create extensible\nprograms.  This set of exercises is probably the most significant in terms of\nOO programming and OO design.  Give students a lot of time to work on\nit (30-45 minutes).  Depending on interest, you can spend a LOT of\ntime discussing aspects of OO. For example, different\ndesign patterns, inheritance hierarchies, abstract base classes, etc.\n\nSection 4.3 does a few experiments with special methods.  I wouldn't\nspend too much time fooling around with this.  Special methods come up\na bit later in Exercise 6.1 and elsewhere.\n\nTiming:   This is usually the end of the 2nd day.\n\n## 5. Inside Objects\n\nThis section takes students behind the scenes of the object system and\nhow it’s built using dictionaries, how instances and classes are tied\ntogether, and how inheritance works.  However, most important part of\nthis section is probably the material about encapsulation (private\nattributes, properties, slots, etc.)\n\nSection 5.1 just peels back the covers and has students observe and\nplay with the underlying dicts of instances and classes.\n\nSection 5.2 is about hiding attributes behind get/set functions and\nusing properties.  I usually emphasize that these techniques are\ncommonly used in libraries and frameworks--especially in situations\nwhere more control over what a user is allowed to do is desired.\n\nAn astute Python master will notice that I do not talk about advanced\ntopics such as descriptors, or attribute access methods (`__getattr__`,\n`__setattr__`) at all.  I have found, through experience, that this is\njust too much mental overload for students taking the intro course.\nEveryone’s head is already on the verge of exploding at this point and\nif you go talk about how something like descriptors work, you’ll lose\nthem for the rest of the day, if not the rest of the course.  Save it\nfor an \"Advanced Python\" course.\n\nIf you're looking at the clock thinking \"There's no way I'm going to\nfinish this course\", you can skip section 5 entirely.\n\n## 6. Generators\n\nThe main purpose of this section is to introduce generators as a way\nto define custom iteration and to use them for various problems\nrelated to data handling.  The course exercises have students analyze\nstreaming data in the form of stock updates being written to a log\nfile.\n\nThere are two big ideas to emphasize. First, generators can be used to\nwrite code based on incremental processing.  This can be very useful\nfor things like streaming data or huge datasets that are too large to\nfit into memory all at once.  The second idea is that you can chain\ngenerators/iterators together to create processing pipelines (kind of\nlike Unix pipes).  Again, this can be a really powerful way to process\nand think about streams, large datasets, etc.\n\nSome omissions: Although the iteration protocol is described, the\nnotes don’t go into detail about creating iterable objects (i.e.,\nclasses with `__iter__()` and `next()`).  In practice, I’ve found that\nit’s not necessary to do this so often (generators are often\nbetter/easier).  So, in the interest of time, I’ve made a conscious\ndecision to omit it.  Also not included are extended generators\n(coroutines) or uses of generators for concurrency (tasklets, etc.).\nThat’s better covered in advanced courses.\n\n## 7. Advanced Topics\n\nBasically this section is an assortment of more advanced topics that\ncould have been covered earlier, but weren’t for various reasons\nrelated to course flow and content of the course exercises.  If you\nmust know, I used to present this material earlier in the course, but\nfound that students were already overloaded with enough information.\nComing back to it later seems to work better---especially since by\nthis point, everyone is much more familiar with working in Python and\nstarting to get the hang of it.\n\nTopics include variadic function arguments (*args, **kwargs), lambda,\nclosures, and decorators.  Discussion of decorators is only a tiny\nhint of what’s possible with metaprogramming.  Feel free to say more\nabout what’s possible, but I’d probably stay out of metaclasses!\nLately, I have been demoing \"numba\" as an example of a more\ninteresting decorator.\n\nIf you're pressed for time, most of section 7 can be skipped or heavily\ncompressed (you could skip exercises for instance).\n\n## 8. Testing and Debugging\n\nThe main purpose of this section is just to introduce various tools\nand techniques related to testing, debugging, and software\ndevelopment.  Show everyone the unittest module.  Introduce the\nlogging module.  Discuss assertions and the idea of “contracts.”  Show\npeople the debugger and profiler.  Most of this is self-explanatory.\n\n## 9. Packages\n\nAt this point, students have written an assortment of files (pcost.py,\nreport.py, fileparse.py, tableformat.py, stock.py, portfolio.py,\nfollow.py, etc.).  Two main goals in this section.  First, put all of\nthe code into a Python package structure.  This is only a gentle\nintroduction to that, but they'll move the files into a directory and\neverything will break.  They'll need to fix their import statements\n(package relative imports) and maybe fiddle with an `__init__.py` file.\nSecond goal, write a simple setup.py file that they can use to package\nup the code and give it away to someone.  That's it.  End of the\ncourse.\n\n[Contents](Contents.md)\n\n\n"
  },
  {
    "path": "README.md",
    "content": "# Welcome!\n\nWhen I first learned Python nearly 27 years ago, I was immediately\nstruck by how I could productively apply it to all sorts of messy work\nprojects. Fast-forward a decade and I found myself teaching others the\nsame fun.  The result of that teaching is this course--A no-nonsense\ntreatment of Python that has been actively taught to more than 400\nin-person groups since 2007.  Traders, systems admins, astronomers,\ntinkerers, and even a few hundred rocket scientists who used Python to\nhelp land a rover on Mars--they've all taken this course. Now, I'm\npleased to make it available under a Creative Commons license--completely\nfree of spam, signups, and other nonsense. Enjoy!\n\n[GitHub Pages](https://dabeaz-course.github.io/practical-python) | [GitHub Repo](https://github.com/dabeaz-course/practical-python).\n\n--David Beazley ([https://dabeaz.com](https://dabeaz.com)), [@dabeaz](https://mastodon.social/@dabeaz)\n\n(P.S. This course is about Python. If you want a Python course that's about programming,\nyou might consider [Advanced Programming with Python](https://www.dabeaz.com/advprog.html))\n\n## What is This?\n\nThe material you see here is the heart of an instructor-led Python\ntraining course used for corporate training and professional\ndevelopment. It was in continual development from 2007 to 2019 and\nbattle tested in real-world classrooms.  Usually, it's taught\nin-person over the span of three or four days--requiring approximately\n25-35 hours of intense work. This includes the completion of\napproximately 130 hands-on coding exercises.\n\n## Target Audience\n\nStudents of this course are usually professional scientists,\nengineers, and programmers who already have experience in at least one\nother programming language. No prior knowledge of Python is required,\nbut knowledge of common programming topics is assumed.  Most\nparticipants find the course challenging--even if they've already been\ndoing a bit of Python programming.\n\n## Course Objectives\n\nThe goal of this course is to cover foundational aspects of Python\nprogramming with an emphasis on script writing, basic data manipulation, and\nprogram organization.  By the end of this course, students should be\nable to start writing useful Python programs on their own or be able\nto understand and modify Python code written by their\ncoworkers.\n\n## Requirements\n\nTo complete this course, you need nothing more than a basic\ninstallation of Python 3.6 or newer and time to work on it.\n\n## What This Course is Not\n\nThis is not a course for absolute beginners on how to program a\ncomputer.  It is assumed that you already have programming experience\nin some other programming language or Python itself.\n\nThis is not a course on web development.  That's a different\ncircus. However, if you stick around for this circus, you'll still see\nsome interesting acts--just nothing involving animals.\n\nThis is not a course on using tools that happen to be written\nin Python. It's about learning the core Python language.\n\nThis is not a course for software engineers on how to write or\nmaintain a one-million line Python application. I don't write programs\nlike that, nor do most companies who use Python, and neither should\nyou. Delete something already!\n\n## Take me to the Course Already!\n\nOk, ok. Point your browser [HERE](Notes/Contents.md)!\n\n## Community Discussion\n\nWant to discuss the course?  Feel free to use [GitHub Discussions](https://github.com/dabeaz-course/practical-python/discussions).\nI can't promise an individual response, but perhaps others can jump in to help.\n\n## Acknowledgements\n\nLlorenç Muntaner was instrumental in converting the course content from\nApple Keynote to the online structure that you see here.\n\nVarious instructors have presented this course at one time or another\nover the last 12 years. This includes (in alphabetical order): Ned\nBatchelder, Juan Pablo Claude, Mark Fenner, Michael Foord, Matt\nHarrison, Raymond Hettinger, Daniel Klein, Travis Oliphant, James\nPowell, Michael Selik, Hugo Shi, Ian Stokes-Rees, Yarko Tymciurak,\nBryan Van de ven, Peter Wang, and Mark Wiebe.\n\nI'd also like to thank the thousands of students who have taken this\ncourse and contributed to its success with their feedback and\ndiscussion.\n\n## Questions and Answers\n\n### Q: Are there course videos I can watch?\n\nNo. This course is about you writing Python code, not watching someone else.\n\n### Q: How is this course licensed?\n\nPractical Python Programming is licensed under a Creative Commons Attribution ShareAlike 4.0 International License.\n\n### Q: May I use this material to teach my own Python course?\n\nYes, as long as appropriate attribution is given.\n\n### Q: May I make derivative works?\n\nYes, as long as such works carry the same license terms and provide attribution.\n\n### Q: Can I translate this to another language?\n\nYes, that would be awesome.  Send me a link when you're done.\n\n### Q: Can I live-stream the course or make a video?\n\nYes, go for it!  You'll probably learn a lot of Python doing that.\n\n### Q: Why wasn't topic X covered?\n\nThere is only so much material that you can cover in 3-4 days.  If\nit wasn't covered, it was probably because it was once covered and it\ncaused everyone's head to explode or there was never enough time to\ncover it in the first place.   Also, this is a course, not a Python\nreference manual.\n\n### Q: Why isn't awesome `{command}` in awesome `{tool}` covered?\n\nThe focus of this course is on learning the core Python language,\nnot learning the names of commands in tools.\n\n### Q: Is this course being maintained or updated?\n\nThis course represents a \"finished product\" that was taught and\ndeveloped for more than decade.  I have no plans to significantly\nrevise the material at this time, but will occasionally fix bugs and\nadd clarification.\n\n### Q: Do you accept pull requests?\n\nBug reports are appreciated and may be filed through the\n[issue tracker](https://github.com/dabeaz-course/practical-python/issues).\nPull requests are not accepted except by invitation. Please file an issue first.\n\n"
  },
  {
    "path": "Solutions/1_10/mortgage.py",
    "content": "# mortgage.py\n\nprincipal = 500000.0\nrate = 0.05\npayment = 2684.11\ntotal_paid = 0.0\nmonth = 0\n\nextra_payment = 1000.0\nextra_payment_start_month = 61\nextra_payment_end_month = 108\n\nwhile principal > 0:\n    month = month + 1\n    principal = principal * (1+rate/12) - payment\n    total_paid = total_paid + payment\n\n    if month >= extra_payment_start_month and month <= extra_payment_end_month:\n        principal = principal - extra_payment\n        total_paid = total_paid + extra_payment\n\n    print(month, round(total_paid,2), round(principal, 2))\n    \nprint('Total paid', round(total_paid, 2))\nprint('Months', month)\n\n\n\n"
  },
  {
    "path": "Solutions/1_27/pcost.py",
    "content": "# pcost.py\n\ntotal_cost = 0.0\n\nwith open('../../Work/Data/portfolio.csv', 'rt') as f:\n    headers = next(f)\n    for line in f:\n        row = line.split(',')\n        nshares = int(row[1])\n        price = float(row[2])\n        total_cost += nshares * price\n\nprint('Total cost', total_cost)\n"
  },
  {
    "path": "Solutions/1_33/pcost.py",
    "content": "# pcost.py\n\nimport csv\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    total_cost = 0.0\n\n    with open(filename, 'rt') as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for row in rows:\n            try:\n                nshares = int(row[1])\n                price = float(row[2])\n                total_cost += nshares * price\n            # This catches errors in int() and float() conversions above\n            except ValueError:\n                print('Bad row:', row)\n\n    return total_cost\n\nimport sys\nif len(sys.argv) == 2:\n    filename = sys.argv[1]\nelse:\n    filename = input('Enter a filename:')\n\ncost = portfolio_cost(filename)\nprint('Total cost:', cost)\n"
  },
  {
    "path": "Solutions/1_5/bounce.py",
    "content": "# bounce.py\n\nheight = 100\nbounce = 1\nwhile bounce <= 10:\n    height = height * (3/5)\n    print(bounce, round(height, 4))\n    bounce += 1\n"
  },
  {
    "path": "Solutions/2_11/report.py",
    "content": "# report.py\nimport csv\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    portfolio = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n\n        for row in rows:\n            stock = {\n                 'name'   : row[0],\n                 'shares' : int(row[1]),\n                 'price'   : float(row[2])\n            }\n            portfolio.append(stock)\n\n    return portfolio\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    prices = {}\n    with open(filename) as f:\n        rows = csv.reader(f)\n        for row in rows:\n            try:\n                prices[row[0]] = float(row[1])\n            except IndexError:\n                pass\n\n    return prices\n\ndef make_report_data(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for stock in portfolio:\n        current_price = prices[stock['name']]\n        change = current_price - stock['price']\n        summary = (stock['name'], stock['shares'], current_price, change)\n        rows.append(summary)\n    return rows\n        \n# Read data files and create the report data        \n\nportfolio = read_portfolio('../../Work/Data/portfolio.csv')\nprices = read_prices('../../Work/Data/prices.csv')\n\n# Generate the report data\n\nreport = make_report_data(portfolio, prices)\n\n# Output the report\nheaders = ('Name', 'Shares', 'Price', 'Change')\nprint('%10s %10s %10s %10s' % headers)\nprint(('-' * 10 + ' ') * len(headers))\nfor row in report:\n    print('%10s %10d %10.2f %10.2f' % row)\n"
  },
  {
    "path": "Solutions/2_16/pcost.py",
    "content": "# pcost.py\n\nimport csv\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    total_cost = 0.0\n\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n        for rowno, row in enumerate(rows, start=1):\n            record = dict(zip(headers, row))\n            try:\n                nshares = int(record['shares'])\n                price = float(record['price'])\n                total_cost += nshares * price\n            # This catches errors in int() and float() conversions above\n            except ValueError:\n                print(f'Row {rowno}: Bad row: {row}')\n\n    return total_cost\n\nimport sys\nif len(sys.argv) == 2:\n    filename = sys.argv[1]\nelse:\n    filename = input('Enter a filename:')\n\ncost = portfolio_cost(filename)\nprint('Total cost:', cost)\n"
  },
  {
    "path": "Solutions/2_16/report.py",
    "content": "# report.py\nimport csv\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    portfolio = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n\n        for row in rows:\n            record = dict(zip(headers, row))\n            stock = {\n                 'name'   : record['name'],\n                 'shares' : int(record['shares']),\n                 'price'   : float(record['price'])\n            }\n            portfolio.append(stock)\n\n    return portfolio\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    prices = {}\n    with open(filename) as f:\n        rows = csv.reader(f)\n        for row in rows:\n            try:\n                prices[row[0]] = float(row[1])\n            except IndexError:\n                pass\n\n    return prices\n\ndef make_report_data(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for stock in portfolio:\n        current_price = prices[stock['name']]\n        change        = current_price - stock['price']\n        summary       = (stock['name'], stock['shares'], current_price, change)\n        rows.append(summary)\n    return rows\n        \n# Read data files and create the report data        \n\nportfolio = read_portfolio('../../Work/Data/portfolio.csv')\nprices    = read_prices('../../Work/Data/prices.csv')\n\n# Generate the report data\n\nreport    = make_report_data(portfolio, prices)\n\n# Output the report\nheaders = ('Name', 'Shares', 'Price', 'Change')\nprint('%10s %10s %10s %10s' % headers)\nprint(('-' * 10 + ' ') * len(headers))\nfor row in report:\n    print('%10s %10d %10.2f %10.2f' % row)\n"
  },
  {
    "path": "Solutions/2_7/report.py",
    "content": "# report.py\nimport csv\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    portfolio = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n\n        for row in rows:\n            stock = {\n                 'name'   : row[0],\n                 'shares' : int(row[1]),\n                 'price'   : float(row[2])\n            }\n            portfolio.append(stock)\n\n    return portfolio\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    prices = {}\n    with open(filename) as f:\n        rows = csv.reader(f)\n        for row in rows:\n            try:\n                prices[row[0]] = float(row[1])\n            except IndexError:\n                pass\n\n    return prices\n\nportfolio = read_portfolio('../../Work/Data/portfolio.csv')\nprices    = read_prices('../../Work/Data/prices.csv')\n\n# Calculate the total cost of the portfolio\ntotal_cost = 0.0\nfor s in portfolio:\n    total_cost += s['shares']*s['price']\n\nprint('Total cost', total_cost)\n\n# Compute the current value of the portfolio\ntotal_value = 0.0\nfor s in portfolio:\n    total_value += s['shares']*prices[s['name']]\n\nprint('Current value', total_value)\nprint('Gain', total_value - total_cost)\n"
  },
  {
    "path": "Solutions/3_10/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(filename, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    with open(filename) as f:\n        rows = csv.reader(f, delimiter=delimiter)\n\n        # Read the file headers (if any)\n        headers = next(rows) if has_headers else []\n\n        # If specific columns have been selected, make indices for filtering and set output columns\n        if select:\n            indices = [ headers.index(colname) for colname in select ]\n            headers = select\n\n        records = []\n        for rowno, row in enumerate(rows, 1):\n            if not row:     # Skip rows with no data\n                continue\n\n            # If specific column indices are selected, pick them out\n            if select:\n                row = [ row[index] for index in indices]\n\n            # Apply type conversion to the row\n            if types:\n                try:\n                    row = [func(val) for func, val in zip(types, row)]\n                except ValueError as e:\n                    if not silence_errors:\n                        print(f\"Row {rowno}: Couldn't convert {row}\")\n                        print(f\"Row {rowno}: Reason {e}\")\n                    continue\n\n            # Make a dictionary or a tuple\n            if headers:\n                record = dict(zip(headers, row))\n            else:\n                record = tuple(row)\n            records.append(record)\n\n        return records\n"
  },
  {
    "path": "Solutions/3_14/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(filename, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    with open(filename) as f:\n        rows = csv.reader(f, delimiter=delimiter)\n\n        # Read the file headers (if any)\n        headers = next(rows) if has_headers else []\n\n        # If specific columns have been selected, make indices for filtering and set output columns\n        if select:\n            indices = [ headers.index(colname) for colname in select ]\n            headers = select\n\n        records = []\n        for rowno, row in enumerate(rows, 1):\n            if not row:     # Skip rows with no data\n                continue\n\n            # If specific column indices are selected, pick them out\n            if select:\n                row = [ row[index] for index in indices]\n\n            # Apply type conversion to the row\n            if types:\n                try:\n                    row = [func(val) for func, val in zip(types, row)]\n                except ValueError as e:\n                    if not silence_errors:\n                        print(f\"Row {rowno}: Couldn't convert {row}\")\n                        print(f\"Row {rowno}: Reason {e}\")\n                    continue\n\n            # Make a dictionary or a tuple\n            if headers:\n                record = dict(zip(headers, row))\n            else:\n                record = tuple(row)\n            records.append(record)\n\n        return records\n"
  },
  {
    "path": "Solutions/3_14/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return sum([s['shares']*s['price'] for s in portfolio])\n\nimport sys\nif len(sys.argv) == 2:\n    filename = sys.argv[1]\nelse:\n    filename = input('Enter a filename:')\n\ncost = portfolio_cost(filename)\nprint('Total cost:', cost)\n"
  },
  {
    "path": "Solutions/3_14/report.py",
    "content": "# report.py\n\nimport fileparse\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    return fileparse.parse_csv(filename, select=['name','shares','price'], types=[str,int,float])\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    return dict(fileparse.parse_csv(filename,types=[str,float], has_headers=False))\n\ndef make_report_data(portfolio,prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for stock in portfolio:\n        current_price = prices[stock['name']]\n        change = current_price - stock['price']\n        summary = (stock['name'], stock['shares'], current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    headers = ('Name','Shares','Price','Change')\n    print('%10s %10s %10s %10s' % headers)\n    print(('-'*10 + ' ')*len(headers))\n    for row in reportdata:\n        print('%10s %10d %10.2f %10.2f' % row)\n\ndef portfolio_report(portfoliofile,pricefile):        \n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    print_report(report)\n\nportfolio_report('../../Work/Data/portfolio.csv',\n                 '../../Work/Data/prices.csv')\n"
  },
  {
    "path": "Solutions/3_16/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(filename, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    with open(filename) as f:\n        rows = csv.reader(f, delimiter=delimiter)\n\n        # Read the file headers (if any)\n        headers = next(rows) if has_headers else []\n\n        # If specific columns have been selected, make indices for filtering and set output columns\n        if select:\n            indices = [ headers.index(colname) for colname in select ]\n            headers = select\n\n        records = []\n        for rowno, row in enumerate(rows, 1):\n            if not row:     # Skip rows with no data\n                continue\n\n            # If specific column indices are selected, pick them out\n            if select:\n                row = [ row[index] for index in indices]\n\n            # Apply type conversion to the row\n            if types:\n                try:\n                    row = [func(val) for func, val in zip(types, row)]\n                except ValueError as e:\n                    if not silence_errors:\n                        print(f\"Row {rowno}: Couldn't convert {row}\")\n                        print(f\"Row {rowno}: Reason {e}\")\n                    continue\n\n            # Make a dictionary or a tuple\n            if headers:\n                record = dict(zip(headers, row))\n            else:\n                record = tuple(row)\n            records.append(record)\n\n        return records\n"
  },
  {
    "path": "Solutions/3_16/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return sum([s['shares'] * s['price'] for s in portfolio])\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/3_16/report.py",
    "content": "# report.py\n\nimport fileparse\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    return fileparse.parse_csv(filename, select=['name','shares','price'], types=[str,int,float])\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    return dict(fileparse.parse_csv(filename,types=[str,float], has_headers=False))\n\ndef make_report_data(portfolio,prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for stock in portfolio:\n        current_price = prices[stock['name']]\n        change = current_price - stock['price']\n        summary = (stock['name'], stock['shares'], current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    headers = ('Name','Shares','Price','Change')\n    print('%10s %10s %10s %10s' % headers)\n    print(('-'*10 + ' ')*len(headers))\n    for row in reportdata:\n        print('%10s %10d %10.2f %10.2f' % row)\n\ndef portfolio_report(portfoliofile, pricefile):        \n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    print_report(report)\n\ndef main(args):\n    if len(args) != 3:\n        raise SystemExit('Usage: %s portfile pricefile' % args[0])\n    portfolio_report(args[1], args[2])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/3_18/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    print(f\"Row {rowno}: Couldn't convert {row}\")\n                    print(f\"Row {rowno}: Reason {e}\")\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/3_18/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return sum([s['shares'] * s['price'] for s in portfolio])\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/3_18/report.py",
    "content": "# report.py\n\nimport fileparse\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        return fileparse.parse_csv(lines, select=['name','shares','price'], types=[str,int,float])\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))\n\ndef make_report_data(portfolio,prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for stock in portfolio:\n        current_price = prices[stock['name']]\n        change = current_price - stock['price']\n        summary = (stock['name'], stock['shares'], current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    headers = ('Name','Shares','Price','Change')\n    print('%10s %10s %10s %10s' % headers)\n    print(('-'*10 + ' ')*len(headers))\n    for row in reportdata:\n        print('%10s %10d %10.2f %10.2f' % row)\n\ndef portfolio_report(portfoliofile, pricefile):        \n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    print_report(report)\n\ndef main(args):\n    if len(args) != 3:\n        raise SystemExit('Usage: %s portfile pricefile' % args[0])\n    portfolio_report(args[1], args[2])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/3_2/report.py",
    "content": "# report.py\nimport csv\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    portfolio = []\n    with open(filename) as f:\n        rows = csv.reader(f)\n        headers = next(rows)\n\n        for row in rows:\n            record = dict(zip(headers, row))\n            stock = {\n                'name' : record['name'],\n                'shares' : int(record['shares']),\n                'price' : float(record['price'])\n            }\n            portfolio.append(stock)\n\n    return portfolio\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    prices = {}\n    with open(filename) as f:\n        rows = csv.reader(f)\n        for row in rows:\n            try:\n                prices[row[0]] = float(row[1])\n            except IndexError:\n                pass\n\n    return prices\n\ndef make_report_data(portfolio,prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for stock in portfolio:\n        current_price = prices[stock['name']]\n        change = current_price - stock['price']\n        summary = (stock['name'], stock['shares'], current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    headers = ('Name','Shares','Price','Change')\n    print('%10s %10s %10s %10s' % headers)\n    print(('-'*10 + ' ')*len(headers))\n    for row in reportdata:\n        print('%10s %10d %10.2f %10.2f' % row)\n\ndef portfolio_report(portfoliofile,pricefile):        \n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio,prices)\n\n    # Print it out\n    print_report(report)\n\nportfolio_report('../../Work/Data/portfolio.csv',\n                 '../../Work/Data/prices.csv')\n"
  },
  {
    "path": "Solutions/3_7/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(filename, select=None, types=None, has_headers=True, delimiter=','):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    with open(filename) as f:\n        rows = csv.reader(f, delimiter=delimiter)\n\n        # Read the file headers (if any)\n        headers = next(rows) if has_headers else []\n\n        # If specific columns have been selected, make indices for filtering \n        if select:\n            indices = [ headers.index(colname) for colname in select ]\n            headers = select\n\n        records = []\n        for row in rows:\n            if not row:     # Skip rows with no data\n                continue\n\n            # If specific column indices are selected, pick them out\n            if select:\n                row = [ row[index] for index in indices]\n\n            # Apply type conversion to the row\n            if types:\n                row = [func(val) for func, val in zip(types, row)]\n\n            # Make a dictionary or a tuple\n            if headers:\n                record = dict(zip(headers, row))\n            else:\n                record = tuple(row)\n            records.append(record)\n\n        return records\n"
  },
  {
    "path": "Solutions/4_10/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    print(f\"Row {rowno}: Couldn't convert {row}\")\n                    print(f\"Row {rowno}: Reason {e}\")\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/4_10/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return sum([s.cost() for s in portfolio])\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/4_10/report.py",
    "content": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float])\n\n    portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]\n    return portfolio\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))\n\ndef make_report_data(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfile pricefile format' % args[0])\n    portfolio_report(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/4_10/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    def __init__(self, name, shares, price):\n        self.name   = name\n        self.shares = shares\n        self.price  = price\n\n    def __repr__(self):\n        return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares\n        '''\n        self.shares -= nshares\n\n"
  },
  {
    "path": "Solutions/4_10/tableformat.py",
    "content": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data\n        '''\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    '''\n    Output data in plain-text format.\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    '''\n    Output data in HTML format.\n    '''\n    def headings(self, headers):\n        print('<tr>', end='')\n        for h in headers:\n            print(f'<th>{h}</th>', end='')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end='')\n        for d in rowdata:\n            print(f'<td>{d}</td>', end='')\n        print('</tr>')\n\nclass FormatError(Exception):\n    pass\n\ndef create_formatter(name):\n    '''\n    Create an appropriate formatter given an output format name\n    '''\n    if name == 'txt':\n        return TextTableFormatter()\n    elif name == 'csv':\n        return CSVTableFormatter()\n    elif name == 'html':\n        return HTMLTableFormatter()\n    else:\n        raise FormatError(f'Unknown table format {name}')\n\ndef print_table(objects, columns, formatter):\n    '''\n    Make a nicely formatted table from a list of objects and attribute names.\n    '''\n    formatter.headings(columns)\n    for obj in objects:\n        rowdata = [ str(getattr(obj, name)) for name in columns ]\n        formatter.row(rowdata)\n\n"
  },
  {
    "path": "Solutions/4_4/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    print(f\"Row {rowno}: Couldn't convert {row}\")\n                    print(f\"Row {rowno}: Reason {e}\")\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/4_4/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return sum([s.cost() for s in portfolio])\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/4_4/report.py",
    "content": "# report.py\n\nimport fileparse\nfrom stock import Stock\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float])\n\n    portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]\n    return portfolio\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))\n\ndef make_report_data(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    headers = ('Name','Shares','Price','Change')\n    print('%10s %10s %10s %10s' % headers)\n    print(('-'*10 + ' ')*len(headers))\n    for row in reportdata:\n        print('%10s %10d %10.2f %10.2f' % row)\n\ndef portfolio_report(portfoliofile, pricefile):        \n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    print_report(report)\n\ndef main(args):\n    if len(args) != 3:\n        raise SystemExit('Usage: %s portfile pricefile' % args[0])\n    portfolio_report(args[1], args[2])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/4_4/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    def __init__(self, name, shares, price):\n        self.name   = name\n        self.shares = shares\n        self.price  = price\n\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares\n        '''\n        self.shares -= nshares\n\n"
  },
  {
    "path": "Solutions/5_8/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    print(f\"Row {rowno}: Couldn't convert {row}\")\n                    print(f\"Row {rowno}: Reason {e}\")\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/5_8/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return sum([s.cost() for s in portfolio])\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/5_8/report.py",
    "content": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float])\n\n    portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]\n    return portfolio\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))\n\ndef make_report_data(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfile pricefile format' % args[0])\n    portfolio_report(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/5_8/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    __slots__ = ('name','_shares','price')\n    def __init__(self,name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price  = price\n\n    def __repr__(self):\n        return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    @property\n    def shares(self):\n        return self._shares\n\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value,int):\n            raise TypeError(\"Must be integer\")\n        self._shares = value\n\n    @property\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares and return the remaining number.\n        '''\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/5_8/tableformat.py",
    "content": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data\n        '''\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    '''\n    Output data in plain-text format.\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    '''\n    Output data in HTML format.\n    '''\n    def headings(self, headers):\n        print('<tr>', end='')\n        for h in headers:\n            print(f'<th>{h}</th>', end='')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end='')\n        for d in rowdata:\n            print(f'<td>{d}</td>', end='')\n        print('</tr>')\n\nclass FormatError(Exception):\n    pass\n\ndef create_formatter(name):\n    '''\n    Create an appropriate formatter given an output format name\n    '''\n    if name == 'txt':\n        return TextTableFormatter()\n    elif name == 'csv':\n        return CSVTableFormatter()\n    elif name == 'html':\n        return HTMLTableFormatter()\n    else:\n        raise FormatError(f'Unknown table format {name}')\n\ndef print_table(objects, columns, formatter):\n    '''\n    Make a nicely formatted table from a list of objects and attribute names.\n    '''\n    formatter.headings(columns)\n    for obj in objects:\n        rowdata = [ str(getattr(obj, name)) for name in columns ]\n        formatter.row(rowdata)\n\n"
  },
  {
    "path": "Solutions/6_12/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    print(f\"Row {rowno}: Couldn't convert {row}\")\n                    print(f\"Row {rowno}: Reason {e}\")\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/6_12/follow.py",
    "content": "# follow.py\nimport os\nimport time\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    f = open(filename, 'r')\n    f.seek(0,os.SEEK_END)\n    while True:\n        line = f.readline()\n        if line == '':\n            time.sleep(0.1)    # Sleep briefly to avoid busy wait\n            continue\n        yield line\n\n# Example use\nif __name__ == '__main__':\n    import report\n\n    portfolio = report.read_portfolio('../../Data/portfolio.csv')\n\n    for line in follow('../../Data/stocklog.csv'):\n        row = line.split(',')\n        name = row[0].strip('\"')\n        price = float(row[1])\n        change = float(row[4])\n        if name in portfolio:\n            print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')\n"
  },
  {
    "path": "Solutions/6_12/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return portfolio.total_cost\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/6_12/portfolio.py",
    "content": "# portfolio.py\n\nclass Portfolio:\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(self):\n        return self._holdings.__iter__()\n\n    def __len__(self):\n        return len(self._holdings)\n\n    def __getitem__(self, index):\n        return self._holdings[index]\n\n    def __contains__(self, name):\n        return any([s.name == name for s in self._holdings])\n\n    @property\n    def total_cost(self):\n        return sum([s.shares * s.price for s in self._holdings])\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n\n\n        \n"
  },
  {
    "path": "Solutions/6_12/report.py",
    "content": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\nfrom portfolio import Portfolio\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float])\n\n    portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]\n    return Portfolio(portfolio)\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))\n\ndef make_report_data(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfile pricefile format' % args[0])\n    portfolio_report(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/6_12/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    __slots__ = ('name','_shares','price')\n    def __init__(self,name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price  = price\n\n    def __repr__(self):\n        return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    @property\n    def shares(self):\n        return self._shares\n\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value,int):\n            raise TypeError(\"Must be integer\")\n        self._shares = value\n\n    @property\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares and return the remaining number.\n        '''\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/6_12/tableformat.py",
    "content": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data\n        '''\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    '''\n    Output data in plain-text format.\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    '''\n    Output data in HTML format.\n    '''\n    def headings(self, headers):\n        print('<tr>', end='')\n        for h in headers:\n            print(f'<th>{h}</th>', end='')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end='')\n        for d in rowdata:\n            print(f'<td>{d}</td>', end='')\n        print('</tr>')\n\nclass FormatError(Exception):\n    pass\n\ndef create_formatter(name):\n    '''\n    Create an appropriate formatter given an output format name\n    '''\n    if name == 'txt':\n        return TextTableFormatter()\n    elif name == 'csv':\n        return CSVTableFormatter()\n    elif name == 'html':\n        return HTMLTableFormatter()\n    else:\n        raise FormatError(f'Unknown table format {name}')\n\ndef print_table(objects, columns, formatter):\n    '''\n    Make a nicely formatted table from a list of objects and attribute names.\n    '''\n    formatter.headings(columns)\n    for obj in objects:\n        rowdata = [ str(getattr(obj, name)) for name in columns ]\n        formatter.row(rowdata)\n\n"
  },
  {
    "path": "Solutions/6_12/ticker.py",
    "content": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\nimport time\n\ndef select_columns(rows, indices):\n    for row in rows:\n        yield [row[index] for index in indices]\n\ndef convert_types(rows, types):\n    for row in rows:\n        yield [func(val) for func, val in zip(types, row)]\n\ndef make_dicts(rows, headers):\n    for row in rows:\n        yield dict(zip(headers, row))\n\ndef parse_stock_data(lines):\n    rows = csv.reader(lines)\n    rows = select_columns(rows, [0, 1, 4])\n    rows = convert_types(rows, [str,float,float])\n    rows = make_dicts(rows, ['name','price','change'])\n    return rows\n\ndef filter_symbols(rows, names):\n    for row in rows:\n        if row['name'] in names:\n            yield row\n\ndef ticker(portfile, logfile, fmt):\n    portfolio = report.read_portfolio(portfile)\n    lines = follow(logfile)\n    rows = parse_stock_data(lines)\n    rows = filter_symbols(rows, portfolio)\n    formatter = tableformat.create_formatter(fmt)\n    formatter.headings(['Name','Price','Change'])\n    for row in rows:\n        formatter.row([ row['name'], f\"{row['price']:0.2f}\", f\"{row['change']:0.2f}\"] )\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])\n    ticker(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/6_15/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    print(f\"Row {rowno}: Couldn't convert {row}\")\n                    print(f\"Row {rowno}: Reason {e}\")\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/6_15/follow.py",
    "content": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    with open(filename,'r') as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n             line = f.readline()\n             if line != '':\n                 yield line\n             else:\n                 time.sleep(0.1)    # Sleep briefly to avoid busy wait\n"
  },
  {
    "path": "Solutions/6_15/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return portfolio.total_cost\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/6_15/portfolio.py",
    "content": "# portfolio.py\n\nclass Portfolio:\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(self):\n        return self._holdings.__iter__()\n\n    def __len__(self):\n        return len(self._holdings)\n\n    def __getitem__(self, index):\n        return self._holdings[index]\n\n    def __contains__(self, name):\n        return any(s.name == name for s in self._holdings)\n\n    @property\n    def total_cost(self):\n        return sum(s.shares * s.price for s in self._holdings)\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n\n\n        \n"
  },
  {
    "path": "Solutions/6_15/report.py",
    "content": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\nfrom portfolio import Portfolio\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float])\n\n    portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]\n    return Portfolio(portfolio)\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))\n\ndef make_report_data(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfile pricefile format' % args[0])\n    portfolio_report(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/6_15/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    __slots__ = ('name','_shares','price')\n    def __init__(self,name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price  = price\n\n    def __repr__(self):\n        return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    @property\n    def shares(self):\n        return self._shares\n\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value,int):\n            raise TypeError(\"Must be integer\")\n        self._shares = value\n\n    @property\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares and return the remaining number.\n        '''\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/6_15/tableformat.py",
    "content": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data\n        '''\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    '''\n    Output data in plain-text format.\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    '''\n    Output data in HTML format.\n    '''\n    def headings(self, headers):\n        print('<tr>', end='')\n        for h in headers:\n            print(f'<th>{h}</th>', end='')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end='')\n        for d in rowdata:\n            print(f'<td>{d}</td>', end='')\n        print('</tr>')\n\nclass FormatError(Exception):\n    pass\n\ndef create_formatter(name):\n    '''\n    Create an appropriate formatter given an output format name\n    '''\n    if name == 'txt':\n        return TextTableFormatter()\n    elif name == 'csv':\n        return CSVTableFormatter()\n    elif name == 'html':\n        return HTMLTableFormatter()\n    else:\n        raise FormatError(f'Unknown table format {name}')\n\ndef print_table(objects, columns, formatter):\n    '''\n    Make a nicely formatted table from a list of objects and attribute names.\n    '''\n    formatter.headings(columns)\n    for obj in objects:\n        rowdata = [ str(getattr(obj, name)) for name in columns ]\n        formatter.row(rowdata)\n\n"
  },
  {
    "path": "Solutions/6_15/ticker.py",
    "content": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\nimport time\n\ndef select_columns(rows, indices):\n    for row in rows:\n        yield [row[index] for index in indices]\n\ndef convert_types(rows, types):\n    for row in rows:\n        yield [func(val) for func, val in zip(types, row)]\n\ndef make_dicts(rows, headers):\n    return (dict(zip(headers,row)) for row in rows)\n\ndef parse_stock_data(lines):\n    rows = csv.reader(lines)\n    rows = select_columns(rows, [0, 1, 4])\n    rows = convert_types(rows, [str,float,float])\n    rows = make_dicts(rows, ['name','price','change'])\n    return rows\n\ndef ticker(portfile, logfile, fmt):\n    portfolio = report.read_portfolio(portfile)\n    lines = follow(logfile)\n    rows = parse_stock_data(lines)\n    rows = (row for row in rows if row['name'] in portfolio)\n    formatter = tableformat.create_formatter(fmt)\n    formatter.headings(['Name','Price','Change'])\n    for row in rows:\n        formatter.row([ row['name'], f\"{row['price']:0.2f}\", f\"{row['change']:0.2f}\"] )\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])\n    ticker(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/6_3/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    print(f\"Row {rowno}: Couldn't convert {row}\")\n                    print(f\"Row {rowno}: Reason {e}\")\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/6_3/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return portfolio.total_cost\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/6_3/portfolio.py",
    "content": "# portfolio.py\n\nclass Portfolio:\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(self):\n        return self._holdings.__iter__()\n\n    def __len__(self):\n        return len(self._holdings)\n\n    def __getitem__(self, index):\n        return self._holdings[index]\n\n    def __contains__(self, name):\n        return any([s.name == name for s in self._holdings])\n\n    @property\n    def total_cost(self):\n        return sum([s.shares * s.price for s in self._holdings])\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n\n\n        \n"
  },
  {
    "path": "Solutions/6_3/report.py",
    "content": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\nfrom portfolio import Portfolio\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float])\n\n    portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]\n    return Portfolio(portfolio)\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))\n\ndef make_report_data(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfile pricefile format' % args[0])\n    portfolio_report(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/6_3/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    __slots__ = ('name','_shares','price')\n    def __init__(self,name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price  = price\n\n    def __repr__(self):\n        return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    @property\n    def shares(self):\n        return self._shares\n\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value,int):\n            raise TypeError(\"Must be integer\")\n        self._shares = value\n\n    @property\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares and return the remaining number.\n        '''\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/6_3/tableformat.py",
    "content": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data\n        '''\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    '''\n    Output data in plain-text format.\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    '''\n    Output data in HTML format.\n    '''\n    def headings(self, headers):\n        print('<tr>', end='')\n        for h in headers:\n            print(f'<th>{h}</th>', end='')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end='')\n        for d in rowdata:\n            print(f'<td>{d}</td>', end='')\n        print('</tr>')\n\nclass FormatError(Exception):\n    pass\n\ndef create_formatter(name):\n    '''\n    Create an appropriate formatter given an output format name\n    '''\n    if name == 'txt':\n        return TextTableFormatter()\n    elif name == 'csv':\n        return CSVTableFormatter()\n    elif name == 'html':\n        return HTMLTableFormatter()\n    else:\n        raise FormatError(f'Unknown table format {name}')\n\ndef print_table(objects, columns, formatter):\n    '''\n    Make a nicely formatted table from a list of objects and attribute names.\n    '''\n    formatter.headings(columns)\n    for obj in objects:\n        rowdata = [ str(getattr(obj, name)) for name in columns ]\n        formatter.row(rowdata)\n\n"
  },
  {
    "path": "Solutions/6_7/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    print(f\"Row {rowno}: Couldn't convert {row}\")\n                    print(f\"Row {rowno}: Reason {e}\")\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/6_7/follow.py",
    "content": "# follow.py\nimport os\nimport time\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    f = open(filename, 'r')\n    f.seek(0,os.SEEK_END)\n    while True:\n        line = f.readline()\n        if line == '':\n            time.sleep(0.1)    # Sleep briefly to avoid busy wait\n            continue\n        yield line\n\n# Example use\nif __name__ == '__main__':\n    import report\n\n    portfolio = report.read_portfolio('../../Data/portfolio.csv')\n\n    for line in follow('../../Data/stocklog.csv'):\n        row = line.split(',')\n        name = row[0].strip('\"')\n        price = float(row[1])\n        change = float(row[4])\n        if name in portfolio:\n            print(f'{name:>10s} {price:>10.2f} {change:>10.2f}')\n"
  },
  {
    "path": "Solutions/6_7/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return portfolio.total_cost\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/6_7/portfolio.py",
    "content": "# portfolio.py\n\nclass Portfolio:\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(self):\n        return self._holdings.__iter__()\n\n    def __len__(self):\n        return len(self._holdings)\n\n    def __getitem__(self, index):\n        return self._holdings[index]\n\n    def __contains__(self, name):\n        return any([s.name == name for s in self._holdings])\n\n    @property\n    def total_cost(self):\n        return sum([s.shares * s.price for s in self._holdings])\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n\n\n        \n"
  },
  {
    "path": "Solutions/6_7/report.py",
    "content": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\nfrom portfolio import Portfolio\n\ndef read_portfolio(filename):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float])\n\n    portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]\n    return Portfolio(portfolio)\n\ndef read_prices(filename):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False))\n\ndef make_report_data(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfile pricefile format' % args[0])\n    portfolio_report(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/6_7/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    __slots__ = ('name','_shares','price')\n    def __init__(self,name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price  = price\n\n    def __repr__(self):\n        return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    @property\n    def shares(self):\n        return self._shares\n\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value,int):\n            raise TypeError(\"Must be integer\")\n        self._shares = value\n\n    @property\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares and return the remaining number.\n        '''\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/6_7/tableformat.py",
    "content": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data\n        '''\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    '''\n    Output data in plain-text format.\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    '''\n    Output data in HTML format.\n    '''\n    def headings(self, headers):\n        print('<tr>', end='')\n        for h in headers:\n            print(f'<th>{h}</th>', end='')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end='')\n        for d in rowdata:\n            print(f'<td>{d}</td>', end='')\n        print('</tr>')\n\nclass FormatError(Exception):\n    pass\n\ndef create_formatter(name):\n    '''\n    Create an appropriate formatter given an output format name\n    '''\n    if name == 'txt':\n        return TextTableFormatter()\n    elif name == 'csv':\n        return CSVTableFormatter()\n    elif name == 'html':\n        return HTMLTableFormatter()\n    else:\n        raise FormatError(f'Unknown table format {name}')\n\ndef print_table(objects, columns, formatter):\n    '''\n    Make a nicely formatted table from a list of objects and attribute names.\n    '''\n    formatter.headings(columns)\n    for obj in objects:\n        rowdata = [ str(getattr(obj, name)) for name in columns ]\n        formatter.row(rowdata)\n\n"
  },
  {
    "path": "Solutions/7_10/timethis.py",
    "content": "# timethis.py\n\nimport time\n\ndef timethis(func):\n    def wrapper(*args, **kwargs):\n        start = time.time()\n        try:\n            return func(*args,**kwargs)\n        finally:\n            end = time.time()\n            print(\"%s.%s : %f\" % (func.__module__,func.__name__,end-start))\n    return wrapper\n\nif __name__ == '__main__':\n    @timethis\n    def countdown(n):\n        while n > 0:\n            n-= 1\n\n    countdown(1000000)\n"
  },
  {
    "path": "Solutions/7_11/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    print(f\"Row {rowno}: Couldn't convert {row}\")\n                    print(f\"Row {rowno}: Reason {e}\")\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/7_11/follow.py",
    "content": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    with open(filename,'r') as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n             line = f.readline()\n             if line != '':\n                 yield line\n             else:\n                 time.sleep(0.1)    # Sleep briefly to avoid busy wait\n"
  },
  {
    "path": "Solutions/7_11/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return portfolio.total_cost\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/7_11/portfolio.py",
    "content": "# portfolio.py\n\nimport fileparse\nimport stock\n\nclass Portfolio:\n    def __init__(self):\n        self._holdings = []\n\n    @classmethod\n    def from_csv(cls, lines, **opts):\n        self = cls()\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float],\n                                        **opts)\n\n        for d in portdicts:\n            self.append(stock.Stock(**d))\n\n        return self\n\n    def append(self, holding):\n        self._holdings.append(holding)\n\n    def __iter__(self):\n        return self._holdings.__iter__()\n\n    def __len__(self):\n        return len(self._holdings)\n\n    def __getitem__(self, index):\n        return self._holdings[index]\n\n    def __contains__(self, name):\n        return any(s.name == name for s in self._holdings)\n\n    @property\n    def total_cost(self):\n        return sum(s.shares * s.price for s in self._holdings)\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n\n\n\n        \n"
  },
  {
    "path": "Solutions/7_11/report.py",
    "content": "# report.py\n\nimport fileparse\nfrom stock import Stock\nfrom portfolio import Portfolio\nimport tableformat\n\ndef read_portfolio(filename, **opts):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        return Portfolio.from_csv(lines, **opts)\n\ndef read_prices(filename, **opts):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False, **opts))\n\ndef make_report(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfile pricefile format' % args[0])\n    portfolio_report(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/7_11/stock.py",
    "content": "# stock.py\n\nfrom typedproperty import String, Integer, Float\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    name = String('name')\n    shares = Integer('shares')\n    price = Float('price')\n\n    def __init__(self,name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price  = price\n\n    def __repr__(self):\n        return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    @property\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares and return the remaining number.\n        '''\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/7_11/tableformat.py",
    "content": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data\n        '''\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    '''\n    Output data in plain-text format.\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    '''\n    Output data in HTML format.\n    '''\n    def headings(self, headers):\n        print('<tr>', end='')\n        for h in headers:\n            print(f'<th>{h}</th>', end='')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end='')\n        for d in rowdata:\n            print(f'<td>{d}</td>', end='')\n        print('</tr>')\n\nclass FormatError(Exception):\n    pass\n\ndef create_formatter(name):\n    '''\n    Create an appropriate formatter given an output format name\n    '''\n    if name == 'txt':\n        return TextTableFormatter()\n    elif name == 'csv':\n        return CSVTableFormatter()\n    elif name == 'html':\n        return HTMLTableFormatter()\n    else:\n        raise FormatError(f'Unknown table format {name}')\n\ndef print_table(objects, columns, formatter):\n    '''\n    Make a nicely formatted table from a list of objects and attribute names.\n    '''\n    formatter.headings(columns)\n    for obj in objects:\n        rowdata = [ str(getattr(obj, name)) for name in columns ]\n        formatter.row(rowdata)\n\n"
  },
  {
    "path": "Solutions/7_11/ticker.py",
    "content": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\n\ndef select_columns(rows, indices):\n    for row in rows:\n        yield [row[index] for index in indices]\n\ndef convert_types(rows, types):\n    for row in rows:\n        yield [func(val) for func, val in zip(types, row)]\n\ndef make_dicts(rows, headers):\n    return (dict(zip(headers,row)) for row in rows)\n\ndef parse_stock_data(lines):\n    rows = csv.reader(lines)\n    rows = select_columns(rows, [0, 1, 4])\n    rows = convert_types(rows, [str,float,float])\n    rows = make_dicts(rows, ['name','price','change'])\n    return rows\n\ndef ticker(portfile, logfile, fmt):\n    portfolio = report.read_portfolio(portfile)\n    lines = follow(logfile)\n    rows = parse_stock_data(lines)\n    rows = (row for row in rows if row['name'] in portfolio)\n    formatter = tableformat.create_formatter(fmt)\n    formatter.headings(['Name','Price','Change'])\n    for row in rows:\n        formatter.row([ row['name'], f\"{row['price']:0.2f}\", f\"{row['change']:0.2f}\"] )\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])\n    ticker(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/7_11/timethis.py",
    "content": "# timethis.py\n\nimport time\n\ndef timethis(func):\n    def wrapper(*args, **kwargs):\n        start = time.time()\n        try:\n            return func(*args,**kwargs)\n        finally:\n            end = time.time()\n            print(\"%s.%s : %f\" % (func.__module__,func.__name__,end-start))\n    return wrapper\n\nif __name__ == '__main__':\n    @timethis\n    def countdown(n):\n        while n > 0:\n            n-= 1\n\n    countdown(1000000)\n"
  },
  {
    "path": "Solutions/7_11/typedproperty.py",
    "content": "# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n    @property\n    def prop(self):\n        return getattr(self, private_name)\n\n    @prop.setter\n    def prop(self, value):\n        if not isinstance(value, expected_type):\n            raise TypeError(f'Expected {expected_type}')\n        setattr(self, private_name, value)\n\n    return prop\n\nString = lambda name: typedproperty(name, str)\nInteger = lambda name: typedproperty(name, int)\nFloat = lambda name: typedproperty(name, float)\n\n# Example\nif __name__ == '__main__':\n    class Stock:\n        name = typedproperty('name', str)\n        shares = typedproperty('shares', int)\n        price = typedproperty('price', float)\n\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n\n    class Stock2:\n        name = String('name')\n        shares = Integer('shares')\n        price = Float('price')\n\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n    \n\n"
  },
  {
    "path": "Solutions/7_4/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    print(f\"Row {rowno}: Couldn't convert {row}\")\n                    print(f\"Row {rowno}: Reason {e}\")\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/7_4/follow.py",
    "content": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    with open(filename,'r') as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n             line = f.readline()\n             if line != '':\n                 yield line\n             else:\n                 time.sleep(0.1)    # Sleep briefly to avoid busy wait\n"
  },
  {
    "path": "Solutions/7_4/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return portfolio.total_cost\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/7_4/portfolio.py",
    "content": "# portfolio.py\n\nclass Portfolio:\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(self):\n        return self._holdings.__iter__()\n\n    def __len__(self):\n        return len(self._holdings)\n\n    def __getitem__(self, index):\n        return self._holdings[index]\n\n    def __contains__(self, name):\n        return any(s.name == name for s in self._holdings)\n\n    @property\n    def total_cost(self):\n        return sum(s.shares * s.price for s in self._holdings)\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n\n\n        \n"
  },
  {
    "path": "Solutions/7_4/report.py",
    "content": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\nfrom portfolio import Portfolio\n\ndef read_portfolio(filename, **opts):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float],\n                                        **opts)\n\n    portfolio = [ Stock(**d) for d in portdicts ]\n    return Portfolio(portfolio)\n\ndef read_prices(filename, **opts):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines,types=[str,float], has_headers=False, **opts))\n\ndef make_report_data(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfile pricefile format' % args[0])\n    portfolio_report(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/7_4/stock.py",
    "content": "# stock.py\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    __slots__ = ('name','_shares','price')\n    def __init__(self,name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price  = price\n\n    def __repr__(self):\n        return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    @property\n    def shares(self):\n        return self._shares\n\n    @shares.setter\n    def shares(self, value):\n        if not isinstance(value,int):\n            raise TypeError(\"Must be integer\")\n        self._shares = value\n\n    @property\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares and return the remaining number.\n        '''\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/7_4/tableformat.py",
    "content": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data\n        '''\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    '''\n    Output data in plain-text format.\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    '''\n    Output data in HTML format.\n    '''\n    def headings(self, headers):\n        print('<tr>', end='')\n        for h in headers:\n            print(f'<th>{h}</th>', end='')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end='')\n        for d in rowdata:\n            print(f'<td>{d}</td>', end='')\n        print('</tr>')\n\nclass FormatError(Exception):\n    pass\n\ndef create_formatter(name):\n    '''\n    Create an appropriate formatter given an output format name\n    '''\n    if name == 'txt':\n        return TextTableFormatter()\n    elif name == 'csv':\n        return CSVTableFormatter()\n    elif name == 'html':\n        return HTMLTableFormatter()\n    else:\n        raise FormatError(f'Unknown table format {name}')\n\ndef print_table(objects, columns, formatter):\n    '''\n    Make a nicely formatted table from a list of objects and attribute names.\n    '''\n    formatter.headings(columns)\n    for obj in objects:\n        rowdata = [ str(getattr(obj, name)) for name in columns ]\n        formatter.row(rowdata)\n\n"
  },
  {
    "path": "Solutions/7_4/ticker.py",
    "content": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\nimport time\n\ndef select_columns(rows, indices):\n    for row in rows:\n        yield [row[index] for index in indices]\n\ndef convert_types(rows, types):\n    for row in rows:\n        yield [func(val) for func, val in zip(types, row)]\n\ndef make_dicts(rows, headers):\n    return (dict(zip(headers,row)) for row in rows)\n\ndef parse_stock_data(lines):\n    rows = csv.reader(lines)\n    rows = select_columns(rows, [0, 1, 4])\n    rows = convert_types(rows, [str,float,float])\n    rows = make_dicts(rows, ['name','price','change'])\n    return rows\n\ndef ticker(portfile, logfile, fmt):\n    portfolio = report.read_portfolio(portfile)\n    lines = follow(logfile)\n    rows = parse_stock_data(lines)\n    rows = (row for row in rows if row['name'] in portfolio)\n    formatter = tableformat.create_formatter(fmt)\n    formatter.headings(['Name','Price','Change'])\n    for row in rows:\n        formatter.row([ row['name'], f\"{row['price']:0.2f}\", f\"{row['change']:0.2f}\"] )\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])\n    ticker(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/7_9/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    print(f\"Row {rowno}: Couldn't convert {row}\")\n                    print(f\"Row {rowno}: Reason {e}\")\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/7_9/follow.py",
    "content": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    with open(filename,'r') as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n             line = f.readline()\n             if line != '':\n                 yield line\n             else:\n                 time.sleep(0.1)    # Sleep briefly to avoid busy wait\n"
  },
  {
    "path": "Solutions/7_9/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return portfolio.total_cost\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/7_9/portfolio.py",
    "content": "# portfolio.py\n\nclass Portfolio:\n    def __init__(self, holdings):\n        self._holdings = holdings\n\n    def __iter__(self):\n        return self._holdings.__iter__()\n\n    def __len__(self):\n        return len(self._holdings)\n\n    def __getitem__(self, index):\n        return self._holdings[index]\n\n    def __contains__(self, name):\n        return any(s.name == name for s in self._holdings)\n\n    @property\n    def total_cost(self):\n        return sum(s.shares * s.price for s in self._holdings)\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n\n\n        \n"
  },
  {
    "path": "Solutions/7_9/report.py",
    "content": "# report.py\n\nimport fileparse\nfrom stock import Stock\nimport tableformat\nfrom portfolio import Portfolio\n\ndef read_portfolio(filename, **opts):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float],\n                                        **opts)\n\n    portfolio = [ Stock(**d) for d in portdicts ]\n    return Portfolio(portfolio)\n\ndef read_prices(filename, **opts):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines,types=[str,float], has_headers=False, **opts))\n\ndef make_report_data(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report_data(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfile pricefile format' % args[0])\n    portfolio_report(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/7_9/stock.py",
    "content": "# stock.py\n\nfrom typedproperty import String, Integer, Float\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    name = String('name')\n    shares = Integer('shares')\n    price = Float('price')\n\n    def __init__(self,name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price  = price\n\n    def __repr__(self):\n        return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    @property\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares and return the remaining number.\n        '''\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/7_9/tableformat.py",
    "content": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data\n        '''\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    '''\n    Output data in plain-text format.\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    '''\n    Output data in HTML format.\n    '''\n    def headings(self, headers):\n        print('<tr>', end='')\n        for h in headers:\n            print(f'<th>{h}</th>', end='')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end='')\n        for d in rowdata:\n            print(f'<td>{d}</td>', end='')\n        print('</tr>')\n\nclass FormatError(Exception):\n    pass\n\ndef create_formatter(name):\n    '''\n    Create an appropriate formatter given an output format name\n    '''\n    if name == 'txt':\n        return TextTableFormatter()\n    elif name == 'csv':\n        return CSVTableFormatter()\n    elif name == 'html':\n        return HTMLTableFormatter()\n    else:\n        raise FormatError(f'Unknown table format {name}')\n\ndef print_table(objects, columns, formatter):\n    '''\n    Make a nicely formatted table from a list of objects and attribute names.\n    '''\n    formatter.headings(columns)\n    for obj in objects:\n        rowdata = [ str(getattr(obj, name)) for name in columns ]\n        formatter.row(rowdata)\n\n"
  },
  {
    "path": "Solutions/7_9/ticker.py",
    "content": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\nimport time\n\ndef select_columns(rows, indices):\n    for row in rows:\n        yield [row[index] for index in indices]\n\ndef convert_types(rows, types):\n    for row in rows:\n        yield [func(val) for func, val in zip(types, row)]\n\ndef make_dicts(rows, headers):\n    return (dict(zip(headers,row)) for row in rows)\n\ndef parse_stock_data(lines):\n    rows = csv.reader(lines)\n    rows = select_columns(rows, [0, 1, 4])\n    rows = convert_types(rows, [str,float,float])\n    rows = make_dicts(rows, ['name','price','change'])\n    return rows\n\ndef ticker(portfile, logfile, fmt):\n    portfolio = report.read_portfolio(portfile)\n    lines = follow(logfile)\n    rows = parse_stock_data(lines)\n    rows = (row for row in rows if row['name'] in portfolio)\n    formatter = tableformat.create_formatter(fmt)\n    formatter.headings(['Name','Price','Change'])\n    for row in rows:\n        formatter.row([ row['name'], f\"{row['price']:0.2f}\", f\"{row['change']:0.2f}\"] )\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])\n    ticker(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/7_9/typedproperty.py",
    "content": "# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n    @property\n    def prop(self):\n        return getattr(self, private_name)\n\n    @prop.setter\n    def prop(self, value):\n        if not isinstance(value, expected_type):\n            raise TypeError(f'Expected {expected_type}')\n        setattr(self, private_name, value)\n\n    return prop\n\nString = lambda name: typedproperty(name, str)\nInteger = lambda name: typedproperty(name, int)\nFloat = lambda name: typedproperty(name, float)\n\n# Example\nif __name__ == '__main__':\n    class Stock:\n        name = typedproperty('name', str)\n        shares = typedproperty('shares', int)\n        price = typedproperty('price', float)\n\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n    class Stock2:\n        name = String('name')\n        shares = Integer('shares')\n        price = Float('price')\n\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n    \n\n"
  },
  {
    "path": "Solutions/8_1/fileparse.py",
    "content": "# fileparse.py\nimport csv\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    print(f\"Row {rowno}: Couldn't convert {row}\")\n                    print(f\"Row {rowno}: Reason {e}\")\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/8_1/follow.py",
    "content": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    with open(filename,'r') as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n             line = f.readline()\n             if line != '':\n                 yield line\n             else:\n                 time.sleep(0.1)    # Sleep briefly to avoid busy wait\n"
  },
  {
    "path": "Solutions/8_1/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return portfolio.total_cost\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/8_1/portfolio.py",
    "content": "# portfolio.py\n\nimport fileparse\nimport stock\n\nclass Portfolio:\n    def __init__(self):\n        self._holdings = []\n\n    @classmethod\n    def from_csv(cls, lines, **opts):\n        self = cls()\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float],\n                                        **opts)\n\n        for d in portdicts:\n            self.append(stock.Stock(**d))\n\n        return self\n\n    def append(self, holding):\n        self._holdings.append(holding)\n\n    def __iter__(self):\n        return self._holdings.__iter__()\n\n    def __len__(self):\n        return len(self._holdings)\n\n    def __getitem__(self, index):\n        return self._holdings[index]\n\n    def __contains__(self, name):\n        return any(s.name == name for s in self._holdings)\n\n    @property\n    def total_cost(self):\n        return sum(s.shares * s.price for s in self._holdings)\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n\n\n\n        \n"
  },
  {
    "path": "Solutions/8_1/report.py",
    "content": "# report.py\n\nimport fileparse\nfrom stock import Stock\nfrom portfolio import Portfolio\nimport tableformat\n\ndef read_portfolio(filename, **opts):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        return Portfolio.from_csv(lines, **opts)\n\ndef read_prices(filename, **opts):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False, **opts))\n\ndef make_report(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfile pricefile format' % args[0])\n    portfolio_report(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/8_1/stock.py",
    "content": "# stock.py\n\nfrom typedproperty import String, Integer, Float\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    name = String('name')\n    shares = Integer('shares')\n    price = Float('price')\n\n    def __init__(self,name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price  = price\n\n    def __repr__(self):\n        return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    @property\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares and return the remaining number.\n        '''\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/8_1/tableformat.py",
    "content": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data\n        '''\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    '''\n    Output data in plain-text format.\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    '''\n    Output data in HTML format.\n    '''\n    def headings(self, headers):\n        print('<tr>', end='')\n        for h in headers:\n            print(f'<th>{h}</th>', end='')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end='')\n        for d in rowdata:\n            print(f'<td>{d}</td>', end='')\n        print('</tr>')\n\nclass FormatError(Exception):\n    pass\n\ndef create_formatter(name):\n    '''\n    Create an appropriate formatter given an output format name\n    '''\n    if name == 'txt':\n        return TextTableFormatter()\n    elif name == 'csv':\n        return CSVTableFormatter()\n    elif name == 'html':\n        return HTMLTableFormatter()\n    else:\n        raise FormatError(f'Unknown table format {name}')\n\ndef print_table(objects, columns, formatter):\n    '''\n    Make a nicely formatted table from a list of objects and attribute names.\n    '''\n    formatter.headings(columns)\n    for obj in objects:\n        rowdata = [ str(getattr(obj, name)) for name in columns ]\n        formatter.row(rowdata)\n\n"
  },
  {
    "path": "Solutions/8_1/test_stock.py",
    "content": "# test_stock.py\n\nimport unittest\nimport stock\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_shares_check(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '100'\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "Solutions/8_1/ticker.py",
    "content": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\n\ndef select_columns(rows, indices):\n    for row in rows:\n        yield [row[index] for index in indices]\n\ndef convert_types(rows, types):\n    for row in rows:\n        yield [func(val) for func, val in zip(types, row)]\n\ndef make_dicts(rows, headers):\n    return (dict(zip(headers,row)) for row in rows)\n\ndef parse_stock_data(lines):\n    rows = csv.reader(lines)\n    rows = select_columns(rows, [0, 1, 4])\n    rows = convert_types(rows, [str,float,float])\n    rows = make_dicts(rows, ['name','price','change'])\n    return rows\n\ndef ticker(portfile, logfile, fmt):\n    portfolio = report.read_portfolio(portfile)\n    lines = follow(logfile)\n    rows = parse_stock_data(lines)\n    rows = (row for row in rows if row['name'] in portfolio)\n    formatter = tableformat.create_formatter(fmt)\n    formatter.headings(['Name','Price','Change'])\n    for row in rows:\n        formatter.row([ row['name'], f\"{row['price']:0.2f}\", f\"{row['change']:0.2f}\"] )\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])\n    ticker(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/8_1/timethis.py",
    "content": "# timethis.py\n\nimport time\n\ndef timethis(func):\n    def wrapper(*args, **kwargs):\n        start = time.time()\n        try:\n            return func(*args,**kwargs)\n        finally:\n            end = time.time()\n            print(\"%s.%s : %f\" % (func.__module__,func.__name__,end-start))\n    return wrapper\n\nif __name__ == '__main__':\n    @timethis\n    def countdown(n):\n        while n > 0:\n            n-= 1\n\n    countdown(1000000)\n"
  },
  {
    "path": "Solutions/8_1/typedproperty.py",
    "content": "# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n    @property\n    def prop(self):\n        return getattr(self, private_name)\n\n    @prop.setter\n    def prop(self, value):\n        if not isinstance(value, expected_type):\n            raise TypeError(f'Expected {expected_type}')\n        setattr(self, private_name, value)\n\n    return prop\n\nString = lambda name: typedproperty(name, str)\nInteger = lambda name: typedproperty(name, int)\nFloat = lambda name: typedproperty(name, float)\n\n# Example\nif __name__ == '__main__':\n    class Stock:\n        name = typedproperty('name', str)\n        shares = typedproperty('shares', int)\n        price = typedproperty('price', float)\n\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n    \n\n"
  },
  {
    "path": "Solutions/8_2/fileparse.py",
    "content": "# fileparse.py\nimport csv\nimport logging\nlog = logging.getLogger(__name__)\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    if select and not has_headers:\n        raise RuntimeError('select requires column headers')\n\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    log.warning(\"Row %d: Couldn't convert %s\", rowno, row)\n                    log.debug(\"Row %d: Reason %s\", rowno, e)\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/8_2/follow.py",
    "content": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    with open(filename,'r') as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n             line = f.readline()\n             if line != '':\n                 yield line\n             else:\n                 time.sleep(0.1)    # Sleep briefly to avoid busy wait\n"
  },
  {
    "path": "Solutions/8_2/pcost.py",
    "content": "# pcost.py\n\nimport report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return portfolio.total_cost\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/8_2/portfolio.py",
    "content": "# portfolio.py\n\nimport fileparse\nimport stock\n\nclass Portfolio:\n    def __init__(self):\n        self._holdings = []\n\n    @classmethod\n    def from_csv(cls, lines, **opts):\n        self = cls()\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float],\n                                        **opts)\n\n        for d in portdicts:\n            self.append(stock.Stock(**d))\n\n        return self\n\n    def append(self, holding):\n        self._holdings.append(holding)\n\n    def __iter__(self):\n        return self._holdings.__iter__()\n\n    def __len__(self):\n        return len(self._holdings)\n\n    def __getitem__(self, index):\n        return self._holdings[index]\n\n    def __contains__(self, name):\n        return any(s.name == name for s in self._holdings)\n\n    @property\n    def total_cost(self):\n        return sum(s.shares * s.price for s in self._holdings)\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n\n\n\n        \n"
  },
  {
    "path": "Solutions/8_2/report.py",
    "content": "# report.py\n\nimport fileparse\nfrom stock import Stock\nfrom portfolio import Portfolio\nimport tableformat\n\ndef read_portfolio(filename, **opts):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        return Portfolio.from_csv(lines, **opts)\n\ndef read_prices(filename, **opts):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False, **opts))\n\ndef make_report(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfile pricefile format' % args[0])\n    portfolio_report(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/8_2/stock.py",
    "content": "# stock.py\n\nfrom typedproperty import String, Integer, Float\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    name = String('name')\n    shares = Integer('shares')\n    price = Float('price')\n\n    def __init__(self,name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price  = price\n\n    def __repr__(self):\n        return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    @property\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares and return the remaining number.\n        '''\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/8_2/tableformat.py",
    "content": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data\n        '''\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    '''\n    Output data in plain-text format.\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    '''\n    Output data in HTML format.\n    '''\n    def headings(self, headers):\n        print('<tr>', end='')\n        for h in headers:\n            print(f'<th>{h}</th>', end='')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end='')\n        for d in rowdata:\n            print(f'<td>{d}</td>', end='')\n        print('</tr>')\n\nclass FormatError(Exception):\n    pass\n\ndef create_formatter(name):\n    '''\n    Create an appropriate formatter given an output format name\n    '''\n    if name == 'txt':\n        return TextTableFormatter()\n    elif name == 'csv':\n        return CSVTableFormatter()\n    elif name == 'html':\n        return HTMLTableFormatter()\n    else:\n        raise FormatError(f'Unknown table format {name}')\n\ndef print_table(objects, columns, formatter):\n    '''\n    Make a nicely formatted table from a list of objects and attribute names.\n    '''\n    formatter.headings(columns)\n    for obj in objects:\n        rowdata = [ str(getattr(obj, name)) for name in columns ]\n        formatter.row(rowdata)\n\n"
  },
  {
    "path": "Solutions/8_2/test_stock.py",
    "content": "# test_stock.py\n\nimport unittest\nimport stock\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_shares_check(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '100'\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "Solutions/8_2/ticker.py",
    "content": "# ticker.py\n\nimport csv\nimport report\nimport tableformat\nfrom follow import follow\n\ndef select_columns(rows, indices):\n    for row in rows:\n        yield [row[index] for index in indices]\n\ndef convert_types(rows, types):\n    for row in rows:\n        yield [func(val) for func, val in zip(types, row)]\n\ndef make_dicts(rows, headers):\n    return (dict(zip(headers,row)) for row in rows)\n\ndef parse_stock_data(lines):\n    rows = csv.reader(lines)\n    rows = select_columns(rows, [0, 1, 4])\n    rows = convert_types(rows, [str,float,float])\n    rows = make_dicts(rows, ['name','price','change'])\n    return rows\n\ndef ticker(portfile, logfile, fmt):\n    portfolio = report.read_portfolio(portfile)\n    lines = follow(logfile)\n    rows = parse_stock_data(lines)\n    rows = (row for row in rows if row['name'] in portfolio)\n    formatter = tableformat.create_formatter(fmt)\n    formatter.headings(['Name','Price','Change'])\n    for row in rows:\n        formatter.row([ row['name'], f\"{row['price']:0.2f}\", f\"{row['change']:0.2f}\"] )\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])\n    ticker(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/8_2/timethis.py",
    "content": "# timethis.py\n\nimport time\n\ndef timethis(func):\n    def wrapper(*args, **kwargs):\n        start = time.time()\n        try:\n            return func(*args,**kwargs)\n        finally:\n            end = time.time()\n            print(\"%s.%s : %f\" % (func.__module__,func.__name__,end-start))\n    return wrapper\n\nif __name__ == '__main__':\n    @timethis\n    def countdown(n):\n        while n > 0:\n            n-= 1\n\n    countdown(1000000)\n"
  },
  {
    "path": "Solutions/8_2/typedproperty.py",
    "content": "# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n    @property\n    def prop(self):\n        return getattr(self, private_name)\n\n    @prop.setter\n    def prop(self, value):\n        if not isinstance(value, expected_type):\n            raise TypeError(f'Expected {expected_type}')\n        setattr(self, private_name, value)\n\n    return prop\n\nString = lambda name: typedproperty(name, str)\nInteger = lambda name: typedproperty(name, int)\nFloat = lambda name: typedproperty(name, float)\n\n# Example\nif __name__ == '__main__':\n    class Stock:\n        name = typedproperty('name', str)\n        shares = typedproperty('shares', int)\n        price = typedproperty('price', float)\n\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n    \n\n"
  },
  {
    "path": "Solutions/9_3/porty-app/README.txt",
    "content": "Code from Practical Python.\n\nThe \"porty\" directory is a Python package of code that's loaded via \nimport.  The \"print-report.py\" program is a top-level script that\nproduces a report.  Try it:\n\nshell % python3 print-report.py portfolio.csv prices.csv txt\n      Name     Shares      Price     Change \n---------- ---------- ---------- ---------- \n        AA        100       9.22     -22.98 \n       IBM         50     106.28      15.18 \n       CAT        150      35.46     -47.98 \n      MSFT        200      20.89     -30.34 \n        GE         95      13.48     -26.89 \n      MSFT         50      20.89     -44.21 \n       IBM        100     106.28      35.84 \nshell %\n"
  },
  {
    "path": "Solutions/9_3/porty-app/portfolio.csv",
    "content": "name,shares,price\n\"AA\",100,32.20\n\"IBM\",50,91.10\n\"CAT\",150,83.44\n\"MSFT\",200,51.23\n\"GE\",95,40.37\n\"MSFT\",50,65.10\n\"IBM\",100,70.44\n"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/__init__.py",
    "content": ""
  },
  {
    "path": "Solutions/9_3/porty-app/porty/fileparse.py",
    "content": "# fileparse.py\nimport csv\nimport logging\nlog = logging.getLogger(__name__)\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    assert not (select and not has_headers), 'select requires column headers'\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    log.warning(\"Row %d: Couldn't convert %s\", rowno, row)\n                    log.debug(\"Row %d: Reason %s\", rowno, e)\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/follow.py",
    "content": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    with open(filename,'r') as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n             line = f.readline()\n             if line != '':\n                 yield line\n             else:\n                 time.sleep(0.1)    # Sleep briefly to avoid busy wait\n"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/pcost.py",
    "content": "# pcost.py\n\nfrom . import report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return portfolio.total_cost\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/portfolio.py",
    "content": "# portfolio.py\n\nfrom . import fileparse\nfrom . import stock\n\nclass Portfolio:\n    def __init__(self):\n        self._holdings = []\n\n    @classmethod\n    def from_csv(cls, lines, **opts):\n        self = cls()\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float],\n                                        **opts)\n\n        for d in portdicts:\n            self.append(stock.Stock(**d))\n\n        return self\n\n    def append(self, holding):\n        self._holdings.append(holding)\n\n    def __iter__(self):\n        return self._holdings.__iter__()\n\n    def __len__(self):\n        return len(self._holdings)\n\n    def __getitem__(self, index):\n        return self._holdings[index]\n\n    def __contains__(self, name):\n        return any(s.name == name for s in self._holdings)\n\n    @property\n    def total_cost(self):\n        return sum(s.shares * s.price for s in self._holdings)\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n\n\n\n        \n"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/report.py",
    "content": "# report.py\n\nfrom . import fileparse\nfrom .stock import Stock\nfrom .portfolio import Portfolio\nfrom . import tableformat\n\ndef read_portfolio(filename, **opts):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        return Portfolio.from_csv(lines, **opts)\n\ndef read_prices(filename, **opts):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False, **opts))\n\ndef make_report(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfile pricefile format' % args[0])\n    portfolio_report(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/stock.py",
    "content": "# stock.py\n\nfrom .typedproperty import String, Integer, Float\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    if __debug__:\n        name = String('name')\n        shares = Integer('shares')\n        price = Float('price')\n\n    def __init__(self,name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price  = price\n\n    def __repr__(self):\n        return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    @property\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares and return the remaining number.\n        '''\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/tableformat.py",
    "content": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data\n        '''\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    '''\n    Output data in plain-text format.\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    '''\n    Output data in HTML format.\n    '''\n    def headings(self, headers):\n        print('<tr>', end='')\n        for h in headers:\n            print(f'<th>{h}</th>', end='')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end='')\n        for d in rowdata:\n            print(f'<td>{d}</td>', end='')\n        print('</tr>')\n\nclass FormatError(Exception):\n    pass\n\ndef create_formatter(name):\n    '''\n    Create an appropriate formatter given an output format name\n    '''\n    if name == 'txt':\n        return TextTableFormatter()\n    elif name == 'csv':\n        return CSVTableFormatter()\n    elif name == 'html':\n        return HTMLTableFormatter()\n    else:\n        raise FormatError(f'Unknown table format {name}')\n\ndef print_table(objects, columns, formatter):\n    '''\n    Make a nicely formatted table from a list of objects and attribute names.\n    '''\n    formatter.headings(columns)\n    for obj in objects:\n        rowdata = [ str(getattr(obj, name)) for name in columns ]\n        formatter.row(rowdata)\n\n"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/test_stock.py",
    "content": "# test_stock.py\n\nimport unittest\nfrom . import stock\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_shares_check(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '100'\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/ticker.py",
    "content": "# ticker.py\n\nimport csv\nfrom . import report\nfrom . import tableformat\nfrom .follow import follow\n\ndef select_columns(rows, indices):\n    for row in rows:\n        yield [row[index] for index in indices]\n\ndef convert_types(rows, types):\n    for row in rows:\n        yield [func(val) for func, val in zip(types, row)]\n\ndef make_dicts(rows, headers):\n    return (dict(zip(headers,row)) for row in rows)\n\ndef parse_stock_data(lines):\n    rows = csv.reader(lines)\n    rows = select_columns(rows, [0, 1, 4])\n    rows = convert_types(rows, [str,float,float])\n    rows = make_dicts(rows, ['name','price','change'])\n    return rows\n\ndef ticker(portfile, logfile, fmt):\n    portfolio = report.read_portfolio(portfile)\n    lines = follow(logfile)\n    rows = parse_stock_data(lines)\n    rows = (row for row in rows if row['name'] in portfolio)\n    formatter = tableformat.create_formatter(fmt)\n    formatter.headings(['Name','Price','Change'])\n    for row in rows:\n        formatter.row([ row['name'], f\"{row['price']:0.2f}\", f\"{row['change']:0.2f}\"] )\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])\n    ticker(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/9_3/porty-app/porty/typedproperty.py",
    "content": "# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n    @property\n    def prop(self):\n        return getattr(self, private_name)\n\n    @prop.setter\n    def prop(self, value):\n        if not isinstance(value, expected_type):\n            raise TypeError(f'Expected {expected_type}')\n        setattr(self, private_name, value)\n\n    return prop\n\nString = lambda name: typedproperty(name, str)\nInteger = lambda name: typedproperty(name, int)\nFloat = lambda name: typedproperty(name, float)\n\n# Example\nif __name__ == '__main__':\n    class Stock:\n        name = typedproperty('name', str)\n        shares = typedproperty('shares', int)\n        price = typedproperty('price', float)\n\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n    \n\n"
  },
  {
    "path": "Solutions/9_3/porty-app/prices.csv",
    "content": "\"AA\",9.22\r\n\"AXP\",24.85\r\n\"BA\",44.85\r\n\"BAC\",11.27\r\n\"C\",3.72\r\n\"CAT\",35.46\r\n\"CVX\",66.67\r\n\"DD\",28.47\r\n\"DIS\",24.22\r\n\"GE\",13.48\r\n\"GM\",0.75\r\n\"HD\",23.16\r\n\"HPQ\",34.35\r\n\"IBM\",106.28\r\n\"INTC\",15.72\r\n\"JNJ\",55.16\r\n\"JPM\",36.90\r\n\"KFT\",26.11\r\n\"KO\",49.16\r\n\"MCD\",58.99\r\n\"MMM\",57.10\r\n\"MRK\",27.58\r\n\"MSFT\",20.89\r\n\"PFE\",15.19\r\n\"PG\",51.94\r\n\"T\",24.79\r\n\"UTX\",52.61\r\n\"VZ\",29.26\r\n\"WMT\",49.74\r\n\"XOM\",69.35\r\n\r\n"
  },
  {
    "path": "Solutions/9_3/porty-app/print-report.py",
    "content": "#!/usr/bin/env python3\n# print-report.py\n\nimport sys\nfrom porty.report import main\nmain(sys.argv)\n"
  },
  {
    "path": "Solutions/9_5/porty-app/MANIFEST.in",
    "content": "include *.csv\n"
  },
  {
    "path": "Solutions/9_5/porty-app/README.txt",
    "content": "Code from Practical Python.\n\nThe \"porty\" directory is a Python package of code that's loaded via \nimport.  The \"print-report.py\" program is a top-level script that\nproduces a report.  Try it:\n\nshell % python3 print-report.py portfolio.csv prices.csv txt\n      Name     Shares      Price     Change \n---------- ---------- ---------- ---------- \n        AA        100       9.22     -22.98 \n       IBM         50     106.28      15.18 \n       CAT        150      35.46     -47.98 \n      MSFT        200      20.89     -30.34 \n        GE         95      13.48     -26.89 \n      MSFT         50      20.89     -44.21 \n       IBM        100     106.28      35.84 \nshell %\n"
  },
  {
    "path": "Solutions/9_5/porty-app/portfolio.csv",
    "content": "name,shares,price\n\"AA\",100,32.20\n\"IBM\",50,91.10\n\"CAT\",150,83.44\n\"MSFT\",200,51.23\n\"GE\",95,40.37\n\"MSFT\",50,65.10\n\"IBM\",100,70.44\n"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/__init__.py",
    "content": ""
  },
  {
    "path": "Solutions/9_5/porty-app/porty/fileparse.py",
    "content": "# fileparse.py\nimport csv\nimport logging\nlog = logging.getLogger(__name__)\n\ndef parse_csv(lines, select=None, types=None, has_headers=True, delimiter=',', silence_errors=False):\n    '''\n    Parse a CSV file into a list of records with type conversion.\n    '''\n    assert not (select and not has_headers), 'select requires column headers'\n    rows = csv.reader(lines, delimiter=delimiter)\n\n    # Read the file headers (if any)\n    headers = next(rows) if has_headers else []\n\n    # If specific columns have been selected, make indices for filtering and set output columns\n    if select:\n        indices = [ headers.index(colname) for colname in select ]\n        headers = select\n\n    records = []\n    for rowno, row in enumerate(rows, 1):\n        if not row:     # Skip rows with no data\n            continue\n\n        # If specific column indices are selected, pick them out\n        if select:\n            row = [ row[index] for index in indices]\n\n        # Apply type conversion to the row\n        if types:\n            try:\n                row = [func(val) for func, val in zip(types, row)]\n            except ValueError as e:\n                if not silence_errors:\n                    log.warning(\"Row %d: Couldn't convert %s\", rowno, row)\n                    log.debug(\"Row %d: Reason %s\", rowno, e)\n                continue\n\n        # Make a dictionary or a tuple\n        if headers:\n            record = dict(zip(headers, row))\n        else:\n            record = tuple(row)\n        records.append(record)\n\n    return records\n"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/follow.py",
    "content": "# follow.py\n\nimport time\nimport os\n\ndef follow(filename):\n    '''\n    Generator that produces a sequence of lines being written at the end of a file.\n    '''\n    with open(filename,'r') as f:\n        f.seek(0,os.SEEK_END)\n        while True:\n             line = f.readline()\n             if line != '':\n                 yield line\n             else:\n                 time.sleep(0.1)    # Sleep briefly to avoid busy wait\n"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/pcost.py",
    "content": "# pcost.py\n\nfrom . import report\n\ndef portfolio_cost(filename):\n    '''\n    Computes the total cost (shares*price) of a portfolio file\n    '''\n    portfolio = report.read_portfolio(filename)\n    return portfolio.total_cost\n\ndef main(args):\n    if len(args) != 2:\n        raise SystemExit('Usage: %s portfoliofile' % args[0])\n    filename = args[1]\n    print('Total cost:', portfolio_cost(filename))\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/portfolio.py",
    "content": "# portfolio.py\n\nfrom . import fileparse\nfrom . import stock\n\nclass Portfolio:\n    def __init__(self):\n        self._holdings = []\n\n    @classmethod\n    def from_csv(cls, lines, **opts):\n        self = cls()\n        portdicts = fileparse.parse_csv(lines, \n                                        select=['name','shares','price'], \n                                        types=[str,int,float],\n                                        **opts)\n\n        for d in portdicts:\n            self.append(stock.Stock(**d))\n\n        return self\n\n    def append(self, holding):\n        self._holdings.append(holding)\n\n    def __iter__(self):\n        return self._holdings.__iter__()\n\n    def __len__(self):\n        return len(self._holdings)\n\n    def __getitem__(self, index):\n        return self._holdings[index]\n\n    def __contains__(self, name):\n        return any(s.name == name for s in self._holdings)\n\n    @property\n    def total_cost(self):\n        return sum(s.shares * s.price for s in self._holdings)\n\n    def tabulate_shares(self):\n        from collections import Counter\n        total_shares = Counter()\n        for s in self._holdings:\n            total_shares[s.name] += s.shares\n        return total_shares\n\n\n\n        \n"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/report.py",
    "content": "# report.py\n\nfrom . import fileparse\nfrom .stock import Stock\nfrom .portfolio import Portfolio\nfrom . import tableformat\n\ndef read_portfolio(filename, **opts):\n    '''\n    Read a stock portfolio file into a list of dictionaries with keys\n    name, shares, and price.\n    '''\n    with open(filename) as lines:\n        return Portfolio.from_csv(lines, **opts)\n\ndef read_prices(filename, **opts):\n    '''\n    Read a CSV file of price data into a dict mapping names to prices.\n    '''\n    with open(filename) as lines:\n        return dict(fileparse.parse_csv(lines, types=[str,float], has_headers=False, **opts))\n\ndef make_report(portfolio, prices):\n    '''\n    Make a list of (name, shares, price, change) tuples given a portfolio list\n    and prices dictionary.\n    '''\n    rows = []\n    for s in portfolio:\n        current_price = prices[s.name]\n        change = current_price - s.price\n        summary = (s.name, s.shares, current_price, change)\n        rows.append(summary)\n    return rows\n\ndef print_report(reportdata, formatter):\n    '''\n    Print a nicely formated table from a list of (name, shares, price, change) tuples.\n    '''\n    formatter.headings(['Name','Shares','Price','Change'])\n    for name, shares, price, change in reportdata:\n        rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]\n        formatter.row(rowdata)\n\ndef portfolio_report(portfoliofile, pricefile, fmt='txt'):\n    '''\n    Make a stock report given portfolio and price data files.\n    '''\n    # Read data files \n    portfolio = read_portfolio(portfoliofile)\n    prices = read_prices(pricefile)\n\n    # Create the report data\n    report = make_report(portfolio, prices)\n\n    # Print it out\n    formatter = tableformat.create_formatter(fmt)\n    print_report(report, formatter)\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfile pricefile format' % args[0])\n    portfolio_report(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/stock.py",
    "content": "# stock.py\n\nfrom .typedproperty import String, Integer, Float\n\nclass Stock:\n    '''\n    An instance of a stock holding consisting of name, shares, and price.\n    '''\n    if __debug__:\n        name = String('name')\n        shares = Integer('shares')\n        price = Float('price')\n\n    def __init__(self,name, shares, price):\n        self.name = name\n        self.shares = shares\n        self.price  = price\n\n    def __repr__(self):\n        return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'\n\n    @property\n    def cost(self):\n        '''\n        Return the cost as shares*price\n        '''\n        return self.shares * self.price\n\n    def sell(self, nshares):\n        '''\n        Sell a number of shares and return the remaining number.\n        '''\n        self.shares -= nshares\n"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/tableformat.py",
    "content": "# tableformat.py\n\nclass TableFormatter:\n    def headings(self, headers):\n        '''\n        Emit the table headers\n        '''\n        raise NotImplementedError()\n\n    def row(self, rowdata):\n        '''\n        Emit a single row of table data\n        '''\n        raise NotImplementedError()\n\nclass TextTableFormatter(TableFormatter):\n    '''\n    Output data in plain-text format.\n    '''\n    def headings(self, headers):\n        for h in headers:\n            print(f'{h:>10s}', end=' ')\n        print()\n        print(('-'*10 + ' ')*len(headers))\n\n    def row(self, rowdata):\n        for d in rowdata:\n            print(f'{d:>10s}', end=' ')\n        print()\n\nclass CSVTableFormatter(TableFormatter):\n    '''\n    Output data in CSV format.\n    '''\n    def headings(self, headers):\n        print(','.join(headers))\n\n    def row(self, rowdata):\n        print(','.join(rowdata))\n\nclass HTMLTableFormatter(TableFormatter):\n    '''\n    Output data in HTML format.\n    '''\n    def headings(self, headers):\n        print('<tr>', end='')\n        for h in headers:\n            print(f'<th>{h}</th>', end='')\n        print('</tr>')\n\n    def row(self, rowdata):\n        print('<tr>', end='')\n        for d in rowdata:\n            print(f'<td>{d}</td>', end='')\n        print('</tr>')\n\nclass FormatError(Exception):\n    pass\n\ndef create_formatter(name):\n    '''\n    Create an appropriate formatter given an output format name\n    '''\n    if name == 'txt':\n        return TextTableFormatter()\n    elif name == 'csv':\n        return CSVTableFormatter()\n    elif name == 'html':\n        return HTMLTableFormatter()\n    else:\n        raise FormatError(f'Unknown table format {name}')\n\ndef print_table(objects, columns, formatter):\n    '''\n    Make a nicely formatted table from a list of objects and attribute names.\n    '''\n    formatter.headings(columns)\n    for obj in objects:\n        rowdata = [ str(getattr(obj, name)) for name in columns ]\n        formatter.row(rowdata)\n\n"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/test_stock.py",
    "content": "# test_stock.py\n\nimport unittest\nfrom . import stock\n\nclass TestStock(unittest.TestCase):\n    def test_create(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.name, 'GOOG')\n        self.assertEqual(s.shares, 100)\n        self.assertEqual(s.price, 490.1)\n\n    def test_cost(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        self.assertEqual(s.cost, 49010.0)\n\n    def test_sell(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        s.sell(25)\n        self.assertEqual(s.shares, 75)\n\n    def test_shares_check(self):\n        s = stock.Stock('GOOG', 100, 490.1)\n        with self.assertRaises(TypeError):\n            s.shares = '100'\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/ticker.py",
    "content": "# ticker.py\n\nimport csv\nfrom . import report\nfrom . import tableformat\nfrom .follow import follow\n\ndef select_columns(rows, indices):\n    for row in rows:\n        yield [row[index] for index in indices]\n\ndef convert_types(rows, types):\n    for row in rows:\n        yield [func(val) for func, val in zip(types, row)]\n\ndef make_dicts(rows, headers):\n    return (dict(zip(headers,row)) for row in rows)\n\ndef parse_stock_data(lines):\n    rows = csv.reader(lines)\n    rows = select_columns(rows, [0, 1, 4])\n    rows = convert_types(rows, [str,float,float])\n    rows = make_dicts(rows, ['name','price','change'])\n    return rows\n\ndef ticker(portfile, logfile, fmt):\n    portfolio = report.read_portfolio(portfile)\n    lines = follow(logfile)\n    rows = parse_stock_data(lines)\n    rows = (row for row in rows if row['name'] in portfolio)\n    formatter = tableformat.create_formatter(fmt)\n    formatter.headings(['Name','Price','Change'])\n    for row in rows:\n        formatter.row([ row['name'], f\"{row['price']:0.2f}\", f\"{row['change']:0.2f}\"] )\n\ndef main(args):\n    if len(args) != 4:\n        raise SystemExit('Usage: %s portfoliofile logfile fmt' % args[0])\n    ticker(args[1], args[2], args[3])\n\nif __name__ == '__main__':\n    import sys\n    main(sys.argv)\n"
  },
  {
    "path": "Solutions/9_5/porty-app/porty/typedproperty.py",
    "content": "# typedproperty.py\n\ndef typedproperty(name, expected_type):\n    private_name = '_' + name\n    @property\n    def prop(self):\n        return getattr(self, private_name)\n\n    @prop.setter\n    def prop(self, value):\n        if not isinstance(value, expected_type):\n            raise TypeError(f'Expected {expected_type}')\n        setattr(self, private_name, value)\n\n    return prop\n\nString = lambda name: typedproperty(name, str)\nInteger = lambda name: typedproperty(name, int)\nFloat = lambda name: typedproperty(name, float)\n\n# Example\nif __name__ == '__main__':\n    class Stock:\n        name = typedproperty('name', str)\n        shares = typedproperty('shares', int)\n        price = typedproperty('price', float)\n\n        def __init__(self, name, shares, price):\n            self.name = name\n            self.shares = shares\n            self.price = price\n\n    \n\n"
  },
  {
    "path": "Solutions/9_5/porty-app/prices.csv",
    "content": "\"AA\",9.22\r\n\"AXP\",24.85\r\n\"BA\",44.85\r\n\"BAC\",11.27\r\n\"C\",3.72\r\n\"CAT\",35.46\r\n\"CVX\",66.67\r\n\"DD\",28.47\r\n\"DIS\",24.22\r\n\"GE\",13.48\r\n\"GM\",0.75\r\n\"HD\",23.16\r\n\"HPQ\",34.35\r\n\"IBM\",106.28\r\n\"INTC\",15.72\r\n\"JNJ\",55.16\r\n\"JPM\",36.90\r\n\"KFT\",26.11\r\n\"KO\",49.16\r\n\"MCD\",58.99\r\n\"MMM\",57.10\r\n\"MRK\",27.58\r\n\"MSFT\",20.89\r\n\"PFE\",15.19\r\n\"PG\",51.94\r\n\"T\",24.79\r\n\"UTX\",52.61\r\n\"VZ\",29.26\r\n\"WMT\",49.74\r\n\"XOM\",69.35\r\n\r\n"
  },
  {
    "path": "Solutions/9_5/porty-app/print-report.py",
    "content": "#!/usr/bin/env python3\n# print-report.py\n\nimport sys\nfrom porty.report import main\nmain(sys.argv)\n"
  },
  {
    "path": "Solutions/9_5/porty-app/setup.py",
    "content": "# setup.py\n\nimport setuptools\n\nsetuptools.setup(\n    name=\"porty\", \n    version=\"0.0.1\",\n    author=\"Your Name\",\n    author_email=\"you@example.com\",\n    description=\"Practical Python Code\",\n    packages=setuptools.find_packages(),\n    scripts=['print-report.py'],\n)\n"
  },
  {
    "path": "Solutions/README.md",
    "content": "# Solutions\n\nThis directory contains solutions to selected exercises.  The code is\nwritten to run within this directory and has file paths set\naccordingly.  If you copy any of the code to the `Work/` directory,\nyou might need to adjust filenames.\n\n"
  },
  {
    "path": "Work/Data/dowstocks.csv",
    "content": "name,price,date,time,change,open,high,low,volume\r\n\"AA\",39.48,\"6/11/2007\",\"9:36am\",-0.18,39.67,39.69,39.45,181800\r\n\"AIG\",71.38,\"6/11/2007\",\"9:36am\",-0.15,71.29,71.60,71.15,195500\r\n\"AXP\",62.58,\"6/11/2007\",\"9:36am\",-0.46,62.79,63.00,62.57,935000\r\n\"BA\",98.31,\"6/11/2007\",\"9:36am\",+0.12,98.25,98.58,98.19,104800\r\n\"C\",53.08,\"6/11/2007\",\"9:36am\",-0.25,53.20,53.25,53.03,360900\r\n\"CAT\",78.29,\"6/11/2007\",\"9:36am\",-0.23,78.32,78.33,78.06,225400\r\n\"DD\",50.75,\"6/11/2007\",\"9:36am\",-0.38,51.13,51.14,50.69,96900\r\n\"DIS\",34.20,\"6/11/2007\",\"9:35am\",0.00,34.28,34.30,34.12,191000\r\n\"GE\",37.23,\"6/11/2007\",\"9:36am\",-0.09,37.07,37.27,37.05,694300\r\n\"GM\",31.44,\"6/11/2007\",\"9:36am\",+0.44,31.00,31.62,30.90,715429\r\n\"HD\",37.67,\"6/11/2007\",\"9:35am\",-0.28,37.78,37.83,37.62,218369\r\n\"HON\",57.12,\"6/11/2007\",\"9:35am\",-0.26,57.25,57.33,57.11,131100\r\n\"HPQ\",45.81,\"6/11/2007\",\"9:36am\",+0.11,45.80,45.85,45.46,303200\r\n\"IBM\",102.86,\"6/11/2007\",\"9:35am\",-0.21,102.87,102.99,102.50,151700\r\n\"INTC\",21.84,\"6/11/2007\",\"9:40am\",+0.01,21.70,21.85,21.69,2927268\r\n\"JNJ\",62.25,\"6/11/2007\",\"9:35am\",+0.12,62.89,62.89,62.15,343500\r\n\"JPM\",50.35,\"6/11/2007\",\"9:36am\",-0.06,50.41,50.49,50.27,351100\r\n\"KO\",51.65,\"6/11/2007\",\"9:35am\",-0.02,51.67,51.73,51.54,3981400\r\n\"MCD\",51.11,\"6/11/2007\",\"9:35am\",-0.30,51.47,51.47,51.00,169100\r\n\"MMM\",85.60,\"6/11/2007\",\"9:35am\",-0.34,85.94,85.98,85.50,190800\r\n\"MO\",70.09,\"6/11/2007\",\"9:36am\",-0.21,70.25,70.30,70.04,471200\r\n\"MRK\",50.21,\"6/11/2007\",\"9:36am\",+0.07,50.30,50.46,50.04,1453300\r\n\"MSFT\",30.08,\"6/11/2007\",\"9:41am\",+0.03,30.05,30.09,29.93,6166010\r\n\"PFE\",26.40,\"6/11/2007\",\"9:36am\",-0.12,26.50,26.50,26.34,835600\r\n\"PG\",62.79,\"6/11/2007\",\"9:35am\",-0.28,62.80,62.87,62.75,256000\r\n\"T\",40.03,\"6/11/2007\",\"9:35am\",-0.23,40.20,40.25,39.89,691400\r\n\"UTX\",69.81,\"6/11/2007\",\"9:36am\",-0.42,69.85,70.20,69.51,153900\r\n\"VZ\",42.92,\"6/11/2007\",\"9:35am\",-0.15,42.95,43.00,42.89,221000\r\n\"WMT\",49.78,\"6/11/2007\",\"9:36am\",-0.30,49.90,50.00,49.76,676200\r\n\"XOM\",82.50,\"6/11/2007\",\"9:36am\",-0.18,82.68,82.84,82.41,481200\r\n\"AA\",39.59,\"6/11/2007\",\"9:40am\",-0.07,39.67,39.69,39.43,252600\r\n\"AIG\",71.46,\"6/11/2007\",\"9:40am\",-0.07,71.29,71.60,71.15,276900\r\n\"AXP\",62.71,\"6/11/2007\",\"9:40am\",-0.33,62.79,63.00,62.42,1002100\r\n\"BA\",98.31,\"6/11/2007\",\"9:40am\",+0.12,98.25,98.58,98.17,149700\r\n\"C\",53.14,\"6/11/2007\",\"9:40am\",-0.19,53.20,53.25,53.02,546500\r\n\"CAT\",78.49,\"6/11/2007\",\"9:40am\",-0.03,78.32,78.49,78.06,262812\r\n\"DD\",50.85,\"6/11/2007\",\"9:40am\",-0.28,51.13,51.14,50.69,155000\r\n\"DIS\",34.36,\"6/11/2007\",\"9:40am\",+0.16,34.28,34.38,34.12,276900\r\n\"GE\",37.30,\"6/11/2007\",\"9:40am\",-0.02,37.07,37.32,37.05,1039900\r\n\"GM\",31.40,\"6/11/2007\",\"9:40am\",+0.40,31.00,31.62,30.90,1074079\r\n\"HD\",37.72,\"6/11/2007\",\"9:40am\",-0.23,37.78,37.83,37.62,321769\r\n\"HON\",57.22,\"6/11/2007\",\"9:40am\",-0.16,57.25,57.33,57.05,150400\r\n\"HPQ\",45.954,\"6/11/2007\",\"9:40am\",+0.254,45.80,45.98,45.46,424600\r\n\"IBM\",102.95,\"6/11/2007\",\"9:40am\",-0.12,102.87,102.99,102.50,229500\r\n\"INTC\",21.85,\"6/11/2007\",\"9:46am\",+0.02,21.70,21.86,21.69,3605793\r\n\"JNJ\",62.42,\"6/11/2007\",\"9:40am\",+0.29,62.89,62.89,62.15,433600\r\n\"JPM\",50.42,\"6/11/2007\",\"9:40am\",+0.01,50.41,50.49,50.27,461400\r\n\"KO\",51.67,\"6/11/2007\",\"9:40am\",0.00,51.67,51.73,51.54,4010650\r\n\"MCD\",51.42,\"6/11/2007\",\"9:40am\",+0.01,51.47,51.47,50.98,245800\r\n\"MMM\",85.45,\"6/11/2007\",\"9:40am\",-0.49,85.94,85.98,85.44,225500\r\n\"MO\",69.95,\"6/11/2007\",\"9:40am\",-0.35,70.25,70.30,69.95,543600\r\n\"MRK\",50.58,\"6/11/2007\",\"9:40am\",+0.44,50.30,50.63,50.04,1586100\r\n\"MSFT\",30.14,\"6/11/2007\",\"9:46am\",+0.09,30.05,30.15,29.93,6758871\r\n\"PFE\",26.46,\"6/11/2007\",\"9:40am\",-0.06,26.50,26.50,26.34,1101900\r\n\"PG\",62.97,\"6/11/2007\",\"9:40am\",-0.10,62.80,63.00,62.75,431246\r\n\"T\",40.19,\"6/11/2007\",\"9:40am\",-0.07,40.20,40.25,39.89,874100\r\n\"UTX\",69.88,\"6/11/2007\",\"9:40am\",-0.35,69.85,70.20,69.51,191800\r\n\"VZ\",43.06,\"6/11/2007\",\"9:40am\",-0.01,42.95,43.07,42.89,322700\r\n\"WMT\",49.72,\"6/11/2007\",\"9:40am\",-0.36,49.90,50.00,49.70,822700\r\n\"XOM\",82.41,\"6/11/2007\",\"9:40am\",-0.27,82.68,82.84,82.35,705500\r\n\"AA\",39.78,\"6/11/2007\",\"9:46am\",+0.12,39.67,39.7946,39.43,347300\r\n\"AIG\",71.41,\"6/11/2007\",\"9:46am\",-0.12,71.29,71.60,71.15,378000\r\n\"AXP\",62.87,\"6/11/2007\",\"9:46am\",-0.17,62.79,63.00,62.42,1033500\r\n\"BA\",98.50,\"6/11/2007\",\"9:46am\",+0.31,98.25,98.58,98.17,195100\r\n\"C\",53.13,\"6/11/2007\",\"9:46am\",-0.20,53.20,53.25,53.02,715514\r\n\"CAT\",78.77,\"6/11/2007\",\"9:46am\",+0.25,78.32,78.8128,78.06,329712\r\n\"DD\",50.81,\"6/11/2007\",\"9:46am\",-0.32,51.13,51.14,50.69,198300\r\n\"DIS\",34.34,\"6/11/2007\",\"9:46am\",+0.14,34.28,34.44,34.12,392500\r\n\"GE\",37.27,\"6/11/2007\",\"9:46am\",-0.05,37.07,37.34,37.05,1311900\r\n\"GM\",31.42,\"6/11/2007\",\"9:46am\",+0.42,31.00,31.62,30.90,1340279\r\n\"HD\",37.75,\"6/11/2007\",\"9:46am\",-0.20,37.78,37.83,37.62,459769\r\n\"HON\",57.20,\"6/11/2007\",\"9:46am\",-0.18,57.25,57.33,57.05,185700\r\n\"HPQ\",46.14,\"6/11/2007\",\"9:46am\",+0.44,45.80,46.15,45.46,797800\r\n\"IBM\",103.39,\"6/11/2007\",\"9:46am\",+0.32,102.87,103.47,102.50,413800\r\n\"INTC\",21.85,\"6/11/2007\",\"9:50am\",+0.02,21.70,21.93,21.69,4380516\r\n\"JNJ\",62.52,\"6/11/2007\",\"9:46am\",+0.39,62.89,62.89,62.15,548200\r\n\"JPM\",50.48,\"6/11/2007\",\"9:46am\",+0.07,50.41,50.55,50.27,657600\r\n\"KO\",51.67,\"6/11/2007\",\"9:45am\",0.00,51.67,51.77,51.54,4092550\r\n\"MCD\",51.36,\"6/11/2007\",\"9:46am\",-0.05,51.47,51.47,50.98,343719\r\n\"MMM\",85.48,\"6/11/2007\",\"9:46am\",-0.46,85.94,85.98,85.41,270900\r\n\"MO\",70.00,\"6/11/2007\",\"9:46am\",-0.30,70.25,70.30,69.88,723100\r\n\"MRK\",50.50,\"6/11/2007\",\"9:46am\",+0.36,50.30,50.63,50.04,1689100\r\n\"MSFT\",30.175,\"6/11/2007\",\"9:50am\",+0.125,30.05,30.20,29.93,7234309\r\n\"PFE\",26.48,\"6/11/2007\",\"9:45am\",-0.04,26.50,26.52,26.34,1363300\r\n\"PG\",62.89,\"6/11/2007\",\"9:46am\",-0.18,62.80,63.00,62.75,553446\r\n\"T\",40.12,\"6/11/2007\",\"9:46am\",-0.14,40.20,40.25,39.89,1073400\r\n\"UTX\",69.92,\"6/11/2007\",\"9:46am\",-0.31,69.85,70.20,69.51,244600\r\n\"VZ\",43.09,\"6/11/2007\",\"9:46am\",+0.02,42.95,43.17,42.89,515110\r\n\"WMT\",49.78,\"6/11/2007\",\"9:46am\",-0.30,49.90,50.00,49.65,949000\r\n\"XOM\",82.60,\"6/11/2007\",\"9:46am\",-0.08,82.68,82.84,82.35,934200\r\n\"AA\",40.15,\"6/11/2007\",\"9:51am\",+0.49,39.67,40.15,39.43,510630\r\n\"AIG\",71.50,\"6/11/2007\",\"9:50am\",-0.03,71.29,71.60,71.15,441900\r\n\"AXP\",62.99,\"6/11/2007\",\"9:50am\",-0.05,62.79,63.07,62.42,1067820\r\n\"BA\",98.73,\"6/11/2007\",\"9:50am\",+0.54,98.25,98.79,98.17,233900\r\n\"C\",53.15,\"6/11/2007\",\"9:51am\",-0.18,53.20,53.25,53.02,885114\r\n\"CAT\",78.88,\"6/11/2007\",\"9:50am\",+0.36,78.32,78.99,78.06,386652\r\n\"DD\",51.13,\"6/11/2007\",\"9:50am\",0.00,51.13,51.14,50.69,274100\r\n\"DIS\",34.42,\"6/11/2007\",\"9:51am\",+0.22,34.28,34.44,34.12,448200\r\n\"GE\",37.35,\"6/11/2007\",\"9:50am\",+0.03,37.07,37.37,37.05,1541100\r\n\"GM\",31.28,\"6/11/2007\",\"9:50am\",+0.28,31.00,31.62,30.90,1715679\r\n\"HD\",37.74,\"6/11/2007\",\"9:51am\",-0.21,37.78,37.83,37.62,553869\r\n\"HON\",57.30,\"6/11/2007\",\"9:51am\",-0.08,57.25,57.40,57.05,218000\r\n\"HPQ\",46.26,\"6/11/2007\",\"9:51am\",+0.56,45.80,46.26,45.46,1497780\r\n\"IBM\",103.3781,\"6/11/2007\",\"9:50am\",+0.3081,102.87,103.47,102.50,485800\r\n\"INTC\",21.83,\"6/11/2007\",\"9:55am\",0.00,21.70,21.93,21.69,4802496\r\n\"JNJ\",62.68,\"6/11/2007\",\"9:51am\",+0.55,62.89,62.89,62.15,756500\r\n\"JPM\",50.50,\"6/11/2007\",\"9:50am\",+0.09,50.41,50.55,50.27,851700\r\n\"KO\",51.79,\"6/11/2007\",\"9:50am\",+0.12,51.67,51.796,51.54,4175960\r\n\"MCD\",51.39,\"6/11/2007\",\"9:50am\",-0.02,51.47,51.47,50.98,407819\r\n\"MMM\",85.74,\"6/11/2007\",\"9:50am\",-0.20,85.94,85.98,85.41,311100\r\n\"MO\",70.08,\"6/11/2007\",\"9:51am\",-0.22,70.25,70.30,69.88,840100\r\n\"MRK\",50.45,\"6/11/2007\",\"9:50am\",+0.31,50.30,50.63,50.04,1830600\r\n\"MSFT\",30.22,\"6/11/2007\",\"9:56am\",+0.17,30.05,30.24,29.93,7724578\r\n\"PFE\",26.45,\"6/11/2007\",\"9:51am\",-0.07,26.50,26.52,26.34,1805950\r\n\"PG\",63.10,\"6/11/2007\",\"9:50am\",+0.03,62.80,63.10,62.75,715146\r\n\"T\",40.16,\"6/11/2007\",\"9:50am\",-0.10,40.20,40.25,39.89,1199500\r\n\"UTX\",70.06,\"6/11/2007\",\"9:50am\",-0.17,69.85,70.20,69.51,270900\r\n\"VZ\",43.16,\"6/11/2007\",\"9:50am\",+0.09,42.95,43.18,42.89,614810\r\n\"WMT\",49.87,\"6/11/2007\",\"9:51am\",-0.21,49.90,50.00,49.65,1181700\r\n\"XOM\",82.77,\"6/11/2007\",\"9:50am\",+0.09,82.68,82.86,82.35,1121100\r\n\"AA\",39.97,\"6/11/2007\",\"9:55am\",+0.31,39.67,40.18,39.43,598130\r\n\"AIG\",71.43,\"6/11/2007\",\"9:55am\",-0.10,71.29,71.60,71.15,487600\r\n\"AXP\",62.89,\"6/11/2007\",\"9:55am\",-0.15,62.79,63.07,62.42,1084520\r\n\"BA\",98.66,\"6/11/2007\",\"9:56am\",+0.47,98.25,98.79,98.17,265800\r\n\"C\",53.14,\"6/11/2007\",\"9:56am\",-0.19,53.20,53.25,53.02,954314\r\n\"CAT\",78.87,\"6/11/2007\",\"9:55am\",+0.35,78.32,78.99,78.06,425952\r\n\"DD\",51.18,\"6/11/2007\",\"9:55am\",+0.05,51.13,51.21,50.69,341500\r\n\"DIS\",34.39,\"6/11/2007\",\"9:55am\",+0.19,34.28,34.44,34.12,523200\r\n\"GE\",37.39,\"6/11/2007\",\"9:56am\",+0.07,37.07,37.40,37.05,1786900\r\n\"GM\",31.31,\"6/11/2007\",\"9:55am\",+0.31,31.00,31.62,30.90,2399379\r\n\"HD\",37.75,\"6/11/2007\",\"9:55am\",-0.20,37.78,37.83,37.62,622969\r\n\"HON\",57.29,\"6/11/2007\",\"9:56am\",-0.09,57.25,57.40,57.05,245000\r\n\"HPQ\",46.25,\"6/11/2007\",\"9:56am\",+0.55,45.80,46.27,45.46,1615680\r\n\"IBM\",103.60,\"6/11/2007\",\"9:56am\",+0.53,102.87,103.62,102.50,569900\r\n\"INTC\",21.85,\"6/11/2007\",\"10:00am\",+0.02,21.70,21.95,21.69,5440945\r\n\"JNJ\",62.74,\"6/11/2007\",\"9:56am\",+0.61,62.89,62.89,62.15,887700\r\n\"JPM\",50.48,\"6/11/2007\",\"9:56am\",+0.07,50.41,50.55,50.27,924300\r\n\"KO\",51.63,\"6/11/2007\",\"9:56am\",-0.04,51.67,51.82,51.54,4247360\r\n\"MCD\",51.40,\"6/11/2007\",\"9:56am\",-0.01,51.47,51.47,50.98,448219\r\n\"MMM\",85.69,\"6/11/2007\",\"9:55am\",-0.25,85.94,85.98,85.41,323200\r\n\"MO\",70.10,\"6/11/2007\",\"9:56am\",-0.20,70.25,70.30,69.88,932900\r\n\"MRK\",50.38,\"6/11/2007\",\"9:55am\",+0.24,50.30,50.63,50.04,1943600\r\n\"MSFT\",30.20,\"6/11/2007\",\"10:01am\",+0.15,30.05,30.24,29.93,8053754\r\n\"PFE\",26.48,\"6/11/2007\",\"9:55am\",-0.04,26.50,26.53,26.34,2029150\r\n\"PG\",63.07,\"6/11/2007\",\"9:56am\",0.00,62.80,63.10,62.75,786646\r\n\"T\",40.08,\"6/11/2007\",\"9:56am\",-0.18,40.20,40.25,39.89,1364200\r\n\"UTX\",70.06,\"6/11/2007\",\"9:56am\",-0.17,69.85,70.20,69.51,289300\r\n\"VZ\",43.17,\"6/11/2007\",\"9:56am\",+0.10,42.95,43.19,42.89,683810\r\n\"WMT\",49.88,\"6/11/2007\",\"9:56am\",-0.20,49.90,50.00,49.65,1321100\r\n\"XOM\",82.68,\"6/11/2007\",\"9:56am\",0.00,82.68,82.86,82.35,1235100\r\n\"AA\",39.94,\"6/11/2007\",\"10:00am\",+0.28,39.67,40.18,39.43,660280\r\n\"AIG\",71.36,\"6/11/2007\",\"10:00am\",-0.17,71.29,71.60,71.15,575042\r\n\"AXP\",62.83,\"6/11/2007\",\"10:00am\",-0.21,62.79,63.07,62.42,1110520\r\n\"BA\",98.62,\"6/11/2007\",\"10:01am\",+0.43,98.25,98.79,98.17,285700\r\n\"C\",53.06,\"6/11/2007\",\"10:01am\",-0.27,53.20,53.25,53.02,1030199\r\n\"CAT\",78.66,\"6/11/2007\",\"10:00am\",+0.14,78.32,78.99,78.06,452352\r\n\"DD\",51.09,\"6/11/2007\",\"10:01am\",-0.04,51.13,51.21,50.69,402900\r\n\"DIS\",34.33,\"6/11/2007\",\"10:01am\",+0.13,34.28,34.44,34.12,740300\r\n\"GE\",37.41,\"6/11/2007\",\"10:01am\",+0.09,37.07,37.41,37.05,2106900\r\n\"GM\",31.29,\"6/11/2007\",\"10:01am\",+0.29,31.00,31.62,30.90,2570679\r\n\"HD\",37.76,\"6/11/2007\",\"10:00am\",-0.19,37.78,37.83,37.62,683769\r\n\"HON\",57.335,\"6/11/2007\",\"10:01am\",-0.045,57.25,57.40,57.05,289100\r\n\"HPQ\",46.25,\"6/11/2007\",\"10:01am\",+0.55,45.80,46.29,45.46,1752080\r\n\"IBM\",103.51,\"6/11/2007\",\"10:01am\",+0.44,102.87,103.63,102.50,607300\r\n\"INTC\",21.85,\"6/11/2007\",\"10:05am\",+0.02,21.70,21.95,21.69,5955030\r\n\"JNJ\",62.75,\"6/11/2007\",\"10:01am\",+0.62,62.89,62.89,62.15,1031900\r\n\"JPM\",50.45,\"6/11/2007\",\"10:00am\",+0.04,50.41,50.55,50.27,1010000\r\n\"KO\",51.55,\"6/11/2007\",\"10:00am\",-0.12,51.67,51.82,51.54,4283460\r\n\"MCD\",51.34,\"6/11/2007\",\"10:01am\",-0.07,51.47,51.47,50.98,481919\r\n\"MMM\",85.75,\"6/11/2007\",\"10:00am\",-0.19,85.94,85.98,85.41,332600\r\n\"MO\",70.07,\"6/11/2007\",\"10:00am\",-0.23,70.25,70.30,69.88,1007630\r\n\"MRK\",50.32,\"6/11/2007\",\"10:01am\",+0.18,50.30,50.64,50.04,2165200\r\n\"MSFT\",30.21,\"6/11/2007\",\"10:05am\",+0.16,30.05,30.25,29.93,8448925\r\n\"PFE\",26.45,\"6/11/2007\",\"10:01am\",-0.07,26.50,26.53,26.34,2252150\r\n\"PG\",63.02,\"6/11/2007\",\"10:01am\",-0.05,62.80,63.10,62.75,867746\r\n\"T\",40.05,\"6/11/2007\",\"10:01am\",-0.21,40.20,40.25,39.89,1496200\r\n\"UTX\",70.00,\"6/11/2007\",\"10:00am\",-0.23,69.85,70.20,69.51,308500\r\n\"VZ\",43.11,\"6/11/2007\",\"10:00am\",+0.04,42.95,43.19,42.89,804110\r\n\"WMT\",49.78,\"6/11/2007\",\"10:01am\",-0.30,49.90,50.00,49.65,1378500\r\n\"XOM\",82.71,\"6/11/2007\",\"10:00am\",+0.03,82.68,82.86,82.35,1379100\r\n\"AA\",39.92,\"6/11/2007\",\"10:05am\",+0.26,39.67,40.18,39.43,693080\r\n\"AIG\",71.29,\"6/11/2007\",\"10:05am\",-0.24,71.29,71.60,71.15,630742\r\n\"AXP\",62.74,\"6/11/2007\",\"10:05am\",-0.30,62.79,63.07,62.42,1149120\r\n\"BA\",98.47,\"6/11/2007\",\"10:06am\",+0.28,98.25,98.79,98.17,306700\r\n\"C\",52.97,\"6/11/2007\",\"10:06am\",-0.36,53.20,53.25,52.92,1191699\r\n\"CAT\",78.56,\"6/11/2007\",\"10:06am\",+0.04,78.32,78.99,78.06,512752\r\n\"DD\",51.068,\"6/11/2007\",\"10:05am\",-0.062,51.13,51.21,50.69,490000\r\n\"DIS\",34.29,\"6/11/2007\",\"10:05am\",+0.09,34.28,34.44,34.12,790550\r\n\"GE\",37.36,\"6/11/2007\",\"10:06am\",+0.04,37.07,37.41,37.05,2388519\r\n\"GM\",31.35,\"6/11/2007\",\"10:05am\",+0.35,31.00,31.62,30.90,2770879\r\n\"HD\",37.75,\"6/11/2007\",\"10:06am\",-0.20,37.78,37.83,37.62,909569\r\n\"HON\",57.22,\"6/11/2007\",\"10:06am\",-0.16,57.25,57.40,57.05,307300\r\n\"HPQ\",46.22,\"6/11/2007\",\"10:05am\",+0.52,45.80,46.29,45.46,1904480\r\n\"IBM\",103.57,\"6/11/2007\",\"10:05am\",+0.50,102.87,103.63,102.50,666400\r\n\"INTC\",21.83,\"6/11/2007\",\"10:10am\",0.00,21.70,21.95,21.69,6319637\r\n\"JNJ\",62.70,\"6/11/2007\",\"10:06am\",+0.57,62.89,62.89,62.15,1138900\r\n\"JPM\",50.35,\"6/11/2007\",\"10:05am\",-0.06,50.41,50.55,50.27,1112900\r\n\"KO\",51.57,\"6/11/2007\",\"10:06am\",-0.10,51.67,51.82,51.53,4320060\r\n\"MCD\",51.31,\"6/11/2007\",\"10:06am\",-0.10,51.47,51.47,50.98,533219\r\n\"MMM\",85.76,\"6/11/2007\",\"10:05am\",-0.18,85.94,85.98,85.41,384000\r\n\"MO\",69.99,\"6/11/2007\",\"10:05am\",-0.31,70.25,70.30,69.88,1191130\r\n\"MRK\",50.35,\"6/11/2007\",\"10:06am\",+0.21,50.30,50.64,50.04,2250500\r\n\"MSFT\",30.18,\"6/11/2007\",\"10:11am\",+0.13,30.05,30.25,29.93,9049059\r\n\"PFE\",26.41,\"6/11/2007\",\"10:05am\",-0.11,26.50,26.53,26.34,2353550\r\n\"PG\",63.03,\"6/11/2007\",\"10:06am\",-0.04,62.80,63.10,62.75,920246\r\n\"T\",40.04,\"6/11/2007\",\"10:06am\",-0.22,40.20,40.25,39.89,1644100\r\n\"UTX\",69.95,\"6/11/2007\",\"10:05am\",-0.28,69.85,70.20,69.51,332000\r\n\"VZ\",43.04,\"6/11/2007\",\"10:05am\",-0.03,42.95,43.19,42.89,886910\r\n\"WMT\",49.70,\"6/11/2007\",\"10:05am\",-0.38,49.90,50.00,49.65,1450500\r\n\"XOM\",82.8681,\"6/11/2007\",\"10:05am\",+0.1881,82.68,82.89,82.35,1544600\r\n\"AA\",39.91,\"6/11/2007\",\"10:11am\",+0.25,39.67,40.18,39.43,755880\r\n\"AIG\",71.29,\"6/11/2007\",\"10:11am\",-0.24,71.29,71.60,71.15,706842\r\n\"AXP\",62.79,\"6/11/2007\",\"10:10am\",-0.25,62.79,63.07,62.42,1183320\r\n\"BA\",98.45,\"6/11/2007\",\"10:11am\",+0.26,98.25,98.79,98.17,344500\r\n\"C\",52.926,\"6/11/2007\",\"10:11am\",-0.404,53.20,53.25,52.91,1347199\r\n\"CAT\",78.58,\"6/11/2007\",\"10:11am\",+0.06,78.32,78.99,78.06,559652\r\n\"DD\",50.999,\"6/11/2007\",\"10:11am\",-0.131,51.13,51.21,50.69,542900\r\n\"DIS\",34.29,\"6/11/2007\",\"10:11am\",+0.09,34.28,34.44,34.12,836650\r\n\"GE\",37.38,\"6/11/2007\",\"10:11am\",+0.06,37.07,37.41,37.05,2643919\r\n\"GM\",31.30,\"6/11/2007\",\"10:11am\",+0.30,31.00,31.62,30.90,2943679\r\n\"HD\",37.75,\"6/11/2007\",\"10:11am\",-0.20,37.78,37.83,37.62,1015369\r\n\"HON\",57.23,\"6/11/2007\",\"10:11am\",-0.15,57.25,57.40,57.05,332200\r\n\"HPQ\",46.18,\"6/11/2007\",\"10:11am\",+0.48,45.80,46.29,45.46,2006780\r\n\"IBM\",103.35,\"6/11/2007\",\"10:11am\",+0.28,102.87,103.63,102.50,734000\r\n\"INTC\",21.83,\"6/11/2007\",\"10:16am\",0.00,21.70,21.95,21.69,6790042\r\n\"JNJ\",62.71,\"6/11/2007\",\"10:11am\",+0.58,62.89,62.89,62.15,1274000\r\n\"JPM\",50.28,\"6/11/2007\",\"10:11am\",-0.13,50.41,50.55,50.26,1187600\r\n\"KO\",51.56,\"6/11/2007\",\"10:11am\",-0.11,51.67,51.82,51.53,4370560\r\n\"MCD\",51.25,\"6/11/2007\",\"10:11am\",-0.16,51.47,51.47,50.98,587519\r\n\"MMM\",85.78,\"6/11/2007\",\"10:10am\",-0.16,85.94,85.98,85.41,414300\r\n\"MO\",69.95,\"6/11/2007\",\"10:11am\",-0.35,70.25,70.30,69.88,1273530\r\n\"MRK\",50.29,\"6/11/2007\",\"10:11am\",+0.15,50.30,50.64,50.04,2332500\r\n\"MSFT\",30.14,\"6/11/2007\",\"10:16am\",+0.09,30.05,30.25,29.93,9998935\r\n\"PFE\",26.449,\"6/11/2007\",\"10:10am\",-0.071,26.50,26.53,26.34,2551150\r\n\"PG\",63.07,\"6/11/2007\",\"10:10am\",0.00,62.80,63.10,62.75,1010846\r\n\"T\",40.04,\"6/11/2007\",\"10:10am\",-0.22,40.20,40.25,39.89,1922900\r\n\"UTX\",69.91,\"6/11/2007\",\"10:11am\",-0.32,69.85,70.20,69.51,354300\r\n\"VZ\",43.10,\"6/11/2007\",\"10:10am\",+0.03,42.95,43.19,42.89,998110\r\n\"WMT\",49.73,\"6/11/2007\",\"10:11am\",-0.35,49.90,50.00,49.65,1577400\r\n\"XOM\",83.02,\"6/11/2007\",\"10:11am\",+0.34,82.68,83.11,82.35,1789600\r\n\"AA\",39.76,\"6/11/2007\",\"10:15am\",+0.10,39.67,40.18,39.43,804580\r\n\"AIG\",71.29,\"6/11/2007\",\"10:16am\",-0.24,71.29,71.60,71.15,781442\r\n\"AXP\",62.69,\"6/11/2007\",\"10:16am\",-0.35,62.79,63.07,62.42,1212120\r\n\"BA\",98.29,\"6/11/2007\",\"10:16am\",+0.10,98.25,98.79,98.17,373200\r\n\"C\",52.91,\"6/11/2007\",\"10:16am\",-0.42,53.20,53.25,52.84,1455899\r\n\"CAT\",78.51,\"6/11/2007\",\"10:16am\",-0.01,78.32,78.99,78.06,607452\r\n\"DD\",51.02,\"6/11/2007\",\"10:16am\",-0.11,51.13,51.21,50.69,576600\r\n\"DIS\",34.25,\"6/11/2007\",\"10:16am\",+0.05,34.28,34.44,34.12,880150\r\n\"GE\",37.32,\"6/11/2007\",\"10:16am\",0.00,37.07,37.41,37.05,2784119\r\n\"GM\",31.28,\"6/11/2007\",\"10:16am\",+0.28,31.00,31.62,30.90,3270779\r\n\"HD\",37.76,\"6/11/2007\",\"10:16am\",-0.19,37.78,37.83,37.62,1141469\r\n\"HON\",57.11,\"6/11/2007\",\"10:16am\",-0.27,57.25,57.40,57.05,379600\r\n\"HPQ\",46.06,\"6/11/2007\",\"10:16am\",+0.36,45.80,46.29,45.46,2073280\r\n\"IBM\",103.17,\"6/11/2007\",\"10:16am\",+0.10,102.87,103.63,102.50,798600\r\n\"INTC\",21.84,\"6/11/2007\",\"10:20am\",+0.01,21.70,21.95,21.69,7235829\r\n\"JNJ\",62.66,\"6/11/2007\",\"10:16am\",+0.53,62.89,62.89,62.15,1412600\r\n\"JPM\",50.23,\"6/11/2007\",\"10:16am\",-0.18,50.41,50.55,50.20,1304200\r\n\"KO\",51.52,\"6/11/2007\",\"10:16am\",-0.15,51.67,51.82,51.50,4471160\r\n\"MCD\",51.17,\"6/11/2007\",\"10:15am\",-0.24,51.47,51.47,50.98,640119\r\n\"MMM\",85.69,\"6/11/2007\",\"10:15am\",-0.25,85.94,85.98,85.41,436200\r\n\"MO\",69.90,\"6/11/2007\",\"10:15am\",-0.40,70.25,70.30,69.88,1350130\r\n\"MRK\",50.375,\"6/11/2007\",\"10:16am\",+0.235,50.30,50.64,50.04,2501800\r\n\"MSFT\",30.105,\"6/11/2007\",\"10:21am\",+0.055,30.05,30.25,29.93,11615862\r\n\"PFE\",26.43,\"6/11/2007\",\"10:16am\",-0.09,26.50,26.53,26.34,2699150\r\n\"PG\",63.03,\"6/11/2007\",\"10:15am\",-0.04,62.80,63.10,62.75,1084346\r\n\"T\",40.04,\"6/11/2007\",\"10:16am\",-0.22,40.20,40.25,39.89,2049000\r\n\"UTX\",69.80,\"6/11/2007\",\"10:16am\",-0.43,69.85,70.20,69.51,390200\r\n\"VZ\",43.12,\"6/11/2007\",\"10:16am\",+0.05,42.95,43.19,42.89,1540810\r\n\"WMT\",49.71,\"6/11/2007\",\"10:16am\",-0.37,49.90,50.00,49.65,1700900\r\n\"XOM\",82.79,\"6/11/2007\",\"10:16am\",+0.11,82.68,83.11,82.35,1945300\r\n\"AA\",39.7264,\"6/11/2007\",\"10:21am\",+0.0664,39.67,40.18,39.43,860780\r\n\"AIG\",71.28,\"6/11/2007\",\"10:21am\",-0.25,71.29,71.60,71.15,826742\r\n\"AXP\",62.775,\"6/11/2007\",\"10:21am\",-0.265,62.79,63.07,62.42,1262720\r\n\"BA\",98.17,\"6/11/2007\",\"10:21am\",-0.02,98.25,98.79,98.15,420500\r\n\"C\",52.89,\"6/11/2007\",\"10:20am\",-0.44,53.20,53.25,52.84,1554299\r\n\"CAT\",78.46,\"6/11/2007\",\"10:21am\",-0.06,78.32,78.99,78.06,645152\r\n\"DD\",51.02,\"6/11/2007\",\"10:20am\",-0.11,51.13,51.21,50.69,616000\r\n\"DIS\",34.29,\"6/11/2007\",\"10:21am\",+0.09,34.28,34.44,34.12,961150\r\n\"GE\",37.31,\"6/11/2007\",\"10:21am\",-0.01,37.07,37.41,37.05,2994719\r\n\"GM\",31.16,\"6/11/2007\",\"10:21am\",+0.16,31.00,31.62,30.90,3450979\r\n\"HD\",37.75,\"6/11/2007\",\"10:20am\",-0.20,37.78,37.83,37.62,1239969\r\n\"HON\",57.14,\"6/11/2007\",\"10:21am\",-0.24,57.25,57.40,57.05,427800\r\n\"HPQ\",46.08,\"6/11/2007\",\"10:21am\",+0.38,45.80,46.29,45.46,2169080\r\n\"IBM\",103.24,\"6/11/2007\",\"10:21am\",+0.17,102.87,103.63,102.50,1110200\r\n\"INTC\",21.85,\"6/11/2007\",\"10:26am\",+0.02,21.70,21.95,21.69,7668158\r\n\"JNJ\",62.65,\"6/11/2007\",\"10:21am\",+0.52,62.89,62.89,62.15,1466978\r\n\"JPM\",50.14,\"6/11/2007\",\"10:21am\",-0.27,50.41,50.55,50.13,1391100\r\n\"KO\",51.44,\"6/11/2007\",\"10:20am\",-0.23,51.67,51.82,51.44,4528060\r\n\"MCD\",51.19,\"6/11/2007\",\"10:20am\",-0.22,51.47,51.47,50.98,694019\r\n\"MMM\",85.58,\"6/11/2007\",\"10:20am\",-0.36,85.94,85.98,85.41,455600\r\n\"MO\",69.86,\"6/11/2007\",\"10:21am\",-0.44,70.25,70.30,69.76,1542230\r\n\"MRK\",50.34,\"6/11/2007\",\"10:21am\",+0.20,50.30,50.64,50.04,2582400\r\n\"MSFT\",30.10,\"6/11/2007\",\"10:26am\",+0.05,30.05,30.25,29.93,12004109\r\n\"PFE\",26.43,\"6/11/2007\",\"10:21am\",-0.09,26.50,26.53,26.34,2986570\r\n\"PG\",63.06,\"6/11/2007\",\"10:21am\",-0.01,62.80,63.10,62.75,1165646\r\n\"T\",40.04,\"6/11/2007\",\"10:20am\",-0.22,40.20,40.25,39.89,2229600\r\n\"UTX\",69.66,\"6/11/2007\",\"10:21am\",-0.57,69.85,70.20,69.51,420400\r\n\"VZ\",43.12,\"6/11/2007\",\"10:21am\",+0.05,42.95,43.19,42.89,1615410\r\n\"WMT\",49.65,\"6/11/2007\",\"10:21am\",-0.43,49.90,50.00,49.65,1851200\r\n\"XOM\",82.84,\"6/11/2007\",\"10:21am\",+0.16,82.68,83.11,82.35,2132200\r\n\"AA\",39.63,\"6/11/2007\",\"10:26am\",-0.03,39.67,40.18,39.43,899080\r\n\"AIG\",71.30,\"6/11/2007\",\"10:26am\",-0.23,71.29,71.60,71.15,896542\r\n\"AXP\",62.80,\"6/11/2007\",\"10:26am\",-0.24,62.79,63.07,62.42,1296620\r\n\"BA\",98.04,\"6/11/2007\",\"10:26am\",-0.15,98.25,98.79,97.96,466900\r\n\"C\",52.91,\"6/11/2007\",\"10:26am\",-0.42,53.20,53.25,52.81,1672699\r\n\"CAT\",78.37,\"6/11/2007\",\"10:26am\",-0.15,78.32,78.99,78.06,704152\r\n\"DD\",50.94,\"6/11/2007\",\"10:25am\",-0.19,51.13,51.21,50.69,643200\r\n\"DIS\",34.24,\"6/11/2007\",\"10:26am\",+0.04,34.28,34.44,34.12,1025550\r\n\"GE\",37.26,\"6/11/2007\",\"10:26am\",-0.06,37.07,37.41,37.05,3290619\r\n\"GM\",31.22,\"6/11/2007\",\"10:26am\",+0.22,31.00,31.62,30.90,4096679\r\n\"HD\",37.75,\"6/11/2007\",\"10:26am\",-0.20,37.78,37.83,37.62,1559369\r\n\"HON\",57.10,\"6/11/2007\",\"10:26am\",-0.28,57.25,57.40,57.03,450900\r\n\"HPQ\",46.10,\"6/11/2007\",\"10:26am\",+0.40,45.80,46.29,45.46,2260680\r\n\"IBM\",103.20,\"6/11/2007\",\"10:26am\",+0.13,102.87,103.63,102.50,1147300\r\n\"INTC\",21.84,\"6/11/2007\",\"10:31am\",+0.01,21.70,21.95,21.69,8125254\r\n\"JNJ\",62.55,\"6/11/2007\",\"10:26am\",+0.42,62.89,62.89,62.15,1616778\r\n\"JPM\",50.14,\"6/11/2007\",\"10:26am\",-0.27,50.41,50.55,50.05,1498700\r\n\"KO\",51.39,\"6/11/2007\",\"10:26am\",-0.28,51.67,51.82,51.32,4607560\r\n\"MCD\",51.17,\"6/11/2007\",\"10:25am\",-0.24,51.47,51.47,50.98,740919\r\n\"MMM\",85.51,\"6/11/2007\",\"10:26am\",-0.43,85.94,85.98,85.41,478800\r\n\"MO\",69.85,\"6/11/2007\",\"10:26am\",-0.45,70.25,70.30,69.76,1670405\r\n\"MRK\",50.32,\"6/11/2007\",\"10:25am\",+0.18,50.30,50.64,50.04,2654700\r\n\"MSFT\",30.09,\"6/11/2007\",\"10:31am\",+0.04,30.05,30.25,29.93,12221463\r\n\"PFE\",26.39,\"6/11/2007\",\"10:26am\",-0.13,26.50,26.53,26.34,3224570\r\n\"PG\",63.04,\"6/11/2007\",\"10:25am\",-0.03,62.80,63.10,62.75,1243746\r\n\"T\",40.001,\"6/11/2007\",\"10:26am\",-0.259,40.20,40.25,39.89,2441800\r\n\"UTX\",69.63,\"6/11/2007\",\"10:26am\",-0.60,69.85,70.20,69.51,446700\r\n\"VZ\",43.12,\"6/11/2007\",\"10:26am\",+0.05,42.95,43.19,42.89,1681910\r\n\"WMT\",49.595,\"6/11/2007\",\"10:26am\",-0.485,49.90,50.00,49.57,1951400\r\n\"XOM\",82.79,\"6/11/2007\",\"10:26am\",+0.11,82.68,83.11,82.35,2330500\r\n\"AA\",39.555,\"6/11/2007\",\"10:31am\",-0.105,39.67,40.18,39.43,944980\r\n\"AIG\",71.32,\"6/11/2007\",\"10:30am\",-0.21,71.29,71.60,71.15,953042\r\n\"AXP\",62.79,\"6/11/2007\",\"10:31am\",-0.25,62.79,63.07,62.42,1329020\r\n\"BA\",97.85,\"6/11/2007\",\"10:31am\",-0.34,98.25,98.79,97.74,531200\r\n\"C\",52.92,\"6/11/2007\",\"10:31am\",-0.41,53.20,53.25,52.81,1746999\r\n\"CAT\",78.43,\"6/11/2007\",\"10:30am\",-0.09,78.32,78.99,78.06,751652\r\n\"DD\",50.88,\"6/11/2007\",\"10:30am\",-0.25,51.13,51.21,50.69,698200\r\n\"DIS\",34.21,\"6/11/2007\",\"10:30am\",+0.01,34.28,34.44,34.12,1097150\r\n\"GE\",37.28,\"6/11/2007\",\"10:31am\",-0.04,37.07,37.41,37.05,3653019\r\n\"GM\",31.15,\"6/11/2007\",\"10:31am\",+0.15,31.00,31.62,30.90,4256179\r\n\"HD\",37.75,\"6/11/2007\",\"10:31am\",-0.20,37.78,37.83,37.62,1712669\r\n\"HON\",57.09,\"6/11/2007\",\"10:30am\",-0.29,57.25,57.40,57.03,481100\r\n\"HPQ\",46.08,\"6/11/2007\",\"10:30am\",+0.38,45.80,46.29,45.46,2341480\r\n\"IBM\",103.20,\"6/11/2007\",\"10:30am\",+0.13,102.87,103.63,102.50,1182000\r\n\"INTC\",21.85,\"6/11/2007\",\"10:36am\",+0.02,21.70,21.95,21.69,8728776\r\n\"JNJ\",62.59,\"6/11/2007\",\"10:31am\",+0.46,62.89,62.89,62.15,1771778\r\n\"JPM\",50.13,\"6/11/2007\",\"10:30am\",-0.28,50.41,50.55,50.05,1552000\r\n\"KO\",51.34,\"6/11/2007\",\"10:31am\",-0.33,51.67,51.82,51.32,4669560\r\n\"MCD\",51.20,\"6/11/2007\",\"10:30am\",-0.21,51.47,51.47,50.98,779219\r\n\"MMM\",85.54,\"6/11/2007\",\"10:30am\",-0.40,85.94,85.98,85.41,502100\r\n\"MO\",69.89,\"6/11/2007\",\"10:31am\",-0.41,70.25,70.30,69.76,1759605\r\n\"MRK\",50.35,\"6/11/2007\",\"10:30am\",+0.21,50.30,50.64,50.04,2706300\r\n\"MSFT\",30.11,\"6/11/2007\",\"10:36am\",+0.06,30.05,30.25,29.93,12723552\r\n\"PFE\",26.38,\"6/11/2007\",\"10:30am\",-0.14,26.50,26.53,26.34,3420170\r\n\"PG\",63.07,\"6/11/2007\",\"10:31am\",0.00,62.80,63.10,62.75,1285746\r\n\"T\",40.05,\"6/11/2007\",\"10:30am\",-0.21,40.20,40.25,39.89,2609700\r\n\"UTX\",69.60,\"6/11/2007\",\"10:31am\",-0.63,69.85,70.20,69.51,500000\r\n\"VZ\",43.15,\"6/11/2007\",\"10:30am\",+0.08,42.95,43.19,42.89,1759910\r\n\"WMT\",49.61,\"6/11/2007\",\"10:31am\",-0.47,49.90,50.00,49.57,2038300\r\n\"XOM\",82.69,\"6/11/2007\",\"10:31am\",+0.01,82.68,83.11,82.35,2496800\r\n\"AA\",39.56,\"6/11/2007\",\"10:36am\",-0.10,39.67,40.18,39.43,999880\r\n\"AIG\",71.37,\"6/11/2007\",\"10:36am\",-0.16,71.29,71.60,71.15,991742\r\n\"AXP\",62.89,\"6/11/2007\",\"10:36am\",-0.15,62.79,63.07,62.42,1356220\r\n\"BA\",98.00,\"6/11/2007\",\"10:36am\",-0.19,98.25,98.79,97.74,561900\r\n\"C\",52.97,\"6/11/2007\",\"10:36am\",-0.36,53.20,53.25,52.81,1864099\r\n\"CAT\",78.53,\"6/11/2007\",\"10:36am\",+0.01,78.32,78.99,78.06,785752\r\n\"DD\",50.85,\"6/11/2007\",\"10:35am\",-0.28,51.13,51.21,50.69,754500\r\n\"DIS\",34.22,\"6/11/2007\",\"10:36am\",+0.02,34.28,34.44,34.12,1157550\r\n\"GE\",37.30,\"6/11/2007\",\"10:36am\",-0.02,37.07,37.41,37.05,3926919\r\n\"GM\",31.15,\"6/11/2007\",\"10:36am\",+0.15,31.00,31.62,30.90,4421279\r\n\"HD\",37.76,\"6/11/2007\",\"10:36am\",-0.19,37.78,37.83,37.62,1911069\r\n\"HON\",57.08,\"6/11/2007\",\"10:36am\",-0.30,57.25,57.40,57.00,530800\r\n\"HPQ\",46.01,\"6/11/2007\",\"10:35am\",+0.31,45.80,46.29,45.46,2464880\r\n\"IBM\",103.19,\"6/11/2007\",\"10:36am\",+0.12,102.87,103.63,102.50,1242400\r\n\"INTC\",21.88,\"6/11/2007\",\"10:41am\",+0.05,21.70,21.95,21.69,9039718\r\n\"JNJ\",62.54,\"6/11/2007\",\"10:35am\",+0.41,62.89,62.89,62.15,1848378\r\n\"JPM\",50.17,\"6/11/2007\",\"10:36am\",-0.24,50.41,50.55,50.05,1615800\r\n\"KO\",51.37,\"6/11/2007\",\"10:35am\",-0.30,51.67,51.82,51.32,4715160\r\n\"MCD\",51.19,\"6/11/2007\",\"10:36am\",-0.22,51.47,51.47,50.98,862419\r\n\"MMM\",85.60,\"6/11/2007\",\"10:36am\",-0.34,85.94,85.98,85.41,535100\r\n\"MO\",69.90,\"6/11/2007\",\"10:36am\",-0.40,70.25,70.30,69.76,1818105\r\n\"MRK\",50.405,\"6/11/2007\",\"10:36am\",+0.265,50.30,50.64,50.04,2773500\r\n\"MSFT\",30.08,\"6/11/2007\",\"10:41am\",+0.03,30.05,30.25,29.93,13110210\r\n\"PFE\",26.37,\"6/11/2007\",\"10:36am\",-0.15,26.50,26.53,26.34,3656096\r\n\"PG\",63.07,\"6/11/2007\",\"10:36am\",0.00,62.80,63.10,62.75,1344746\r\n\"T\",40.03,\"6/11/2007\",\"10:36am\",-0.23,40.20,40.25,39.89,2759900\r\n\"UTX\",69.6875,\"6/11/2007\",\"10:36am\",-0.5425,69.85,70.20,69.51,573400\r\n\"VZ\",43.17,\"6/11/2007\",\"10:36am\",+0.10,42.95,43.19,42.89,1809710\r\n\"WMT\",49.71,\"6/11/2007\",\"10:36am\",-0.37,49.90,50.00,49.56,2171500\r\n\"XOM\",82.87,\"6/11/2007\",\"10:36am\",+0.19,82.68,83.11,82.35,2657200\r\n\"AA\",39.59,\"6/11/2007\",\"10:41am\",-0.07,39.67,40.18,39.43,1035980\r\n\"AIG\",71.38,\"6/11/2007\",\"10:41am\",-0.15,71.29,71.60,71.15,1036342\r\n\"AXP\",62.99,\"6/11/2007\",\"10:40am\",-0.05,62.79,63.07,62.42,1401320\r\n\"BA\",97.98,\"6/11/2007\",\"10:40am\",-0.21,98.25,98.79,97.74,586200\r\n\"C\",53.04,\"6/11/2007\",\"10:41am\",-0.29,53.20,53.25,52.81,1974394\r\n\"CAT\",78.47,\"6/11/2007\",\"10:40am\",-0.05,78.32,78.99,78.06,811452\r\n\"DD\",50.75,\"6/11/2007\",\"10:41am\",-0.38,51.13,51.21,50.69,816700\r\n\"DIS\",34.22,\"6/11/2007\",\"10:41am\",+0.02,34.28,34.44,34.12,1264950\r\n\"GE\",37.32,\"6/11/2007\",\"10:41am\",0.00,37.07,37.41,37.05,4056919\r\n\"GM\",31.15,\"6/11/2007\",\"10:41am\",+0.15,31.00,31.62,30.90,4500679\r\n\"HD\",37.74,\"6/11/2007\",\"10:40am\",-0.21,37.78,37.83,37.62,1949569\r\n\"HON\",57.05,\"6/11/2007\",\"10:40am\",-0.33,57.25,57.40,57.00,566900\r\n\"HPQ\",45.95,\"6/11/2007\",\"10:41am\",+0.25,45.80,46.29,45.46,2574530\r\n\"IBM\",103.23,\"6/11/2007\",\"10:40am\",+0.16,102.87,103.63,102.50,1272000\r\n\"INTC\",21.88,\"6/11/2007\",\"10:46am\",+0.05,21.70,21.95,21.69,9537433\r\n\"JNJ\",62.52,\"6/11/2007\",\"10:41am\",+0.39,62.89,62.89,62.15,1896678\r\n\"JPM\",50.18,\"6/11/2007\",\"10:41am\",-0.23,50.41,50.55,50.05,1682000\r\n\"KO\",51.38,\"6/11/2007\",\"10:40am\",-0.29,51.67,51.82,51.32,4744560\r\n\"MCD\",51.20,\"6/11/2007\",\"10:40am\",-0.21,51.47,51.47,50.98,903219\r\n\"MMM\",85.61,\"6/11/2007\",\"10:40am\",-0.33,85.94,85.98,85.41,544900\r\n\"MO\",69.95,\"6/11/2007\",\"10:40am\",-0.35,70.25,70.30,69.76,1858005\r\n\"MRK\",50.49,\"6/11/2007\",\"10:40am\",+0.35,50.30,50.64,50.04,2889100\r\n\"MSFT\",30.022,\"6/11/2007\",\"10:45am\",-0.028,30.05,30.25,29.93,13503536\r\n\"PFE\",26.365,\"6/11/2007\",\"10:40am\",-0.155,26.50,26.53,26.34,3770896\r\n\"PG\",63.08,\"6/11/2007\",\"10:41am\",+0.01,62.80,63.12,62.75,1435146\r\n\"T\",39.99,\"6/11/2007\",\"10:41am\",-0.27,40.20,40.25,39.89,2921500\r\n\"UTX\",69.77,\"6/11/2007\",\"10:41am\",-0.46,69.85,70.20,69.51,589900\r\n\"VZ\",43.19,\"6/11/2007\",\"10:40am\",+0.12,42.95,43.19,42.89,1878010\r\n\"WMT\",49.625,\"6/11/2007\",\"10:41am\",-0.455,49.90,50.00,49.56,2239000\r\n\"XOM\",82.89,\"6/11/2007\",\"10:40am\",+0.21,82.68,83.11,82.35,2766800\r\n\"AA\",39.54,\"6/11/2007\",\"10:46am\",-0.12,39.67,40.18,39.43,1080380\r\n\"AIG\",71.32,\"6/11/2007\",\"10:46am\",-0.21,71.29,71.60,71.15,1071642\r\n\"AXP\",62.85,\"6/11/2007\",\"10:46am\",-0.19,62.79,63.07,62.42,1441520\r\n\"BA\",98.00,\"6/11/2007\",\"10:45am\",-0.19,98.25,98.79,97.74,632300\r\n\"C\",52.95,\"6/11/2007\",\"10:46am\",-0.38,53.20,53.25,52.81,2045194\r\n\"CAT\",78.50,\"6/11/2007\",\"10:45am\",-0.02,78.32,78.99,78.06,834252\r\n\"DD\",50.64,\"6/11/2007\",\"10:46am\",-0.49,51.13,51.21,50.63,893400\r\n\"DIS\",34.21,\"6/11/2007\",\"10:45am\",+0.01,34.28,34.44,34.12,1344850\r\n\"GE\",37.2627,\"6/11/2007\",\"10:46am\",-0.0573,37.07,37.41,37.05,4537419\r\n\"GM\",31.13,\"6/11/2007\",\"10:45am\",+0.13,31.00,31.62,30.90,4598979\r\n\"HD\",37.73,\"6/11/2007\",\"10:46am\",-0.22,37.78,37.83,37.62,2039969\r\n\"HON\",57.05,\"6/11/2007\",\"10:45am\",-0.33,57.25,57.40,57.00,611200\r\n\"HPQ\",45.89,\"6/11/2007\",\"10:46am\",+0.19,45.80,46.29,45.46,2729030\r\n\"IBM\",103.19,\"6/11/2007\",\"10:46am\",+0.12,102.87,103.63,102.50,1319400\r\n\"INTC\",21.90,\"6/11/2007\",\"10:51am\",+0.07,21.70,21.95,21.69,9897575\r\n\"JNJ\",62.42,\"6/11/2007\",\"10:46am\",+0.29,62.89,62.89,62.15,1948833\r\n\"JPM\",50.20,\"6/11/2007\",\"10:46am\",-0.21,50.41,50.55,50.05,1760600\r\n\"KO\",51.38,\"6/11/2007\",\"10:46am\",-0.29,51.67,51.82,51.32,4787060\r\n\"MCD\",51.21,\"6/11/2007\",\"10:45am\",-0.20,51.47,51.47,50.98,957919\r\n\"MMM\",85.54,\"6/11/2007\",\"10:45am\",-0.40,85.94,85.98,85.41,565800\r\n\"MO\",69.96,\"6/11/2007\",\"10:45am\",-0.34,70.25,70.30,69.76,1992005\r\n\"MRK\",50.41,\"6/11/2007\",\"10:45am\",+0.27,50.30,50.64,50.04,2985100\r\n\"MSFT\",30.04,\"6/11/2007\",\"10:50am\",-0.01,30.05,30.25,29.93,13852152\r\n\"PFE\",26.37,\"6/11/2007\",\"10:46am\",-0.15,26.50,26.53,26.34,4020496\r\n\"PG\",63.02,\"6/11/2007\",\"10:45am\",-0.05,62.80,63.12,62.75,1542146\r\n\"T\",39.95,\"6/11/2007\",\"10:46am\",-0.31,40.20,40.25,39.89,3119000\r\n\"UTX\",69.72,\"6/11/2007\",\"10:45am\",-0.51,69.85,70.20,69.51,611500\r\n\"VZ\",43.18,\"6/11/2007\",\"10:45am\",+0.11,42.95,43.20,42.89,1971710\r\n\"WMT\",49.60,\"6/11/2007\",\"10:46am\",-0.48,49.90,50.00,49.56,2280600\r\n\"XOM\",82.72,\"6/11/2007\",\"10:46am\",+0.04,82.68,83.11,82.35,2888400\r\n\"AA\",39.65,\"6/11/2007\",\"10:50am\",-0.01,39.67,40.18,39.43,1109080\r\n\"AIG\",71.44,\"6/11/2007\",\"10:50am\",-0.09,71.29,71.60,71.15,1102442\r\n\"AXP\",62.83,\"6/11/2007\",\"10:50am\",-0.21,62.79,63.07,62.42,1484920\r\n\"BA\",98.11,\"6/11/2007\",\"10:51am\",-0.08,98.25,98.79,97.74,653100\r\n\"C\",53.01,\"6/11/2007\",\"10:51am\",-0.32,53.20,53.25,52.81,2147094\r\n\"CAT\",78.56,\"6/11/2007\",\"10:50am\",+0.04,78.32,78.99,78.06,857252\r\n\"DD\",50.78,\"6/11/2007\",\"10:50am\",-0.35,51.13,51.21,50.62,931500\r\n\"DIS\",34.24,\"6/11/2007\",\"10:51am\",+0.04,34.28,34.44,34.12,1456950\r\n\"GE\",37.29,\"6/11/2007\",\"10:51am\",-0.03,37.07,37.41,37.05,6374978\r\n\"GM\",31.17,\"6/11/2007\",\"10:50am\",+0.17,31.00,31.62,30.90,4686579\r\n\"HD\",37.745,\"6/11/2007\",\"10:51am\",-0.205,37.78,37.83,37.62,2111769\r\n\"HON\",57.14,\"6/11/2007\",\"10:51am\",-0.24,57.25,57.40,57.00,672700\r\n\"HPQ\",46.03,\"6/11/2007\",\"10:51am\",+0.33,45.80,46.29,45.46,2822530\r\n\"IBM\",103.39,\"6/11/2007\",\"10:51am\",+0.32,102.87,103.63,102.50,1365400\r\n\"INTC\",21.89,\"6/11/2007\",\"10:56am\",+0.06,21.70,21.95,21.69,10794661\r\n\"JNJ\",62.60,\"6/11/2007\",\"10:51am\",+0.47,62.89,62.89,62.15,2051122\r\n\"JPM\",50.25,\"6/11/2007\",\"10:50am\",-0.16,50.41,50.55,50.05,1853000\r\n\"KO\",51.46,\"6/11/2007\",\"10:51am\",-0.21,51.67,51.82,51.32,4861660\r\n\"MCD\",51.34,\"6/11/2007\",\"10:50am\",-0.07,51.47,51.47,50.98,999019\r\n\"MMM\",85.53,\"6/11/2007\",\"10:50am\",-0.41,85.94,85.98,85.41,582200\r\n\"MO\",70.0411,\"6/11/2007\",\"10:51am\",-0.2589,70.25,70.30,69.76,2064105\r\n\"MRK\",50.48,\"6/11/2007\",\"10:51am\",+0.34,50.30,50.64,50.04,3053000\r\n\"MSFT\",30.01,\"6/11/2007\",\"10:56am\",-0.04,30.05,30.25,29.93,14553915\r\n\"PFE\",26.385,\"6/11/2007\",\"10:51am\",-0.135,26.50,26.53,26.34,4373096\r\n\"PG\",63.04,\"6/11/2007\",\"10:51am\",-0.03,62.80,63.12,62.75,1613346\r\n\"T\",39.97,\"6/11/2007\",\"10:51am\",-0.29,40.20,40.25,39.89,3263300\r\n\"UTX\",69.79,\"6/11/2007\",\"10:51am\",-0.44,69.85,70.20,69.51,629700\r\n\"VZ\",43.19,\"6/11/2007\",\"10:50am\",+0.12,42.95,43.20,42.89,2067610\r\n\"WMT\",49.70,\"6/11/2007\",\"10:51am\",-0.38,49.90,50.00,49.56,2349800\r\n\"XOM\",82.73,\"6/11/2007\",\"10:51am\",+0.05,82.68,83.11,82.35,2994800\r\n\"AA\",39.67,\"6/11/2007\",\"10:56am\",+0.01,39.67,40.18,39.43,1139080\r\n\"AIG\",71.45,\"6/11/2007\",\"10:55am\",-0.08,71.29,71.60,71.15,1141342\r\n\"AXP\",62.94,\"6/11/2007\",\"10:56am\",-0.10,62.79,63.07,62.42,1517420\r\n\"BA\",98.18,\"6/11/2007\",\"10:56am\",-0.01,98.25,98.79,97.74,695900\r\n\"C\",53.02,\"6/11/2007\",\"10:56am\",-0.31,53.20,53.25,52.81,2245494\r\n\"CAT\",78.57,\"6/11/2007\",\"10:56am\",+0.05,78.32,78.99,78.06,892252\r\n\"DD\",50.8276,\"6/11/2007\",\"10:55am\",-0.3024,51.13,51.21,50.62,991900\r\n\"DIS\",34.23,\"6/11/2007\",\"10:56am\",+0.03,34.28,34.44,34.12,1533350\r\n\"GE\",37.34,\"6/11/2007\",\"10:56am\",+0.02,37.07,37.41,37.05,6871678\r\n\"GM\",31.20,\"6/11/2007\",\"10:56am\",+0.20,31.00,31.62,30.90,4845779\r\n\"HD\",37.74,\"6/11/2007\",\"10:56am\",-0.21,37.78,37.83,37.62,2282669\r\n\"HON\",57.17,\"6/11/2007\",\"10:56am\",-0.21,57.25,57.40,57.00,700800\r\n\"HPQ\",46.07,\"6/11/2007\",\"10:56am\",+0.37,45.80,46.29,45.46,2941930\r\n\"IBM\",103.48,\"6/11/2007\",\"10:56am\",+0.41,102.87,103.63,102.50,1449900\r\n\"INTC\",21.93,\"6/11/2007\",\"11:01am\",+0.10,21.70,21.95,21.69,11463765\r\n\"JNJ\",62.47,\"6/11/2007\",\"10:56am\",+0.34,62.89,62.89,62.15,2184222\r\n\"JPM\",50.27,\"6/11/2007\",\"10:56am\",-0.14,50.41,50.55,50.05,1992300\r\n\"KO\",51.45,\"6/11/2007\",\"10:56am\",-0.22,51.67,51.82,51.32,4959242\r\n\"MCD\",51.39,\"6/11/2007\",\"10:56am\",-0.02,51.47,51.47,50.98,1065114\r\n\"MMM\",85.58,\"6/11/2007\",\"10:56am\",-0.36,85.94,85.98,85.41,603300\r\n\"MO\",70.09,\"6/11/2007\",\"10:56am\",-0.21,70.25,70.30,69.76,2152505\r\n\"MRK\",50.45,\"6/11/2007\",\"10:56am\",+0.31,50.30,50.64,50.04,3120700\r\n\"MSFT\",29.985,\"6/11/2007\",\"11:01am\",-0.065,30.05,30.25,29.93,15025412\r\n\"PFE\",26.35,\"6/11/2007\",\"10:55am\",-0.17,26.50,26.53,26.34,4707596\r\n\"PG\",63.01,\"6/11/2007\",\"10:56am\",-0.06,62.80,63.12,62.75,1804846\r\n\"T\",40.01,\"6/11/2007\",\"10:56am\",-0.25,40.20,40.25,39.89,3433900\r\n\"UTX\",69.90,\"6/11/2007\",\"10:56am\",-0.33,69.85,70.20,69.51,647800\r\n\"VZ\",43.17,\"6/11/2007\",\"10:56am\",+0.10,42.95,43.20,42.89,2246610\r\n\"WMT\",49.61,\"6/11/2007\",\"10:56am\",-0.47,49.90,50.00,49.56,2553200\r\n\"XOM\",82.90,\"6/11/2007\",\"10:56am\",+0.22,82.68,83.11,82.35,3157200\r\n\"AA\",39.65,\"6/11/2007\",\"11:01am\",-0.01,39.67,40.18,39.43,1162580\r\n\"AIG\",71.46,\"6/11/2007\",\"11:01am\",-0.07,71.29,71.60,71.15,1167442\r\n\"AXP\",62.99,\"6/11/2007\",\"11:00am\",-0.05,62.79,63.07,62.42,1532720\r\n\"BA\",98.09,\"6/11/2007\",\"11:01am\",-0.10,98.25,98.79,97.74,714300\r\n\"C\",53.11,\"6/11/2007\",\"11:01am\",-0.22,53.20,53.25,52.81,2344994\r\n\"CAT\",78.52,\"6/11/2007\",\"11:00am\",0.00,78.32,78.99,78.06,915752\r\n\"DD\",50.75,\"6/11/2007\",\"11:01am\",-0.38,51.13,51.21,50.62,1025100\r\n\"DIS\",34.22,\"6/11/2007\",\"11:01am\",+0.02,34.28,34.44,34.12,1567750\r\n\"GE\",37.32,\"6/11/2007\",\"11:01am\",0.00,37.07,37.41,37.05,7016378\r\n\"GM\",31.22,\"6/11/2007\",\"11:01am\",+0.22,31.00,31.62,30.90,4944879\r\n\"HD\",37.73,\"6/11/2007\",\"11:01am\",-0.22,37.78,37.83,37.62,2556369\r\n\"HON\",57.10,\"6/11/2007\",\"11:01am\",-0.28,57.25,57.40,57.00,794183\r\n\"HPQ\",46.06,\"6/11/2007\",\"11:01am\",+0.36,45.80,46.29,45.46,3023656\r\n\"IBM\",103.37,\"6/11/2007\",\"11:01am\",+0.30,102.87,103.63,102.50,1483400\r\n\"INTC\",21.97,\"6/11/2007\",\"11:06am\",+0.14,21.70,21.97,21.69,12553376\r\n\"JNJ\",62.46,\"6/11/2007\",\"11:01am\",+0.33,62.89,62.89,62.15,2309522\r\n\"JPM\",50.32,\"6/11/2007\",\"11:01am\",-0.09,50.41,50.55,50.05,2110500\r\n\"KO\",51.42,\"6/11/2007\",\"11:01am\",-0.25,51.67,51.82,51.32,5000442\r\n\"MCD\",51.50,\"6/11/2007\",\"11:01am\",+0.09,51.47,51.50,50.98,1190914\r\n\"MMM\",85.62,\"6/11/2007\",\"11:00am\",-0.32,85.94,85.98,85.41,620500\r\n\"MO\",70.12,\"6/11/2007\",\"11:01am\",-0.18,70.25,70.30,69.76,2246305\r\n\"MRK\",50.505,\"6/11/2007\",\"11:01am\",+0.365,50.30,50.64,50.04,3232600\r\n\"MSFT\",30.02,\"6/11/2007\",\"11:06am\",-0.03,30.05,30.25,29.93,15371602\r\n\"PFE\",26.34,\"6/11/2007\",\"11:01am\",-0.18,26.50,26.53,26.33,5134096\r\n\"PG\",62.98,\"6/11/2007\",\"11:01am\",-0.09,62.80,63.12,62.75,1854046\r\n\"T\",40.02,\"6/11/2007\",\"11:01am\",-0.24,40.20,40.25,39.89,3531300\r\n\"UTX\",69.91,\"6/11/2007\",\"11:00am\",-0.32,69.85,70.20,69.51,658800\r\n\"VZ\",43.16,\"6/11/2007\",\"11:01am\",+0.09,42.95,43.20,42.89,2288410\r\n\"WMT\",49.59,\"6/11/2007\",\"11:01am\",-0.49,49.90,50.00,49.56,2659033\r\n\"XOM\",82.98,\"6/11/2007\",\"11:01am\",+0.30,82.68,83.11,82.35,3296100\r\n\"AA\",39.60,\"6/11/2007\",\"11:06am\",-0.06,39.67,40.18,39.43,1189680\r\n\"AIG\",71.49,\"6/11/2007\",\"11:06am\",-0.04,71.29,71.60,71.15,1226342\r\n\"AXP\",63.11,\"6/11/2007\",\"11:06am\",+0.07,62.79,63.11,62.42,1576120\r\n\"BA\",98.20,\"6/11/2007\",\"11:06am\",+0.01,98.25,98.79,97.74,743800\r\n\"C\",53.17,\"6/11/2007\",\"11:06am\",-0.16,53.20,53.25,52.81,2445294\r\n\"CAT\",78.70,\"6/11/2007\",\"11:06am\",+0.18,78.32,78.99,78.06,950252\r\n\"DD\",50.73,\"6/11/2007\",\"11:06am\",-0.40,51.13,51.21,50.62,1165000\r\n\"DIS\",34.24,\"6/11/2007\",\"11:06am\",+0.04,34.28,34.44,34.12,1636250\r\n\"GE\",37.3314,\"6/11/2007\",\"11:06am\",+0.0114,37.07,37.41,37.05,7140328\r\n\"GM\",31.19,\"6/11/2007\",\"11:06am\",+0.19,31.00,31.62,30.90,5065479\r\n\"HD\",37.72,\"6/11/2007\",\"11:06am\",-0.23,37.78,37.83,37.62,2746169\r\n\"HON\",57.04,\"6/11/2007\",\"11:06am\",-0.34,57.25,57.40,57.00,851383\r\n\"HPQ\",46.09,\"6/11/2007\",\"11:06am\",+0.39,45.80,46.29,45.46,3113456\r\n\"IBM\",103.35,\"6/11/2007\",\"11:05am\",+0.28,102.87,103.63,102.50,1528300\r\n\"INTC\",21.95,\"6/11/2007\",\"11:11am\",+0.12,21.70,21.98,21.69,13630344\r\n\"JNJ\",62.45,\"6/11/2007\",\"11:06am\",+0.32,62.89,62.89,62.15,2409122\r\n\"JPM\",50.38,\"6/11/2007\",\"11:06am\",-0.03,50.41,50.55,50.05,2209200\r\n\"KO\",51.44,\"6/11/2007\",\"11:05am\",-0.23,51.67,51.82,51.32,5034642\r\n\"MCD\",51.61,\"6/11/2007\",\"11:06am\",+0.20,51.47,51.61,50.98,1307114\r\n\"MMM\",85.66,\"6/11/2007\",\"11:06am\",-0.28,85.94,85.98,85.41,642300\r\n\"MO\",70.15,\"6/11/2007\",\"11:06am\",-0.15,70.25,70.30,69.76,2298805\r\n\"MRK\",50.52,\"6/11/2007\",\"11:06am\",+0.38,50.30,50.64,50.04,3272300\r\n\"MSFT\",30.06,\"6/11/2007\",\"11:11am\",+0.01,30.05,30.25,29.93,15638561\r\n\"PFE\",26.36,\"6/11/2007\",\"11:06am\",-0.16,26.50,26.53,26.33,5407496\r\n\"PG\",62.99,\"6/11/2007\",\"11:06am\",-0.08,62.80,63.12,62.75,1906246\r\n\"T\",40.01,\"6/11/2007\",\"11:06am\",-0.25,40.20,40.25,39.89,3728000\r\n\"UTX\",69.92,\"6/11/2007\",\"11:06am\",-0.31,69.85,70.20,69.51,669300\r\n\"VZ\",43.17,\"6/11/2007\",\"11:06am\",+0.10,42.95,43.20,42.89,2322610\r\n\"WMT\",49.55,\"6/11/2007\",\"11:06am\",-0.53,49.90,50.00,49.55,2733633\r\n\"XOM\",83.08,\"6/11/2007\",\"11:06am\",+0.40,82.68,83.11,82.35,3448500\r\n\"AA\",39.52,\"6/11/2007\",\"11:11am\",-0.14,39.67,40.18,39.43,1258280\r\n\"AIG\",71.50,\"6/11/2007\",\"11:11am\",-0.03,71.29,71.60,71.15,1250442\r\n\"AXP\",63.05,\"6/11/2007\",\"11:10am\",+0.01,62.79,63.12,62.42,1588220\r\n\"BA\",98.16,\"6/11/2007\",\"11:11am\",-0.03,98.25,98.79,97.74,759800\r\n\"C\",53.18,\"6/11/2007\",\"11:11am\",-0.15,53.20,53.25,52.81,2614994\r\n\"CAT\",78.82,\"6/11/2007\",\"11:11am\",+0.30,78.32,78.99,78.06,992052\r\n\"DD\",50.72,\"6/11/2007\",\"11:11am\",-0.41,51.13,51.21,50.59,1241700\r\n\"DIS\",34.22,\"6/11/2007\",\"11:11am\",+0.02,34.28,34.44,34.12,1691750\r\n\"GE\",37.36,\"6/11/2007\",\"11:11am\",+0.04,37.07,37.41,37.05,7342028\r\n\"GM\",31.14,\"6/11/2007\",\"11:10am\",+0.14,31.00,31.62,30.90,5195679\r\n\"HD\",37.74,\"6/11/2007\",\"11:11am\",-0.21,37.78,37.83,37.62,2859969\r\n\"HON\",57.08,\"6/11/2007\",\"11:10am\",-0.30,57.25,57.40,57.00,888783\r\n\"HPQ\",46.08,\"6/11/2007\",\"11:11am\",+0.38,45.80,46.29,45.46,3164856\r\n\"IBM\",103.34,\"6/11/2007\",\"11:11am\",+0.27,102.87,103.63,102.50,1554100\r\n\"INTC\",21.92,\"6/11/2007\",\"11:15am\",+0.09,21.70,21.98,21.69,14546699\r\n\"JNJ\",62.49,\"6/11/2007\",\"11:11am\",+0.36,62.89,62.89,62.15,2467522\r\n\"JPM\",50.39,\"6/11/2007\",\"11:11am\",-0.02,50.41,50.55,50.05,2298000\r\n\"KO\",51.46,\"6/11/2007\",\"11:11am\",-0.21,51.67,51.82,51.32,5073542\r\n\"MCD\",51.60,\"6/11/2007\",\"11:11am\",+0.19,51.47,51.62,50.98,1438514\r\n\"MMM\",85.70,\"6/11/2007\",\"11:11am\",-0.24,85.94,85.98,85.41,673300\r\n\"MO\",70.2306,\"6/11/2007\",\"11:11am\",-0.0694,70.25,70.30,69.76,2382105\r\n\"MRK\",50.46,\"6/11/2007\",\"11:11am\",+0.32,50.30,50.64,50.04,3337000\r\n\"MSFT\",30.04,\"6/11/2007\",\"11:16am\",-0.01,30.05,30.25,29.93,15908978\r\n\"PFE\",26.32,\"6/11/2007\",\"11:11am\",-0.20,26.50,26.53,26.31,5832646\r\n\"PG\",62.99,\"6/11/2007\",\"11:11am\",-0.08,62.80,63.12,62.75,1960846\r\n\"T\",40.001,\"6/11/2007\",\"11:11am\",-0.259,40.20,40.25,39.89,3794300\r\n\"UTX\",69.85,\"6/11/2007\",\"11:10am\",-0.38,69.85,70.20,69.51,678300\r\n\"VZ\",43.16,\"6/11/2007\",\"11:10am\",+0.09,42.95,43.20,42.89,2358345\r\n\"WMT\",49.64,\"6/11/2007\",\"11:11am\",-0.44,49.90,50.00,49.55,2915433\r\n\"XOM\",83.09,\"6/11/2007\",\"11:11am\",+0.41,82.68,83.11,82.35,3609300\r\n\"AA\",39.48,\"6/11/2007\",\"11:16am\",-0.18,39.67,40.18,39.43,1290380\r\n\"AIG\",71.42,\"6/11/2007\",\"11:16am\",-0.11,71.29,71.60,71.15,1276442\r\n\"AXP\",63.01,\"6/11/2007\",\"11:16am\",-0.03,62.79,63.12,62.42,1616220\r\n\"BA\",98.01,\"6/11/2007\",\"11:16am\",-0.18,98.25,98.79,97.74,777300\r\n\"C\",53.23,\"6/11/2007\",\"11:16am\",-0.10,53.20,53.25,52.81,2793394\r\n\"CAT\",78.65,\"6/11/2007\",\"11:16am\",+0.13,78.32,78.99,78.06,1046952\r\n\"DD\",50.70,\"6/11/2007\",\"11:16am\",-0.43,51.13,51.21,50.59,1337600\r\n\"DIS\",34.20,\"6/11/2007\",\"11:15am\",0.00,34.28,34.44,34.12,1805250\r\n\"GE\",37.35,\"6/11/2007\",\"11:16am\",+0.03,37.07,37.41,37.05,7601178\r\n\"GM\",31.10,\"6/11/2007\",\"11:15am\",+0.10,31.00,31.62,30.90,5260279\r\n\"HD\",37.73,\"6/11/2007\",\"11:16am\",-0.22,37.78,37.83,37.62,2927469\r\n\"HON\",56.965,\"6/11/2007\",\"11:16am\",-0.415,57.25,57.40,56.93,940183\r\n\"HPQ\",46.04,\"6/11/2007\",\"11:16am\",+0.34,45.80,46.29,45.46,3255956\r\n\"IBM\",103.24,\"6/11/2007\",\"11:16am\",+0.17,102.87,103.63,102.50,1595900\r\n\"INTC\",21.92,\"6/11/2007\",\"11:21am\",+0.09,21.70,21.98,21.69,14851182\r\n\"JNJ\",62.55,\"6/11/2007\",\"11:16am\",+0.42,62.89,62.89,62.15,2628222\r\n\"JPM\",50.32,\"6/11/2007\",\"11:16am\",-0.09,50.41,50.55,50.05,2372200\r\n\"KO\",51.46,\"6/11/2007\",\"11:16am\",-0.21,51.67,51.82,51.32,5091242\r\n\"MCD\",51.49,\"6/11/2007\",\"11:15am\",+0.08,51.47,51.62,50.98,1719114\r\n\"MMM\",85.57,\"6/11/2007\",\"11:15am\",-0.37,85.94,85.98,85.41,691600\r\n\"MO\",70.17,\"6/11/2007\",\"11:16am\",-0.13,70.25,70.30,69.76,2466057\r\n\"MRK\",50.52,\"6/11/2007\",\"11:16am\",+0.38,50.30,50.64,50.04,3543600\r\n\"MSFT\",30.025,\"6/11/2007\",\"11:20am\",-0.025,30.05,30.25,29.93,16110835\r\n\"PFE\",26.33,\"6/11/2007\",\"11:16am\",-0.19,26.50,26.53,26.31,6034446\r\n\"PG\",62.96,\"6/11/2007\",\"11:16am\",-0.11,62.80,63.12,62.75,2028646\r\n\"T\",39.97,\"6/11/2007\",\"11:16am\",-0.29,40.20,40.25,39.89,3937400\r\n\"UTX\",69.81,\"6/11/2007\",\"11:15am\",-0.42,69.85,70.20,69.51,697600\r\n\"VZ\",43.15,\"6/11/2007\",\"11:16am\",+0.08,42.95,43.20,42.89,2414845\r\n\"WMT\",49.63,\"6/11/2007\",\"11:16am\",-0.45,49.90,50.00,49.55,3029233\r\n\"XOM\",82.96,\"6/11/2007\",\"11:16am\",+0.28,82.68,83.11,82.35,3750000\r\n\"AA\",39.46,\"6/11/2007\",\"11:20am\",-0.20,39.67,40.18,39.43,1318080\r\n\"AIG\",71.37,\"6/11/2007\",\"11:20am\",-0.16,71.29,71.60,71.15,1313142\r\n\"AXP\",63.09,\"6/11/2007\",\"11:21am\",+0.05,62.79,63.12,62.42,1639320\r\n\"BA\",98.06,\"6/11/2007\",\"11:21am\",-0.13,98.25,98.79,97.74,796300\r\n\"C\",53.24,\"6/11/2007\",\"11:21am\",-0.09,53.20,53.26,52.81,2937494\r\n\"CAT\",78.72,\"6/11/2007\",\"11:21am\",+0.20,78.32,78.99,78.06,1095252\r\n\"DD\",50.67,\"6/11/2007\",\"11:21am\",-0.46,51.13,51.21,50.59,1421500\r\n\"DIS\",34.21,\"6/11/2007\",\"11:21am\",+0.01,34.28,34.44,34.12,1882850\r\n\"GE\",37.41,\"6/11/2007\",\"11:21am\",+0.09,37.07,37.43,37.05,8394078\r\n\"GM\",31.06,\"6/11/2007\",\"11:21am\",+0.06,31.00,31.62,30.90,5321979\r\n\"HD\",37.72,\"6/11/2007\",\"11:21am\",-0.23,37.78,37.83,37.62,2989469\r\n\"HON\",56.98,\"6/11/2007\",\"11:21am\",-0.40,57.25,57.40,56.91,1018183\r\n\"HPQ\",46.01,\"6/11/2007\",\"11:21am\",+0.31,45.80,46.29,45.46,3347656\r\n\"IBM\",103.27,\"6/11/2007\",\"11:21am\",+0.20,102.87,103.63,102.50,1635600\r\n\"INTC\",21.93,\"6/11/2007\",\"11:26am\",+0.10,21.70,21.98,21.69,15018868\r\n\"JNJ\",62.58,\"6/11/2007\",\"11:21am\",+0.45,62.89,62.89,62.15,2706422\r\n\"JPM\",50.30,\"6/11/2007\",\"11:21am\",-0.11,50.41,50.55,50.05,2419700\r\n\"KO\",51.45,\"6/11/2007\",\"11:20am\",-0.22,51.67,51.82,51.32,5160942\r\n\"MCD\",51.46,\"6/11/2007\",\"11:20am\",+0.05,51.47,51.62,50.98,1812914\r\n\"MMM\",85.47,\"6/11/2007\",\"11:21am\",-0.47,85.94,85.98,85.41,710100\r\n\"MO\",70.20,\"6/11/2007\",\"11:21am\",-0.10,70.25,70.30,69.76,2517457\r\n\"MRK\",50.48,\"6/11/2007\",\"11:21am\",+0.34,50.30,50.64,50.04,3625200\r\n\"MSFT\",30.07,\"6/11/2007\",\"11:26am\",+0.02,30.05,30.25,29.93,16343142\r\n\"PFE\",26.349,\"6/11/2007\",\"11:21am\",-0.171,26.50,26.53,26.31,6275846\r\n\"PG\",62.98,\"6/11/2007\",\"11:21am\",-0.09,62.80,63.12,62.75,2075846\r\n\"T\",39.99,\"6/11/2007\",\"11:21am\",-0.27,40.20,40.25,39.89,4085900\r\n\"UTX\",69.76,\"6/11/2007\",\"11:21am\",-0.47,69.85,70.20,69.51,714000\r\n\"VZ\",43.16,\"6/11/2007\",\"11:20am\",+0.09,42.95,43.20,42.89,2522445\r\n\"WMT\",49.63,\"6/11/2007\",\"11:21am\",-0.45,49.90,50.00,49.55,3096833\r\n\"XOM\",82.91,\"6/11/2007\",\"11:21am\",+0.23,82.68,83.11,82.35,3880100\r\n\"AA\",39.50,\"6/11/2007\",\"11:26am\",-0.16,39.67,40.18,39.43,1352980\r\n\"AIG\",71.37,\"6/11/2007\",\"11:26am\",-0.16,71.29,71.60,71.15,1345042\r\n\"AXP\",63.03,\"6/11/2007\",\"11:26am\",-0.01,62.79,63.12,62.42,1654030\r\n\"BA\",98.06,\"6/11/2007\",\"11:26am\",-0.13,98.25,98.79,97.74,817500\r\n\"C\",53.24,\"6/11/2007\",\"11:26am\",-0.09,53.20,53.26,52.81,3004394\r\n\"CAT\",78.75,\"6/11/2007\",\"11:26am\",+0.23,78.32,78.99,78.06,1112652\r\n\"DD\",50.80,\"6/11/2007\",\"11:26am\",-0.33,51.13,51.21,50.59,1443900\r\n\"DIS\",34.22,\"6/11/2007\",\"11:25am\",+0.02,34.28,34.44,34.12,1977950\r\n\"GE\",37.401,\"6/11/2007\",\"11:26am\",+0.081,37.07,37.43,37.05,8549378\r\n\"GM\",31.18,\"6/11/2007\",\"11:26am\",+0.18,31.00,31.62,30.90,5486579\r\n\"HD\",37.74,\"6/11/2007\",\"11:26am\",-0.21,37.78,37.83,37.62,3055769\r\n\"HON\",57.03,\"6/11/2007\",\"11:26am\",-0.35,57.25,57.40,56.91,1098842\r\n\"HPQ\",46.00,\"6/11/2007\",\"11:26am\",+0.30,45.80,46.29,45.46,3418156\r\n\"IBM\",103.33,\"6/11/2007\",\"11:26am\",+0.26,102.87,103.63,102.50,1657200\r\n\"INTC\",21.94,\"6/11/2007\",\"11:31am\",+0.11,21.70,21.98,21.69,15378550\r\n\"JNJ\",62.59,\"6/11/2007\",\"11:26am\",+0.46,62.89,62.89,62.15,2772722\r\n\"JPM\",50.38,\"6/11/2007\",\"11:26am\",-0.03,50.41,50.55,50.05,2481600\r\n\"KO\",51.45,\"6/11/2007\",\"11:26am\",-0.22,51.67,51.82,51.32,5180942\r\n\"MCD\",51.49,\"6/11/2007\",\"11:26am\",+0.08,51.47,51.62,50.98,1867514\r\n\"MMM\",85.45,\"6/11/2007\",\"11:25am\",-0.49,85.94,85.98,85.39,738400\r\n\"MO\",70.21,\"6/11/2007\",\"11:26am\",-0.09,70.25,70.30,69.76,2582357\r\n\"MRK\",50.55,\"6/11/2007\",\"11:26am\",+0.41,50.30,50.64,50.04,3773900\r\n\"MSFT\",30.05,\"6/11/2007\",\"11:31am\",0.00,30.05,30.25,29.93,16793888\r\n\"PFE\",26.34,\"6/11/2007\",\"11:26am\",-0.18,26.50,26.53,26.31,6551746\r\n\"PG\",62.99,\"6/11/2007\",\"11:26am\",-0.08,62.80,63.12,62.75,2114846\r\n\"T\",40.06,\"6/11/2007\",\"11:26am\",-0.20,40.20,40.25,39.89,4212600\r\n\"UTX\",69.73,\"6/11/2007\",\"11:25am\",-0.50,69.85,70.20,69.51,725600\r\n\"VZ\",43.16,\"6/11/2007\",\"11:26am\",+0.09,42.95,43.20,42.89,2595445\r\n\"WMT\",49.64,\"6/11/2007\",\"11:25am\",-0.44,49.90,50.00,49.55,3167033\r\n\"XOM\",82.89,\"6/11/2007\",\"11:26am\",+0.21,82.68,83.11,82.35,4029000\r\n\"AA\",39.47,\"6/11/2007\",\"11:31am\",-0.19,39.67,40.18,39.43,1409980\r\n\"AIG\",71.48,\"6/11/2007\",\"11:31am\",-0.05,71.29,71.60,71.15,1424042\r\n\"AXP\",63.07,\"6/11/2007\",\"11:30am\",+0.03,62.79,63.14,62.42,1695430\r\n\"BA\",98.00,\"6/11/2007\",\"11:31am\",-0.19,98.25,98.79,97.74,865400\r\n\"C\",53.31,\"6/11/2007\",\"11:31am\",-0.02,53.20,53.31,52.81,3119094\r\n\"CAT\",78.72,\"6/11/2007\",\"11:31am\",+0.20,78.32,78.99,78.06,1140252\r\n\"DD\",50.75,\"6/11/2007\",\"11:31am\",-0.38,51.13,51.21,50.59,1474900\r\n\"DIS\",34.20,\"6/11/2007\",\"11:30am\",0.00,34.28,34.44,34.12,2013250\r\n\"GE\",37.41,\"6/11/2007\",\"11:31am\",+0.09,37.07,37.44,37.05,8715001\r\n\"GM\",31.23,\"6/11/2007\",\"11:31am\",+0.23,31.00,31.62,30.90,5548179\r\n\"HD\",37.72,\"6/11/2007\",\"11:30am\",-0.23,37.78,37.83,37.62,3183869\r\n\"HON\",57.01,\"6/11/2007\",\"11:31am\",-0.37,57.25,57.40,56.91,1290742\r\n\"HPQ\",45.99,\"6/11/2007\",\"11:31am\",+0.29,45.80,46.29,45.46,3514456\r\n\"IBM\",103.31,\"6/11/2007\",\"11:31am\",+0.24,102.87,103.63,102.50,1841300\r\n\"INTC\",21.94,\"6/11/2007\",\"11:36am\",+0.11,21.70,21.98,21.69,15795668\r\n\"JNJ\",62.59,\"6/11/2007\",\"11:31am\",+0.46,62.89,62.89,62.15,2811022\r\n\"JPM\",50.41,\"6/11/2007\",\"11:30am\",0.00,50.41,50.55,50.05,2553800\r\n\"KO\",51.45,\"6/11/2007\",\"11:30am\",-0.22,51.67,51.82,51.32,5207142\r\n\"MCD\",51.47,\"6/11/2007\",\"11:30am\",+0.06,51.47,51.62,50.98,1913314\r\n\"MMM\",85.41,\"6/11/2007\",\"11:31am\",-0.53,85.94,85.98,85.39,805100\r\n\"MO\",70.36,\"6/11/2007\",\"11:31am\",+0.06,70.25,70.36,69.76,2672857\r\n\"MRK\",50.55,\"6/11/2007\",\"11:31am\",+0.41,50.30,50.64,50.04,3841500\r\n\"MSFT\",30.09,\"6/11/2007\",\"11:36am\",+0.04,30.05,30.25,29.93,17107810\r\n\"PFE\",26.35,\"6/11/2007\",\"11:31am\",-0.17,26.50,26.53,26.31,6720346\r\n\"PG\",62.98,\"6/11/2007\",\"11:31am\",-0.09,62.80,63.12,62.75,2155746\r\n\"T\",40.07,\"6/11/2007\",\"11:31am\",-0.19,40.20,40.25,39.89,4436800\r\n\"UTX\",69.74,\"6/11/2007\",\"11:31am\",-0.49,69.85,70.20,69.51,757000\r\n\"VZ\",43.18,\"6/11/2007\",\"11:31am\",+0.11,42.95,43.22,42.89,2918545\r\n\"WMT\",49.66,\"6/11/2007\",\"11:31am\",-0.42,49.90,50.00,49.55,3258333\r\n\"XOM\",82.92,\"6/11/2007\",\"11:31am\",+0.24,82.68,83.11,82.35,4163200\r\n\"AA\",39.50,\"6/11/2007\",\"11:36am\",-0.16,39.67,40.18,39.43,1444680\r\n\"AIG\",71.48,\"6/11/2007\",\"11:35am\",-0.05,71.29,71.60,71.15,1468642\r\n\"AXP\",63.0703,\"6/11/2007\",\"11:35am\",+0.0303,62.79,63.14,62.42,1717830\r\n\"BA\",98.0028,\"6/11/2007\",\"11:36am\",-0.1872,98.25,98.79,97.74,951000\r\n\"C\",53.34,\"6/11/2007\",\"11:36am\",+0.01,53.20,53.37,52.81,3207894\r\n\"CAT\",78.66,\"6/11/2007\",\"11:36am\",+0.14,78.32,78.99,78.06,1150952\r\n\"DD\",50.73,\"6/11/2007\",\"11:36am\",-0.40,51.13,51.21,50.59,1500000\r\n\"DIS\",34.19,\"6/11/2007\",\"11:36am\",-0.01,34.28,34.44,34.12,2167150\r\n\"GE\",37.38,\"6/11/2007\",\"11:36am\",+0.06,37.07,37.44,37.05,9041801\r\n\"GM\",31.25,\"6/11/2007\",\"11:35am\",+0.25,31.00,31.62,30.90,5608979\r\n\"HD\",37.75,\"6/11/2007\",\"11:36am\",-0.20,37.78,37.83,37.62,3269369\r\n\"HON\",56.98,\"6/11/2007\",\"11:36am\",-0.40,57.25,57.40,56.91,1330742\r\n\"HPQ\",45.94,\"6/11/2007\",\"11:36am\",+0.24,45.80,46.29,45.46,3621556\r\n\"IBM\",103.28,\"6/11/2007\",\"11:36am\",+0.21,102.87,103.63,102.50,1859400\r\n\"INTC\",21.97,\"6/11/2007\",\"11:41am\",+0.14,21.70,21.98,21.69,16127869\r\n\"JNJ\",62.56,\"6/11/2007\",\"11:35am\",+0.43,62.89,62.89,62.15,2852322\r\n\"JPM\",50.43,\"6/11/2007\",\"11:36am\",+0.02,50.41,50.55,50.05,2656300\r\n\"KO\",51.40,\"6/11/2007\",\"11:36am\",-0.27,51.67,51.82,51.32,5232342\r\n\"MCD\",51.47,\"6/11/2007\",\"11:35am\",+0.06,51.47,51.62,50.98,1953314\r\n\"MMM\",85.43,\"6/11/2007\",\"11:35am\",-0.51,85.94,85.98,85.39,828900\r\n\"MO\",70.39,\"6/11/2007\",\"11:35am\",+0.09,70.25,70.48,69.76,2823257\r\n\"MRK\",50.551,\"6/11/2007\",\"11:36am\",+0.411,50.30,50.64,50.04,3911800\r\n\"MSFT\",30.11,\"6/11/2007\",\"11:41am\",+0.06,30.05,30.25,29.93,17353272\r\n\"PFE\",26.38,\"6/11/2007\",\"11:36am\",-0.14,26.50,26.53,26.31,6992796\r\n\"PG\",62.93,\"6/11/2007\",\"11:35am\",-0.14,62.80,63.12,62.75,2196846\r\n\"T\",40.03,\"6/11/2007\",\"11:35am\",-0.23,40.20,40.25,39.89,4610000\r\n\"UTX\",69.77,\"6/11/2007\",\"11:35am\",-0.46,69.85,70.20,69.51,788900\r\n\"VZ\",43.18,\"6/11/2007\",\"11:35am\",+0.11,42.95,43.22,42.89,2997845\r\n\"WMT\",49.74,\"6/11/2007\",\"11:36am\",-0.34,49.90,50.00,49.55,3415433\r\n\"XOM\",83.04,\"6/11/2007\",\"11:36am\",+0.36,82.68,83.11,82.35,4297200\r\n\"AA\",39.53,\"6/11/2007\",\"11:41am\",-0.13,39.67,40.18,39.43,1481180\r\n\"AIG\",71.53,\"6/11/2007\",\"11:41am\",0.00,71.29,71.60,71.15,1498042\r\n\"AXP\",63.07,\"6/11/2007\",\"11:41am\",+0.03,62.79,63.14,62.42,1760630\r\n\"BA\",98.06,\"6/11/2007\",\"11:41am\",-0.13,98.25,98.79,97.74,968400\r\n\"C\",53.41,\"6/11/2007\",\"11:41am\",+0.08,53.20,53.41,52.81,3275394\r\n\"CAT\",78.72,\"6/11/2007\",\"11:41am\",+0.20,78.32,78.99,78.06,1165852\r\n\"DD\",50.77,\"6/11/2007\",\"11:41am\",-0.36,51.13,51.21,50.59,1523000\r\n\"DIS\",34.19,\"6/11/2007\",\"11:41am\",-0.01,34.28,34.44,34.12,2223450\r\n\"GE\",37.42,\"6/11/2007\",\"11:41am\",+0.10,37.07,37.44,37.05,9239801\r\n\"GM\",31.30,\"6/11/2007\",\"11:41am\",+0.30,31.00,31.62,30.90,5699079\r\n\"HD\",37.75,\"6/11/2007\",\"11:41am\",-0.20,37.78,37.83,37.62,3307869\r\n\"HON\",56.99,\"6/11/2007\",\"11:41am\",-0.39,57.25,57.40,56.91,1376642\r\n\"HPQ\",45.96,\"6/11/2007\",\"11:41am\",+0.26,45.80,46.29,45.46,3706756\r\n\"IBM\",103.44,\"6/11/2007\",\"11:41am\",+0.37,102.87,103.63,102.50,1880700\r\n\"INTC\",21.97,\"6/11/2007\",\"11:45am\",+0.14,21.70,21.98,21.69,16406027\r\n\"JNJ\",62.64,\"6/11/2007\",\"11:41am\",+0.51,62.89,62.89,62.15,2899022\r\n\"JPM\",50.50,\"6/11/2007\",\"11:41am\",+0.09,50.41,50.55,50.05,2715800\r\n\"KO\",51.45,\"6/11/2007\",\"11:41am\",-0.22,51.67,51.82,51.32,5260042\r\n\"MCD\",51.49,\"6/11/2007\",\"11:40am\",+0.08,51.47,51.62,50.98,2029314\r\n\"MMM\",85.52,\"6/11/2007\",\"11:41am\",-0.42,85.94,85.98,85.39,843500\r\n\"MO\",70.47,\"6/11/2007\",\"11:41am\",+0.17,70.25,70.50,69.76,3705557\r\n\"MRK\",50.65,\"6/11/2007\",\"11:41am\",+0.51,50.30,50.65,50.04,4091400\r\n\"MSFT\",30.135,\"6/11/2007\",\"11:46am\",+0.085,30.05,30.25,29.93,17741848\r\n\"PFE\",26.42,\"6/11/2007\",\"11:41am\",-0.10,26.50,26.53,26.31,7347396\r\n\"PG\",63.00,\"6/11/2007\",\"11:41am\",-0.07,62.80,63.12,62.75,2256146\r\n\"T\",40.09,\"6/11/2007\",\"11:41am\",-0.17,40.20,40.25,39.89,4805400\r\n\"UTX\",69.82,\"6/11/2007\",\"11:40am\",-0.41,69.85,70.20,69.51,796500\r\n\"VZ\",43.23,\"6/11/2007\",\"11:40am\",+0.16,42.95,43.23,42.89,3077145\r\n\"WMT\",49.81,\"6/11/2007\",\"11:41am\",-0.27,49.90,50.00,49.55,3532333\r\n\"XOM\",83.18,\"6/11/2007\",\"11:41am\",+0.50,82.68,83.18,82.35,4455700\r\n\"AA\",39.54,\"6/11/2007\",\"11:46am\",-0.12,39.67,40.18,39.43,1499880\r\n\"AIG\",71.59,\"6/11/2007\",\"11:45am\",+0.06,71.29,71.61,71.15,1522942\r\n\"AXP\",63.09,\"6/11/2007\",\"11:46am\",+0.05,62.79,63.14,62.42,1799530\r\n\"BA\",98.01,\"6/11/2007\",\"11:46am\",-0.18,98.25,98.79,97.74,1016800\r\n\"C\",53.48,\"6/11/2007\",\"11:46am\",+0.15,53.20,53.52,52.81,3465694\r\n\"CAT\",78.7346,\"6/11/2007\",\"11:45am\",+0.2146,78.32,78.99,78.06,1189152\r\n\"DD\",50.72,\"6/11/2007\",\"11:46am\",-0.41,51.13,51.21,50.59,1542800\r\n\"DIS\",34.202,\"6/11/2007\",\"11:46am\",+0.002,34.28,34.44,34.12,2263250\r\n\"GE\",37.43,\"6/11/2007\",\"11:46am\",+0.11,37.07,37.44,37.05,9402901\r\n\"GM\",31.30,\"6/11/2007\",\"11:46am\",+0.30,31.00,31.62,30.90,5791779\r\n\"HD\",37.74,\"6/11/2007\",\"11:46am\",-0.21,37.78,37.83,37.62,3438769\r\n\"HON\",57.01,\"6/11/2007\",\"11:45am\",-0.37,57.25,57.40,56.91,1411942\r\n\"HPQ\",45.99,\"6/11/2007\",\"11:46am\",+0.29,45.80,46.29,45.46,3780456\r\n\"IBM\",103.53,\"6/11/2007\",\"11:46am\",+0.46,102.87,103.63,102.50,1931500\r\n\"INTC\",21.98,\"6/11/2007\",\"11:51am\",+0.15,21.70,21.98,21.69,16615722\r\n\"JNJ\",62.64,\"6/11/2007\",\"11:45am\",+0.51,62.89,62.89,62.15,2931522\r\n\"JPM\",50.52,\"6/11/2007\",\"11:46am\",+0.11,50.41,50.55,50.05,2820200\r\n\"KO\",51.48,\"6/11/2007\",\"11:45am\",-0.19,51.67,51.82,51.32,5289842\r\n\"MCD\",51.54,\"6/11/2007\",\"11:45am\",+0.13,51.47,51.62,50.98,2063014\r\n\"MMM\",85.53,\"6/11/2007\",\"11:46am\",-0.41,85.94,85.98,85.39,860900\r\n\"MO\",70.30,\"6/11/2007\",\"11:46am\",0.00,70.25,70.50,69.76,3796557\r\n\"MRK\",50.73,\"6/11/2007\",\"11:46am\",+0.59,50.30,50.74,50.04,4201600\r\n\"MSFT\",30.1603,\"6/11/2007\",\"11:50am\",+0.1103,30.05,30.25,29.93,18232994\r\n\"PFE\",26.43,\"6/11/2007\",\"11:46am\",-0.09,26.50,26.53,26.31,7612046\r\n\"PG\",62.95,\"6/11/2007\",\"11:45am\",-0.12,62.80,63.12,62.75,2315446\r\n\"T\",40.14,\"6/11/2007\",\"11:46am\",-0.12,40.20,40.25,39.89,4928500\r\n\"UTX\",69.83,\"6/11/2007\",\"11:46am\",-0.40,69.85,70.20,69.51,808300\r\n\"VZ\",43.25,\"6/11/2007\",\"11:45am\",+0.18,42.95,43.26,42.89,3194645\r\n\"WMT\",49.81,\"6/11/2007\",\"11:46am\",-0.27,49.90,50.00,49.55,3691733\r\n\"XOM\",83.20,\"6/11/2007\",\"11:46am\",+0.52,82.68,83.25,82.35,4596200\r\n\"AA\",39.59,\"6/11/2007\",\"11:51am\",-0.07,39.67,40.18,39.43,1549580\r\n\"AIG\",71.62,\"6/11/2007\",\"11:51am\",+0.09,71.29,71.68,71.15,1587442\r\n\"AXP\",63.09,\"6/11/2007\",\"11:51am\",+0.05,62.79,63.19,62.42,1825430\r\n\"BA\",97.98,\"6/11/2007\",\"11:50am\",-0.21,98.25,98.79,97.74,1036600\r\n\"C\",53.52,\"6/11/2007\",\"11:50am\",+0.19,53.20,53.57,52.81,3665594\r\n\"CAT\",78.78,\"6/11/2007\",\"11:51am\",+0.26,78.32,78.99,78.06,1234052\r\n\"DD\",50.74,\"6/11/2007\",\"11:51am\",-0.39,51.13,51.21,50.59,1579200\r\n\"DIS\",34.23,\"6/11/2007\",\"11:51am\",+0.03,34.28,34.44,34.12,2335050\r\n\"GE\",37.469,\"6/11/2007\",\"11:51am\",+0.149,37.07,37.48,37.05,9637101\r\n\"GM\",31.31,\"6/11/2007\",\"11:50am\",+0.31,31.00,31.62,30.90,5900779\r\n\"HD\",37.77,\"6/11/2007\",\"11:51am\",-0.18,37.78,37.83,37.62,3574469\r\n\"HON\",56.99,\"6/11/2007\",\"11:50am\",-0.39,57.25,57.40,56.91,1469142\r\n\"HPQ\",46.0616,\"6/11/2007\",\"11:50am\",+0.3616,45.80,46.29,45.46,3886556\r\n\"IBM\",103.70,\"6/11/2007\",\"11:51am\",+0.63,102.87,103.71,102.50,1993700\r\n\"INTC\",22.01,\"6/11/2007\",\"11:56am\",+0.18,21.70,22.01,21.69,17848980\r\n\"JNJ\",62.719,\"6/11/2007\",\"11:51am\",+0.589,62.89,62.89,62.15,3194722\r\n\"JPM\",50.53,\"6/11/2007\",\"11:51am\",+0.12,50.41,50.55,50.05,2963700\r\n\"KO\",51.50,\"6/11/2007\",\"11:50am\",-0.17,51.67,51.82,51.32,5313142\r\n\"MCD\",51.52,\"6/11/2007\",\"11:51am\",+0.11,51.47,51.62,50.98,2105814\r\n\"MMM\",85.53,\"6/11/2007\",\"11:50am\",-0.41,85.94,85.98,85.39,879300\r\n\"MO\",70.43,\"6/11/2007\",\"11:50am\",+0.13,70.25,70.50,69.76,3873057\r\n\"MRK\",50.78,\"6/11/2007\",\"11:51am\",+0.64,50.30,50.87,50.04,4316300\r\n\"MSFT\",30.18,\"6/11/2007\",\"11:56am\",+0.13,30.05,30.25,29.93,18458900\r\n\"PFE\",26.50,\"6/11/2007\",\"11:51am\",-0.02,26.50,26.53,26.31,7882446\r\n\"PG\",63.04,\"6/11/2007\",\"11:50am\",-0.03,62.80,63.12,62.75,2384246\r\n\"T\",40.15,\"6/11/2007\",\"11:51am\",-0.11,40.20,40.25,39.89,5143600\r\n\"UTX\",69.85,\"6/11/2007\",\"11:50am\",-0.38,69.85,70.20,69.51,824600\r\n\"VZ\",43.29,\"6/11/2007\",\"11:51am\",+0.22,42.95,43.30,42.89,3253445\r\n\"WMT\",49.84,\"6/11/2007\",\"11:51am\",-0.24,49.90,50.00,49.55,3812233\r\n\"XOM\",83.26,\"6/11/2007\",\"11:51am\",+0.58,82.68,83.35,82.35,4722700\r\n\"AA\",39.62,\"6/11/2007\",\"11:56am\",-0.04,39.67,40.18,39.43,1573080\r\n\"AIG\",71.6254,\"6/11/2007\",\"11:56am\",+0.0954,71.29,71.68,71.15,1630242\r\n\"AXP\",63.14,\"6/11/2007\",\"11:56am\",+0.10,62.79,63.19,62.42,1839830\r\n\"BA\",97.97,\"6/11/2007\",\"11:55am\",-0.22,98.25,98.79,97.74,1056000\r\n\"C\",53.58,\"6/11/2007\",\"11:56am\",+0.25,53.20,53.58,52.81,3892094\r\n\"CAT\",78.76,\"6/11/2007\",\"11:55am\",+0.24,78.32,78.99,78.06,1252852\r\n\"DD\",50.73,\"6/11/2007\",\"11:56am\",-0.40,51.13,51.21,50.59,1632600\r\n\"DIS\",34.24,\"6/11/2007\",\"11:56am\",+0.04,34.28,34.44,34.12,2414850\r\n\"GE\",37.44,\"6/11/2007\",\"11:56am\",+0.12,37.07,37.49,37.05,9766201\r\n\"GM\",31.32,\"6/11/2007\",\"11:56am\",+0.32,31.00,31.62,30.90,5949301\r\n\"HD\",37.74,\"6/11/2007\",\"11:56am\",-0.21,37.78,37.83,37.62,3692669\r\n\"HON\",57.00,\"6/11/2007\",\"11:56am\",-0.38,57.25,57.40,56.91,1521042\r\n\"HPQ\",46.10,\"6/11/2007\",\"11:56am\",+0.40,45.80,46.29,45.46,4001456\r\n\"IBM\",103.62,\"6/11/2007\",\"11:56am\",+0.55,102.87,103.71,102.50,2026100\r\n\"INTC\",22.00,\"6/11/2007\",\"12:01pm\",+0.17,21.70,22.01,21.69,18388938\r\n\"JNJ\",62.66,\"6/11/2007\",\"11:55am\",+0.53,62.89,62.89,62.15,3236322\r\n\"JPM\",50.54,\"6/11/2007\",\"11:56am\",+0.13,50.41,50.56,50.05,3019600\r\n\"KO\",51.52,\"6/11/2007\",\"11:55am\",-0.15,51.67,51.82,51.32,5345042\r\n\"MCD\",51.53,\"6/11/2007\",\"11:56am\",+0.12,51.47,51.62,50.98,2134514\r\n\"MMM\",85.44,\"6/11/2007\",\"11:56am\",-0.50,85.94,85.98,85.39,905400\r\n\"MO\",70.42,\"6/11/2007\",\"11:56am\",+0.12,70.25,70.50,69.76,3926257\r\n\"MRK\",50.79,\"6/11/2007\",\"11:56am\",+0.65,50.30,50.87,50.04,4401700\r\n\"MSFT\",30.18,\"6/11/2007\",\"12:01pm\",+0.13,30.05,30.25,29.93,18749244\r\n\"PFE\",26.46,\"6/11/2007\",\"11:56am\",-0.06,26.50,26.53,26.31,8528842\r\n\"PG\",63.04,\"6/11/2007\",\"11:56am\",-0.03,62.80,63.12,62.75,2418246\r\n\"T\",40.16,\"6/11/2007\",\"11:56am\",-0.10,40.20,40.25,39.89,5294500\r\n\"UTX\",69.8227,\"6/11/2007\",\"11:56am\",-0.4073,69.85,70.20,69.51,840000\r\n\"VZ\",43.28,\"6/11/2007\",\"11:56am\",+0.21,42.95,43.31,42.89,3317345\r\n\"WMT\",49.87,\"6/11/2007\",\"11:56am\",-0.21,49.90,50.00,49.55,3928833\r\n\"XOM\",83.30,\"6/11/2007\",\"11:56am\",+0.62,82.68,83.39,82.35,4932400\r\n\"AA\",39.615,\"6/11/2007\",\"12:00pm\",-0.045,39.67,40.18,39.43,1587980\r\n\"AIG\",71.63,\"6/11/2007\",\"12:01pm\",+0.10,71.29,71.68,71.15,1669942\r\n\"AXP\",63.142,\"6/11/2007\",\"12:01pm\",+0.102,62.79,63.19,62.42,1852230\r\n\"BA\",98.00,\"6/11/2007\",\"12:01pm\",-0.19,98.25,98.79,97.74,1079000\r\n\"C\",53.53,\"6/11/2007\",\"12:01pm\",+0.20,53.20,53.58,52.81,3992194\r\n\"CAT\",78.76,\"6/11/2007\",\"12:01pm\",+0.24,78.32,78.99,78.06,1270152\r\n\"DD\",50.69,\"6/11/2007\",\"12:01pm\",-0.44,51.13,51.21,50.59,1671400\r\n\"DIS\",34.21,\"6/11/2007\",\"12:00pm\",+0.01,34.28,34.44,34.12,2450550\r\n\"GE\",37.425,\"6/11/2007\",\"12:01pm\",+0.105,37.07,37.49,37.05,9895501\r\n\"GM\",31.32,\"6/11/2007\",\"12:00pm\",+0.32,31.00,31.62,30.90,5989901\r\n\"HD\",37.75,\"6/11/2007\",\"12:01pm\",-0.20,37.78,37.83,37.62,3816469\r\n\"HON\",57.00,\"6/11/2007\",\"12:01pm\",-0.38,57.25,57.40,56.91,1543942\r\n\"HPQ\",46.12,\"6/11/2007\",\"12:01pm\",+0.42,45.80,46.29,45.46,4076156\r\n\"IBM\",103.63,\"6/11/2007\",\"12:00pm\",+0.56,102.87,103.71,102.50,2060500\r\n\"INTC\",21.99,\"6/11/2007\",\"12:06pm\",+0.16,21.70,22.02,21.69,18564224\r\n\"JNJ\",62.631,\"6/11/2007\",\"12:01pm\",+0.501,62.89,62.89,62.15,3282922\r\n\"JPM\",50.60,\"6/11/2007\",\"12:01pm\",+0.19,50.41,50.60,50.05,3118700\r\n\"KO\",51.53,\"6/11/2007\",\"12:00pm\",-0.14,51.67,51.82,51.32,5393742\r\n\"MCD\",51.50,\"6/11/2007\",\"12:01pm\",+0.09,51.47,51.62,50.98,2171014\r\n\"MMM\",85.43,\"6/11/2007\",\"12:00pm\",-0.51,85.94,85.98,85.39,922700\r\n\"MO\",70.40,\"6/11/2007\",\"12:01pm\",+0.10,70.25,70.50,69.76,3957957\r\n\"MRK\",50.7475,\"6/11/2007\",\"12:01pm\",+0.6075,50.30,50.87,50.04,4475600\r\n\"MSFT\",30.1597,\"6/11/2007\",\"12:06pm\",+0.1097,30.05,30.25,29.93,18960388\r\n\"PFE\",26.46,\"6/11/2007\",\"12:01pm\",-0.06,26.50,26.53,26.31,8624142\r\n\"PG\",63.03,\"6/11/2007\",\"12:01pm\",-0.04,62.80,63.12,62.75,2475246\r\n\"T\",40.20,\"6/11/2007\",\"12:01pm\",-0.06,40.20,40.25,39.89,5445500\r\n\"UTX\",69.83,\"6/11/2007\",\"12:00pm\",-0.40,69.85,70.20,69.51,860200\r\n\"VZ\",43.28,\"6/11/2007\",\"12:00pm\",+0.21,42.95,43.31,42.89,3361245\r\n\"WMT\",49.87,\"6/11/2007\",\"12:01pm\",-0.21,49.90,50.00,49.55,4143733\r\n\"XOM\",83.40,\"6/11/2007\",\"12:01pm\",+0.72,82.68,83.46,82.35,5119800\r\n\"AA\",39.61,\"6/11/2007\",\"12:06pm\",-0.05,39.67,40.18,39.43,1627080\r\n\"AIG\",71.68,\"6/11/2007\",\"12:06pm\",+0.15,71.29,71.68,71.15,1706542\r\n\"AXP\",63.0546,\"6/11/2007\",\"12:05pm\",+0.0146,62.79,63.19,62.42,1880930\r\n\"BA\",97.92,\"6/11/2007\",\"12:06pm\",-0.27,98.25,98.79,97.74,1090100\r\n\"C\",53.55,\"6/11/2007\",\"12:06pm\",+0.22,53.20,53.65,52.81,4319594\r\n\"CAT\",78.74,\"6/11/2007\",\"12:06pm\",+0.22,78.32,78.99,78.06,1288152\r\n\"DD\",50.70,\"6/11/2007\",\"12:06pm\",-0.43,51.13,51.21,50.59,1732500\r\n\"DIS\",34.22,\"6/11/2007\",\"12:06pm\",+0.02,34.28,34.44,34.12,2493950\r\n\"GE\",37.4227,\"6/11/2007\",\"12:06pm\",+0.1027,37.07,37.49,37.05,10023701\r\n\"GM\",31.32,\"6/11/2007\",\"12:06pm\",+0.32,31.00,31.62,30.90,6029201\r\n\"HD\",37.7401,\"6/11/2007\",\"12:06pm\",-0.2099,37.78,37.83,37.62,3862469\r\n\"HON\",57.00,\"6/11/2007\",\"12:06pm\",-0.38,57.25,57.40,56.91,1627642\r\n\"HPQ\",46.10,\"6/11/2007\",\"12:06pm\",+0.40,45.80,46.29,45.46,4113656\r\n\"IBM\",103.67,\"6/11/2007\",\"12:06pm\",+0.60,102.87,103.71,102.50,2085800\r\n\"INTC\",21.99,\"6/11/2007\",\"12:11pm\",+0.16,21.70,22.02,21.69,18898568\r\n\"JNJ\",62.62,\"6/11/2007\",\"12:06pm\",+0.49,62.89,62.89,62.15,3318522\r\n\"JPM\",50.60,\"6/11/2007\",\"12:06pm\",+0.19,50.41,50.62,50.05,3165700\r\n\"KO\",51.52,\"6/11/2007\",\"12:05pm\",-0.15,51.67,51.82,51.32,5416242\r\n\"MCD\",51.47,\"6/11/2007\",\"12:06pm\",+0.06,51.47,51.62,50.98,2192514\r\n\"MMM\",85.41,\"6/11/2007\",\"12:05pm\",-0.53,85.94,85.98,85.39,941800\r\n\"MO\",70.41,\"6/11/2007\",\"12:06pm\",+0.11,70.25,70.50,69.76,3992557\r\n\"MRK\",50.75,\"6/11/2007\",\"12:06pm\",+0.61,50.30,50.87,50.04,4518500\r\n\"MSFT\",30.13,\"6/11/2007\",\"12:11pm\",+0.08,30.05,30.25,29.93,19296222\r\n\"PFE\",26.43,\"6/11/2007\",\"12:06pm\",-0.09,26.50,26.53,26.31,8765142\r\n\"PG\",63.02,\"6/11/2007\",\"12:06pm\",-0.05,62.80,63.12,62.75,2498846\r\n\"T\",40.18,\"6/11/2007\",\"12:06pm\",-0.08,40.20,40.25,39.89,5544800\r\n\"UTX\",69.89,\"6/11/2007\",\"12:05pm\",-0.34,69.85,70.20,69.51,873700\r\n\"VZ\",43.32,\"6/11/2007\",\"12:06pm\",+0.25,42.95,43.34,42.89,3454720\r\n\"WMT\",49.84,\"6/11/2007\",\"12:06pm\",-0.24,49.90,50.00,49.55,4217433\r\n\"XOM\",83.40,\"6/11/2007\",\"12:06pm\",+0.72,82.68,83.48,82.35,5317100\r\n\"AA\",39.57,\"6/11/2007\",\"12:11pm\",-0.09,39.67,40.18,39.43,1657480\r\n\"AIG\",71.68,\"6/11/2007\",\"12:11pm\",+0.15,71.29,71.70,71.15,1754142\r\n\"AXP\",63.01,\"6/11/2007\",\"12:11pm\",-0.03,62.79,63.19,62.42,1905230\r\n\"BA\",97.856,\"6/11/2007\",\"12:11pm\",-0.334,98.25,98.79,97.74,1115200\r\n\"C\",53.57,\"6/11/2007\",\"12:11pm\",+0.24,53.20,53.65,52.81,4395794\r\n\"CAT\",78.82,\"6/11/2007\",\"12:10pm\",+0.30,78.32,78.99,78.06,1308952\r\n\"DD\",50.71,\"6/11/2007\",\"12:10pm\",-0.42,51.13,51.21,50.59,1760700\r\n\"DIS\",34.23,\"6/11/2007\",\"12:11pm\",+0.03,34.28,34.44,34.12,2537850\r\n\"GE\",37.4025,\"6/11/2007\",\"12:11pm\",+0.0825,37.07,37.49,37.05,10173701\r\n\"GM\",31.27,\"6/11/2007\",\"12:11pm\",+0.27,31.00,31.62,30.90,6100601\r\n\"HD\",37.75,\"6/11/2007\",\"12:11pm\",-0.20,37.78,37.83,37.62,4021569\r\n\"HON\",57.00,\"6/11/2007\",\"12:11pm\",-0.38,57.25,57.40,56.91,1666742\r\n\"HPQ\",46.08,\"6/11/2007\",\"12:10pm\",+0.38,45.80,46.29,45.46,4173956\r\n\"IBM\",103.56,\"6/11/2007\",\"12:11pm\",+0.49,102.87,103.71,102.50,2111700\r\n\"INTC\",21.98,\"6/11/2007\",\"12:16pm\",+0.15,21.70,22.02,21.69,18999740\r\n\"JNJ\",62.56,\"6/11/2007\",\"12:11pm\",+0.43,62.89,62.89,62.15,3392622\r\n\"JPM\",50.60,\"6/11/2007\",\"12:11pm\",+0.19,50.41,50.62,50.05,3283000\r\n\"KO\",51.53,\"6/11/2007\",\"12:11pm\",-0.14,51.67,51.82,51.32,5438042\r\n\"MCD\",51.455,\"6/11/2007\",\"12:11pm\",+0.045,51.47,51.62,50.98,2250714\r\n\"MMM\",85.32,\"6/11/2007\",\"12:11pm\",-0.62,85.94,85.98,85.32,975500\r\n\"MO\",70.38,\"6/11/2007\",\"12:11pm\",+0.08,70.25,70.50,69.76,4010457\r\n\"MRK\",50.75,\"6/11/2007\",\"12:10pm\",+0.61,50.30,50.87,50.04,4599200\r\n\"MSFT\",30.13,\"6/11/2007\",\"12:16pm\",+0.08,30.05,30.25,29.93,19453430\r\n\"PFE\",26.38,\"6/11/2007\",\"12:11pm\",-0.14,26.50,26.53,26.31,9435387\r\n\"PG\",63.01,\"6/11/2007\",\"12:11pm\",-0.06,62.80,63.12,62.75,2550946\r\n\"T\",40.13,\"6/11/2007\",\"12:11pm\",-0.13,40.20,40.25,39.89,5619200\r\n\"UTX\",69.89,\"6/11/2007\",\"12:11pm\",-0.34,69.85,70.20,69.51,893500\r\n\"VZ\",43.31,\"6/11/2007\",\"12:11pm\",+0.24,42.95,43.34,42.89,3525820\r\n\"WMT\",49.86,\"6/11/2007\",\"12:11pm\",-0.22,49.90,50.00,49.55,4320433\r\n\"XOM\",83.38,\"6/11/2007\",\"12:11pm\",+0.70,82.68,83.48,82.35,5384300\r\n\"AA\",39.51,\"6/11/2007\",\"12:15pm\",-0.15,39.67,40.18,39.43,1707780\r\n\"AIG\",71.68,\"6/11/2007\",\"12:16pm\",+0.15,71.29,71.70,71.15,1797642\r\n\"AXP\",62.98,\"6/11/2007\",\"12:15pm\",-0.06,62.79,63.19,62.42,1919830\r\n\"BA\",97.81,\"6/11/2007\",\"12:16pm\",-0.38,98.25,98.79,97.74,1189800\r\n\"C\",53.51,\"6/11/2007\",\"12:15pm\",+0.18,53.20,53.65,52.81,4582294\r\n\"CAT\",78.87,\"6/11/2007\",\"12:16pm\",+0.35,78.32,78.99,78.06,1321852\r\n\"DD\",50.69,\"6/11/2007\",\"12:16pm\",-0.44,51.13,51.21,50.59,1790800\r\n\"DIS\",34.21,\"6/11/2007\",\"12:16pm\",+0.01,34.28,34.44,34.12,2586450\r\n\"GE\",37.40,\"6/11/2007\",\"12:16pm\",+0.08,37.07,37.49,37.05,10295201\r\n\"GM\",31.27,\"6/11/2007\",\"12:16pm\",+0.27,31.00,31.62,30.90,6373201\r\n\"HD\",37.752,\"6/11/2007\",\"12:16pm\",-0.198,37.78,37.83,37.62,4072169\r\n\"HON\",57.00,\"6/11/2007\",\"12:16pm\",-0.38,57.25,57.40,56.91,1683042\r\n\"HPQ\",46.06,\"6/11/2007\",\"12:15pm\",+0.36,45.80,46.29,45.46,4225556\r\n\"IBM\",103.54,\"6/11/2007\",\"12:16pm\",+0.47,102.87,103.71,102.50,2128600\r\n\"INTC\",21.98,\"6/11/2007\",\"12:21pm\",+0.15,21.70,22.02,21.69,19143976\r\n\"JNJ\",62.57,\"6/11/2007\",\"12:16pm\",+0.44,62.89,62.89,62.15,3439222\r\n\"JPM\",50.59,\"6/11/2007\",\"12:16pm\",+0.18,50.41,50.62,50.05,3372000\r\n\"KO\",51.5418,\"6/11/2007\",\"12:16pm\",-0.1282,51.67,51.82,51.32,5475442\r\n\"MCD\",51.44,\"6/11/2007\",\"12:16pm\",+0.03,51.47,51.62,50.98,2321914\r\n\"MMM\",85.34,\"6/11/2007\",\"12:16pm\",-0.60,85.94,85.98,85.32,985300\r\n\"MO\",70.31,\"6/11/2007\",\"12:15pm\",+0.01,70.25,70.50,69.76,4067257\r\n\"MRK\",50.74,\"6/11/2007\",\"12:16pm\",+0.60,50.30,50.87,50.04,4655400\r\n\"MSFT\",30.10,\"6/11/2007\",\"12:21pm\",+0.05,30.05,30.25,29.93,19617530\r\n\"PFE\",26.38,\"6/11/2007\",\"12:16pm\",-0.14,26.50,26.53,26.31,9916032\r\n\"PG\",62.99,\"6/11/2007\",\"12:16pm\",-0.08,62.80,63.12,62.75,2626746\r\n\"T\",40.10,\"6/11/2007\",\"12:16pm\",-0.16,40.20,40.25,39.89,5730400\r\n\"UTX\",69.89,\"6/11/2007\",\"12:15pm\",-0.34,69.85,70.20,69.51,919000\r\n\"VZ\",43.252,\"6/11/2007\",\"12:16pm\",+0.182,42.95,43.34,42.89,3563720\r\n\"WMT\",49.882,\"6/11/2007\",\"12:16pm\",-0.198,49.90,50.00,49.55,4432033\r\n\"XOM\",83.37,\"6/11/2007\",\"12:16pm\",+0.69,82.68,83.48,82.35,5492200\r\n\"AA\",39.48,\"6/11/2007\",\"12:21pm\",-0.18,39.67,40.18,39.43,1815880\r\n\"AIG\",71.71,\"6/11/2007\",\"12:21pm\",+0.18,71.29,71.71,71.15,1835142\r\n\"AXP\",62.92,\"6/11/2007\",\"12:20pm\",-0.12,62.79,63.19,62.42,1934230\r\n\"BA\",97.79,\"6/11/2007\",\"12:21pm\",-0.40,98.25,98.79,97.74,1228800\r\n\"C\",53.53,\"6/11/2007\",\"12:21pm\",+0.20,53.20,53.65,52.81,4665694\r\n\"CAT\",78.84,\"6/11/2007\",\"12:20pm\",+0.32,78.32,78.99,78.06,1332852\r\n\"DD\",50.69,\"6/11/2007\",\"12:21pm\",-0.44,51.13,51.21,50.59,1803100\r\n\"DIS\",34.21,\"6/11/2007\",\"12:21pm\",+0.01,34.28,34.44,34.12,2632250\r\n\"GE\",37.41,\"6/11/2007\",\"12:21pm\",+0.09,37.07,37.49,37.05,10406001\r\n\"GM\",31.29,\"6/11/2007\",\"12:21pm\",+0.29,31.00,31.62,30.90,6432201\r\n\"HD\",37.75,\"6/11/2007\",\"12:20pm\",-0.20,37.78,37.83,37.62,5032769\r\n\"HON\",56.98,\"6/11/2007\",\"12:20pm\",-0.40,57.25,57.40,56.91,1693442\r\n\"HPQ\",46.03,\"6/11/2007\",\"12:21pm\",+0.33,45.80,46.29,45.46,4304056\r\n\"IBM\",103.56,\"6/11/2007\",\"12:20pm\",+0.49,102.87,103.71,102.50,2151600\r\n\"INTC\",21.963,\"6/11/2007\",\"12:26pm\",+0.133,21.70,22.02,21.69,19623448\r\n\"JNJ\",62.56,\"6/11/2007\",\"12:21pm\",+0.43,62.89,62.89,62.15,3517522\r\n\"JPM\",50.58,\"6/11/2007\",\"12:21pm\",+0.17,50.41,50.62,50.05,3435300\r\n\"KO\",51.52,\"6/11/2007\",\"12:21pm\",-0.15,51.67,51.82,51.32,5546242\r\n\"MCD\",51.40,\"6/11/2007\",\"12:20pm\",-0.01,51.47,51.62,50.98,2357014\r\n\"MMM\",85.33,\"6/11/2007\",\"12:20pm\",-0.61,85.94,85.98,85.32,996200\r\n\"MO\",70.27,\"6/11/2007\",\"12:21pm\",-0.03,70.25,70.50,69.76,4106257\r\n\"MRK\",50.72,\"6/11/2007\",\"12:21pm\",+0.58,50.30,50.87,50.04,4713700\r\n\"MSFT\",30.12,\"6/11/2007\",\"12:26pm\",+0.07,30.05,30.25,29.93,19901670\r\n\"PFE\",26.37,\"6/11/2007\",\"12:21pm\",-0.15,26.50,26.53,26.31,10036232\r\n\"PG\",63.01,\"6/11/2007\",\"12:20pm\",-0.06,62.80,63.12,62.75,2656246\r\n\"T\",40.09,\"6/11/2007\",\"12:21pm\",-0.17,40.20,40.25,39.89,5849000\r\n\"UTX\",69.80,\"6/11/2007\",\"12:20pm\",-0.43,69.85,70.20,69.51,935600\r\n\"VZ\",43.24,\"6/11/2007\",\"12:20pm\",+0.17,42.95,43.34,42.89,3608620\r\n\"WMT\",49.87,\"6/11/2007\",\"12:21pm\",-0.21,49.90,50.00,49.55,4485833\r\n\"XOM\",83.39,\"6/11/2007\",\"12:21pm\",+0.71,82.68,83.48,82.35,5559000\r\n\"AA\",39.49,\"6/11/2007\",\"12:26pm\",-0.17,39.67,40.18,39.43,1856980\r\n\"AIG\",71.73,\"6/11/2007\",\"12:26pm\",+0.20,71.29,71.74,71.15,1875942\r\n\"AXP\",62.90,\"6/11/2007\",\"12:26pm\",-0.14,62.79,63.19,62.42,1944730\r\n\"BA\",97.75,\"6/11/2007\",\"12:26pm\",-0.44,98.25,98.79,97.74,1241700\r\n\"C\",53.55,\"6/11/2007\",\"12:26pm\",+0.22,53.20,53.65,52.81,4736994\r\n\"CAT\",78.87,\"6/11/2007\",\"12:25pm\",+0.35,78.32,78.99,78.06,1347152\r\n\"DD\",50.70,\"6/11/2007\",\"12:26pm\",-0.43,51.13,51.21,50.59,1812800\r\n\"DIS\",34.20,\"6/11/2007\",\"12:26pm\",0.00,34.28,34.44,34.12,2667150\r\n\"GE\",37.41,\"6/11/2007\",\"12:26pm\",+0.09,37.07,37.49,37.05,10594701\r\n\"GM\",31.27,\"6/11/2007\",\"12:26pm\",+0.27,31.00,31.62,30.90,6554401\r\n\"HD\",37.73,\"6/11/2007\",\"12:26pm\",-0.22,37.78,37.83,37.62,5061669\r\n\"HON\",56.96,\"6/11/2007\",\"12:26pm\",-0.42,57.25,57.40,56.91,1723242\r\n\"HPQ\",46.02,\"6/11/2007\",\"12:26pm\",+0.32,45.80,46.29,45.46,4379056\r\n\"IBM\",103.64,\"6/11/2007\",\"12:26pm\",+0.57,102.87,103.71,102.50,2198300\r\n\"INTC\",21.97,\"6/11/2007\",\"12:31pm\",+0.14,21.70,22.02,21.69,19931936\r\n\"JNJ\",62.60,\"6/11/2007\",\"12:26pm\",+0.47,62.89,62.89,62.15,3566938\r\n\"JPM\",50.61,\"6/11/2007\",\"12:26pm\",+0.20,50.41,50.62,50.05,3481000\r\n\"KO\",51.55,\"6/11/2007\",\"12:26pm\",-0.12,51.67,51.82,51.32,5589842\r\n\"MCD\",51.37,\"6/11/2007\",\"12:26pm\",-0.04,51.47,51.62,50.98,2377914\r\n\"MMM\",85.33,\"6/11/2007\",\"12:25pm\",-0.61,85.94,85.98,85.32,1000600\r\n\"MO\",70.23,\"6/11/2007\",\"12:26pm\",-0.07,70.25,70.50,69.76,4146657\r\n\"MRK\",50.79,\"6/11/2007\",\"12:26pm\",+0.65,50.30,50.87,50.04,4803500\r\n\"MSFT\",30.145,\"6/11/2007\",\"12:31pm\",+0.095,30.05,30.25,29.93,20108456\r\n\"PFE\",26.38,\"6/11/2007\",\"12:26pm\",-0.14,26.50,26.53,26.31,10177632\r\n\"PG\",62.98,\"6/11/2007\",\"12:26pm\",-0.09,62.80,63.12,62.75,2697146\r\n\"T\",40.12,\"6/11/2007\",\"12:26pm\",-0.14,40.20,40.25,39.89,5959900\r\n\"UTX\",69.80,\"6/11/2007\",\"12:26pm\",-0.43,69.85,70.20,69.51,946900\r\n\"VZ\",43.23,\"6/11/2007\",\"12:26pm\",+0.16,42.95,43.34,42.89,3648320\r\n\"WMT\",49.90,\"6/11/2007\",\"12:26pm\",-0.18,49.90,50.00,49.55,4562633\r\n\"XOM\",83.43,\"6/11/2007\",\"12:26pm\",+0.75,82.68,83.48,82.35,5652500\r\n\"AA\",39.50,\"6/11/2007\",\"12:30pm\",-0.16,39.67,40.18,39.43,1898080\r\n\"AIG\",71.74,\"6/11/2007\",\"12:31pm\",+0.21,71.29,71.74,71.15,1905942\r\n\"AXP\",62.9008,\"6/11/2007\",\"12:31pm\",-0.1392,62.79,63.19,62.42,1968730\r\n\"BA\",97.67,\"6/11/2007\",\"12:30pm\",-0.52,98.25,98.79,97.63,1294900\r\n\"C\",53.57,\"6/11/2007\",\"12:31pm\",+0.24,53.20,53.65,52.81,4767094\r\n\"CAT\",78.89,\"6/11/2007\",\"12:31pm\",+0.37,78.32,78.99,78.06,1360152\r\n\"DD\",50.73,\"6/11/2007\",\"12:31pm\",-0.40,51.13,51.21,50.59,1833000\r\n\"DIS\",34.195,\"6/11/2007\",\"12:31pm\",-0.005,34.28,34.44,34.12,2687550\r\n\"GE\",37.41,\"6/11/2007\",\"12:31pm\",+0.09,37.07,37.49,37.05,10633601\r\n\"GM\",31.25,\"6/11/2007\",\"12:31pm\",+0.25,31.00,31.62,30.90,6609301\r\n\"HD\",37.74,\"6/11/2007\",\"12:30pm\",-0.21,37.78,37.83,37.62,5089969\r\n\"HON\",56.95,\"6/11/2007\",\"12:31pm\",-0.43,57.25,57.40,56.91,1741042\r\n\"HPQ\",46.02,\"6/11/2007\",\"12:30pm\",+0.32,45.80,46.29,45.46,4453056\r\n\"IBM\",103.67,\"6/11/2007\",\"12:31pm\",+0.60,102.87,103.71,102.50,2226700\r\n\"INTC\",21.96,\"6/11/2007\",\"12:36pm\",+0.13,21.70,22.02,21.69,20005174\r\n\"JNJ\",62.60,\"6/11/2007\",\"12:31pm\",+0.47,62.89,62.89,62.15,3584438\r\n\"JPM\",50.60,\"6/11/2007\",\"12:31pm\",+0.19,50.41,50.62,50.05,3513000\r\n\"KO\",51.56,\"6/11/2007\",\"12:30pm\",-0.11,51.67,51.82,51.32,5620842\r\n\"MCD\",51.34,\"6/11/2007\",\"12:31pm\",-0.07,51.47,51.62,50.98,2442514\r\n\"MMM\",85.28,\"6/11/2007\",\"12:30pm\",-0.66,85.94,85.98,85.28,1017900\r\n\"MO\",70.24,\"6/11/2007\",\"12:30pm\",-0.06,70.25,70.50,69.76,4185257\r\n\"MRK\",50.84,\"6/11/2007\",\"12:31pm\",+0.70,50.30,50.87,50.04,4848100\r\n\"MSFT\",30.14,\"6/11/2007\",\"12:36pm\",+0.09,30.05,30.25,29.93,20198736\r\n\"PFE\",26.39,\"6/11/2007\",\"12:31pm\",-0.13,26.50,26.53,26.31,10293432\r\n\"PG\",63.00,\"6/11/2007\",\"12:31pm\",-0.07,62.80,63.12,62.75,2745846\r\n\"T\",40.07,\"6/11/2007\",\"12:31pm\",-0.19,40.20,40.25,39.89,6112900\r\n\"UTX\",69.81,\"6/11/2007\",\"12:31pm\",-0.42,69.85,70.20,69.51,958400\r\n\"VZ\",43.27,\"6/11/2007\",\"12:31pm\",+0.20,42.95,43.34,42.89,3693720\r\n\"WMT\",49.92,\"6/11/2007\",\"12:31pm\",-0.16,49.90,50.00,49.55,4676833\r\n\"XOM\",83.46,\"6/11/2007\",\"12:31pm\",+0.78,82.68,83.48,82.35,5711300\r\n\"AA\",39.47,\"6/11/2007\",\"12:35pm\",-0.19,39.67,40.18,39.43,1938380\r\n\"AIG\",71.705,\"6/11/2007\",\"12:35pm\",+0.175,71.29,71.76,71.15,1958142\r\n\"AXP\",62.91,\"6/11/2007\",\"12:36pm\",-0.13,62.79,63.19,62.42,1982630\r\n\"BA\",97.65,\"6/11/2007\",\"12:35pm\",-0.54,98.25,98.79,97.61,1324100\r\n\"C\",53.56,\"6/11/2007\",\"12:36pm\",+0.23,53.20,53.65,52.81,4843994\r\n\"CAT\",78.82,\"6/11/2007\",\"12:36pm\",+0.30,78.32,78.99,78.06,1381852\r\n\"DD\",50.70,\"6/11/2007\",\"12:36pm\",-0.43,51.13,51.21,50.59,1854300\r\n\"DIS\",34.19,\"6/11/2007\",\"12:36pm\",-0.01,34.28,34.44,34.12,2871150\r\n\"GE\",37.41,\"6/11/2007\",\"12:36pm\",+0.09,37.07,37.49,37.05,10703601\r\n\"GM\",31.28,\"6/11/2007\",\"12:36pm\",+0.28,31.00,31.62,30.90,6667301\r\n\"HD\",37.74,\"6/11/2007\",\"12:35pm\",-0.21,37.78,37.83,37.62,5127869\r\n\"HON\",56.93,\"6/11/2007\",\"12:36pm\",-0.45,57.25,57.40,56.91,1772242\r\n\"HPQ\",46.01,\"6/11/2007\",\"12:35pm\",+0.31,45.80,46.29,45.46,4478756\r\n\"IBM\",103.70,\"6/11/2007\",\"12:36pm\",+0.63,102.87,103.73,102.50,2253600\r\n\"INTC\",21.96,\"6/11/2007\",\"12:41pm\",+0.13,21.70,22.02,21.69,20062818\r\n\"JNJ\",62.59,\"6/11/2007\",\"12:36pm\",+0.46,62.89,62.89,62.15,3609538\r\n\"JPM\",50.60,\"6/11/2007\",\"12:36pm\",+0.19,50.41,50.62,50.05,3631000\r\n\"KO\",51.58,\"6/11/2007\",\"12:36pm\",-0.09,51.67,51.82,51.32,5643442\r\n\"MCD\",51.37,\"6/11/2007\",\"12:36pm\",-0.04,51.47,51.62,50.98,2479114\r\n\"MMM\",85.34,\"6/11/2007\",\"12:36pm\",-0.60,85.94,85.98,85.28,1032100\r\n\"MO\",70.32,\"6/11/2007\",\"12:36pm\",+0.02,70.25,70.50,69.76,4255057\r\n\"MRK\",50.90,\"6/11/2007\",\"12:36pm\",+0.76,50.30,50.91,50.04,4953900\r\n\"MSFT\",30.13,\"6/11/2007\",\"12:41pm\",+0.08,30.05,30.25,29.93,20258588\r\n\"PFE\",26.41,\"6/11/2007\",\"12:36pm\",-0.11,26.50,26.53,26.31,10510182\r\n\"PG\",63.03,\"6/11/2007\",\"12:36pm\",-0.04,62.80,63.12,62.75,2776446\r\n\"T\",40.08,\"6/11/2007\",\"12:36pm\",-0.18,40.20,40.25,39.89,6259175\r\n\"UTX\",69.81,\"6/11/2007\",\"12:35pm\",-0.42,69.85,70.20,69.51,964300\r\n\"VZ\",43.28,\"6/11/2007\",\"12:36pm\",+0.21,42.95,43.34,42.89,3720320\r\n\"WMT\",49.94,\"6/11/2007\",\"12:36pm\",-0.14,49.90,50.00,49.55,4837533\r\n\"XOM\",83.45,\"6/11/2007\",\"12:36pm\",+0.77,82.68,83.50,82.35,5799000\r\n\"AA\",39.48,\"6/11/2007\",\"12:41pm\",-0.18,39.67,40.18,39.43,1988580\r\n\"AIG\",71.70,\"6/11/2007\",\"12:40pm\",+0.17,71.29,71.76,71.15,2020642\r\n\"AXP\",62.91,\"6/11/2007\",\"12:41pm\",-0.13,62.79,63.19,62.42,1993830\r\n\"BA\",97.73,\"6/11/2007\",\"12:40pm\",-0.46,98.25,98.79,97.59,1344400\r\n\"C\",53.52,\"6/11/2007\",\"12:41pm\",+0.19,53.20,53.65,52.81,5118094\r\n\"CAT\",78.88,\"6/11/2007\",\"12:41pm\",+0.36,78.32,78.99,78.06,1400252\r\n\"DD\",50.73,\"6/11/2007\",\"12:41pm\",-0.40,51.13,51.21,50.59,1866700\r\n\"DIS\",34.181,\"6/11/2007\",\"12:41pm\",-0.019,34.28,34.44,34.12,2913850\r\n\"GE\",37.42,\"6/11/2007\",\"12:41pm\",+0.10,37.07,37.49,37.05,10856501\r\n\"GM\",31.32,\"6/11/2007\",\"12:41pm\",+0.32,31.00,31.62,30.90,6784901\r\n\"HD\",37.75,\"6/11/2007\",\"12:41pm\",-0.20,37.78,37.83,37.62,5186969\r\n\"HON\",56.96,\"6/11/2007\",\"12:41pm\",-0.42,57.25,57.40,56.91,1802642\r\n\"HPQ\",46.01,\"6/11/2007\",\"12:41pm\",+0.31,45.80,46.29,45.46,4507656\r\n\"IBM\",103.74,\"6/11/2007\",\"12:40pm\",+0.67,102.87,103.76,102.50,2288300\r\n\"INTC\",21.96,\"6/11/2007\",\"12:46pm\",+0.13,21.70,22.02,21.69,20169300\r\n\"JNJ\",62.62,\"6/11/2007\",\"12:41pm\",+0.49,62.89,62.89,62.15,3651538\r\n\"JPM\",50.55,\"6/11/2007\",\"12:41pm\",+0.14,50.41,50.62,50.05,3814700\r\n\"KO\",51.56,\"6/11/2007\",\"12:40pm\",-0.11,51.67,51.82,51.32,5678742\r\n\"MCD\",51.40,\"6/11/2007\",\"12:41pm\",-0.01,51.47,51.62,50.98,2510514\r\n\"MMM\",85.33,\"6/11/2007\",\"12:40pm\",-0.61,85.94,85.98,85.28,1043700\r\n\"MO\",70.35,\"6/11/2007\",\"12:41pm\",+0.05,70.25,70.50,69.76,4300857\r\n\"MRK\",51.06,\"6/11/2007\",\"12:41pm\",+0.92,50.30,51.07,50.04,5195700\r\n\"MSFT\",30.13,\"6/11/2007\",\"12:46pm\",+0.08,30.05,30.25,29.93,20422172\r\n\"PFE\",26.41,\"6/11/2007\",\"12:41pm\",-0.11,26.50,26.53,26.31,10595782\r\n\"PG\",63.05,\"6/11/2007\",\"12:41pm\",-0.02,62.80,63.12,62.75,2806346\r\n\"T\",40.06,\"6/11/2007\",\"12:41pm\",-0.20,40.20,40.25,39.89,6338875\r\n\"UTX\",69.81,\"6/11/2007\",\"12:41pm\",-0.42,69.85,70.20,69.51,968100\r\n\"VZ\",43.28,\"6/11/2007\",\"12:41pm\",+0.21,42.95,43.34,42.89,3802620\r\n\"WMT\",49.92,\"6/11/2007\",\"12:41pm\",-0.16,49.90,50.00,49.55,4964533\r\n\"XOM\",83.55,\"6/11/2007\",\"12:41pm\",+0.87,82.68,83.57,82.35,5902800\r\n\"AA\",39.48,\"6/11/2007\",\"12:46pm\",-0.18,39.67,40.18,39.43,2011580\r\n\"AIG\",71.68,\"6/11/2007\",\"12:45pm\",+0.15,71.29,71.76,71.15,2066242\r\n\"AXP\",62.93,\"6/11/2007\",\"12:46pm\",-0.11,62.79,63.19,62.42,2001530\r\n\"BA\",97.68,\"6/11/2007\",\"12:46pm\",-0.51,98.25,98.79,97.59,1387800\r\n\"C\",53.54,\"6/11/2007\",\"12:46pm\",+0.21,53.20,53.65,52.81,5168294\r\n\"CAT\",78.87,\"6/11/2007\",\"12:46pm\",+0.35,78.32,78.99,78.06,1416552\r\n\"DD\",50.70,\"6/11/2007\",\"12:46pm\",-0.43,51.13,51.21,50.59,1884900\r\n\"DIS\",34.20,\"6/11/2007\",\"12:46pm\",0.00,34.28,34.44,34.12,2958150\r\n\"GE\",37.40,\"6/11/2007\",\"12:46pm\",+0.08,37.07,37.49,37.05,10931901\r\n\"GM\",31.29,\"6/11/2007\",\"12:46pm\",+0.29,31.00,31.62,30.90,6930401\r\n\"HD\",37.75,\"6/11/2007\",\"12:46pm\",-0.20,37.78,37.83,37.62,5233269\r\n\"HON\",56.99,\"6/11/2007\",\"12:46pm\",-0.39,57.25,57.40,56.91,1827642\r\n\"HPQ\",46.01,\"6/11/2007\",\"12:46pm\",+0.31,45.80,46.29,45.46,4557656\r\n\"IBM\",103.75,\"6/11/2007\",\"12:46pm\",+0.68,102.87,103.78,102.50,2310700\r\n\"INTC\",22.00,\"6/11/2007\",\"12:51pm\",+0.17,21.70,22.02,21.69,20568860\r\n\"JNJ\",62.59,\"6/11/2007\",\"12:46pm\",+0.46,62.89,62.89,62.15,3702738\r\n\"JPM\",50.57,\"6/11/2007\",\"12:46pm\",+0.16,50.41,50.62,50.05,3902300\r\n\"KO\",51.58,\"6/11/2007\",\"12:46pm\",-0.09,51.67,51.82,51.32,5707242\r\n\"MCD\",51.44,\"6/11/2007\",\"12:46pm\",+0.03,51.47,51.62,50.98,2534014\r\n\"MMM\",85.33,\"6/11/2007\",\"12:45pm\",-0.61,85.94,85.98,85.28,1051300\r\n\"MO\",70.32,\"6/11/2007\",\"12:46pm\",+0.02,70.25,70.50,69.76,4330057\r\n\"MRK\",50.97,\"6/11/2007\",\"12:46pm\",+0.83,50.30,51.07,50.04,5430200\r\n\"MSFT\",30.13,\"6/11/2007\",\"12:51pm\",+0.08,30.05,30.25,29.93,20730214\r\n\"PFE\",26.43,\"6/11/2007\",\"12:46pm\",-0.09,26.50,26.53,26.31,10698282\r\n\"PG\",63.05,\"6/11/2007\",\"12:46pm\",-0.02,62.80,63.12,62.75,2909046\r\n\"T\",40.06,\"6/11/2007\",\"12:46pm\",-0.20,40.20,40.25,39.89,6387575\r\n\"UTX\",69.81,\"6/11/2007\",\"12:46pm\",-0.42,69.85,70.20,69.51,976200\r\n\"VZ\",43.28,\"6/11/2007\",\"12:46pm\",+0.21,42.95,43.34,42.89,3826420\r\n\"WMT\",49.95,\"6/11/2007\",\"12:46pm\",-0.13,49.90,50.00,49.55,5329433\r\n\"XOM\",83.58,\"6/11/2007\",\"12:46pm\",+0.90,82.68,83.59,82.35,5965700\r\n\"AA\",39.48,\"6/11/2007\",\"12:51pm\",-0.18,39.67,40.18,39.43,2052980\r\n\"AIG\",71.67,\"6/11/2007\",\"12:51pm\",+0.14,71.29,71.76,71.15,2109942\r\n\"AXP\",62.90,\"6/11/2007\",\"12:50pm\",-0.14,62.79,63.19,62.42,2024830\r\n\"BA\",97.78,\"6/11/2007\",\"12:51pm\",-0.41,98.25,98.79,97.59,1430600\r\n\"C\",53.56,\"6/11/2007\",\"12:51pm\",+0.23,53.20,53.65,52.81,5509594\r\n\"CAT\",79.00,\"6/11/2007\",\"12:50pm\",+0.48,78.32,79.00,78.06,1448852\r\n\"DD\",50.71,\"6/11/2007\",\"12:51pm\",-0.42,51.13,51.21,50.59,1916300\r\n\"DIS\",34.192,\"6/11/2007\",\"12:50pm\",-0.008,34.28,34.44,34.12,3022750\r\n\"GE\",37.41,\"6/11/2007\",\"12:51pm\",+0.09,37.07,37.49,37.05,11159401\r\n\"GM\",31.38,\"6/11/2007\",\"12:51pm\",+0.38,31.00,31.62,30.90,7135301\r\n\"HD\",37.76,\"6/11/2007\",\"12:51pm\",-0.19,37.78,37.83,37.62,5362369\r\n\"HON\",57.05,\"6/11/2007\",\"12:51pm\",-0.33,57.25,57.40,56.91,1871542\r\n\"HPQ\",46.03,\"6/11/2007\",\"12:51pm\",+0.33,45.80,46.29,45.46,4684356\r\n\"IBM\",103.87,\"6/11/2007\",\"12:51pm\",+0.80,102.87,103.89,102.50,2362500\r\n\"INTC\",22.00,\"6/11/2007\",\"12:56pm\",+0.17,21.70,22.02,21.69,20977118\r\n\"JNJ\",62.58,\"6/11/2007\",\"12:51pm\",+0.45,62.89,62.89,62.15,3790288\r\n\"JPM\",50.60,\"6/11/2007\",\"12:51pm\",+0.19,50.41,50.63,50.05,3966000\r\n\"KO\",51.59,\"6/11/2007\",\"12:51pm\",-0.08,51.67,51.82,51.32,5755942\r\n\"MCD\",51.42,\"6/11/2007\",\"12:50pm\",+0.01,51.47,51.62,50.98,2580014\r\n\"MMM\",85.36,\"6/11/2007\",\"12:50pm\",-0.58,85.94,85.98,85.28,1063300\r\n\"MO\",70.37,\"6/11/2007\",\"12:51pm\",+0.07,70.25,70.50,69.76,4395657\r\n\"MRK\",50.97,\"6/11/2007\",\"12:51pm\",+0.83,50.30,51.07,50.04,5527700\r\n\"MSFT\",30.17,\"6/11/2007\",\"12:56pm\",+0.12,30.05,30.25,29.93,21243952\r\n\"PFE\",26.43,\"6/11/2007\",\"12:51pm\",-0.09,26.50,26.53,26.31,10870382\r\n\"PG\",63.04,\"6/11/2007\",\"12:51pm\",-0.03,62.80,63.12,62.75,3002346\r\n\"T\",40.01,\"6/11/2007\",\"12:51pm\",-0.25,40.20,40.25,39.89,6578675\r\n\"UTX\",69.88,\"6/11/2007\",\"12:50pm\",-0.35,69.85,70.20,69.51,986100\r\n\"VZ\",43.30,\"6/11/2007\",\"12:51pm\",+0.23,42.95,43.34,42.89,3880620\r\n\"WMT\",49.9786,\"6/11/2007\",\"12:51pm\",-0.1014,49.90,50.00,49.55,5399333\r\n\"XOM\",83.67,\"6/11/2007\",\"12:51pm\",+0.99,82.68,83.67,82.35,6065100\r\n\"AA\",39.50,\"6/11/2007\",\"12:56pm\",-0.16,39.67,40.18,39.43,2111680\r\n\"AIG\",71.68,\"6/11/2007\",\"12:56pm\",+0.15,71.29,71.76,71.15,2166942\r\n\"AXP\",63.00,\"6/11/2007\",\"12:56pm\",-0.04,62.79,63.19,62.42,2038530\r\n\"BA\",97.71,\"6/11/2007\",\"12:56pm\",-0.48,98.25,98.79,97.59,1475700\r\n\"C\",53.5614,\"6/11/2007\",\"12:56pm\",+0.2314,53.20,53.65,52.81,5665694\r\n\"CAT\",79.14,\"6/11/2007\",\"12:56pm\",+0.62,78.32,79.14,78.06,1510252\r\n\"DD\",50.79,\"6/11/2007\",\"12:56pm\",-0.34,51.13,51.21,50.59,1934600\r\n\"DIS\",34.20,\"6/11/2007\",\"12:56pm\",0.00,34.28,34.44,34.12,3112050\r\n\"GE\",37.47,\"6/11/2007\",\"12:56pm\",+0.15,37.07,37.49,37.05,11425501\r\n\"GM\",31.37,\"6/11/2007\",\"12:56pm\",+0.37,31.00,31.62,30.90,7225026\r\n\"HD\",37.77,\"6/11/2007\",\"12:56pm\",-0.18,37.78,37.83,37.62,5433169\r\n\"HON\",57.11,\"6/11/2007\",\"12:56pm\",-0.27,57.25,57.40,56.91,1911042\r\n\"HPQ\",46.07,\"6/11/2007\",\"12:55pm\",+0.37,45.80,46.29,45.46,4729356\r\n\"IBM\",103.97,\"6/11/2007\",\"12:56pm\",+0.90,102.87,104.00,102.50,2444600\r\n\"INTC\",22.00,\"6/11/2007\",\"1:01pm\",+0.17,21.70,22.02,21.69,21559788\r\n\"JNJ\",62.59,\"6/11/2007\",\"12:56pm\",+0.46,62.89,62.89,62.15,3837538\r\n\"JPM\",50.65,\"6/11/2007\",\"12:56pm\",+0.24,50.41,50.65,50.05,4042900\r\n\"KO\",51.67,\"6/11/2007\",\"12:56pm\",0.00,51.67,51.82,51.32,5793842\r\n\"MCD\",51.44,\"6/11/2007\",\"12:56pm\",+0.03,51.47,51.62,50.98,2635114\r\n\"MMM\",85.46,\"6/11/2007\",\"12:55pm\",-0.48,85.94,85.98,85.28,1082400\r\n\"MO\",70.35,\"6/11/2007\",\"12:56pm\",+0.05,70.25,70.50,69.76,4427157\r\n\"MRK\",50.96,\"6/11/2007\",\"12:56pm\",+0.82,50.30,51.07,50.04,5634400\r\n\"MSFT\",30.14,\"6/11/2007\",\"1:01pm\",+0.09,30.05,30.25,29.93,21696948\r\n\"PFE\",26.45,\"6/11/2007\",\"12:56pm\",-0.07,26.50,26.53,26.31,11036032\r\n\"PG\",63.08,\"6/11/2007\",\"12:56pm\",+0.01,62.80,63.12,62.75,3066446\r\n\"T\",40.04,\"6/11/2007\",\"12:56pm\",-0.22,40.20,40.25,39.89,6709275\r\n\"UTX\",69.92,\"6/11/2007\",\"12:56pm\",-0.31,69.85,70.20,69.51,1008800\r\n\"VZ\",43.32,\"6/11/2007\",\"12:56pm\",+0.25,42.95,43.34,42.89,3928820\r\n\"WMT\",50.01,\"6/11/2007\",\"12:56pm\",-0.07,49.90,50.04,49.55,5568433\r\n\"XOM\",83.66,\"6/11/2007\",\"12:56pm\",+0.98,82.68,83.72,82.35,6158700\r\n\"AA\",39.51,\"6/11/2007\",\"1:01pm\",-0.15,39.67,40.18,39.43,2207380\r\n\"AIG\",71.64,\"6/11/2007\",\"1:01pm\",+0.11,71.29,71.76,71.15,2223342\r\n\"AXP\",62.96,\"6/11/2007\",\"1:01pm\",-0.08,62.79,63.19,62.42,2056230\r\n\"BA\",97.65,\"6/11/2007\",\"1:01pm\",-0.54,98.25,98.79,97.59,1502900\r\n\"C\",53.58,\"6/11/2007\",\"1:01pm\",+0.25,53.20,53.65,52.81,5805794\r\n\"CAT\",79.07,\"6/11/2007\",\"1:00pm\",+0.55,78.32,79.14,78.06,1532652\r\n\"DD\",50.7784,\"6/11/2007\",\"1:01pm\",-0.3516,51.13,51.21,50.59,1957300\r\n\"DIS\",34.20,\"6/11/2007\",\"1:01pm\",0.00,34.28,34.44,34.12,3159250\r\n\"GE\",37.48,\"6/11/2007\",\"1:01pm\",+0.16,37.07,37.49,37.05,11769301\r\n\"GM\",31.34,\"6/11/2007\",\"1:01pm\",+0.34,31.00,31.62,30.90,7327251\r\n\"HD\",37.75,\"6/11/2007\",\"1:01pm\",-0.20,37.78,37.83,37.62,5482469\r\n\"HON\",56.98,\"6/11/2007\",\"1:01pm\",-0.40,57.25,57.40,56.91,1964142\r\n\"HPQ\",46.05,\"6/11/2007\",\"1:01pm\",+0.35,45.80,46.29,45.46,4778356\r\n\"IBM\",103.79,\"6/11/2007\",\"1:01pm\",+0.72,102.87,104.00,102.50,2477300\r\n\"INTC\",22.01,\"6/11/2007\",\"1:06pm\",+0.18,21.70,22.02,21.69,21974040\r\n\"JNJ\",62.55,\"6/11/2007\",\"1:01pm\",+0.42,62.89,62.89,62.15,4653538\r\n\"JPM\",50.64,\"6/11/2007\",\"1:01pm\",+0.23,50.41,50.66,50.05,4113700\r\n\"KO\",51.61,\"6/11/2007\",\"1:01pm\",-0.06,51.67,51.82,51.32,5829142\r\n\"MCD\",51.40,\"6/11/2007\",\"1:01pm\",-0.01,51.47,51.62,50.98,2668014\r\n\"MMM\",85.38,\"6/11/2007\",\"1:00pm\",-0.56,85.94,85.98,85.28,1091500\r\n\"MO\",70.29,\"6/11/2007\",\"1:01pm\",-0.01,70.25,70.50,69.76,4483057\r\n\"MRK\",50.95,\"6/11/2007\",\"1:01pm\",+0.81,50.30,51.07,50.04,5728600\r\n\"MSFT\",30.14,\"6/11/2007\",\"1:06pm\",+0.09,30.05,30.25,29.93,21878754\r\n\"PFE\",26.44,\"6/11/2007\",\"1:01pm\",-0.08,26.50,26.53,26.31,11232332\r\n\"PG\",63.01,\"6/11/2007\",\"1:01pm\",-0.06,62.80,63.12,62.75,3117846\r\n\"T\",40.03,\"6/11/2007\",\"1:01pm\",-0.23,40.20,40.25,39.89,6799375\r\n\"UTX\",69.90,\"6/11/2007\",\"1:00pm\",-0.33,69.85,70.20,69.51,1060200\r\n\"VZ\",43.25,\"6/11/2007\",\"1:01pm\",+0.18,42.95,43.34,42.89,4008920\r\n\"WMT\",50.01,\"6/11/2007\",\"1:01pm\",-0.07,49.90,50.04,49.55,5777993\r\n\"XOM\",83.57,\"6/11/2007\",\"1:01pm\",+0.89,82.68,83.72,82.35,6284800\r\n\"AA\",39.52,\"6/11/2007\",\"1:06pm\",-0.14,39.67,40.18,39.43,2284880\r\n\"AIG\",71.69,\"6/11/2007\",\"1:06pm\",+0.16,71.29,71.76,71.15,2274642\r\n\"AXP\",62.95,\"6/11/2007\",\"1:06pm\",-0.09,62.79,63.19,62.42,2065330\r\n\"BA\",97.65,\"6/11/2007\",\"1:06pm\",-0.54,98.25,98.79,97.59,1513500\r\n\"C\",53.57,\"6/11/2007\",\"1:06pm\",+0.24,53.20,53.65,52.81,5889294\r\n\"CAT\",79.07,\"6/11/2007\",\"1:05pm\",+0.55,78.32,79.14,78.06,1549052\r\n\"DD\",50.79,\"6/11/2007\",\"1:06pm\",-0.34,51.13,51.21,50.59,1978300\r\n\"DIS\",34.195,\"6/11/2007\",\"1:06pm\",-0.005,34.28,34.44,34.12,3179550\r\n\"GE\",37.48,\"6/11/2007\",\"1:06pm\",+0.16,37.07,37.49,37.05,11921001\r\n\"GM\",31.34,\"6/11/2007\",\"1:06pm\",+0.34,31.00,31.62,30.90,7418551\r\n\"HD\",37.75,\"6/11/2007\",\"1:06pm\",-0.20,37.78,37.83,37.62,5559869\r\n\"HON\",56.99,\"6/11/2007\",\"1:06pm\",-0.39,57.25,57.40,56.91,2007542\r\n\"HPQ\",46.0627,\"6/11/2007\",\"1:05pm\",+0.3627,45.80,46.29,45.46,4829456\r\n\"IBM\",103.76,\"6/11/2007\",\"1:05pm\",+0.69,102.87,104.00,102.50,2503900\r\n\"INTC\",22.04,\"6/11/2007\",\"1:11pm\",+0.21,21.70,22.05,21.69,22780160\r\n\"JNJ\",62.58,\"6/11/2007\",\"1:06pm\",+0.45,62.89,62.89,62.15,4682578\r\n\"JPM\",50.649,\"6/11/2007\",\"1:06pm\",+0.239,50.41,50.66,50.05,4156000\r\n\"KO\",51.68,\"6/11/2007\",\"1:06pm\",+0.01,51.67,51.82,51.32,5958742\r\n\"MCD\",51.38,\"6/11/2007\",\"1:06pm\",-0.03,51.47,51.62,50.98,2710414\r\n\"MMM\",85.40,\"6/11/2007\",\"1:05pm\",-0.54,85.94,85.98,85.28,1115800\r\n\"MO\",70.29,\"6/11/2007\",\"1:06pm\",-0.01,70.25,70.50,69.76,4510357\r\n\"MRK\",50.95,\"6/11/2007\",\"1:06pm\",+0.81,50.30,51.07,50.04,5784900\r\n\"MSFT\",30.16,\"6/11/2007\",\"1:11pm\",+0.11,30.05,30.25,29.93,22141974\r\n\"PFE\",26.50,\"6/11/2007\",\"1:06pm\",-0.02,26.50,26.53,26.31,11587539\r\n\"PG\",63.07,\"6/11/2007\",\"1:06pm\",0.00,62.80,63.12,62.75,3192946\r\n\"T\",40.13,\"6/11/2007\",\"1:06pm\",-0.13,40.20,40.25,39.89,6957775\r\n\"UTX\",69.91,\"6/11/2007\",\"1:06pm\",-0.32,69.85,70.20,69.51,1075500\r\n\"VZ\",43.2716,\"6/11/2007\",\"1:05pm\",+0.2016,42.95,43.34,42.89,4057020\r\n\"WMT\",50.07,\"6/11/2007\",\"1:06pm\",-0.01,49.90,50.08,49.55,5985207\r\n\"XOM\",83.53,\"6/11/2007\",\"1:06pm\",+0.85,82.68,83.72,82.35,6388600\r\n\"AA\",39.53,\"6/11/2007\",\"1:11pm\",-0.13,39.67,40.18,39.43,2319180\r\n\"AIG\",71.74,\"6/11/2007\",\"1:11pm\",+0.21,71.29,71.76,71.15,2359542\r\n\"AXP\",63.12,\"6/11/2007\",\"1:11pm\",+0.08,62.79,63.19,62.42,2086730\r\n\"BA\",97.75,\"6/11/2007\",\"1:11pm\",-0.44,98.25,98.79,97.59,1556900\r\n\"C\",53.67,\"6/11/2007\",\"1:11pm\",+0.34,53.20,53.69,52.81,6010594\r\n\"CAT\",79.17,\"6/11/2007\",\"1:11pm\",+0.65,78.32,79.19,78.06,1577652\r\n\"DD\",50.79,\"6/11/2007\",\"1:11pm\",-0.34,51.13,51.21,50.59,2054300\r\n\"DIS\",34.22,\"6/11/2007\",\"1:11pm\",+0.02,34.28,34.44,34.12,3245950\r\n\"GE\",37.53,\"6/11/2007\",\"1:11pm\",+0.21,37.07,37.53,37.05,12200301\r\n\"GM\",31.42,\"6/11/2007\",\"1:11pm\",+0.42,31.00,31.62,30.90,7518551\r\n\"HD\",37.78,\"6/11/2007\",\"1:11pm\",-0.17,37.78,37.83,37.62,5639569\r\n\"HON\",57.13,\"6/11/2007\",\"1:11pm\",-0.25,57.25,57.40,56.91,2042642\r\n\"HPQ\",46.12,\"6/11/2007\",\"1:11pm\",+0.42,45.80,46.29,45.46,5833149\r\n\"IBM\",103.86,\"6/11/2007\",\"1:11pm\",+0.79,102.87,104.00,102.50,2536300\r\n\"INTC\",22.05,\"6/11/2007\",\"1:16pm\",+0.22,21.70,22.07,21.69,23218578\r\n\"JNJ\",62.65,\"6/11/2007\",\"1:11pm\",+0.52,62.89,62.89,62.15,4727878\r\n\"JPM\",50.69,\"6/11/2007\",\"1:11pm\",+0.28,50.41,50.695,50.05,4239700\r\n\"KO\",51.74,\"6/11/2007\",\"1:11pm\",+0.07,51.67,51.82,51.32,6054742\r\n\"MCD\",51.44,\"6/11/2007\",\"1:11pm\",+0.03,51.47,51.62,50.98,2762114\r\n\"MMM\",85.53,\"6/11/2007\",\"1:11pm\",-0.41,85.94,85.98,85.28,1145200\r\n\"MO\",70.41,\"6/11/2007\",\"1:11pm\",+0.11,70.25,70.50,69.76,4563257\r\n\"MRK\",51.12,\"6/11/2007\",\"1:11pm\",+0.98,50.30,51.13,50.04,5899900\r\n\"MSFT\",30.17,\"6/11/2007\",\"1:16pm\",+0.12,30.05,30.25,29.93,22399100\r\n\"PFE\",26.5275,\"6/11/2007\",\"1:11pm\",+0.0075,26.50,26.53,26.31,12044278\r\n\"PG\",63.0995,\"6/11/2007\",\"1:11pm\",+0.0295,62.80,63.12,62.75,3229446\r\n\"T\",40.19,\"6/11/2007\",\"1:11pm\",-0.07,40.20,40.25,39.89,7073875\r\n\"UTX\",69.97,\"6/11/2007\",\"1:11pm\",-0.26,69.85,70.20,69.51,1092400\r\n\"VZ\",43.34,\"6/11/2007\",\"1:10pm\",+0.27,42.95,43.34,42.88,4847820\r\n\"WMT\",50.08,\"6/11/2007\",\"1:11pm\",0.00,49.90,50.12,49.55,6200407\r\n\"XOM\",83.66,\"6/11/2007\",\"1:11pm\",+0.98,82.68,83.72,82.35,6490400\r\n\"AA\",39.56,\"6/11/2007\",\"1:16pm\",-0.10,39.67,40.18,39.43,2433580\r\n\"AIG\",71.80,\"6/11/2007\",\"1:16pm\",+0.27,71.29,71.83,71.15,2440996\r\n\"AXP\",63.20,\"6/11/2007\",\"1:15pm\",+0.16,62.79,63.21,62.42,2110030\r\n\"BA\",97.85,\"6/11/2007\",\"1:16pm\",-0.34,98.25,98.79,97.59,1590400\r\n\"C\",53.679,\"6/11/2007\",\"1:16pm\",+0.349,53.20,53.71,52.81,6103294\r\n\"CAT\",79.35,\"6/11/2007\",\"1:16pm\",+0.83,78.32,79.39,78.06,1651052\r\n\"DD\",50.85,\"6/11/2007\",\"1:16pm\",-0.28,51.13,51.21,50.59,2111497\r\n\"DIS\",34.21,\"6/11/2007\",\"1:15pm\",+0.01,34.28,34.44,34.12,3343750\r\n\"GE\",37.535,\"6/11/2007\",\"1:16pm\",+0.215,37.07,37.54,37.05,12385801\r\n\"GM\",31.45,\"6/11/2007\",\"1:16pm\",+0.45,31.00,31.62,30.90,7588451\r\n\"HD\",37.77,\"6/11/2007\",\"1:16pm\",-0.18,37.78,37.83,37.62,5820967\r\n\"HON\",57.11,\"6/11/2007\",\"1:16pm\",-0.27,57.25,57.40,56.91,2057642\r\n\"HPQ\",46.12,\"6/11/2007\",\"1:16pm\",+0.42,45.80,46.29,45.46,5922449\r\n\"IBM\",103.83,\"6/11/2007\",\"1:16pm\",+0.76,102.87,104.00,102.50,2579600\r\n\"INTC\",22.06,\"6/11/2007\",\"1:21pm\",+0.23,21.70,22.08,21.69,23707494\r\n\"JNJ\",62.64,\"6/11/2007\",\"1:16pm\",+0.51,62.89,62.89,62.15,4771978\r\n\"JPM\",50.75,\"6/11/2007\",\"1:16pm\",+0.34,50.41,50.78,50.05,4349600\r\n\"KO\",51.73,\"6/11/2007\",\"1:16pm\",+0.06,51.67,51.82,51.32,6085542\r\n\"MCD\",51.47,\"6/11/2007\",\"1:16pm\",+0.06,51.47,51.62,50.98,2786414\r\n\"MMM\",85.56,\"6/11/2007\",\"1:16pm\",-0.38,85.94,85.98,85.28,1180100\r\n\"MO\",70.37,\"6/11/2007\",\"1:16pm\",+0.07,70.25,70.50,69.76,4636857\r\n\"MRK\",51.15,\"6/11/2007\",\"1:16pm\",+1.01,50.30,51.16,50.04,6009400\r\n\"MSFT\",30.14,\"6/11/2007\",\"1:21pm\",+0.09,30.05,30.25,29.93,23011740\r\n\"PFE\",26.51,\"6/11/2007\",\"1:16pm\",-0.01,26.50,26.54,26.31,12225128\r\n\"PG\",63.10,\"6/11/2007\",\"1:16pm\",+0.03,62.80,63.12,62.75,3329546\r\n\"T\",40.22,\"6/11/2007\",\"1:16pm\",-0.04,40.20,40.26,39.89,7235875\r\n\"UTX\",69.95,\"6/11/2007\",\"1:16pm\",-0.28,69.85,70.20,69.51,1122100\r\n\"VZ\",43.37,\"6/11/2007\",\"1:16pm\",+0.30,42.95,43.38,42.88,4953820\r\n\"WMT\",50.04,\"6/11/2007\",\"1:16pm\",-0.04,49.90,50.12,49.55,6345707\r\n\"XOM\",83.58,\"6/11/2007\",\"1:16pm\",+0.90,82.68,83.72,82.35,6598900\r\n\"AA\",39.58,\"6/11/2007\",\"1:21pm\",-0.08,39.67,40.18,39.43,2500980\r\n\"AIG\",71.90,\"6/11/2007\",\"1:21pm\",+0.37,71.29,71.89,71.15,2494796\r\n\"AXP\",63.22,\"6/11/2007\",\"1:21pm\",+0.18,62.79,63.25,62.42,2135530\r\n\"BA\",97.81,\"6/11/2007\",\"1:20pm\",-0.38,98.25,98.79,97.59,1608700\r\n\"C\",53.76,\"6/11/2007\",\"1:21pm\",+0.43,53.20,53.77,52.81,6249294\r\n\"CAT\",79.45,\"6/11/2007\",\"1:21pm\",+0.93,78.32,79.45,78.06,1689152\r\n\"DD\",50.89,\"6/11/2007\",\"1:21pm\",-0.24,51.13,51.21,50.59,2132297\r\n\"DIS\",34.23,\"6/11/2007\",\"1:20pm\",+0.03,34.28,34.44,34.12,3370250\r\n\"GE\",37.53,\"6/11/2007\",\"1:21pm\",+0.21,37.07,37.56,37.05,12700001\r\n\"GM\",31.43,\"6/11/2007\",\"1:21pm\",+0.43,31.00,31.62,30.90,7705751\r\n\"HD\",37.76,\"6/11/2007\",\"1:21pm\",-0.19,37.78,37.83,37.62,5879467\r\n\"HON\",57.17,\"6/11/2007\",\"1:21pm\",-0.21,57.25,57.40,56.91,2107242\r\n\"HPQ\",46.17,\"6/11/2007\",\"1:21pm\",+0.47,45.80,46.29,45.46,5961749\r\n\"IBM\",103.81,\"6/11/2007\",\"1:21pm\",+0.74,102.87,104.00,102.50,2608100\r\n\"INTC\",22.04,\"6/11/2007\",\"1:26pm\",+0.21,21.70,22.08,21.69,24314782\r\n\"JNJ\",62.66,\"6/11/2007\",\"1:21pm\",+0.53,62.89,62.89,62.15,4802578\r\n\"JPM\",50.83,\"6/11/2007\",\"1:21pm\",+0.42,50.41,50.84,50.05,4419800\r\n\"KO\",51.79,\"6/11/2007\",\"1:21pm\",+0.12,51.67,51.82,51.32,6135742\r\n\"MCD\",51.48,\"6/11/2007\",\"1:21pm\",+0.07,51.47,51.62,50.98,2853314\r\n\"MMM\",85.64,\"6/11/2007\",\"1:21pm\",-0.30,85.94,85.98,85.28,1258800\r\n\"MO\",70.41,\"6/11/2007\",\"1:21pm\",+0.11,70.25,70.50,69.76,4723085\r\n\"MRK\",51.22,\"6/11/2007\",\"1:21pm\",+1.08,50.30,51.23,50.04,6148300\r\n\"MSFT\",30.1301,\"6/11/2007\",\"1:25pm\",+0.0801,30.05,30.25,29.93,23292696\r\n\"PFE\",26.50,\"6/11/2007\",\"1:21pm\",-0.02,26.50,26.54,26.31,12653153\r\n\"PG\",63.10,\"6/11/2007\",\"1:21pm\",+0.03,62.80,63.12,62.75,3736946\r\n\"T\",40.28,\"6/11/2007\",\"1:21pm\",+0.02,40.20,40.29,39.89,7365200\r\n\"UTX\",70.02,\"6/11/2007\",\"1:21pm\",-0.21,69.85,70.20,69.51,1158800\r\n\"VZ\",43.43,\"6/11/2007\",\"1:21pm\",+0.36,42.95,43.44,42.88,5053620\r\n\"WMT\",50.02,\"6/11/2007\",\"1:21pm\",-0.06,49.90,50.12,49.55,6585307\r\n\"XOM\",83.66,\"6/11/2007\",\"1:21pm\",+0.98,82.68,83.72,82.35,6705800\r\n\"AA\",39.52,\"6/11/2007\",\"1:26pm\",-0.14,39.67,40.18,39.43,2540480\r\n\"AIG\",71.85,\"6/11/2007\",\"1:26pm\",+0.32,71.29,71.90,71.15,2549996\r\n\"AXP\",63.18,\"6/11/2007\",\"1:26pm\",+0.14,62.79,63.26,62.42,2157330\r\n\"BA\",97.71,\"6/11/2007\",\"1:26pm\",-0.48,98.25,98.79,97.59,1636600\r\n\"C\",53.72,\"6/11/2007\",\"1:26pm\",+0.39,53.20,53.77,52.81,6427294\r\n\"CAT\",79.20,\"6/11/2007\",\"1:26pm\",+0.68,78.32,79.46,78.06,1768752\r\n\"DD\",50.86,\"6/11/2007\",\"1:26pm\",-0.27,51.13,51.21,50.59,2150397\r\n\"DIS\",34.22,\"6/11/2007\",\"1:26pm\",+0.02,34.28,34.44,34.12,3496150\r\n\"GE\",37.50,\"6/11/2007\",\"1:26pm\",+0.18,37.07,37.56,37.05,12934301\r\n\"GM\",31.40,\"6/11/2007\",\"1:26pm\",+0.40,31.00,31.62,30.90,7805351\r\n\"HD\",37.75,\"6/11/2007\",\"1:26pm\",-0.20,37.78,37.83,37.62,5995967\r\n\"HON\",57.20,\"6/11/2007\",\"1:26pm\",-0.18,57.25,57.40,56.91,2149442\r\n\"HPQ\",46.14,\"6/11/2007\",\"1:26pm\",+0.44,45.80,46.29,45.46,6019149\r\n\"IBM\",103.70,\"6/11/2007\",\"1:26pm\",+0.63,102.87,104.00,102.50,2638000\r\n\"INTC\",22.04,\"6/11/2007\",\"1:31pm\",+0.21,21.70,22.08,21.69,24406300\r\n\"JNJ\",62.63,\"6/11/2007\",\"1:26pm\",+0.50,62.89,62.89,62.15,4835178\r\n\"JPM\",50.81,\"6/11/2007\",\"1:26pm\",+0.40,50.41,50.84,50.05,4480900\r\n\"KO\",51.78,\"6/11/2007\",\"1:26pm\",+0.11,51.67,51.85,51.32,6233852\r\n\"MCD\",51.44,\"6/11/2007\",\"1:26pm\",+0.03,51.47,51.62,50.98,2881414\r\n\"MMM\",85.62,\"6/11/2007\",\"1:26pm\",-0.32,85.94,85.98,85.28,1282500\r\n\"MO\",70.36,\"6/11/2007\",\"1:26pm\",+0.06,70.25,70.50,69.76,4767885\r\n\"MRK\",51.23,\"6/11/2007\",\"1:26pm\",+1.09,50.30,51.27,50.04,6334000\r\n\"MSFT\",30.12,\"6/11/2007\",\"1:31pm\",+0.07,30.05,30.25,29.93,23578844\r\n\"PFE\",26.50,\"6/11/2007\",\"1:26pm\",-0.02,26.50,26.54,26.31,14778927\r\n\"PG\",63.09,\"6/11/2007\",\"1:26pm\",+0.02,62.80,63.12,62.75,3928946\r\n\"T\",40.31,\"6/11/2007\",\"1:26pm\",+0.05,40.20,40.33,39.89,7505100\r\n\"UTX\",69.98,\"6/11/2007\",\"1:26pm\",-0.25,69.85,70.20,69.51,1210900\r\n\"VZ\",43.40,\"6/11/2007\",\"1:26pm\",+0.33,42.95,43.45,42.88,5123120\r\n\"WMT\",49.96,\"6/11/2007\",\"1:26pm\",-0.12,49.90,50.12,49.55,6687607\r\n\"XOM\",83.57,\"6/11/2007\",\"1:26pm\",+0.89,82.68,83.72,82.35,6797200\r\n\"AA\",39.53,\"6/11/2007\",\"1:31pm\",-0.13,39.67,40.18,39.43,2572580\r\n\"AIG\",71.85,\"6/11/2007\",\"1:31pm\",+0.32,71.29,71.90,71.15,2602596\r\n\"AXP\",63.26,\"6/11/2007\",\"1:31pm\",+0.22,62.79,63.26,62.42,2172130\r\n\"BA\",97.82,\"6/11/2007\",\"1:31pm\",-0.37,98.25,98.79,97.59,1659000\r\n\"C\",53.74,\"6/11/2007\",\"1:31pm\",+0.41,53.20,53.77,52.81,6462694\r\n\"CAT\",79.20,\"6/11/2007\",\"1:31pm\",+0.68,78.32,79.46,78.06,1829052\r\n\"DD\",50.85,\"6/11/2007\",\"1:31pm\",-0.28,51.13,51.21,50.59,2171097\r\n\"DIS\",34.24,\"6/11/2007\",\"1:31pm\",+0.04,34.28,34.44,34.12,3579950\r\n\"GE\",37.52,\"6/11/2007\",\"1:31pm\",+0.20,37.07,37.56,37.05,13012501\r\n\"GM\",31.42,\"6/11/2007\",\"1:31pm\",+0.42,31.00,31.62,30.90,7927151\r\n\"HD\",37.76,\"6/11/2007\",\"1:31pm\",-0.19,37.78,37.83,37.62,6189667\r\n\"HON\",57.21,\"6/11/2007\",\"1:31pm\",-0.17,57.25,57.40,56.91,2172142\r\n\"HPQ\",46.14,\"6/11/2007\",\"1:31pm\",+0.44,45.80,46.29,45.46,6057949\r\n\"IBM\",103.63,\"6/11/2007\",\"1:31pm\",+0.56,102.87,104.00,102.50,2660504\r\n\"INTC\",22.04,\"6/11/2007\",\"1:36pm\",+0.21,21.70,22.08,21.69,24689632\r\n\"JNJ\",62.61,\"6/11/2007\",\"1:31pm\",+0.48,62.89,62.89,62.15,4868578\r\n\"JPM\",50.80,\"6/11/2007\",\"1:31pm\",+0.39,50.41,50.84,50.05,4526800\r\n\"KO\",51.75,\"6/11/2007\",\"1:31pm\",+0.08,51.67,51.85,51.32,6294152\r\n\"MCD\",51.43,\"6/11/2007\",\"1:31pm\",+0.02,51.47,51.62,50.98,2901114\r\n\"MMM\",85.59,\"6/11/2007\",\"1:31pm\",-0.35,85.94,85.98,85.28,1297000\r\n\"MO\",70.34,\"6/11/2007\",\"1:31pm\",+0.04,70.25,70.50,69.76,4818885\r\n\"MRK\",51.24,\"6/11/2007\",\"1:31pm\",+1.10,50.30,51.27,50.04,6438900\r\n\"MSFT\",30.125,\"6/11/2007\",\"1:36pm\",+0.075,30.05,30.25,29.93,23983122\r\n\"PFE\",26.51,\"6/11/2007\",\"1:31pm\",-0.01,26.50,26.54,26.31,14886127\r\n\"PG\",63.08,\"6/11/2007\",\"1:31pm\",+0.01,62.80,63.13,62.75,3971046\r\n\"T\",40.31,\"6/11/2007\",\"1:31pm\",+0.05,40.20,40.33,39.89,7610100\r\n\"UTX\",70.05,\"6/11/2007\",\"1:31pm\",-0.18,69.85,70.20,69.51,1239200\r\n\"VZ\",43.39,\"6/11/2007\",\"1:31pm\",+0.32,42.95,43.45,42.88,5198120\r\n\"WMT\",50.00,\"6/11/2007\",\"1:31pm\",-0.08,49.90,50.12,49.55,6808307\r\n\"XOM\",83.60,\"6/11/2007\",\"1:31pm\",+0.92,82.68,83.72,82.35,6858500\r\n\"AA\",39.54,\"6/11/2007\",\"1:36pm\",-0.12,39.67,40.18,39.43,2615380\r\n\"AIG\",71.90,\"6/11/2007\",\"1:36pm\",+0.37,71.29,71.90,71.15,2648396\r\n\"AXP\",63.27,\"6/11/2007\",\"1:36pm\",+0.23,62.79,63.28,62.42,2187930\r\n\"BA\",97.88,\"6/11/2007\",\"1:36pm\",-0.31,98.25,98.79,97.59,1700700\r\n\"C\",53.73,\"6/11/2007\",\"1:36pm\",+0.40,53.20,53.77,52.81,6558794\r\n\"CAT\",79.20,\"6/11/2007\",\"1:36pm\",+0.68,78.32,79.46,78.06,1869352\r\n\"DD\",50.89,\"6/11/2007\",\"1:36pm\",-0.24,51.13,51.21,50.59,2184797\r\n\"DIS\",34.22,\"6/11/2007\",\"1:36pm\",+0.02,34.28,34.44,34.12,3654250\r\n\"GE\",37.51,\"6/11/2007\",\"1:36pm\",+0.19,37.07,37.56,37.05,13175301\r\n\"GM\",31.45,\"6/11/2007\",\"1:36pm\",+0.45,31.00,31.62,30.90,8227251\r\n\"HD\",37.76,\"6/11/2007\",\"1:36pm\",-0.19,37.78,37.83,37.62,6273567\r\n\"HON\",57.24,\"6/11/2007\",\"1:36pm\",-0.14,57.25,57.40,56.91,2203642\r\n\"HPQ\",46.17,\"6/11/2007\",\"1:36pm\",+0.47,45.80,46.29,45.46,6121849\r\n\"IBM\",103.59,\"6/11/2007\",\"1:36pm\",+0.52,102.87,104.00,102.50,2702104\r\n\"INTC\",22.02,\"6/11/2007\",\"1:41pm\",+0.19,21.70,22.08,21.69,25010946\r\n\"JNJ\",62.60,\"6/11/2007\",\"1:36pm\",+0.47,62.89,62.89,62.15,4923778\r\n\"JPM\",50.77,\"6/11/2007\",\"1:36pm\",+0.36,50.41,50.84,50.05,4660900\r\n\"KO\",51.79,\"6/11/2007\",\"1:36pm\",+0.12,51.67,51.85,51.32,6365852\r\n\"MCD\",51.42,\"6/11/2007\",\"1:36pm\",+0.01,51.47,51.62,50.98,2935214\r\n\"MMM\",85.57,\"6/11/2007\",\"1:36pm\",-0.37,85.94,85.98,85.28,1313500\r\n\"MO\",70.37,\"6/11/2007\",\"1:36pm\",+0.07,70.25,70.50,69.76,4877785\r\n\"MRK\",51.24,\"6/11/2007\",\"1:36pm\",+1.10,50.30,51.28,50.04,6515500\r\n\"MSFT\",30.12,\"6/11/2007\",\"1:41pm\",+0.07,30.05,30.25,29.93,24255316\r\n\"PFE\",26.491,\"6/11/2007\",\"1:36pm\",-0.029,26.50,26.54,26.31,15109927\r\n\"PG\",63.12,\"6/11/2007\",\"1:36pm\",+0.05,62.80,63.13,62.75,4002446\r\n\"T\",40.30,\"6/11/2007\",\"1:36pm\",+0.04,40.20,40.33,39.89,7712400\r\n\"UTX\",70.10,\"6/11/2007\",\"1:36pm\",-0.13,69.85,70.20,69.51,1247400\r\n\"VZ\",43.40,\"6/11/2007\",\"1:36pm\",+0.33,42.95,43.45,42.88,5341720\r\n\"WMT\",49.95,\"6/11/2007\",\"1:36pm\",-0.13,49.90,50.12,49.55,6914407\r\n\"XOM\",83.56,\"6/11/2007\",\"1:36pm\",+0.88,82.68,83.72,82.35,6917800\r\n\"AA\",39.53,\"6/11/2007\",\"1:41pm\",-0.13,39.67,40.18,39.43,2647380\r\n\"AIG\",71.87,\"6/11/2007\",\"1:41pm\",+0.34,71.29,71.90,71.15,2686796\r\n\"AXP\",63.28,\"6/11/2007\",\"1:41pm\",+0.24,62.79,63.32,62.42,2205330\r\n\"BA\",97.82,\"6/11/2007\",\"1:41pm\",-0.37,98.25,98.79,97.59,1725200\r\n\"C\",53.67,\"6/11/2007\",\"1:41pm\",+0.34,53.20,53.77,52.81,6740594\r\n\"CAT\",79.11,\"6/11/2007\",\"1:41pm\",+0.59,78.32,79.46,78.06,1880652\r\n\"DD\",50.861,\"6/11/2007\",\"1:41pm\",-0.269,51.13,51.21,50.59,2199797\r\n\"DIS\",34.21,\"6/11/2007\",\"1:41pm\",+0.01,34.28,34.44,34.12,3734350\r\n\"GE\",37.52,\"6/11/2007\",\"1:41pm\",+0.20,37.07,37.56,37.05,13566001\r\n\"GM\",31.47,\"6/11/2007\",\"1:41pm\",+0.47,31.00,31.62,30.90,8330251\r\n\"HD\",37.75,\"6/11/2007\",\"1:41pm\",-0.20,37.78,37.83,37.62,6627167\r\n\"HON\",57.17,\"6/11/2007\",\"1:41pm\",-0.21,57.25,57.40,56.91,2246142\r\n\"HPQ\",46.14,\"6/11/2007\",\"1:41pm\",+0.44,45.80,46.29,45.46,6190049\r\n\"IBM\",103.54,\"6/11/2007\",\"1:41pm\",+0.47,102.87,104.00,102.50,2715404\r\n\"INTC\",22.02,\"6/11/2007\",\"1:46pm\",+0.19,21.70,22.08,21.69,25285316\r\n\"JNJ\",62.54,\"6/11/2007\",\"1:41pm\",+0.41,62.89,62.89,62.15,5098378\r\n\"JPM\",50.77,\"6/11/2007\",\"1:41pm\",+0.36,50.41,50.84,50.05,5025900\r\n\"KO\",51.725,\"6/11/2007\",\"1:41pm\",+0.055,51.67,51.85,51.32,6390552\r\n\"MCD\",51.391,\"6/11/2007\",\"1:41pm\",-0.019,51.47,51.62,50.98,2972614\r\n\"MMM\",85.58,\"6/11/2007\",\"1:41pm\",-0.36,85.94,85.98,85.28,1337300\r\n\"MO\",70.29,\"6/11/2007\",\"1:41pm\",-0.01,70.25,70.50,69.76,4907285\r\n\"MRK\",51.21,\"6/11/2007\",\"1:41pm\",+1.07,50.30,51.28,50.04,6587800\r\n\"MSFT\",30.13,\"6/11/2007\",\"1:46pm\",+0.08,30.05,30.25,29.93,24497374\r\n\"PFE\",26.49,\"6/11/2007\",\"1:41pm\",-0.03,26.50,26.54,26.31,15538627\r\n\"PG\",63.13,\"6/11/2007\",\"1:41pm\",+0.06,62.80,63.14,62.75,4048446\r\n\"T\",40.31,\"6/11/2007\",\"1:41pm\",+0.05,40.20,40.34,39.89,7832500\r\n\"UTX\",70.07,\"6/11/2007\",\"1:41pm\",-0.16,69.85,70.20,69.51,1264700\r\n\"VZ\",43.38,\"6/11/2007\",\"1:41pm\",+0.31,42.95,43.45,42.88,5395220\r\n\"WMT\",49.90,\"6/11/2007\",\"1:41pm\",-0.18,49.90,50.12,49.55,7009107\r\n\"XOM\",83.53,\"6/11/2007\",\"1:41pm\",+0.85,82.68,83.72,82.35,7007700\r\n\"AA\",39.55,\"6/11/2007\",\"1:46pm\",-0.11,39.67,40.18,39.43,2672280\r\n\"AIG\",71.88,\"6/11/2007\",\"1:46pm\",+0.35,71.29,71.90,71.15,2724196\r\n\"AXP\",63.28,\"6/11/2007\",\"1:46pm\",+0.24,62.79,63.32,62.42,2214330\r\n\"BA\",97.98,\"6/11/2007\",\"1:46pm\",-0.21,98.25,98.79,97.59,1773500\r\n\"C\",53.61,\"6/11/2007\",\"1:46pm\",+0.28,53.20,53.77,52.81,6867594\r\n\"CAT\",79.07,\"6/11/2007\",\"1:46pm\",+0.55,78.32,79.46,78.06,1923652\r\n\"DD\",50.86,\"6/11/2007\",\"1:46pm\",-0.27,51.13,51.21,50.59,2221297\r\n\"DIS\",34.201,\"6/11/2007\",\"1:46pm\",+0.001,34.28,34.44,34.12,3795650\r\n\"GE\",37.52,\"6/11/2007\",\"1:46pm\",+0.20,37.07,37.56,37.05,13711301\r\n\"GM\",31.50,\"6/11/2007\",\"1:46pm\",+0.50,31.00,31.62,30.90,8678851\r\n\"HD\",37.77,\"6/11/2007\",\"1:46pm\",-0.18,37.78,37.83,37.62,6687867\r\n\"HON\",57.162,\"6/11/2007\",\"1:46pm\",-0.218,57.25,57.40,56.91,2293142\r\n\"HPQ\",46.13,\"6/11/2007\",\"1:46pm\",+0.43,45.80,46.29,45.46,6259749\r\n\"IBM\",103.57,\"6/11/2007\",\"1:46pm\",+0.50,102.87,104.00,102.50,2740104\r\n\"INTC\",22.02,\"6/11/2007\",\"1:51pm\",+0.19,21.70,22.08,21.69,25632092\r\n\"JNJ\",62.54,\"6/11/2007\",\"1:46pm\",+0.41,62.89,62.89,62.15,5147978\r\n\"JPM\",50.80,\"6/11/2007\",\"1:46pm\",+0.39,50.41,50.84,50.05,5114400\r\n\"KO\",51.75,\"6/11/2007\",\"1:46pm\",+0.08,51.67,51.85,51.32,6441352\r\n\"MCD\",51.40,\"6/11/2007\",\"1:46pm\",-0.01,51.47,51.62,50.98,3049814\r\n\"MMM\",85.59,\"6/11/2007\",\"1:46pm\",-0.35,85.94,85.98,85.28,1347600\r\n\"MO\",70.28,\"6/11/2007\",\"1:46pm\",-0.02,70.25,70.50,69.76,4959585\r\n\"MRK\",51.18,\"6/11/2007\",\"1:46pm\",+1.04,50.30,51.28,50.04,6651700\r\n\"MSFT\",30.11,\"6/11/2007\",\"1:51pm\",+0.06,30.05,30.25,29.93,24742128\r\n\"PFE\",26.44,\"6/11/2007\",\"1:46pm\",-0.08,26.50,26.54,26.31,15813727\r\n\"PG\",63.12,\"6/11/2007\",\"1:46pm\",+0.05,62.80,63.14,62.75,4099546\r\n\"T\",40.3073,\"6/11/2007\",\"1:46pm\",+0.0473,40.20,40.34,39.89,7901800\r\n\"UTX\",70.10,\"6/11/2007\",\"1:46pm\",-0.13,69.85,70.20,69.51,1284300\r\n\"VZ\",43.42,\"6/11/2007\",\"1:46pm\",+0.35,42.95,43.45,42.88,5473215\r\n\"WMT\",49.94,\"6/11/2007\",\"1:46pm\",-0.14,49.90,50.12,49.55,7155307\r\n\"XOM\",83.66,\"6/11/2007\",\"1:46pm\",+0.98,82.68,83.72,82.35,7157900\r\n\"AA\",39.55,\"6/11/2007\",\"1:51pm\",-0.11,39.67,40.18,39.43,2705680\r\n\"AIG\",71.84,\"6/11/2007\",\"1:51pm\",+0.31,71.29,71.90,71.15,2760896\r\n\"AXP\",63.245,\"6/11/2007\",\"1:51pm\",+0.205,62.79,63.32,62.42,2226930\r\n\"BA\",97.96,\"6/11/2007\",\"1:51pm\",-0.23,98.25,98.79,97.59,1793700\r\n\"C\",53.63,\"6/11/2007\",\"1:51pm\",+0.30,53.20,53.77,52.81,6926294\r\n\"CAT\",79.04,\"6/11/2007\",\"1:51pm\",+0.52,78.32,79.46,78.06,1958652\r\n\"DD\",50.83,\"6/11/2007\",\"1:51pm\",-0.30,51.13,51.21,50.59,2232897\r\n\"DIS\",34.20,\"6/11/2007\",\"1:50pm\",0.00,34.28,34.44,34.12,3823150\r\n\"GE\",37.52,\"6/11/2007\",\"1:51pm\",+0.20,37.07,37.56,37.05,13797801\r\n\"GM\",31.47,\"6/11/2007\",\"1:51pm\",+0.47,31.00,31.62,30.90,8801851\r\n\"HD\",37.77,\"6/11/2007\",\"1:51pm\",-0.18,37.78,37.83,37.62,6761167\r\n\"HON\",57.13,\"6/11/2007\",\"1:51pm\",-0.25,57.25,57.40,56.91,2327542\r\n\"HPQ\",46.12,\"6/11/2007\",\"1:51pm\",+0.42,45.80,46.29,45.46,6316649\r\n\"IBM\",103.55,\"6/11/2007\",\"1:51pm\",+0.48,102.87,104.00,102.50,2777904\r\n\"INTC\",22.03,\"6/11/2007\",\"1:56pm\",+0.20,21.70,22.08,21.69,26065954\r\n\"JNJ\",62.53,\"6/11/2007\",\"1:51pm\",+0.40,62.89,62.89,62.15,5210478\r\n\"JPM\",50.81,\"6/11/2007\",\"1:51pm\",+0.40,50.41,50.84,50.05,5222100\r\n\"KO\",51.75,\"6/11/2007\",\"1:51pm\",+0.08,51.67,51.85,51.32,6558652\r\n\"MCD\",51.39,\"6/11/2007\",\"1:51pm\",-0.02,51.47,51.62,50.98,3074814\r\n\"MMM\",85.548,\"6/11/2007\",\"1:51pm\",-0.392,85.94,85.98,85.28,1372000\r\n\"MO\",70.27,\"6/11/2007\",\"1:50pm\",-0.03,70.25,70.50,69.76,4986985\r\n\"MRK\",51.16,\"6/11/2007\",\"1:51pm\",+1.02,50.30,51.28,50.04,6821600\r\n\"MSFT\",30.13,\"6/11/2007\",\"1:56pm\",+0.08,30.05,30.25,29.93,24970772\r\n\"PFE\",26.448,\"6/11/2007\",\"1:51pm\",-0.072,26.50,26.54,26.31,16248877\r\n\"PG\",63.13,\"6/11/2007\",\"1:51pm\",+0.06,62.80,63.14,62.75,4157646\r\n\"T\",40.31,\"6/11/2007\",\"1:51pm\",+0.05,40.20,40.34,39.89,8018500\r\n\"UTX\",70.15,\"6/11/2007\",\"1:51pm\",-0.08,69.85,70.20,69.51,1321400\r\n\"VZ\",43.44,\"6/11/2007\",\"1:51pm\",+0.37,42.95,43.45,42.88,5538015\r\n\"WMT\",49.86,\"6/11/2007\",\"1:51pm\",-0.22,49.90,50.12,49.55,7285407\r\n\"XOM\",83.56,\"6/11/2007\",\"1:51pm\",+0.88,82.68,83.72,82.35,7364600\r\n\"AA\",39.53,\"6/11/2007\",\"1:56pm\",-0.13,39.67,40.18,39.43,2721380\r\n\"AIG\",71.7918,\"6/11/2007\",\"1:56pm\",+0.2618,71.29,71.90,71.15,2817696\r\n\"AXP\",63.20,\"6/11/2007\",\"1:56pm\",+0.16,62.79,63.32,62.42,2237230\r\n\"BA\",97.89,\"6/11/2007\",\"1:56pm\",-0.30,98.25,98.79,97.59,1808600\r\n\"C\",53.60,\"6/11/2007\",\"1:56pm\",+0.27,53.20,53.77,52.81,7078394\r\n\"CAT\",79.02,\"6/11/2007\",\"1:56pm\",+0.50,78.32,79.46,78.06,1980452\r\n\"DD\",50.83,\"6/11/2007\",\"1:56pm\",-0.30,51.13,51.21,50.59,2245697\r\n\"DIS\",34.195,\"6/11/2007\",\"1:56pm\",-0.005,34.28,34.44,34.12,3863950\r\n\"GE\",37.52,\"6/11/2007\",\"1:56pm\",+0.20,37.07,37.56,37.05,13986001\r\n\"GM\",31.49,\"6/11/2007\",\"1:56pm\",+0.49,31.00,31.62,30.90,8849851\r\n\"HD\",37.75,\"6/11/2007\",\"1:56pm\",-0.20,37.78,37.83,37.62,6819267\r\n\"HON\",57.11,\"6/11/2007\",\"1:56pm\",-0.27,57.25,57.40,56.91,2354542\r\n\"HPQ\",46.122,\"6/11/2007\",\"1:56pm\",+0.422,45.80,46.29,45.46,6380249\r\n\"IBM\",103.53,\"6/11/2007\",\"1:56pm\",+0.46,102.87,104.00,102.50,2802904\r\n\"INTC\",22.01,\"6/11/2007\",\"2:01pm\",+0.18,21.70,22.08,21.69,26445260\r\n\"JNJ\",62.52,\"6/11/2007\",\"1:56pm\",+0.39,62.89,62.89,62.15,5265678\r\n\"JPM\",50.77,\"6/11/2007\",\"1:56pm\",+0.36,50.41,50.84,50.05,5291300\r\n\"KO\",51.7619,\"6/11/2007\",\"1:56pm\",+0.0919,51.67,51.85,51.32,6606152\r\n\"MCD\",51.36,\"6/11/2007\",\"1:56pm\",-0.05,51.47,51.62,50.98,3110114\r\n\"MMM\",85.53,\"6/11/2007\",\"1:56pm\",-0.41,85.94,85.98,85.28,1402100\r\n\"MO\",70.27,\"6/11/2007\",\"1:56pm\",-0.03,70.25,70.50,69.76,5016285\r\n\"MRK\",51.11,\"6/11/2007\",\"1:56pm\",+0.97,50.30,51.28,50.04,6899000\r\n\"MSFT\",30.12,\"6/11/2007\",\"2:01pm\",+0.07,30.05,30.25,29.93,25277544\r\n\"PFE\",26.44,\"6/11/2007\",\"1:56pm\",-0.08,26.50,26.54,26.31,16455677\r\n\"PG\",63.13,\"6/11/2007\",\"1:56pm\",+0.06,62.80,63.15,62.75,4256946\r\n\"T\",40.28,\"6/11/2007\",\"1:56pm\",+0.02,40.20,40.34,39.89,8132800\r\n\"UTX\",70.154,\"6/11/2007\",\"1:56pm\",-0.076,69.85,70.20,69.51,1344300\r\n\"VZ\",43.47,\"6/11/2007\",\"1:56pm\",+0.40,42.95,43.47,42.88,5621815\r\n\"WMT\",49.88,\"6/11/2007\",\"1:56pm\",-0.20,49.90,50.12,49.55,7420907\r\n\"XOM\",83.52,\"6/11/2007\",\"1:56pm\",+0.84,82.68,83.72,82.35,7459300\r\n\"AA\",39.491,\"6/11/2007\",\"2:01pm\",-0.169,39.67,40.18,39.43,2763280\r\n\"AIG\",71.77,\"6/11/2007\",\"2:01pm\",+0.24,71.29,71.90,71.15,2879896\r\n\"AXP\",63.17,\"6/11/2007\",\"2:01pm\",+0.13,62.79,63.32,62.42,2261630\r\n\"BA\",97.83,\"6/11/2007\",\"2:01pm\",-0.36,98.25,98.79,97.59,1833100\r\n\"C\",53.55,\"6/11/2007\",\"2:01pm\",+0.22,53.20,53.77,52.81,7307094\r\n\"CAT\",79.00,\"6/11/2007\",\"2:01pm\",+0.48,78.32,79.46,78.06,2025552\r\n\"DD\",50.85,\"6/11/2007\",\"2:01pm\",-0.28,51.13,51.21,50.59,2277197\r\n\"DIS\",34.20,\"6/11/2007\",\"2:01pm\",0.00,34.28,34.44,34.12,3931450\r\n\"GE\",37.51,\"6/11/2007\",\"2:01pm\",+0.19,37.07,37.56,37.05,14260601\r\n\"GM\",31.50,\"6/11/2007\",\"2:01pm\",+0.50,31.00,31.62,30.90,8948751\r\n\"HD\",37.75,\"6/11/2007\",\"2:01pm\",-0.20,37.78,37.83,37.62,7227367\r\n\"HON\",57.13,\"6/11/2007\",\"2:01pm\",-0.25,57.25,57.40,56.91,2422142\r\n\"HPQ\",46.08,\"6/11/2007\",\"2:01pm\",+0.38,45.80,46.29,45.46,6426249\r\n\"IBM\",103.51,\"6/11/2007\",\"2:01pm\",+0.44,102.87,104.00,102.50,2839204\r\n\"INTC\",22.02,\"6/11/2007\",\"2:06pm\",+0.19,21.70,22.08,21.69,26751104\r\n\"JNJ\",62.50,\"6/11/2007\",\"2:01pm\",+0.37,62.89,62.89,62.15,5350858\r\n\"JPM\",50.70,\"6/11/2007\",\"2:01pm\",+0.29,50.41,50.84,50.05,5396400\r\n\"KO\",51.76,\"6/11/2007\",\"2:01pm\",+0.09,51.67,51.85,51.32,6660752\r\n\"MCD\",51.3727,\"6/11/2007\",\"2:01pm\",-0.0373,51.47,51.62,50.98,3168414\r\n\"MMM\",85.58,\"6/11/2007\",\"2:01pm\",-0.36,85.94,85.98,85.28,1440600\r\n\"MO\",70.24,\"6/11/2007\",\"2:01pm\",-0.06,70.25,70.50,69.76,5077085\r\n\"MRK\",51.09,\"6/11/2007\",\"2:01pm\",+0.95,50.30,51.28,50.04,6995800\r\n\"MSFT\",30.12,\"6/11/2007\",\"2:06pm\",+0.07,30.05,30.25,29.93,25466984\r\n\"PFE\",26.44,\"6/11/2007\",\"2:01pm\",-0.08,26.50,26.54,26.31,16750477\r\n\"PG\",63.12,\"6/11/2007\",\"2:01pm\",+0.05,62.80,63.15,62.75,4334646\r\n\"T\",40.27,\"6/11/2007\",\"2:01pm\",+0.01,40.20,40.34,39.89,8457200\r\n\"UTX\",70.11,\"6/11/2007\",\"2:01pm\",-0.12,69.85,70.20,69.51,1362600\r\n\"VZ\",43.46,\"6/11/2007\",\"2:01pm\",+0.39,42.95,43.47,42.88,5698015\r\n\"WMT\",49.79,\"6/11/2007\",\"2:01pm\",-0.29,49.90,50.12,49.55,7569507\r\n\"XOM\",83.43,\"6/11/2007\",\"2:01pm\",+0.75,82.68,83.72,82.35,7716900\r\n\"AA\",39.50,\"6/11/2007\",\"2:06pm\",-0.16,39.67,40.18,39.43,2841280\r\n\"AIG\",71.85,\"6/11/2007\",\"2:06pm\",+0.32,71.29,71.90,71.15,2945096\r\n\"AXP\",63.23,\"6/11/2007\",\"2:06pm\",+0.19,62.79,63.32,62.42,2280130\r\n\"BA\",97.92,\"6/11/2007\",\"2:06pm\",-0.27,98.25,98.79,97.59,1847000\r\n\"C\",53.56,\"6/11/2007\",\"2:06pm\",+0.23,53.20,53.77,52.81,7466394\r\n\"CAT\",79.11,\"6/11/2007\",\"2:06pm\",+0.59,78.32,79.46,78.06,2048252\r\n\"DD\",50.86,\"6/11/2007\",\"2:06pm\",-0.27,51.13,51.21,50.59,2309597\r\n\"DIS\",34.19,\"6/11/2007\",\"2:06pm\",-0.01,34.28,34.44,34.12,3986950\r\n\"GE\",37.51,\"6/11/2007\",\"2:06pm\",+0.19,37.07,37.56,37.05,14444801\r\n\"GM\",31.50,\"6/11/2007\",\"2:06pm\",+0.50,31.00,31.62,30.90,9108451\r\n\"HD\",37.75,\"6/11/2007\",\"2:06pm\",-0.20,37.78,37.83,37.62,7407867\r\n\"HON\",57.11,\"6/11/2007\",\"2:06pm\",-0.27,57.25,57.40,56.91,2462942\r\n\"HPQ\",46.08,\"6/11/2007\",\"2:06pm\",+0.38,45.80,46.29,45.46,6509849\r\n\"IBM\",103.54,\"6/11/2007\",\"2:06pm\",+0.47,102.87,104.00,102.50,2861904\r\n\"INTC\",22.03,\"6/11/2007\",\"2:11pm\",+0.20,21.70,22.08,21.69,26974348\r\n\"JNJ\",62.50,\"6/11/2007\",\"2:06pm\",+0.37,62.89,62.89,62.15,5513358\r\n\"JPM\",50.65,\"6/11/2007\",\"2:06pm\",+0.24,50.41,50.84,50.05,5565600\r\n\"KO\",51.77,\"6/11/2007\",\"2:06pm\",+0.10,51.67,51.85,51.32,6687652\r\n\"MCD\",51.41,\"6/11/2007\",\"2:06pm\",0.00,51.47,51.62,50.98,3209614\r\n\"MMM\",85.53,\"6/11/2007\",\"2:06pm\",-0.41,85.94,85.98,85.28,1470200\r\n\"MO\",70.29,\"6/11/2007\",\"2:06pm\",-0.01,70.25,70.50,69.76,5107185\r\n\"MRK\",51.05,\"6/11/2007\",\"2:06pm\",+0.91,50.30,51.28,50.04,7162100\r\n\"MSFT\",30.13,\"6/11/2007\",\"2:11pm\",+0.08,30.05,30.25,29.93,25965886\r\n\"PFE\",26.44,\"6/11/2007\",\"2:06pm\",-0.08,26.50,26.54,26.31,17179996\r\n\"PG\",63.14,\"6/11/2007\",\"2:06pm\",+0.07,62.80,63.15,62.75,4427046\r\n\"T\",40.26,\"6/11/2007\",\"2:06pm\",0.00,40.20,40.34,39.89,8620800\r\n\"UTX\",70.12,\"6/11/2007\",\"2:06pm\",-0.11,69.85,70.20,69.51,1386800\r\n\"VZ\",43.44,\"6/11/2007\",\"2:06pm\",+0.37,42.95,43.47,42.88,5829840\r\n\"WMT\",49.83,\"6/11/2007\",\"2:06pm\",-0.25,49.90,50.12,49.55,7695307\r\n\"XOM\",83.44,\"6/11/2007\",\"2:06pm\",+0.76,82.68,83.72,82.35,7916100\r\n\"AA\",39.53,\"6/11/2007\",\"2:11pm\",-0.13,39.67,40.18,39.43,2880880\r\n\"AIG\",71.98,\"6/11/2007\",\"2:11pm\",+0.45,71.29,71.99,71.15,3103496\r\n\"AXP\",63.38,\"6/11/2007\",\"2:11pm\",+0.34,62.79,63.39,62.42,2308630\r\n\"BA\",97.93,\"6/11/2007\",\"2:11pm\",-0.26,98.25,98.79,97.59,1881100\r\n\"C\",53.72,\"6/11/2007\",\"2:11pm\",+0.39,53.20,53.77,52.81,7588294\r\n\"CAT\",79.19,\"6/11/2007\",\"2:11pm\",+0.67,78.32,79.46,78.06,2131352\r\n\"DD\",50.90,\"6/11/2007\",\"2:11pm\",-0.23,51.13,51.21,50.59,2340097\r\n\"DIS\",34.21,\"6/11/2007\",\"2:11pm\",+0.01,34.28,34.44,34.12,4058750\r\n\"GE\",37.57,\"6/11/2007\",\"2:11pm\",+0.25,37.07,37.57,37.05,14734801\r\n\"GM\",31.57,\"6/11/2007\",\"2:11pm\",+0.57,31.00,31.62,30.90,9594251\r\n\"HD\",37.76,\"6/11/2007\",\"2:11pm\",-0.19,37.78,37.83,37.62,7474967\r\n\"HON\",57.19,\"6/11/2007\",\"2:11pm\",-0.19,57.25,57.40,56.91,2592542\r\n\"HPQ\",46.15,\"6/11/2007\",\"2:11pm\",+0.45,45.80,46.29,45.46,6633249\r\n\"IBM\",103.59,\"6/11/2007\",\"2:11pm\",+0.52,102.87,104.00,102.50,2884504\r\n\"INTC\",22.01,\"6/11/2007\",\"2:16pm\",+0.18,21.70,22.08,21.69,27482520\r\n\"JNJ\",62.53,\"6/11/2007\",\"2:11pm\",+0.40,62.89,62.89,62.15,5618158\r\n\"JPM\",50.74,\"6/11/2007\",\"2:11pm\",+0.33,50.41,50.84,50.05,5728500\r\n\"KO\",51.79,\"6/11/2007\",\"2:11pm\",+0.12,51.67,51.85,51.32,6761552\r\n\"MCD\",51.50,\"6/11/2007\",\"2:11pm\",+0.09,51.47,51.62,50.98,3260114\r\n\"MMM\",85.57,\"6/11/2007\",\"2:11pm\",-0.37,85.94,85.98,85.28,1508200\r\n\"MO\",70.34,\"6/11/2007\",\"2:11pm\",+0.04,70.25,70.50,69.76,5156485\r\n\"MRK\",51.21,\"6/11/2007\",\"2:11pm\",+1.07,50.30,51.28,50.04,7355400\r\n\"MSFT\",30.16,\"6/11/2007\",\"2:16pm\",+0.11,30.05,30.25,29.93,26411778\r\n\"PFE\",26.48,\"6/11/2007\",\"2:11pm\",-0.04,26.50,26.54,26.31,17302396\r\n\"PG\",63.1819,\"6/11/2007\",\"2:11pm\",+0.1119,62.80,63.19,62.75,4533846\r\n\"T\",40.25,\"6/11/2007\",\"2:11pm\",-0.01,40.20,40.34,39.89,8839900\r\n\"UTX\",70.20,\"6/11/2007\",\"2:11pm\",-0.03,69.85,70.20,69.51,1410200\r\n\"VZ\",43.49,\"6/11/2007\",\"2:11pm\",+0.42,42.95,43.49,42.88,5904936\r\n\"WMT\",49.882,\"6/11/2007\",\"2:11pm\",-0.198,49.90,50.12,49.55,7831607\r\n\"XOM\",83.51,\"6/11/2007\",\"2:11pm\",+0.83,82.68,83.72,82.35,8147200\r\n\"AA\",39.49,\"6/11/2007\",\"2:16pm\",-0.17,39.67,40.18,39.43,2953380\r\n\"AIG\",71.90,\"6/11/2007\",\"2:16pm\",+0.37,71.29,72.03,71.15,3208796\r\n\"AXP\",63.31,\"6/11/2007\",\"2:16pm\",+0.27,62.79,63.42,62.42,2337730\r\n\"BA\",97.92,\"6/11/2007\",\"2:16pm\",-0.27,98.25,98.79,97.59,1921100\r\n\"C\",53.65,\"6/11/2007\",\"2:16pm\",+0.32,53.20,53.77,52.81,7685194\r\n\"CAT\",79.08,\"6/11/2007\",\"2:16pm\",+0.56,78.32,79.46,78.06,2163952\r\n\"DD\",50.86,\"6/11/2007\",\"2:16pm\",-0.27,51.13,51.21,50.59,2355597\r\n\"DIS\",34.20,\"6/11/2007\",\"2:16pm\",0.00,34.28,34.44,34.12,4227650\r\n\"GE\",37.54,\"6/11/2007\",\"2:16pm\",+0.22,37.07,37.61,37.05,15329801\r\n\"GM\",31.63,\"6/11/2007\",\"2:16pm\",+0.63,31.00,31.64,30.90,9900251\r\n\"HD\",37.75,\"6/11/2007\",\"2:16pm\",-0.20,37.78,37.83,37.62,7614667\r\n\"HON\",57.16,\"6/11/2007\",\"2:16pm\",-0.22,57.25,57.40,56.91,2626742\r\n\"HPQ\",46.14,\"6/11/2007\",\"2:16pm\",+0.44,45.80,46.29,45.46,6705149\r\n\"IBM\",103.48,\"6/11/2007\",\"2:16pm\",+0.41,102.87,104.00,102.50,2932004\r\n\"INTC\",22.01,\"6/11/2007\",\"2:21pm\",+0.18,21.70,22.08,21.69,27617028\r\n\"JNJ\",62.49,\"6/11/2007\",\"2:16pm\",+0.36,62.89,62.89,62.15,5748158\r\n\"JPM\",50.71,\"6/11/2007\",\"2:16pm\",+0.30,50.41,50.84,50.05,5968400\r\n\"KO\",51.75,\"6/11/2007\",\"2:16pm\",+0.08,51.67,51.85,51.32,6798052\r\n\"MCD\",51.40,\"6/11/2007\",\"2:16pm\",-0.01,51.47,51.62,50.98,3310514\r\n\"MMM\",85.53,\"6/11/2007\",\"2:16pm\",-0.41,85.94,85.98,85.28,1531600\r\n\"MO\",70.33,\"6/11/2007\",\"2:16pm\",+0.03,70.25,70.50,69.76,5204085\r\n\"MRK\",51.17,\"6/11/2007\",\"2:16pm\",+1.03,50.30,51.28,50.04,7421000\r\n\"MSFT\",30.18,\"6/11/2007\",\"2:21pm\",+0.13,30.05,30.25,29.93,26830224\r\n\"PFE\",26.46,\"6/11/2007\",\"2:16pm\",-0.06,26.50,26.54,26.31,17549496\r\n\"PG\",63.18,\"6/11/2007\",\"2:16pm\",+0.11,62.80,63.20,62.75,4586346\r\n\"T\",40.30,\"6/11/2007\",\"2:16pm\",+0.04,40.20,40.34,39.89,9022910\r\n\"UTX\",70.192,\"6/11/2007\",\"2:16pm\",-0.038,69.85,70.25,69.51,1444200\r\n\"VZ\",43.52,\"6/11/2007\",\"2:16pm\",+0.45,42.95,43.535,42.88,6098036\r\n\"WMT\",49.85,\"6/11/2007\",\"2:16pm\",-0.23,49.90,50.12,49.55,7900607\r\n\"XOM\",83.37,\"6/11/2007\",\"2:16pm\",+0.69,82.68,83.85,82.35,8845100\r\n\"AA\",39.46,\"6/11/2007\",\"2:21pm\",-0.20,39.67,40.18,39.43,3029880\r\n\"AIG\",71.92,\"6/11/2007\",\"2:21pm\",+0.39,71.29,72.03,71.15,3245196\r\n\"AXP\",63.35,\"6/11/2007\",\"2:21pm\",+0.31,62.79,63.42,62.42,2353430\r\n\"BA\",97.97,\"6/11/2007\",\"2:21pm\",-0.22,98.25,98.79,97.59,1944500\r\n\"C\",53.69,\"6/11/2007\",\"2:21pm\",+0.36,53.20,53.77,52.81,7757194\r\n\"CAT\",79.12,\"6/11/2007\",\"2:21pm\",+0.60,78.32,79.46,78.06,2214452\r\n\"DD\",50.899,\"6/11/2007\",\"2:21pm\",-0.231,51.13,51.21,50.59,2373297\r\n\"DIS\",34.23,\"6/11/2007\",\"2:21pm\",+0.03,34.28,34.44,34.12,4262150\r\n\"GE\",37.52,\"6/11/2007\",\"2:21pm\",+0.20,37.07,37.61,37.05,15596301\r\n\"GM\",31.68,\"6/11/2007\",\"2:21pm\",+0.68,31.00,31.69,30.90,10286001\r\n\"HD\",37.7618,\"6/11/2007\",\"2:21pm\",-0.1882,37.78,37.83,37.62,7739967\r\n\"HON\",57.28,\"6/11/2007\",\"2:21pm\",-0.10,57.25,57.40,56.91,2699742\r\n\"HPQ\",46.16,\"6/11/2007\",\"2:21pm\",+0.46,45.80,46.29,45.46,6773849\r\n\"IBM\",103.47,\"6/11/2007\",\"2:21pm\",+0.40,102.87,104.00,102.50,2958304\r\n\"INTC\",22.008,\"6/11/2007\",\"2:26pm\",+0.178,21.70,22.08,21.69,28003456\r\n\"JNJ\",62.53,\"6/11/2007\",\"2:21pm\",+0.40,62.89,62.89,62.15,5814358\r\n\"JPM\",50.721,\"6/11/2007\",\"2:21pm\",+0.311,50.41,50.84,50.05,6057700\r\n\"KO\",51.77,\"6/11/2007\",\"2:21pm\",+0.10,51.67,51.85,51.32,6832552\r\n\"MCD\",51.37,\"6/11/2007\",\"2:21pm\",-0.04,51.47,51.62,50.98,3514114\r\n\"MMM\",85.58,\"6/11/2007\",\"2:21pm\",-0.36,85.94,85.98,85.28,1584600\r\n\"MO\",70.39,\"6/11/2007\",\"2:21pm\",+0.09,70.25,70.50,69.76,5237185\r\n\"MRK\",51.25,\"6/11/2007\",\"2:21pm\",+1.11,50.30,51.28,50.04,7541700\r\n\"MSFT\",30.18,\"6/11/2007\",\"2:26pm\",+0.13,30.05,30.25,29.93,27263748\r\n\"PFE\",26.45,\"6/11/2007\",\"2:21pm\",-0.07,26.50,26.54,26.31,17743896\r\n\"PG\",63.17,\"6/11/2007\",\"2:21pm\",+0.10,62.80,63.20,62.75,4649246\r\n\"T\",40.36,\"6/11/2007\",\"2:21pm\",+0.10,40.20,40.37,39.89,9204010\r\n\"UTX\",70.20,\"6/11/2007\",\"2:21pm\",-0.03,69.85,70.25,69.51,1518400\r\n\"VZ\",43.57,\"6/11/2007\",\"2:21pm\",+0.50,42.95,43.58,42.88,6405057\r\n\"WMT\",49.84,\"6/11/2007\",\"2:21pm\",-0.24,49.90,50.12,49.55,7979707\r\n\"XOM\",83.46,\"6/11/2007\",\"2:21pm\",+0.78,82.68,83.85,82.35,9131100\r\n\"AA\",39.46,\"6/11/2007\",\"2:26pm\",-0.20,39.67,40.18,39.43,3106580\r\n\"AIG\",71.92,\"6/11/2007\",\"2:26pm\",+0.39,71.29,72.03,71.15,3332596\r\n\"AXP\",63.34,\"6/11/2007\",\"2:26pm\",+0.30,62.79,63.42,62.42,2372530\r\n\"BA\",97.92,\"6/11/2007\",\"2:26pm\",-0.27,98.25,98.79,97.59,1972600\r\n\"C\",53.68,\"6/11/2007\",\"2:26pm\",+0.35,53.20,53.77,52.81,7862194\r\n\"CAT\",79.06,\"6/11/2007\",\"2:26pm\",+0.54,78.32,79.46,78.06,2621452\r\n\"DD\",50.85,\"6/11/2007\",\"2:26pm\",-0.28,51.13,51.21,50.59,2400597\r\n\"DIS\",34.21,\"6/11/2007\",\"2:26pm\",+0.01,34.28,34.44,34.12,4329650\r\n\"GE\",37.48,\"6/11/2007\",\"2:26pm\",+0.16,37.07,37.61,37.05,15908401\r\n\"GM\",31.76,\"6/11/2007\",\"2:26pm\",+0.76,31.00,31.77,30.90,10673201\r\n\"HD\",37.76,\"6/11/2007\",\"2:26pm\",-0.19,37.78,37.83,37.62,7797367\r\n\"HON\",57.23,\"6/11/2007\",\"2:26pm\",-0.15,57.25,57.40,56.91,2798642\r\n\"HPQ\",46.16,\"6/11/2007\",\"2:26pm\",+0.46,45.80,46.29,45.46,6850349\r\n\"IBM\",103.52,\"6/11/2007\",\"2:26pm\",+0.45,102.87,104.00,102.50,2994204\r\n\"INTC\",22.01,\"6/11/2007\",\"2:31pm\",+0.18,21.70,22.08,21.69,28166784\r\n\"JNJ\",62.52,\"6/11/2007\",\"2:26pm\",+0.39,62.89,62.89,62.15,5858958\r\n\"JPM\",50.72,\"6/11/2007\",\"2:26pm\",+0.31,50.41,50.84,50.05,6141900\r\n\"KO\",51.80,\"6/11/2007\",\"2:26pm\",+0.13,51.67,51.85,51.32,6873652\r\n\"MCD\",51.37,\"6/11/2007\",\"2:26pm\",-0.04,51.47,51.62,50.98,3549714\r\n\"MMM\",85.55,\"6/11/2007\",\"2:26pm\",-0.39,85.94,85.98,85.28,1616200\r\n\"MO\",70.3611,\"6/11/2007\",\"2:26pm\",+0.0611,70.25,70.50,69.76,5288885\r\n\"MRK\",51.26,\"6/11/2007\",\"2:26pm\",+1.12,50.30,51.28,50.04,7641900\r\n\"MSFT\",30.18,\"6/11/2007\",\"2:31pm\",+0.13,30.05,30.25,29.93,27477918\r\n\"PFE\",26.45,\"6/11/2007\",\"2:26pm\",-0.07,26.50,26.54,26.31,17889796\r\n\"PG\",63.13,\"6/11/2007\",\"2:26pm\",+0.06,62.80,63.20,62.75,4759446\r\n\"T\",40.43,\"6/11/2007\",\"2:26pm\",+0.17,40.20,40.47,39.89,9441710\r\n\"UTX\",70.15,\"6/11/2007\",\"2:26pm\",-0.08,69.85,70.25,69.51,1552400\r\n\"VZ\",43.58,\"6/11/2007\",\"2:26pm\",+0.51,42.95,43.61,42.88,6495057\r\n\"WMT\",49.82,\"6/11/2007\",\"2:26pm\",-0.26,49.90,50.12,49.55,8014807\r\n\"XOM\",83.40,\"6/11/2007\",\"2:26pm\",+0.72,82.68,83.85,82.35,9250000\r\n\"AA\",39.43,\"6/11/2007\",\"2:31pm\",-0.23,39.67,40.18,39.42,3146680\r\n\"AIG\",71.88,\"6/11/2007\",\"2:31pm\",+0.35,71.29,72.03,71.15,3377696\r\n\"AXP\",63.30,\"6/11/2007\",\"2:31pm\",+0.26,62.79,63.42,62.42,2396630\r\n\"BA\",97.83,\"6/11/2007\",\"2:31pm\",-0.36,98.25,98.79,97.59,1992300\r\n\"C\",53.64,\"6/11/2007\",\"2:31pm\",+0.31,53.20,53.77,52.81,7963894\r\n\"CAT\",79.00,\"6/11/2007\",\"2:31pm\",+0.48,78.32,79.46,78.06,2657252\r\n\"DD\",50.85,\"6/11/2007\",\"2:31pm\",-0.28,51.13,51.21,50.59,2419197\r\n\"DIS\",34.20,\"6/11/2007\",\"2:31pm\",0.00,34.28,34.44,34.12,4416950\r\n\"GE\",37.48,\"6/11/2007\",\"2:31pm\",+0.16,37.07,37.61,37.05,16180901\r\n\"GM\",31.74,\"6/11/2007\",\"2:31pm\",+0.74,31.00,31.77,30.90,10859101\r\n\"HD\",37.76,\"6/11/2007\",\"2:31pm\",-0.19,37.78,37.83,37.62,8074667\r\n\"HON\",57.20,\"6/11/2007\",\"2:31pm\",-0.18,57.25,57.40,56.91,2836642\r\n\"HPQ\",46.15,\"6/11/2007\",\"2:31pm\",+0.45,45.80,46.29,45.46,6906449\r\n\"IBM\",103.46,\"6/11/2007\",\"2:31pm\",+0.39,102.87,104.00,102.50,3014104\r\n\"INTC\",22.01,\"6/11/2007\",\"2:36pm\",+0.18,21.70,22.08,21.69,28623660\r\n\"JNJ\",62.48,\"6/11/2007\",\"2:31pm\",+0.35,62.89,62.89,62.15,5916458\r\n\"JPM\",50.68,\"6/11/2007\",\"2:31pm\",+0.27,50.41,50.84,50.05,6212400\r\n\"KO\",51.75,\"6/11/2007\",\"2:31pm\",+0.08,51.67,51.85,51.32,6921352\r\n\"MCD\",51.31,\"6/11/2007\",\"2:31pm\",-0.10,51.47,51.62,50.98,3570814\r\n\"MMM\",85.56,\"6/11/2007\",\"2:31pm\",-0.38,85.94,85.98,85.28,1632000\r\n\"MO\",70.34,\"6/11/2007\",\"2:31pm\",+0.04,70.25,70.50,69.76,5316285\r\n\"MRK\",51.25,\"6/11/2007\",\"2:31pm\",+1.11,50.30,51.35,50.04,7816300\r\n\"MSFT\",30.18,\"6/11/2007\",\"2:36pm\",+0.13,30.05,30.25,29.93,27776860\r\n\"PFE\",26.43,\"6/11/2007\",\"2:31pm\",-0.09,26.50,26.54,26.31,18072696\r\n\"PG\",63.09,\"6/11/2007\",\"2:31pm\",+0.02,62.80,63.20,62.75,4849346\r\n\"T\",40.37,\"6/11/2007\",\"2:31pm\",+0.11,40.20,40.47,39.89,9632910\r\n\"UTX\",70.15,\"6/11/2007\",\"2:31pm\",-0.08,69.85,70.25,69.51,1566500\r\n\"VZ\",43.53,\"6/11/2007\",\"2:31pm\",+0.46,42.95,43.61,42.88,6607457\r\n\"WMT\",49.77,\"6/11/2007\",\"2:31pm\",-0.31,49.90,50.12,49.55,8080507\r\n\"XOM\",83.35,\"6/11/2007\",\"2:31pm\",+0.67,82.68,83.85,82.35,9365400\r\n\"AA\",39.39,\"6/11/2007\",\"2:36pm\",-0.27,39.67,40.18,39.38,3233280\r\n\"AIG\",71.87,\"6/11/2007\",\"2:36pm\",+0.34,71.29,72.03,71.15,3455896\r\n\"AXP\",63.22,\"6/11/2007\",\"2:36pm\",+0.18,62.79,63.42,62.42,2427730\r\n\"BA\",97.77,\"6/11/2007\",\"2:36pm\",-0.42,98.25,98.79,97.59,2005500\r\n\"C\",53.63,\"6/11/2007\",\"2:36pm\",+0.30,53.20,53.77,52.81,8024794\r\n\"CAT\",79.01,\"6/11/2007\",\"2:36pm\",+0.49,78.32,79.46,78.06,2680952\r\n\"DD\",50.83,\"6/11/2007\",\"2:36pm\",-0.30,51.13,51.21,50.59,2435997\r\n\"DIS\",34.19,\"6/11/2007\",\"2:36pm\",-0.01,34.28,34.44,34.12,4466950\r\n\"GE\",37.44,\"6/11/2007\",\"2:36pm\",+0.12,37.07,37.61,37.05,16472401\r\n\"GM\",31.70,\"6/11/2007\",\"2:36pm\",+0.70,31.00,31.79,30.90,11121251\r\n\"HD\",37.75,\"6/11/2007\",\"2:36pm\",-0.20,37.78,37.83,37.62,8123767\r\n\"HON\",57.16,\"6/11/2007\",\"2:36pm\",-0.22,57.25,57.40,56.91,2882942\r\n\"HPQ\",46.15,\"6/11/2007\",\"2:36pm\",+0.45,45.80,46.29,45.46,6971049\r\n\"IBM\",103.42,\"6/11/2007\",\"2:36pm\",+0.35,102.87,104.00,102.50,3050204\r\n\"INTC\",22.0203,\"6/11/2007\",\"2:41pm\",+0.1903,21.70,22.08,21.69,28975326\r\n\"JNJ\",62.48,\"6/11/2007\",\"2:36pm\",+0.35,62.89,62.89,62.15,5985258\r\n\"JPM\",50.67,\"6/11/2007\",\"2:36pm\",+0.26,50.41,50.84,50.05,6275300\r\n\"KO\",51.75,\"6/11/2007\",\"2:36pm\",+0.08,51.67,51.85,51.32,7031052\r\n\"MCD\",51.33,\"6/11/2007\",\"2:36pm\",-0.08,51.47,51.62,50.98,3605014\r\n\"MMM\",85.47,\"6/11/2007\",\"2:36pm\",-0.47,85.94,85.98,85.28,1656600\r\n\"MO\",70.33,\"6/11/2007\",\"2:36pm\",+0.03,70.25,70.50,69.76,5350885\r\n\"MRK\",51.20,\"6/11/2007\",\"2:36pm\",+1.06,50.30,51.35,50.04,7984900\r\n\"MSFT\",30.18,\"6/11/2007\",\"2:41pm\",+0.13,30.05,30.25,29.93,30696744\r\n\"PFE\",26.44,\"6/11/2007\",\"2:36pm\",-0.08,26.50,26.54,26.31,18273796\r\n\"PG\",63.09,\"6/11/2007\",\"2:36pm\",+0.02,62.80,63.20,62.75,4902446\r\n\"T\",40.29,\"6/11/2007\",\"2:36pm\",+0.03,40.20,40.47,39.89,9901810\r\n\"UTX\",70.11,\"6/11/2007\",\"2:36pm\",-0.12,69.85,70.25,69.51,1589000\r\n\"VZ\",43.50,\"6/11/2007\",\"2:36pm\",+0.43,42.95,43.61,42.88,6714857\r\n\"WMT\",49.76,\"6/11/2007\",\"2:36pm\",-0.32,49.90,50.12,49.55,8135107\r\n\"XOM\",83.33,\"6/11/2007\",\"2:36pm\",+0.65,82.68,83.85,82.35,9480100\r\n\"AA\",39.39,\"6/11/2007\",\"2:41pm\",-0.27,39.67,40.18,39.34,3314380\r\n\"AIG\",71.89,\"6/11/2007\",\"2:41pm\",+0.36,71.29,72.03,71.15,3656596\r\n\"AXP\",63.23,\"6/11/2007\",\"2:41pm\",+0.19,62.79,63.42,62.42,2445430\r\n\"BA\",97.74,\"6/11/2007\",\"2:41pm\",-0.45,98.25,98.79,97.59,2031200\r\n\"C\",53.68,\"6/11/2007\",\"2:41pm\",+0.35,53.20,53.77,52.81,8087994\r\n\"CAT\",79.04,\"6/11/2007\",\"2:41pm\",+0.52,78.32,79.46,78.06,2712552\r\n\"DD\",50.81,\"6/11/2007\",\"2:41pm\",-0.32,51.13,51.21,50.59,2447897\r\n\"DIS\",34.19,\"6/11/2007\",\"2:41pm\",-0.01,34.28,34.44,34.12,4526150\r\n\"GE\",37.47,\"6/11/2007\",\"2:41pm\",+0.15,37.07,37.61,37.05,16819600\r\n\"GM\",31.65,\"6/11/2007\",\"2:41pm\",+0.65,31.00,31.79,30.90,11306951\r\n\"HD\",37.76,\"6/11/2007\",\"2:41pm\",-0.19,37.78,37.83,37.62,8189567\r\n\"HON\",57.13,\"6/11/2007\",\"2:41pm\",-0.25,57.25,57.40,56.91,2913842\r\n\"HPQ\",46.13,\"6/11/2007\",\"2:41pm\",+0.43,45.80,46.29,45.46,7057949\r\n\"IBM\",103.44,\"6/11/2007\",\"2:41pm\",+0.37,102.87,104.00,102.50,3087004\r\n\"INTC\",22.02,\"6/11/2007\",\"2:46pm\",+0.19,21.70,22.08,21.69,29252326\r\n\"JNJ\",62.48,\"6/11/2007\",\"2:41pm\",+0.35,62.89,62.89,62.15,6046058\r\n\"JPM\",50.71,\"6/11/2007\",\"2:41pm\",+0.30,50.41,50.84,50.05,6338800\r\n\"KO\",51.76,\"6/11/2007\",\"2:41pm\",+0.09,51.67,51.85,51.32,7064552\r\n\"MCD\",51.29,\"6/11/2007\",\"2:41pm\",-0.12,51.47,51.62,50.98,3648414\r\n\"MMM\",85.50,\"6/11/2007\",\"2:41pm\",-0.44,85.94,85.98,85.28,1687900\r\n\"MO\",70.34,\"6/11/2007\",\"2:41pm\",+0.04,70.25,70.50,69.76,5406985\r\n\"MRK\",51.18,\"6/11/2007\",\"2:41pm\",+1.04,50.30,51.35,50.04,8103700\r\n\"MSFT\",30.22,\"6/11/2007\",\"2:46pm\",+0.17,30.05,30.25,29.93,31209364\r\n\"PFE\",26.43,\"6/11/2007\",\"2:41pm\",-0.09,26.50,26.54,26.31,18438496\r\n\"PG\",63.11,\"6/11/2007\",\"2:41pm\",+0.04,62.80,63.20,62.75,4959446\r\n\"T\",40.27,\"6/11/2007\",\"2:41pm\",+0.01,40.20,40.47,39.89,10050710\r\n\"UTX\",70.11,\"6/11/2007\",\"2:41pm\",-0.12,69.85,70.25,69.51,1626700\r\n\"VZ\",43.50,\"6/11/2007\",\"2:41pm\",+0.43,42.95,43.61,42.88,6863457\r\n\"WMT\",49.79,\"6/11/2007\",\"2:41pm\",-0.29,49.90,50.12,49.55,8245307\r\n\"XOM\",83.37,\"6/11/2007\",\"2:41pm\",+0.69,82.68,83.85,82.35,9563400\r\n\"AA\",39.33,\"6/11/2007\",\"2:46pm\",-0.33,39.67,40.18,39.30,3399080\r\n\"AIG\",71.86,\"6/11/2007\",\"2:46pm\",+0.33,71.29,72.03,71.15,3740596\r\n\"AXP\",63.20,\"6/11/2007\",\"2:46pm\",+0.16,62.79,63.42,62.42,2463030\r\n\"BA\",97.78,\"6/11/2007\",\"2:46pm\",-0.41,98.25,98.79,97.59,2065200\r\n\"C\",53.67,\"6/11/2007\",\"2:46pm\",+0.34,53.20,53.77,52.81,8200694\r\n\"CAT\",79.00,\"6/11/2007\",\"2:46pm\",+0.48,78.32,79.46,78.06,2725452\r\n\"DD\",50.80,\"6/11/2007\",\"2:46pm\",-0.33,51.13,51.21,50.59,2457797\r\n\"DIS\",34.20,\"6/11/2007\",\"2:46pm\",0.00,34.28,34.44,34.12,4601150\r\n\"GE\",37.48,\"6/11/2007\",\"2:46pm\",+0.16,37.07,37.61,37.05,17056300\r\n\"GM\",31.65,\"6/11/2007\",\"2:46pm\",+0.65,31.00,31.79,30.90,11359851\r\n\"HD\",37.75,\"6/11/2007\",\"2:46pm\",-0.20,37.78,37.83,37.62,8424967\r\n\"HON\",57.18,\"6/11/2007\",\"2:46pm\",-0.20,57.25,57.40,56.91,2949842\r\n\"HPQ\",46.13,\"6/11/2007\",\"2:46pm\",+0.43,45.80,46.29,45.46,7195349\r\n\"IBM\",103.49,\"6/11/2007\",\"2:46pm\",+0.42,102.87,104.00,102.50,3126604\r\n\"INTC\",22.02,\"6/11/2007\",\"2:51pm\",+0.19,21.70,22.08,21.69,29404310\r\n\"JNJ\",62.485,\"6/11/2007\",\"2:46pm\",+0.355,62.89,62.89,62.15,6102358\r\n\"JPM\",50.70,\"6/11/2007\",\"2:46pm\",+0.29,50.41,50.84,50.05,6395400\r\n\"KO\",51.73,\"6/11/2007\",\"2:46pm\",+0.06,51.67,51.85,51.32,7104652\r\n\"MCD\",51.32,\"6/11/2007\",\"2:46pm\",-0.09,51.47,51.62,50.98,3708627\r\n\"MMM\",85.50,\"6/11/2007\",\"2:46pm\",-0.44,85.94,85.98,85.28,1710200\r\n\"MO\",70.28,\"6/11/2007\",\"2:46pm\",-0.02,70.25,70.50,69.76,5450985\r\n\"MRK\",51.19,\"6/11/2007\",\"2:46pm\",+1.05,50.30,51.35,50.04,8188500\r\n\"MSFT\",30.16,\"6/11/2007\",\"2:51pm\",+0.11,30.05,30.25,29.93,31914788\r\n\"PFE\",26.435,\"6/11/2007\",\"2:46pm\",-0.085,26.50,26.54,26.31,18623136\r\n\"PG\",63.11,\"6/11/2007\",\"2:46pm\",+0.04,62.80,63.20,62.75,5034546\r\n\"T\",40.29,\"6/11/2007\",\"2:46pm\",+0.03,40.20,40.47,39.89,10311910\r\n\"UTX\",70.19,\"6/11/2007\",\"2:46pm\",-0.04,69.85,70.25,69.51,1662900\r\n\"VZ\",43.52,\"6/11/2007\",\"2:46pm\",+0.45,42.95,43.61,42.88,6914257\r\n\"WMT\",49.76,\"6/11/2007\",\"2:46pm\",-0.32,49.90,50.12,49.55,8356278\r\n\"XOM\",83.30,\"6/11/2007\",\"2:46pm\",+0.62,82.68,83.85,82.35,9689200\r\n\"AA\",39.33,\"6/11/2007\",\"2:51pm\",-0.33,39.67,40.18,39.30,3443580\r\n\"AIG\",71.85,\"6/11/2007\",\"2:51pm\",+0.32,71.29,72.03,71.15,3833496\r\n\"AXP\",63.26,\"6/11/2007\",\"2:51pm\",+0.22,62.79,63.42,62.42,2483430\r\n\"BA\",97.66,\"6/11/2007\",\"2:51pm\",-0.53,98.25,98.79,97.59,2085200\r\n\"C\",53.68,\"6/11/2007\",\"2:51pm\",+0.35,53.20,53.77,52.81,8317394\r\n\"CAT\",78.99,\"6/11/2007\",\"2:51pm\",+0.47,78.32,79.46,78.06,2746952\r\n\"DD\",50.80,\"6/11/2007\",\"2:51pm\",-0.33,51.13,51.21,50.59,2480397\r\n\"DIS\",34.18,\"6/11/2007\",\"2:51pm\",-0.02,34.28,34.44,34.12,4641650\r\n\"GE\",37.48,\"6/11/2007\",\"2:51pm\",+0.16,37.07,37.61,37.05,17252900\r\n\"GM\",31.62,\"6/11/2007\",\"2:51pm\",+0.62,31.00,31.79,30.90,11456251\r\n\"HD\",37.741,\"6/11/2007\",\"2:51pm\",-0.209,37.78,37.83,37.62,8530067\r\n\"HON\",57.17,\"6/11/2007\",\"2:51pm\",-0.21,57.25,57.40,56.91,2989342\r\n\"HPQ\",46.08,\"6/11/2007\",\"2:51pm\",+0.38,45.80,46.29,45.46,7295183\r\n\"IBM\",103.49,\"6/11/2007\",\"2:51pm\",+0.42,102.87,104.00,102.50,3205604\r\n\"INTC\",22.03,\"6/11/2007\",\"2:56pm\",+0.20,21.70,22.08,21.69,29768528\r\n\"JNJ\",62.45,\"6/11/2007\",\"2:51pm\",+0.32,62.89,62.89,62.15,6158558\r\n\"JPM\",50.71,\"6/11/2007\",\"2:51pm\",+0.30,50.41,50.84,50.05,6486400\r\n\"KO\",51.76,\"6/11/2007\",\"2:51pm\",+0.09,51.67,51.85,51.32,7135652\r\n\"MCD\",51.30,\"6/11/2007\",\"2:51pm\",-0.11,51.47,51.62,50.98,3729927\r\n\"MMM\",85.48,\"6/11/2007\",\"2:51pm\",-0.46,85.94,85.98,85.28,1732900\r\n\"MO\",70.21,\"6/11/2007\",\"2:51pm\",-0.09,70.25,70.50,69.76,5501285\r\n\"MRK\",51.09,\"6/11/2007\",\"2:51pm\",+0.95,50.30,51.35,50.04,8318900\r\n\"MSFT\",30.16,\"6/11/2007\",\"2:56pm\",+0.11,30.05,30.25,29.93,32355638\r\n\"PFE\",26.43,\"6/11/2007\",\"2:51pm\",-0.09,26.50,26.54,26.31,18879636\r\n\"PG\",63.16,\"6/11/2007\",\"2:51pm\",+0.09,62.80,63.20,62.75,5127246\r\n\"T\",40.21,\"6/11/2007\",\"2:51pm\",-0.05,40.20,40.47,39.89,10570410\r\n\"UTX\",70.15,\"6/11/2007\",\"2:51pm\",-0.08,69.85,70.25,69.51,1681200\r\n\"VZ\",43.49,\"6/11/2007\",\"2:51pm\",+0.42,42.95,43.61,42.88,6963457\r\n\"WMT\",49.755,\"6/11/2007\",\"2:51pm\",-0.325,49.90,50.12,49.55,8428278\r\n\"XOM\",83.24,\"6/11/2007\",\"2:51pm\",+0.56,82.68,83.85,82.35,9787800\r\n\"AA\",39.32,\"6/11/2007\",\"2:56pm\",-0.34,39.67,40.18,39.30,3480480\r\n\"AIG\",71.86,\"6/11/2007\",\"2:56pm\",+0.33,71.29,72.03,71.15,3935896\r\n\"AXP\",63.31,\"6/11/2007\",\"2:56pm\",+0.27,62.79,63.42,62.42,2509230\r\n\"BA\",97.78,\"6/11/2007\",\"2:56pm\",-0.41,98.25,98.79,97.59,2114200\r\n\"C\",53.70,\"6/11/2007\",\"2:56pm\",+0.37,53.20,53.77,52.81,8402494\r\n\"CAT\",79.08,\"6/11/2007\",\"2:56pm\",+0.56,78.32,79.46,78.06,2766652\r\n\"DD\",50.84,\"6/11/2007\",\"2:56pm\",-0.29,51.13,51.21,50.59,2498497\r\n\"DIS\",34.20,\"6/11/2007\",\"2:56pm\",0.00,34.28,34.44,34.12,4680250\r\n\"GE\",37.53,\"6/11/2007\",\"2:56pm\",+0.21,37.07,37.61,37.05,17452700\r\n\"GM\",31.682,\"6/11/2007\",\"2:56pm\",+0.682,31.00,31.79,30.90,11603351\r\n\"HD\",37.76,\"6/11/2007\",\"2:56pm\",-0.19,37.78,37.83,37.62,8595367\r\n\"HON\",57.23,\"6/11/2007\",\"2:56pm\",-0.15,57.25,57.40,56.91,3114842\r\n\"HPQ\",46.091,\"6/11/2007\",\"2:56pm\",+0.391,45.80,46.29,45.46,7387583\r\n\"IBM\",103.53,\"6/11/2007\",\"2:56pm\",+0.46,102.87,104.00,102.50,3228704\r\n\"INTC\",22.02,\"6/11/2007\",\"3:01pm\",+0.19,21.70,22.08,21.69,30262880\r\n\"JNJ\",62.53,\"6/11/2007\",\"2:56pm\",+0.40,62.89,62.89,62.15,6280508\r\n\"JPM\",50.71,\"6/11/2007\",\"2:56pm\",+0.30,50.41,50.84,50.05,6635200\r\n\"KO\",51.82,\"6/11/2007\",\"2:56pm\",+0.15,51.67,51.85,51.32,7174352\r\n\"MCD\",51.35,\"6/11/2007\",\"2:56pm\",-0.06,51.47,51.62,50.98,3766527\r\n\"MMM\",85.51,\"6/11/2007\",\"2:56pm\",-0.43,85.94,85.98,85.28,1757600\r\n\"MO\",70.30,\"6/11/2007\",\"2:56pm\",0.00,70.25,70.50,69.76,5597885\r\n\"MRK\",51.15,\"6/11/2007\",\"2:56pm\",+1.01,50.30,51.35,50.04,8391000\r\n\"MSFT\",30.16,\"6/11/2007\",\"3:01pm\",+0.11,30.05,30.25,29.93,32950134\r\n\"PFE\",26.43,\"6/11/2007\",\"2:56pm\",-0.09,26.50,26.54,26.31,19031536\r\n\"PG\",63.12,\"6/11/2007\",\"2:56pm\",+0.05,62.80,63.21,62.75,5284846\r\n\"T\",40.26,\"6/11/2007\",\"2:56pm\",0.00,40.20,40.47,39.89,10708610\r\n\"UTX\",70.21,\"6/11/2007\",\"2:56pm\",-0.02,69.85,70.27,69.51,1705800\r\n\"VZ\",43.53,\"6/11/2007\",\"2:56pm\",+0.46,42.95,43.61,42.88,7015557\r\n\"WMT\",49.85,\"6/11/2007\",\"2:56pm\",-0.23,49.90,50.12,49.55,8513478\r\n\"XOM\",83.315,\"6/11/2007\",\"2:56pm\",+0.635,82.68,83.85,82.35,9892200\r\n\"AA\",39.308,\"6/11/2007\",\"3:01pm\",-0.352,39.67,40.18,39.30,3525080\r\n\"AIG\",71.88,\"6/11/2007\",\"3:01pm\",+0.35,71.29,72.03,71.15,4037796\r\n\"AXP\",63.33,\"6/11/2007\",\"3:01pm\",+0.29,62.79,63.42,62.42,2525030\r\n\"BA\",97.69,\"6/11/2007\",\"3:01pm\",-0.50,98.25,98.79,97.59,2139700\r\n\"C\",53.6917,\"6/11/2007\",\"3:01pm\",+0.3617,53.20,53.77,52.81,8489294\r\n\"CAT\",79.06,\"6/11/2007\",\"3:01pm\",+0.54,78.32,79.46,78.06,2812752\r\n\"DD\",50.82,\"6/11/2007\",\"3:01pm\",-0.31,51.13,51.21,50.59,2531197\r\n\"DIS\",34.20,\"6/11/2007\",\"3:01pm\",0.00,34.28,34.44,34.12,4838550\r\n\"GE\",37.56,\"6/11/2007\",\"3:01pm\",+0.24,37.07,37.61,37.05,17708200\r\n\"GM\",31.71,\"6/11/2007\",\"3:01pm\",+0.71,31.00,31.79,30.90,11810951\r\n\"HD\",37.75,\"6/11/2007\",\"3:01pm\",-0.20,37.78,37.83,37.62,8889067\r\n\"HON\",57.16,\"6/11/2007\",\"3:01pm\",-0.22,57.25,57.40,56.91,3138242\r\n\"HPQ\",46.09,\"6/11/2007\",\"3:01pm\",+0.39,45.80,46.29,45.46,7434383\r\n\"IBM\",103.49,\"6/11/2007\",\"3:01pm\",+0.42,102.87,104.00,102.50,3267604\r\n\"INTC\",22.01,\"6/11/2007\",\"3:06pm\",+0.18,21.70,22.08,21.69,30788464\r\n\"JNJ\",62.53,\"6/11/2007\",\"3:01pm\",+0.40,62.89,62.89,62.15,6335808\r\n\"JPM\",50.68,\"6/11/2007\",\"3:01pm\",+0.27,50.41,50.84,50.05,6741800\r\n\"KO\",51.81,\"6/11/2007\",\"3:01pm\",+0.14,51.67,51.85,51.32,7223752\r\n\"MCD\",51.28,\"6/11/2007\",\"3:01pm\",-0.13,51.47,51.62,50.98,3825627\r\n\"MMM\",85.47,\"6/11/2007\",\"3:01pm\",-0.47,85.94,85.98,85.28,1780900\r\n\"MO\",70.26,\"6/11/2007\",\"3:01pm\",-0.04,70.25,70.50,69.76,5686085\r\n\"MRK\",51.14,\"6/11/2007\",\"3:01pm\",+1.00,50.30,51.35,50.04,8454600\r\n\"MSFT\",30.16,\"6/11/2007\",\"3:06pm\",+0.11,30.05,30.25,29.93,33439226\r\n\"PFE\",26.42,\"6/11/2007\",\"3:01pm\",-0.10,26.50,26.54,26.31,19178536\r\n\"PG\",63.09,\"6/11/2007\",\"3:01pm\",+0.02,62.80,63.21,62.75,5381146\r\n\"T\",40.26,\"6/11/2007\",\"3:01pm\",0.00,40.20,40.47,39.89,10923510\r\n\"UTX\",70.21,\"6/11/2007\",\"3:01pm\",-0.02,69.85,70.27,69.51,1741900\r\n\"VZ\",43.54,\"6/11/2007\",\"3:01pm\",+0.47,42.95,43.61,42.88,7071957\r\n\"WMT\",49.7901,\"6/11/2007\",\"3:01pm\",-0.2899,49.90,50.12,49.55,8630878\r\n\"XOM\",83.29,\"6/11/2007\",\"3:01pm\",+0.61,82.68,83.85,82.35,9962400\r\n\"AA\",39.33,\"6/11/2007\",\"3:06pm\",-0.33,39.67,40.18,39.25,3559780\r\n\"AIG\",71.87,\"6/11/2007\",\"3:06pm\",+0.34,71.29,72.03,71.15,4096696\r\n\"AXP\",63.30,\"6/11/2007\",\"3:06pm\",+0.26,62.79,63.42,62.42,2545430\r\n\"BA\",97.70,\"6/11/2007\",\"3:06pm\",-0.49,98.25,98.79,97.58,2170600\r\n\"C\",53.66,\"6/11/2007\",\"3:06pm\",+0.33,53.20,53.77,52.81,8668794\r\n\"CAT\",79.0221,\"6/11/2007\",\"3:06pm\",+0.5021,78.32,79.46,78.06,2871952\r\n\"DD\",50.84,\"6/11/2007\",\"3:06pm\",-0.29,51.13,51.21,50.59,2564597\r\n\"DIS\",34.20,\"6/11/2007\",\"3:06pm\",0.00,34.28,34.44,34.12,4903050\r\n\"GE\",37.54,\"6/11/2007\",\"3:06pm\",+0.22,37.07,37.61,37.05,17946600\r\n\"GM\",31.76,\"6/11/2007\",\"3:06pm\",+0.76,31.00,31.79,30.90,11923751\r\n\"HD\",37.76,\"6/11/2007\",\"3:06pm\",-0.19,37.78,37.83,37.62,9002967\r\n\"HON\",57.17,\"6/11/2007\",\"3:06pm\",-0.21,57.25,57.40,56.91,3177742\r\n\"HPQ\",46.10,\"6/11/2007\",\"3:06pm\",+0.40,45.80,46.29,45.46,7517183\r\n\"IBM\",103.49,\"6/11/2007\",\"3:06pm\",+0.42,102.87,104.00,102.50,3306904\r\n\"INTC\",22.03,\"6/11/2007\",\"3:11pm\",+0.20,21.70,22.08,21.69,31162120\r\n\"JNJ\",62.50,\"6/11/2007\",\"3:06pm\",+0.37,62.89,62.89,62.15,6420308\r\n\"JPM\",50.65,\"6/11/2007\",\"3:06pm\",+0.24,50.41,50.84,50.05,6851800\r\n\"KO\",51.80,\"6/11/2007\",\"3:06pm\",+0.13,51.67,51.85,51.32,7265152\r\n\"MCD\",51.29,\"6/11/2007\",\"3:06pm\",-0.12,51.47,51.62,50.98,3892827\r\n\"MMM\",85.55,\"6/11/2007\",\"3:06pm\",-0.39,85.94,85.98,85.28,1819600\r\n\"MO\",70.23,\"6/11/2007\",\"3:06pm\",-0.07,70.25,70.50,69.76,5764585\r\n\"MRK\",51.12,\"6/11/2007\",\"3:06pm\",+0.98,50.30,51.35,50.04,8577100\r\n\"MSFT\",30.16,\"6/11/2007\",\"3:11pm\",+0.11,30.05,30.25,29.93,34219296\r\n\"PFE\",26.41,\"6/11/2007\",\"3:06pm\",-0.11,26.50,26.54,26.31,19446136\r\n\"PG\",63.07,\"6/11/2007\",\"3:06pm\",0.00,62.80,63.21,62.75,5533446\r\n\"T\",40.23,\"6/11/2007\",\"3:06pm\",-0.03,40.20,40.47,39.89,11134010\r\n\"UTX\",70.22,\"6/11/2007\",\"3:06pm\",-0.01,69.85,70.27,69.51,1781800\r\n\"VZ\",43.54,\"6/11/2007\",\"3:06pm\",+0.47,42.95,43.61,42.88,7163857\r\n\"WMT\",49.795,\"6/11/2007\",\"3:06pm\",-0.285,49.90,50.12,49.55,8740778\r\n\"XOM\",83.31,\"6/11/2007\",\"3:06pm\",+0.63,82.68,83.85,82.35,10075100\r\n\"AA\",39.37,\"6/11/2007\",\"3:11pm\",-0.29,39.67,40.18,39.25,3601080\r\n\"AIG\",71.86,\"6/11/2007\",\"3:11pm\",+0.33,71.29,72.03,71.15,4171396\r\n\"AXP\",63.28,\"6/11/2007\",\"3:11pm\",+0.24,62.79,63.42,62.42,2572030\r\n\"BA\",97.70,\"6/11/2007\",\"3:11pm\",-0.49,98.25,98.79,97.58,2202300\r\n\"C\",53.60,\"6/11/2007\",\"3:11pm\",+0.27,53.20,53.77,52.81,8910394\r\n\"CAT\",79.10,\"6/11/2007\",\"3:11pm\",+0.58,78.32,79.46,78.06,2926952\r\n\"DD\",50.84,\"6/11/2007\",\"3:11pm\",-0.29,51.13,51.21,50.59,2592497\r\n\"DIS\",34.195,\"6/11/2007\",\"3:11pm\",-0.005,34.28,34.44,34.12,5006550\r\n\"GE\",37.56,\"6/11/2007\",\"3:11pm\",+0.24,37.07,37.61,37.05,18148100\r\n\"GM\",31.81,\"6/11/2007\",\"3:11pm\",+0.81,31.00,31.82,30.90,12211151\r\n\"HD\",37.74,\"6/11/2007\",\"3:11pm\",-0.21,37.78,37.83,37.62,9075667\r\n\"HON\",57.24,\"6/11/2007\",\"3:11pm\",-0.14,57.25,57.40,56.91,3219242\r\n\"HPQ\",46.12,\"6/11/2007\",\"3:11pm\",+0.42,45.80,46.29,45.46,7596783\r\n\"IBM\",103.52,\"6/11/2007\",\"3:11pm\",+0.45,102.87,104.00,102.50,3347004\r\n\"INTC\",22.03,\"6/11/2007\",\"3:16pm\",+0.20,21.70,22.08,21.69,31499736\r\n\"JNJ\",62.48,\"6/11/2007\",\"3:11pm\",+0.35,62.89,62.89,62.15,6626708\r\n\"JPM\",50.64,\"6/11/2007\",\"3:11pm\",+0.23,50.41,50.84,50.05,6957100\r\n\"KO\",51.80,\"6/11/2007\",\"3:11pm\",+0.13,51.67,51.85,51.32,7305952\r\n\"MCD\",51.28,\"6/11/2007\",\"3:11pm\",-0.13,51.47,51.62,50.98,3951227\r\n\"MMM\",85.52,\"6/11/2007\",\"3:11pm\",-0.42,85.94,85.98,85.28,1844000\r\n\"MO\",70.26,\"6/11/2007\",\"3:11pm\",-0.04,70.25,70.50,69.76,5802685\r\n\"MRK\",51.19,\"6/11/2007\",\"3:11pm\",+1.05,50.30,51.35,50.04,8710500\r\n\"MSFT\",30.14,\"6/11/2007\",\"3:16pm\",+0.09,30.05,30.25,29.93,35084560\r\n\"PFE\",26.42,\"6/11/2007\",\"3:11pm\",-0.10,26.50,26.54,26.31,19904950\r\n\"PG\",63.08,\"6/11/2007\",\"3:11pm\",+0.01,62.80,63.21,62.75,5640146\r\n\"T\",40.25,\"6/11/2007\",\"3:11pm\",-0.01,40.20,40.47,39.89,11272135\r\n\"UTX\",70.21,\"6/11/2007\",\"3:11pm\",-0.02,69.85,70.27,69.51,1802900\r\n\"VZ\",43.56,\"6/11/2007\",\"3:11pm\",+0.49,42.95,43.61,42.88,7294957\r\n\"WMT\",49.83,\"6/11/2007\",\"3:11pm\",-0.25,49.90,50.12,49.55,8885778\r\n\"XOM\",83.3328,\"6/11/2007\",\"3:11pm\",+0.6528,82.68,83.85,82.35,10226000\r\n\"AA\",39.36,\"6/11/2007\",\"3:16pm\",-0.30,39.67,40.18,39.25,3628080\r\n\"AIG\",71.86,\"6/11/2007\",\"3:16pm\",+0.33,71.29,72.03,71.15,4253196\r\n\"AXP\",63.27,\"6/11/2007\",\"3:16pm\",+0.23,62.79,63.42,62.42,2599530\r\n\"BA\",97.62,\"6/11/2007\",\"3:16pm\",-0.57,98.25,98.79,97.58,2240000\r\n\"C\",53.63,\"6/11/2007\",\"3:16pm\",+0.30,53.20,53.77,52.81,9064594\r\n\"CAT\",79.1263,\"6/11/2007\",\"3:16pm\",+0.6063,78.32,79.46,78.06,2961952\r\n\"DD\",50.84,\"6/11/2007\",\"3:16pm\",-0.29,51.13,51.21,50.59,2614197\r\n\"DIS\",34.19,\"6/11/2007\",\"3:16pm\",-0.01,34.28,34.44,34.12,5055750\r\n\"GE\",37.58,\"6/11/2007\",\"3:16pm\",+0.26,37.07,37.61,37.05,18410300\r\n\"GM\",31.79,\"6/11/2007\",\"3:16pm\",+0.79,31.00,31.82,30.90,12429251\r\n\"HD\",37.73,\"6/11/2007\",\"3:16pm\",-0.22,37.78,37.83,37.62,9252867\r\n\"HON\",57.22,\"6/11/2007\",\"3:16pm\",-0.16,57.25,57.40,56.91,3267342\r\n\"HPQ\",46.13,\"6/11/2007\",\"3:16pm\",+0.43,45.80,46.29,45.46,7732283\r\n\"IBM\",103.52,\"6/11/2007\",\"3:16pm\",+0.45,102.87,104.00,102.50,3390804\r\n\"INTC\",22.03,\"6/11/2007\",\"3:21pm\",+0.20,21.70,22.08,21.69,31747072\r\n\"JNJ\",62.46,\"6/11/2007\",\"3:16pm\",+0.33,62.89,62.89,62.15,6718808\r\n\"JPM\",50.66,\"6/11/2007\",\"3:16pm\",+0.25,50.41,50.84,50.05,7077000\r\n\"KO\",51.81,\"6/11/2007\",\"3:16pm\",+0.14,51.67,51.85,51.32,7356152\r\n\"MCD\",51.28,\"6/11/2007\",\"3:16pm\",-0.13,51.47,51.62,50.98,4004827\r\n\"MMM\",85.50,\"6/11/2007\",\"3:16pm\",-0.44,85.94,85.98,85.28,1863700\r\n\"MO\",70.24,\"6/11/2007\",\"3:16pm\",-0.06,70.25,70.50,69.76,5895585\r\n\"MRK\",51.17,\"6/11/2007\",\"3:16pm\",+1.03,50.30,51.35,50.04,8885200\r\n\"MSFT\",30.18,\"6/11/2007\",\"3:21pm\",+0.13,30.05,30.25,29.93,35482676\r\n\"PFE\",26.41,\"6/11/2007\",\"3:16pm\",-0.11,26.50,26.54,26.31,20145150\r\n\"PG\",63.07,\"6/11/2007\",\"3:16pm\",0.00,62.80,63.21,62.75,5824046\r\n\"T\",40.24,\"6/11/2007\",\"3:16pm\",-0.02,40.20,40.47,39.89,11393435\r\n\"UTX\",70.20,\"6/11/2007\",\"3:16pm\",-0.03,69.85,70.27,69.51,1819800\r\n\"VZ\",43.56,\"6/11/2007\",\"3:16pm\",+0.49,42.95,43.61,42.88,7465357\r\n\"WMT\",49.79,\"6/11/2007\",\"3:16pm\",-0.29,49.90,50.12,49.55,9009578\r\n\"XOM\",83.29,\"6/11/2007\",\"3:16pm\",+0.61,82.68,83.85,82.35,10323500\r\n\"AA\",39.41,\"6/11/2007\",\"3:21pm\",-0.25,39.67,40.18,39.25,3683980\r\n\"AIG\",71.87,\"6/11/2007\",\"3:21pm\",+0.34,71.29,72.03,71.15,4342596\r\n\"AXP\",63.28,\"6/11/2007\",\"3:21pm\",+0.24,62.79,63.42,62.42,2631030\r\n\"BA\",97.79,\"6/11/2007\",\"3:21pm\",-0.40,98.25,98.79,97.58,2283500\r\n\"C\",53.67,\"6/11/2007\",\"3:21pm\",+0.34,53.20,53.77,52.81,9345794\r\n\"CAT\",79.18,\"6/11/2007\",\"3:21pm\",+0.66,78.32,79.46,78.06,3018652\r\n\"DD\",50.85,\"6/11/2007\",\"3:21pm\",-0.28,51.13,51.21,50.59,2681597\r\n\"DIS\",34.189,\"6/11/2007\",\"3:21pm\",-0.011,34.28,34.44,34.12,5129050\r\n\"GE\",37.59,\"6/11/2007\",\"3:21pm\",+0.27,37.07,37.61,37.05,18759500\r\n\"GM\",31.83,\"6/11/2007\",\"3:21pm\",+0.83,31.00,31.84,30.90,12621451\r\n\"HD\",37.74,\"6/11/2007\",\"3:21pm\",-0.21,37.78,37.83,37.62,9337467\r\n\"HON\",57.25,\"6/11/2007\",\"3:21pm\",-0.13,57.25,57.40,56.91,3305486\r\n\"HPQ\",46.15,\"6/11/2007\",\"3:21pm\",+0.45,45.80,46.29,45.46,7875783\r\n\"IBM\",103.58,\"6/11/2007\",\"3:21pm\",+0.51,102.87,104.00,102.50,3430404\r\n\"INTC\",22.03,\"6/11/2007\",\"3:26pm\",+0.20,21.70,22.08,21.69,32604206\r\n\"JNJ\",62.44,\"6/11/2007\",\"3:21pm\",+0.31,62.89,62.89,62.15,6819808\r\n\"JPM\",50.68,\"6/11/2007\",\"3:21pm\",+0.27,50.41,50.84,50.05,7182200\r\n\"KO\",51.80,\"6/11/2007\",\"3:21pm\",+0.13,51.67,51.85,51.32,7430852\r\n\"MCD\",51.288,\"6/11/2007\",\"3:21pm\",-0.122,51.47,51.62,50.98,4044427\r\n\"MMM\",85.54,\"6/11/2007\",\"3:21pm\",-0.40,85.94,85.98,85.28,1903500\r\n\"MO\",70.28,\"6/11/2007\",\"3:21pm\",-0.02,70.25,70.50,69.76,5949185\r\n\"MRK\",51.13,\"6/11/2007\",\"3:21pm\",+0.99,50.30,51.35,50.04,9078900\r\n\"MSFT\",30.15,\"6/11/2007\",\"3:26pm\",+0.10,30.05,30.25,29.93,36214532\r\n\"PFE\",26.44,\"6/11/2007\",\"3:21pm\",-0.08,26.50,26.54,26.31,20502150\r\n\"PG\",63.10,\"6/11/2007\",\"3:21pm\",+0.03,62.80,63.21,62.75,5930246\r\n\"T\",40.26,\"6/11/2007\",\"3:21pm\",0.00,40.20,40.47,39.89,11546235\r\n\"UTX\",70.28,\"6/11/2007\",\"3:21pm\",+0.05,69.85,70.27,69.51,1852300\r\n\"VZ\",43.58,\"6/11/2007\",\"3:21pm\",+0.51,42.95,43.61,42.88,7563557\r\n\"WMT\",49.82,\"6/11/2007\",\"3:21pm\",-0.26,49.90,50.12,49.55,9134278\r\n\"XOM\",83.37,\"6/11/2007\",\"3:21pm\",+0.69,82.68,83.85,82.35,10394600\r\n\"AA\",39.36,\"6/11/2007\",\"3:26pm\",-0.30,39.67,40.18,39.25,3734180\r\n\"AIG\",71.85,\"6/11/2007\",\"3:26pm\",+0.32,71.29,72.03,71.15,4412096\r\n\"AXP\",63.26,\"6/11/2007\",\"3:26pm\",+0.22,62.79,63.42,62.42,2656530\r\n\"BA\",97.75,\"6/11/2007\",\"3:26pm\",-0.44,98.25,98.79,97.58,2318700\r\n\"C\",53.65,\"6/11/2007\",\"3:26pm\",+0.32,53.20,53.77,52.81,9469794\r\n\"CAT\",79.14,\"6/11/2007\",\"3:26pm\",+0.62,78.32,79.46,78.06,3063152\r\n\"DD\",50.85,\"6/11/2007\",\"3:26pm\",-0.28,51.13,51.21,50.59,2734497\r\n\"DIS\",34.20,\"6/11/2007\",\"3:26pm\",0.00,34.28,34.44,34.12,5218750\r\n\"GE\",37.61,\"6/11/2007\",\"3:26pm\",+0.29,37.07,37.61,37.05,19343000\r\n\"GM\",31.88,\"6/11/2007\",\"3:26pm\",+0.88,31.00,31.90,30.90,12954222\r\n\"HD\",37.7382,\"6/11/2007\",\"3:26pm\",-0.2118,37.78,37.83,37.62,9712367\r\n\"HON\",57.23,\"6/11/2007\",\"3:26pm\",-0.15,57.25,57.40,56.91,3354186\r\n\"HPQ\",46.15,\"6/11/2007\",\"3:26pm\",+0.45,45.80,46.29,45.46,7964983\r\n\"IBM\",103.48,\"6/11/2007\",\"3:26pm\",+0.41,102.87,104.00,102.50,3487304\r\n\"INTC\",22.05,\"6/11/2007\",\"3:31pm\",+0.22,21.70,22.08,21.69,33024848\r\n\"JNJ\",62.38,\"6/11/2007\",\"3:26pm\",+0.25,62.89,62.89,62.15,6981631\r\n\"JPM\",50.65,\"6/11/2007\",\"3:26pm\",+0.24,50.41,50.84,50.05,7366500\r\n\"KO\",51.78,\"6/11/2007\",\"3:26pm\",+0.11,51.67,51.85,51.32,7479252\r\n\"MCD\",51.21,\"6/11/2007\",\"3:26pm\",-0.20,51.47,51.62,50.98,4117427\r\n\"MMM\",85.55,\"6/11/2007\",\"3:26pm\",-0.39,85.94,85.98,85.28,1925800\r\n\"MO\",70.2525,\"6/11/2007\",\"3:26pm\",-0.0475,70.25,70.50,69.76,6009485\r\n\"MRK\",51.09,\"6/11/2007\",\"3:26pm\",+0.95,50.30,51.35,50.04,9266600\r\n\"MSFT\",30.165,\"6/11/2007\",\"3:31pm\",+0.115,30.05,30.25,29.93,36431612\r\n\"PFE\",26.40,\"6/11/2007\",\"3:26pm\",-0.12,26.50,26.54,26.31,20767150\r\n\"PG\",63.08,\"6/11/2007\",\"3:26pm\",+0.01,62.80,63.21,62.75,5987546\r\n\"T\",40.25,\"6/11/2007\",\"3:26pm\",-0.01,40.20,40.47,39.88,13610435\r\n\"UTX\",70.25,\"6/11/2007\",\"3:26pm\",+0.02,69.85,70.30,69.51,1883100\r\n\"VZ\",43.58,\"6/11/2007\",\"3:26pm\",+0.51,42.95,43.61,42.88,7711757\r\n\"WMT\",49.80,\"6/11/2007\",\"3:26pm\",-0.28,49.90,50.12,49.55,9419178\r\n\"XOM\",83.2854,\"6/11/2007\",\"3:26pm\",+0.6054,82.68,83.85,82.35,10509700\r\n\"AA\",39.37,\"6/11/2007\",\"3:31pm\",-0.29,39.67,40.18,39.25,3767480\r\n\"AIG\",71.84,\"6/11/2007\",\"3:31pm\",+0.31,71.29,72.03,71.15,4469996\r\n\"AXP\",63.27,\"6/11/2007\",\"3:31pm\",+0.23,62.79,63.42,62.42,2681730\r\n\"BA\",97.66,\"6/11/2007\",\"3:31pm\",-0.53,98.25,98.79,97.58,2347600\r\n\"C\",53.63,\"6/11/2007\",\"3:31pm\",+0.30,53.20,53.77,52.81,9570094\r\n\"CAT\",79.15,\"6/11/2007\",\"3:31pm\",+0.63,78.32,79.46,78.06,3108752\r\n\"DD\",50.83,\"6/11/2007\",\"3:31pm\",-0.30,51.13,51.21,50.59,2758297\r\n\"DIS\",34.20,\"6/11/2007\",\"3:31pm\",0.00,34.28,34.44,34.12,5359050\r\n\"GE\",37.61,\"6/11/2007\",\"3:31pm\",+0.29,37.07,37.61,37.05,19495200\r\n\"GM\",31.83,\"6/11/2007\",\"3:31pm\",+0.83,31.00,31.90,30.90,13338922\r\n\"HD\",37.7275,\"6/11/2007\",\"3:31pm\",-0.2225,37.78,37.83,37.62,9833219\r\n\"HON\",57.23,\"6/11/2007\",\"3:31pm\",-0.15,57.25,57.40,56.91,3411886\r\n\"HPQ\",46.16,\"6/11/2007\",\"3:31pm\",+0.46,45.80,46.29,45.46,8108883\r\n\"IBM\",103.48,\"6/11/2007\",\"3:31pm\",+0.41,102.87,104.00,102.50,3582104\r\n\"INTC\",22.02,\"6/11/2007\",\"3:36pm\",+0.19,21.70,22.08,21.69,33811596\r\n\"JNJ\",62.38,\"6/11/2007\",\"3:31pm\",+0.25,62.89,62.89,62.15,7072531\r\n\"JPM\",50.6225,\"6/11/2007\",\"3:31pm\",+0.2125,50.41,50.84,50.05,7423100\r\n\"KO\",51.78,\"6/11/2007\",\"3:31pm\",+0.11,51.67,51.85,51.32,7527052\r\n\"MCD\",51.17,\"6/11/2007\",\"3:31pm\",-0.24,51.47,51.62,50.98,4207427\r\n\"MMM\",85.50,\"6/11/2007\",\"3:31pm\",-0.44,85.94,85.98,85.28,1954000\r\n\"MO\",70.26,\"6/11/2007\",\"3:31pm\",-0.04,70.25,70.50,69.76,6068285\r\n\"MRK\",51.15,\"6/11/2007\",\"3:31pm\",+1.01,50.30,51.35,50.04,9411500\r\n\"MSFT\",30.14,\"6/11/2007\",\"3:36pm\",+0.09,30.05,30.25,29.93,37331996\r\n\"PFE\",26.40,\"6/11/2007\",\"3:31pm\",-0.12,26.50,26.54,26.31,21355550\r\n\"PG\",63.11,\"6/11/2007\",\"3:31pm\",+0.04,62.80,63.21,62.75,6055546\r\n\"T\",40.24,\"6/11/2007\",\"3:31pm\",-0.02,40.20,40.47,39.88,13730535\r\n\"UTX\",70.26,\"6/11/2007\",\"3:31pm\",+0.03,69.85,70.30,69.51,1985400\r\n\"VZ\",43.58,\"6/11/2007\",\"3:31pm\",+0.51,42.95,43.61,42.88,7833157\r\n\"WMT\",49.80,\"6/11/2007\",\"3:31pm\",-0.28,49.90,50.12,49.55,9490578\r\n\"XOM\",83.275,\"6/11/2007\",\"3:31pm\",+0.595,82.68,83.85,82.35,10612200\r\n\"AA\",39.30,\"6/11/2007\",\"3:36pm\",-0.36,39.67,40.18,39.25,3811480\r\n\"AIG\",71.77,\"6/11/2007\",\"3:36pm\",+0.24,71.29,72.03,71.15,4581496\r\n\"AXP\",63.155,\"6/11/2007\",\"3:36pm\",+0.115,62.79,63.42,62.42,2721130\r\n\"BA\",97.56,\"6/11/2007\",\"3:36pm\",-0.63,98.25,98.79,97.55,2383000\r\n\"C\",53.54,\"6/11/2007\",\"3:36pm\",+0.21,53.20,53.77,52.81,11390094\r\n\"CAT\",79.04,\"6/11/2007\",\"3:36pm\",+0.52,78.32,79.46,78.06,3145152\r\n\"DD\",50.76,\"6/11/2007\",\"3:36pm\",-0.37,51.13,51.21,50.59,2813297\r\n\"DIS\",34.18,\"6/11/2007\",\"3:36pm\",-0.02,34.28,34.44,34.12,5431250\r\n\"GE\",37.58,\"6/11/2007\",\"3:36pm\",+0.26,37.07,37.61,37.05,19977600\r\n\"GM\",31.79,\"6/11/2007\",\"3:36pm\",+0.79,31.00,31.90,30.90,13573622\r\n\"HD\",37.72,\"6/11/2007\",\"3:36pm\",-0.23,37.78,37.83,37.62,10373519\r\n\"HON\",57.18,\"6/11/2007\",\"3:36pm\",-0.20,57.25,57.40,56.91,3490736\r\n\"HPQ\",46.10,\"6/11/2007\",\"3:36pm\",+0.40,45.80,46.29,45.46,8224783\r\n\"IBM\",103.36,\"6/11/2007\",\"3:36pm\",+0.29,102.87,104.00,102.50,3667004\r\n\"INTC\",22.02,\"6/11/2007\",\"3:41pm\",+0.19,21.70,22.08,21.69,34379072\r\n\"JNJ\",62.33,\"6/11/2007\",\"3:36pm\",+0.20,62.89,62.89,62.15,7231131\r\n\"JPM\",50.58,\"6/11/2007\",\"3:36pm\",+0.17,50.41,50.84,50.05,7580900\r\n\"KO\",51.73,\"6/11/2007\",\"3:36pm\",+0.06,51.67,51.85,51.32,7588352\r\n\"MCD\",51.17,\"6/11/2007\",\"3:36pm\",-0.24,51.47,51.62,50.98,4327427\r\n\"MMM\",85.42,\"6/11/2007\",\"3:36pm\",-0.52,85.94,85.98,85.28,1997700\r\n\"MO\",70.24,\"6/11/2007\",\"3:36pm\",-0.06,70.25,70.50,69.76,6182685\r\n\"MRK\",51.15,\"6/11/2007\",\"3:36pm\",+1.01,50.30,51.35,50.04,9552400\r\n\"MSFT\",30.11,\"6/11/2007\",\"3:41pm\",+0.06,30.05,30.25,29.93,37726620\r\n\"PFE\",26.41,\"6/11/2007\",\"3:36pm\",-0.11,26.50,26.54,26.31,21924450\r\n\"PG\",63.0627,\"6/11/2007\",\"3:36pm\",-0.0073,62.80,63.21,62.75,6121146\r\n\"T\",40.23,\"6/11/2007\",\"3:36pm\",-0.03,40.20,40.47,39.88,13995835\r\n\"UTX\",70.17,\"6/11/2007\",\"3:36pm\",-0.06,69.85,70.30,69.51,2031100\r\n\"VZ\",43.55,\"6/11/2007\",\"3:36pm\",+0.48,42.95,43.61,42.88,7990757\r\n\"WMT\",49.811,\"6/11/2007\",\"3:36pm\",-0.269,49.90,50.12,49.55,9655678\r\n\"XOM\",83.17,\"6/11/2007\",\"3:36pm\",+0.49,82.68,83.85,82.35,10797200\r\n\"AA\",39.28,\"6/11/2007\",\"3:41pm\",-0.38,39.67,40.18,39.25,3906080\r\n\"AIG\",71.73,\"6/11/2007\",\"3:41pm\",+0.20,71.29,72.03,71.15,4691496\r\n\"AXP\",63.15,\"6/11/2007\",\"3:41pm\",+0.11,62.79,63.42,62.42,2766030\r\n\"BA\",97.50,\"6/11/2007\",\"3:41pm\",-0.69,98.25,98.79,97.48,2453500\r\n\"C\",53.51,\"6/11/2007\",\"3:41pm\",+0.18,53.20,53.77,52.81,11535394\r\n\"CAT\",78.98,\"6/11/2007\",\"3:41pm\",+0.46,78.32,79.46,78.06,3172952\r\n\"DD\",50.73,\"6/11/2007\",\"3:41pm\",-0.40,51.13,51.21,50.59,2869897\r\n\"DIS\",34.18,\"6/11/2007\",\"3:41pm\",-0.02,34.28,34.44,34.12,5648050\r\n\"GE\",37.58,\"6/11/2007\",\"3:41pm\",+0.26,37.07,37.61,37.05,20658300\r\n\"GM\",31.73,\"6/11/2007\",\"3:41pm\",+0.73,31.00,31.90,30.90,13983822\r\n\"HD\",37.72,\"6/11/2007\",\"3:41pm\",-0.23,37.78,37.83,37.62,10596819\r\n\"HON\",57.16,\"6/11/2007\",\"3:41pm\",-0.22,57.25,57.40,56.91,3548036\r\n\"HPQ\",46.072,\"6/11/2007\",\"3:41pm\",+0.372,45.80,46.29,45.46,8398683\r\n\"IBM\",103.38,\"6/11/2007\",\"3:41pm\",+0.31,102.87,104.00,102.50,3741504\r\n\"INTC\",21.992,\"6/11/2007\",\"3:46pm\",+0.162,21.70,22.08,21.69,35566072\r\n\"JNJ\",62.29,\"6/11/2007\",\"3:41pm\",+0.16,62.89,62.89,62.15,7444481\r\n\"JPM\",50.59,\"6/11/2007\",\"3:41pm\",+0.18,50.41,50.84,50.05,7794800\r\n\"KO\",51.74,\"6/11/2007\",\"3:41pm\",+0.07,51.67,51.85,51.32,7758670\r\n\"MCD\",51.25,\"6/11/2007\",\"3:41pm\",-0.16,51.47,51.62,50.98,4427195\r\n\"MMM\",85.50,\"6/11/2007\",\"3:41pm\",-0.44,85.94,85.98,85.28,2066900\r\n\"MO\",70.25,\"6/11/2007\",\"3:41pm\",-0.05,70.25,70.50,69.76,6272385\r\n\"MRK\",51.14,\"6/11/2007\",\"3:41pm\",+1.00,50.30,51.35,50.04,9785300\r\n\"MSFT\",30.08,\"6/11/2007\",\"3:46pm\",+0.03,30.05,30.25,29.93,38740488\r\n\"PFE\",26.43,\"6/11/2007\",\"3:41pm\",-0.09,26.50,26.54,26.31,22573150\r\n\"PG\",63.06,\"6/11/2007\",\"3:41pm\",-0.01,62.80,63.21,62.75,6204746\r\n\"T\",40.25,\"6/11/2007\",\"3:41pm\",-0.01,40.20,40.47,39.88,14297535\r\n\"UTX\",70.12,\"6/11/2007\",\"3:41pm\",-0.11,69.85,70.30,69.51,2068500\r\n\"VZ\",43.56,\"6/11/2007\",\"3:41pm\",+0.49,42.95,43.61,42.88,8283057\r\n\"WMT\",49.85,\"6/11/2007\",\"3:41pm\",-0.23,49.90,50.12,49.55,9905878\r\n\"XOM\",83.1801,\"6/11/2007\",\"3:41pm\",+0.5001,82.68,83.85,82.35,10980200\r\n\"AA\",39.28,\"6/11/2007\",\"3:46pm\",-0.38,39.67,40.18,39.21,4013480\r\n\"AIG\",71.74,\"6/11/2007\",\"3:46pm\",+0.21,71.29,72.03,71.15,4893496\r\n\"AXP\",63.13,\"6/11/2007\",\"3:46pm\",+0.09,62.79,63.42,62.42,2811230\r\n\"BA\",97.50,\"6/11/2007\",\"3:46pm\",-0.69,98.25,98.79,97.44,2545100\r\n\"C\",53.47,\"6/11/2007\",\"3:46pm\",+0.14,53.20,53.77,52.81,11812294\r\n\"CAT\",78.98,\"6/11/2007\",\"3:46pm\",+0.46,78.32,79.46,78.06,3228752\r\n\"DD\",50.78,\"6/11/2007\",\"3:46pm\",-0.35,51.13,51.21,50.59,2971197\r\n\"DIS\",34.17,\"6/11/2007\",\"3:46pm\",-0.03,34.28,34.44,34.12,5764350\r\n\"GE\",37.53,\"6/11/2007\",\"3:46pm\",+0.21,37.07,37.6202,37.05,21261200\r\n\"GM\",31.74,\"6/11/2007\",\"3:46pm\",+0.74,31.00,31.90,30.90,14221022\r\n\"HD\",37.69,\"6/11/2007\",\"3:46pm\",-0.26,37.78,37.83,37.62,10829419\r\n\"HON\",57.13,\"6/11/2007\",\"3:46pm\",-0.25,57.25,57.40,56.91,3612436\r\n\"HPQ\",46.06,\"6/11/2007\",\"3:46pm\",+0.36,45.80,46.29,45.46,8528683\r\n\"IBM\",103.37,\"6/11/2007\",\"3:46pm\",+0.30,102.87,104.00,102.50,3846604\r\n\"INTC\",21.97,\"6/11/2007\",\"3:51pm\",+0.14,21.70,22.08,21.69,37733764\r\n\"JNJ\",62.28,\"6/11/2007\",\"3:46pm\",+0.15,62.89,62.89,62.15,7592281\r\n\"JPM\",50.57,\"6/11/2007\",\"3:46pm\",+0.16,50.41,50.84,50.05,7987300\r\n\"KO\",51.71,\"6/11/2007\",\"3:46pm\",+0.04,51.67,51.85,51.32,7854570\r\n\"MCD\",51.31,\"6/11/2007\",\"3:46pm\",-0.10,51.47,51.62,50.98,4569195\r\n\"MMM\",85.41,\"6/11/2007\",\"3:46pm\",-0.53,85.94,85.98,85.28,2120500\r\n\"MO\",70.24,\"6/11/2007\",\"3:46pm\",-0.06,70.25,70.50,69.76,6364785\r\n\"MRK\",51.11,\"6/11/2007\",\"3:46pm\",+0.97,50.30,51.35,50.04,9941300\r\n\"MSFT\",30.04,\"6/11/2007\",\"3:51pm\",-0.01,30.05,30.25,29.93,41124800\r\n\"PFE\",26.41,\"6/11/2007\",\"3:46pm\",-0.11,26.50,26.54,26.31,23036750\r\n\"PG\",63.05,\"6/11/2007\",\"3:46pm\",-0.02,62.80,63.21,62.75,6374346\r\n\"T\",40.21,\"6/11/2007\",\"3:46pm\",-0.05,40.20,40.47,39.88,15620735\r\n\"UTX\",70.19,\"6/11/2007\",\"3:46pm\",-0.04,69.85,70.30,69.51,2171000\r\n\"VZ\",43.569,\"6/11/2007\",\"3:46pm\",+0.499,42.95,43.61,42.88,8442057\r\n\"WMT\",49.85,\"6/11/2007\",\"3:46pm\",-0.23,49.90,50.12,49.55,10081678\r\n\"XOM\",83.14,\"6/11/2007\",\"3:46pm\",+0.46,82.68,83.85,82.35,11246800\r\n\"AA\",39.29,\"6/11/2007\",\"3:51pm\",-0.37,39.67,40.18,39.21,4132380\r\n\"AIG\",71.62,\"6/11/2007\",\"3:51pm\",+0.09,71.29,72.03,71.15,5038729\r\n\"AXP\",63.09,\"6/11/2007\",\"3:51pm\",+0.05,62.79,63.42,62.42,2861530\r\n\"BA\",97.49,\"6/11/2007\",\"3:51pm\",-0.70,98.25,98.79,97.43,3063600\r\n\"C\",53.42,\"6/11/2007\",\"3:51pm\",+0.09,53.20,53.77,52.81,12267394\r\n\"CAT\",78.78,\"6/11/2007\",\"3:51pm\",+0.26,78.32,79.46,78.06,3287352\r\n\"DD\",50.73,\"6/11/2007\",\"3:51pm\",-0.40,51.13,51.21,50.59,3033697\r\n\"DIS\",34.135,\"6/11/2007\",\"3:51pm\",-0.065,34.28,34.44,34.12,5919350\r\n\"GE\",37.49,\"6/11/2007\",\"3:51pm\",+0.17,37.07,37.6202,37.05,21532000\r\n\"GM\",31.72,\"6/11/2007\",\"3:51pm\",+0.72,31.00,31.90,30.90,14377693\r\n\"HD\",37.67,\"6/11/2007\",\"3:51pm\",-0.28,37.78,37.83,37.62,11229419\r\n\"HON\",57.04,\"6/11/2007\",\"3:51pm\",-0.34,57.25,57.40,56.91,3682536\r\n\"HPQ\",46.00,\"6/11/2007\",\"3:51pm\",+0.30,45.80,46.29,45.46,8652383\r\n\"IBM\",103.20,\"6/11/2007\",\"3:51pm\",+0.13,102.87,104.00,102.50,3934104\r\n\"INTC\",21.97,\"6/11/2007\",\"3:56pm\",+0.14,21.70,22.08,21.69,38481016\r\n\"JNJ\",62.22,\"6/11/2007\",\"3:51pm\",+0.09,62.89,62.89,62.15,7738581\r\n\"JPM\",50.48,\"6/11/2007\",\"3:51pm\",+0.07,50.41,50.84,50.05,8466180\r\n\"KO\",51.65,\"6/11/2007\",\"3:51pm\",-0.02,51.67,51.85,51.32,7921870\r\n\"MCD\",51.26,\"6/11/2007\",\"3:51pm\",-0.15,51.47,51.62,50.98,5379392\r\n\"MMM\",85.26,\"6/11/2007\",\"3:51pm\",-0.68,85.94,85.98,85.28,2179200\r\n\"MO\",70.22,\"6/11/2007\",\"3:51pm\",-0.08,70.25,70.50,69.76,6468485\r\n\"MRK\",51.09,\"6/11/2007\",\"3:51pm\",+0.95,50.30,51.35,50.04,10126700\r\n\"MSFT\",30.04,\"6/11/2007\",\"3:56pm\",-0.01,30.05,30.25,29.93,45401260\r\n\"PFE\",26.36,\"6/11/2007\",\"3:51pm\",-0.16,26.50,26.54,26.31,23619450\r\n\"PG\",63.01,\"6/11/2007\",\"3:51pm\",-0.06,62.80,63.21,62.75,6447846\r\n\"T\",40.14,\"6/11/2007\",\"3:51pm\",-0.12,40.20,40.47,39.88,15842235\r\n\"UTX\",70.19,\"6/11/2007\",\"3:51pm\",-0.04,69.85,70.30,69.51,2300500\r\n\"VZ\",43.51,\"6/11/2007\",\"3:51pm\",+0.44,42.95,43.61,42.88,8663757\r\n\"WMT\",49.81,\"6/11/2007\",\"3:51pm\",-0.27,49.90,50.12,49.55,10280178\r\n\"XOM\",82.99,\"6/11/2007\",\"3:51pm\",+0.31,82.68,83.85,82.35,11476800\r\n\"AA\",39.29,\"6/11/2007\",\"3:56pm\",-0.37,39.67,40.18,39.21,4279480\r\n\"AIG\",71.63,\"6/11/2007\",\"3:56pm\",+0.10,71.29,72.03,71.15,5259629\r\n\"AXP\",63.06,\"6/11/2007\",\"3:56pm\",+0.02,62.79,63.42,62.42,2932030\r\n\"BA\",97.48,\"6/11/2007\",\"3:56pm\",-0.71,98.25,98.79,97.43,3134100\r\n\"C\",53.46,\"6/11/2007\",\"3:56pm\",+0.13,53.20,53.77,52.81,12689394\r\n\"CAT\",78.76,\"6/11/2007\",\"3:56pm\",+0.24,78.32,79.46,78.06,3364652\r\n\"DD\",50.70,\"6/11/2007\",\"3:56pm\",-0.43,51.13,51.21,50.59,3120297\r\n\"DIS\",34.16,\"6/11/2007\",\"3:56pm\",-0.04,34.28,34.44,34.12,6061350\r\n\"GE\",37.47,\"6/11/2007\",\"3:56pm\",+0.15,37.07,37.6202,37.05,22045900\r\n\"GM\",31.72,\"6/11/2007\",\"3:56pm\",+0.72,31.00,31.90,30.90,14702993\r\n\"HD\",37.67,\"6/11/2007\",\"3:56pm\",-0.28,37.78,37.83,37.62,11654819\r\n\"HON\",56.95,\"6/11/2007\",\"3:56pm\",-0.43,57.25,57.40,56.91,3802936\r\n\"HPQ\",45.93,\"6/11/2007\",\"3:56pm\",+0.23,45.80,46.29,45.46,11355083\r\n\"IBM\",103.01,\"6/11/2007\",\"3:56pm\",-0.06,102.87,104.00,102.50,4096804\r\n\"INTC\",21.93,\"6/11/2007\",\"4:01pm\",+0.10,21.70,22.08,21.69,40860336\r\n\"JNJ\",62.29,\"6/11/2007\",\"3:56pm\",+0.16,62.89,62.89,62.15,7933681\r\n\"JPM\",50.43,\"6/11/2007\",\"3:56pm\",+0.02,50.41,50.84,50.05,8654880\r\n\"KO\",51.66,\"6/11/2007\",\"3:56pm\",-0.01,51.67,51.85,51.32,7987870\r\n\"MCD\",51.30,\"6/11/2007\",\"3:56pm\",-0.11,51.47,51.62,50.98,5548192\r\n\"MMM\",85.18,\"6/11/2007\",\"3:56pm\",-0.76,85.94,85.98,85.17,2274500\r\n\"MO\",70.22,\"6/11/2007\",\"3:56pm\",-0.08,70.25,70.50,69.76,6635085\r\n\"MRK\",51.06,\"6/11/2007\",\"3:56pm\",+0.92,50.30,51.35,50.04,10461500\r\n\"MSFT\",30.02,\"6/11/2007\",\"4:00pm\",-0.03,30.05,30.25,29.93,46709236\r\n\"PFE\",26.37,\"6/11/2007\",\"3:56pm\",-0.15,26.50,26.54,26.31,24178850\r\n\"PG\",63.02,\"6/11/2007\",\"3:56pm\",-0.05,62.80,63.21,62.75,6576946\r\n\"T\",40.0918,\"6/11/2007\",\"3:56pm\",-0.1682,40.20,40.47,39.88,16125935\r\n\"UTX\",70.17,\"6/11/2007\",\"3:56pm\",-0.06,69.85,70.30,69.51,2406200\r\n\"VZ\",43.48,\"6/11/2007\",\"3:56pm\",+0.41,42.95,43.61,42.88,9009057\r\n\"WMT\",49.79,\"6/11/2007\",\"3:56pm\",-0.29,49.90,50.12,49.55,10441378\r\n\"XOM\",82.96,\"6/11/2007\",\"3:56pm\",+0.28,82.68,83.85,82.35,11744600\r\n\"AA\",39.30,\"6/11/2007\",\"4:01pm\",-0.36,39.67,40.18,39.14,4516480\r\n\"AIG\",71.65,\"6/11/2007\",\"4:00pm\",+0.12,71.29,72.03,71.15,5942029\r\n\"AXP\",63.06,\"6/11/2007\",\"4:00pm\",+0.02,62.79,63.42,62.42,3050830\r\n\"BA\",97.55,\"6/11/2007\",\"4:00pm\",-0.64,98.25,98.79,97.42,3300500\r\n\"C\",53.47,\"6/11/2007\",\"4:01pm\",+0.14,53.20,53.77,52.81,13457894\r\n\"CAT\",78.75,\"6/11/2007\",\"4:00pm\",+0.23,78.32,79.46,78.06,3456552\r\n\"DD\",50.73,\"6/11/2007\",\"3:59pm\",-0.40,51.13,51.21,50.59,3162997\r\n\"DIS\",34.15,\"6/11/2007\",\"3:59pm\",-0.05,34.28,34.44,34.12,6162650\r\n\"GE\",37.46,\"6/11/2007\",\"4:01pm\",+0.14,37.07,37.6202,37.05,23163100\r\n\"GM\",31.77,\"6/11/2007\",\"4:00pm\",+0.77,31.00,31.90,30.90,15223093\r\n\"HD\",37.71,\"6/11/2007\",\"4:00pm\",-0.24,37.78,37.83,37.62,12074119\r\n\"HON\",57.02,\"6/11/2007\",\"3:59pm\",-0.36,57.25,57.40,56.91,3911336\r\n\"HPQ\",45.89,\"6/11/2007\",\"4:00pm\",+0.19,45.80,46.29,45.46,11960883\r\n\"IBM\",103.22,\"6/11/2007\",\"4:01pm\",+0.15,102.87,104.00,102.50,4668204\r\n\"INTC\",21.93,\"6/11/2007\",\"4:01pm\",+0.10,21.70,22.08,21.69,41134284\r\n\"JNJ\",62.27,\"6/11/2007\",\"4:00pm\",+0.14,62.89,62.89,62.15,8452146\r\n\"JPM\",50.45,\"6/11/2007\",\"4:00pm\",+0.04,50.41,50.84,50.05,8869280\r\n\"KO\",51.65,\"6/11/2007\",\"3:59pm\",-0.02,51.67,51.85,51.32,8049770\r\n\"MCD\",51.25,\"6/11/2007\",\"4:01pm\",-0.16,51.47,51.62,50.98,5969292\r\n\"MMM\",85.30,\"6/11/2007\",\"4:01pm\",-0.64,85.94,85.98,85.17,2454400\r\n\"MO\",70.22,\"6/11/2007\",\"4:00pm\",-0.08,70.25,70.50,69.76,6887785\r\n\"MRK\",51.09,\"6/11/2007\",\"3:59pm\",+0.95,50.30,51.35,50.04,10623065\r\n\"MSFT\",30.02,\"6/11/2007\",\"4:00pm\",-0.03,30.05,30.25,29.93,46924636\r\n\"PFE\",26.37,\"6/11/2007\",\"4:00pm\",-0.15,26.50,26.54,26.31,25287450\r\n\"PG\",63.05,\"6/11/2007\",\"4:00pm\",-0.02,62.80,63.21,62.75,6920246\r\n\"T\",40.12,\"6/11/2007\",\"4:00pm\",-0.14,40.20,40.47,39.88,16712535\r\n\"UTX\",70.18,\"6/11/2007\",\"4:00pm\",-0.05,69.85,70.30,69.51,2660900\r\n\"VZ\",43.47,\"6/11/2007\",\"3:59pm\",+0.40,42.95,43.61,42.88,9156827\r\n\"WMT\",49.81,\"6/11/2007\",\"4:00pm\",-0.27,49.90,50.12,49.55,10924878\r\n\"XOM\",83.06,\"6/11/2007\",\"4:00pm\",+0.38,82.68,83.85,82.35,12427710"
  },
  {
    "path": "Work/Data/missing.csv",
    "content": "name,shares,price\n\"AA\",100,32.20\n\"IBM\",50,91.10\n\"CAT\",150,83.44\n\"MSFT\",,51.23\n\"GE\",95,40.37\n\"MSFT\",50,65.10\n\"IBM\",,70.44\n"
  },
  {
    "path": "Work/Data/portfolio.csv",
    "content": "name,shares,price\n\"AA\",100,32.20\n\"IBM\",50,91.10\n\"CAT\",150,83.44\n\"MSFT\",200,51.23\n\"GE\",95,40.37\n\"MSFT\",50,65.10\n\"IBM\",100,70.44\n"
  },
  {
    "path": "Work/Data/portfolio2.csv",
    "content": "name,shares,price\n\"AA\",50,27.10\n\"HPQ\",250,43.15\n\"MSFT\",25,50.15\n\"GE\",125,52.10\n"
  },
  {
    "path": "Work/Data/portfolioblank.csv",
    "content": "name,shares,price\n\n\"AA\",100,32.20\n\n\"IBM\",50,91.10\n\n\"CAT\",150,83.44\n\n\"MSFT\",200,51.23\n\n\"GE\",95,40.37\n\n\"MSFT\",50,65.10\n\n\"IBM\",100,70.44\n\n"
  },
  {
    "path": "Work/Data/portfoliodate.csv",
    "content": "name,date,time,shares,price\n\"AA\",\"6/11/2007\",\"9:50am\",100,32.20\n\"IBM\",\"5/13/2007\",\"4:20pm\",50,91.10\n\"CAT\",\"9/23/2006\",\"1:30pm\",150,83.44\n\"MSFT\",\"5/17/2007\",\"10:30am\",200,51.23\n\"GE\",\"2/1/2006\",\"10:45am\",95,40.37\n\"MSFT\",\"10/31/2006\",\"12:05pm\",50,65.10\n\"IBM\",\"7/9/2006\",\"3:15pm\",100,70.44\n"
  },
  {
    "path": "Work/Data/prices.csv",
    "content": "\"AA\",9.22\r\n\"AXP\",24.85\r\n\"BA\",44.85\r\n\"BAC\",11.27\r\n\"C\",3.72\r\n\"CAT\",35.46\r\n\"CVX\",66.67\r\n\"DD\",28.47\r\n\"DIS\",24.22\r\n\"GE\",13.48\r\n\"GM\",0.75\r\n\"HD\",23.16\r\n\"HPQ\",34.35\r\n\"IBM\",106.28\r\n\"INTC\",15.72\r\n\"JNJ\",55.16\r\n\"JPM\",36.90\r\n\"KFT\",26.11\r\n\"KO\",49.16\r\n\"MCD\",58.99\r\n\"MMM\",57.10\r\n\"MRK\",27.58\r\n\"MSFT\",20.89\r\n\"PFE\",15.19\r\n\"PG\",51.94\r\n\"T\",24.79\r\n\"UTX\",52.61\r\n\"VZ\",29.26\r\n\"WMT\",49.74\r\n\"XOM\",69.35\r\n\r\n"
  },
  {
    "path": "Work/Data/stocksim.py",
    "content": "#!/usr/bin/env python\n# stocksim.py\n#\n# Stock market simulator.  This simulator creates stock market\n# data and provides it in several different ways:\n#\n#    1. Makes periodic updates to a log file stocklog.dat\n#    2. Provides stock data through an embedded HTTP server.\n#\n# The purpose of this module is to provide data to the user\n# in different ways in order to write interesting Python examples\n\nimport math\nimport time\n\nhistory_file = \"dowstocks.csv\"\n\n# Convert a time string such as \"4:00pm\" to minutes past midnight\ndef minutes(tm):\n    am_pm = tm[-2:]\n    fields = tm[:-2].split(\":\")\n    hour = int(fields[0])\n    minute = int(fields[1])\n    if hour == 12:\n       hour = 0\n    if am_pm == 'pm':\n       hour += 12\n    return hour*60 + minute\n\n# Convert time in minutes to a format string\ndef minutes_to_str(m):\n    frac,m = math.modf(m)\n    hours = m//60\n    minutes = m % 60\n    seconds = frac * 60\n    return \"%02d:%02d.%02.f\" % (hours,minutes,seconds)\n\n# Read the stock history file as a list of lists\ndef read_history(filename):\n    result = []\n    f = open(filename)\n    next(f)\n    for line in f:\n        str_fields = line.strip().split(\",\")\n        fields = [eval(x) for x in str_fields]\n        fields[3] = minutes(fields[3])\n        result.append(fields)\n    return result\n\n# Format CSV record\ndef csv_record(fields):\n    s = '\"%s\",%0.2f,\"%s\",\"%s\",%0.2f,%0.2f,%0.2f,%0.2f,%d' % tuple(fields)\n    return s\n\nclass StockTrack(object):\n    def __init__(self,name):\n        self.name    = name\n        self.history = []\n        self.price   = 0\n        self.time    = 0\n        self.index   = 0\n        self.open    = 0\n        self.low     = 0\n        self.high    = 0\n        self.volume  = 0\n        self.initial = 0\n        self.change  = 0\n        self.date    = \"\"\n    def add_data(self,record):\n        self.history.append(record)\n    def reset(self,time):\n        self.time = time\n        # Sort the history by time\n        self.history.sort(key=lambda t:t[3])\n        # Find the first entry who's time is behind the given time\n        self.index = 0\n        while self.index < len(self.history):\n            if self.history[self.index][3] > time:\n                break\n            self.index += 1\n        self.open = self.history[0][5]\n        self.initial = self.history[0][1] - self.history[0][4]\n        self.date = self.history[0][2]\n        self.update()\n        self.low = self.price\n        self.high = self.price\n\n    # Calculate interpolated value of a given field based on\n    # current time\n    def interpolate(self,field):\n        first = self.history[self.index][field]\n        next  = self.history[self.index+1][field]\n        first_t = self.history[self.index][3]\n        next_t = self.history[self.index+1][3]\n        try:\n            slope = (next - first)/(next_t-first_t)\n            return first + slope*(self.time - first_t)\n        except ZeroDivisionError:\n            return first\n\n    # Update all computed values\n    def update(self):\n        self.price = round(self.interpolate(1),2)\n        self.volume = int(self.interpolate(-1))\n        if self.price < self.low:\n            self.low = self.price\n        if self.price >= self.high:\n            self.high = self.price\n        self.change = self.price - self.initial\n        \n    # Increment the time by a delta\n    def incr(self,dt):\n        self.time += dt\n        if self.index < (len(self.history) - 2):\n            while self.index < (len(self.history) - 2) and self.time >= self.history[self.index+1][3]:\n                self.index += 1\n        self.update()\n\n    def make_record(self):\n        return [self.name,round(self.price,2),self.date,minutes_to_str(self.time),round(self.change,2),self.open,round(self.high,2),\n                round(self.low,2),self.volume]\n\nclass MarketSimulator(object):\n    def __init__(self):\n        self.stocks = { }\n        self.prices = { }\n        self.time = 0\n        self.observers = []\n    def register(self,observer):\n        self.observers.append(observer)\n\n    def publish(self,record):\n        for obj in self.observers:\n            obj.update(record)\n    def add_history(self,filename):\n        hist = read_history(filename)\n        for record in hist:\n            if record[0] not in self.stocks:\n                self.stocks[record[0]] = StockTrack(record[0])\n            self.stocks[record[0]].add_data(record) \n\n    def reset(self,time):\n        self.time = time\n        for s in self.stocks.values():\n            s.reset(time)\n\n    # Run forever.  Dt is in seconds\n    def run(self,dt):\n        for s in self.stocks:\n            self.prices[s] = self.stocks[s].price\n            self.publish(self.stocks[s].make_record())\n        while self.time < 1000:\n            for s in self.stocks:\n                self.stocks[s].incr(dt/60.0)    # Increment is in minutes\n                if self.stocks[s].price != self.prices[s]:\n                    self.prices[s] = self.stocks[s].price\n                    self.publish(self.stocks[s].make_record())\n            time.sleep(dt)\n            self.time += (dt/60.0)\n\n\nclass BasicPrinter(object):\n    def update(self,record):\n        print(csv_record(record))\n\nclass LogPrinter(object):\n    def __init__(self,filename):\n        self.f = open(filename,\"w\")\n    def update(self,record):\n        self.f.write(csv_record(record)+\"\\n\")\n        self.f.flush()\n\nm = MarketSimulator()\nm.add_history(history_file)\nm.reset(minutes(\"9:30am\"))\n\nm.register(BasicPrinter())\nm.register(LogPrinter(\"stocklog.csv\"))\n\nm.run(1)\n\n\n   \n"
  },
  {
    "path": "Work/README.md",
    "content": "# Work Area\n\nDo all of your coding work here, in the `Work/` directory.  A number of starting\nfiles have been given (`bounce.py`, `mortgage.py`, `pcost.py`, etc.) along with\ntheir corresponding exercise number.\n\nMany of the programs you write reference files found in the `Data/` directory.\nThat is also located here.\n"
  },
  {
    "path": "Work/bounce.py",
    "content": "# bounce.py\n#\n# Exercise 1.5\n"
  },
  {
    "path": "Work/fileparse.py",
    "content": "# fileparse.py\n#\n# Exercise 3.3\n"
  },
  {
    "path": "Work/mortgage.py",
    "content": "# mortgage.py\n#\n# Exercise 1.7\n"
  },
  {
    "path": "Work/pcost.py",
    "content": "# pcost.py\n#\n# Exercise 1.27\n"
  },
  {
    "path": "Work/report.py",
    "content": "# report.py\n#\n# Exercise 2.4\n"
  },
  {
    "path": "_config.yml",
    "content": "theme: jekyll-theme-cayman"
  },
  {
    "path": "_layouts/default.html",
    "content": "<!DOCTYPE html>\n<html lang=\"{{ site.lang | default: \"en-US\" }}\">\n  <head>\n\n    {% if site.google_analytics %}\n      <script async src=\"https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics }}\"></script>\n      <script>\n        window.dataLayer = window.dataLayer || [];\n        function gtag(){dataLayer.push(arguments);}\n        gtag('js', new Date());\n        gtag('config', '{{ site.google_analytics }}');\n      </script>\n    {% endif %}\n    <meta charset=\"UTF-8\">\n\n{% seo %}\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <meta name=\"theme-color\" content=\"#157878\">\n    <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">\n    <link rel=\"stylesheet\" href=\"{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}\">\n  </head>\n  <body>\n<!--    <a id=\"skip-to-content\" href=\"#content\">Skip to the content.</a> -->\n\n    <header class=\"page-header\" role=\"banner\">\n      <h1 class=\"project-name\">Practical Python Programming</h1>\n      <h2 class=\"project-tagline\">A course by David Beazley (<a href=\"https://dabeaz.com\" style=\"color:white\">@dabeaz</a>)</h2>\n      <!--{% if site.github.is_project_page %}\n        <a href=\"{{ site.github.repository_url }}\" class=\"btn\">View on GitHub</a>\n      {% endif %}\n\t-->\n      {% if site.show_downloads %}\n        <a href=\"{{ site.github.zip_url }}\" class=\"btn\">Download .zip</a>\n        <a href=\"{{ site.github.tar_url }}\" class=\"btn\">Download .tar.gz</a>\n      {% endif %}\n    </header>\n\n    <main id=\"content\" class=\"main-content\" role=\"main\">\n      {{ content }}\n\n      <footer class=\"site-footer\">\n<a rel=\"license\" href=\"http://creativecommons.org/licenses/by-sa/4.0/\"><img alt=\"Creative Commons License\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by-sa/4.0/88x31.png\" /></a><br />This work is licensed under a <a rel=\"license\" href=\"http://creativecommons.org/licenses/by-sa/4.0/\">Creative Commons Attribution-ShareAlike 4.0 International License</a>.\n<br/>\nCopyright (C) 2007-2023, <a href=\"https://dabeaz.com\">David Beazley</a>.  Come take an <a href=\"https://dabeaz.com/courses.html\">advanced computer science course</a>. \n        {% if site.github.is_project_page %}\n          <span class=\"site-footer-owner\">Fork me on <a href=\"{{ site.github.repository_url }}\">GitHub</a></span>\n        {% endif %}\n<!--        <span class=\"site-footer-credits\">This page was generated by <a href=\"https://pages.github.com\">GitHub Pages</a>.</span> -->\n      </footer>\n    </main>\n  </body>\n</html>\n"
  }
]