{"id":"reading-files-csv","slug":"csv","title":"Lines with several values","text":"Often each line of a file holds several pieces of data about one thing: a name, an age and a city, say. A common format for this is **CSV**, short for comma-separated values. Each line is one **record**, and a separator character, usually a comma `,` or a semicolon `;`, separates its **fields**. Spreadsheet programs can save and open CSV files, which makes them a common way to move data from one program to another.\n\n### Splitting a string\n\nTo get the fields of a line, use the string method `split`, which you have met in the module on strings and lists. It cuts a string at each separator and returns the pieces as a list of strings. You give it the separator as a string:\n\n```pycon\n>>> \"Rex;dog;4\".split(\";\")\n['Rex', 'dog', '4']\n>>> \"red,green,blue\".split(\",\")\n['red', 'green', 'blue']\n>>> parts = \"Rex;dog;4\".split(\";\")\n>>> parts[0]\n'Rex'\n>>> int(parts[2]) + 1\n5\n>>> \"cats  and   dogs\".split()\n['cats', 'and', 'dogs']\n```\n\nThe separator itself isn't in any of the pieces. The pieces are always strings, even when they look like numbers: convert them with `int` or `float` before you calculate with them. (And remember that without an argument, `split()` splits at spaces, however many there are in a row.)\n\n### A CSV file, line by line\n\n```file pets.csv\nRex;dog;4\nMisty;cat;11\nBubbles;fish;1\n```\n\n```python\nwith open(\"pets.csv\") as file:\n    for line in file:\n        parts = line.strip().split(\";\")\n        name = parts[0]\n        animal = parts[1]\n        age = int(parts[2])\n        if age >= 10:\n            print(f\"{name} the {animal} is a senior, {age} years old\")\n        else:\n            print(f\"{name} the {animal} is {age} years old\")\n```\n\n```output\nRex the dog is 4 years old\nMisty the cat is a senior, 11 years old\nBubbles the fish is 1 years old\n```\n\n`line.strip().split(\";\")` first removes the line break and then splits. Without the `strip`, the last field would keep the line break, `['Rex', 'dog', '4\\n']`. `int` happens to accept that, but a text field at the end of the line, such as a city, would carry its `\\n` into everything you do with it.\n\n### Headers and extra spaces\n\nMany CSV files start with a **header** line that names the columns, and some have a space after each separator. In this file each line has a player, a team and the points from any number of rounds:\n\n```file rounds.csv\nplayer; team; round 1; round 2; round 3\nMaya; Owls; 12; 9; 15\nLeo; Foxes; 8; 14; 10\n```\n\n```python\nwith open(\"rounds.csv\") as file:\n    for line in file:\n        parts = line.split(\";\")\n        if parts[0] == \"player\":\n            continue\n        name = parts[0]\n        team = parts[1].strip()\n        points = []\n        for part in parts[2:]:\n            points.append(int(part))\n        print(f\"{name} ({team}): {points}, total {sum(points)}\")\n```\n\n```output\nMaya (Owls): [12, 9, 15], total 36\nLeo (Foxes): [8, 14, 10], total 32\n```\n\n- The header is recognized by its first field and skipped with `continue`.\n- `parts[1]` is `\" Owls\"` with a space in front, so it gets its own `strip()`.\n- The numbers need no stripping, because `int` ignores spaces and a line break around a number.\n- The slice `parts[2:]` is every field from the third one on, so the same code works however many rounds a line has.\n","task":"Each line of the file `orders.csv` has a customer, a number of items and the price of one item. Fill in the blanks so the program prints what each customer pays. With this file\n\n```text\nAda;3;2.50\nBen;1;4.20\nCleo;2;3.75\n```\n\nthe program prints:\n\n```\nAda pays 7.50\nBen pays 4.20\nCleo pays 7.50\n```\n","kind":"fill","seed":"with open(\"orders.csv\") as file:\n    for line in file:\n        parts = line.strip().[[split]](\";\")\n        customer = parts[0]\n        total = int(parts[1]) * [[float]](parts[2])\n        print(f\"{customer} pays {total:.2f}\")\n","solution":"with open(\"orders.csv\") as file:\n    for line in file:\n        parts = line.strip().split(\";\")\n        customer = parts[0]\n        total = int(parts[1]) * float(parts[2])\n        print(f\"{customer} pays {total:.2f}\")\n","hints":["The first blank is the string method that cuts a string into a list at each separator.","The price `2.50` has a decimal point, so it must become a decimal number before it's multiplied: `float(parts[2])`."],"tests":[{"name":"Test 1","stdin":"","files":{"orders.csv":"Ada;3;2.50\nBen;1;4.20\nCleo;2;3.75\n"},"expect":"Ada pays 7.50\nBen pays 4.20\nCleo pays 7.50","hidden":false},{"name":"Test 2","stdin":"","files":{"orders.csv":"Dana;10;0.99\nEli;4;12.00\n"},"expect":"Dana pays 9.90\nEli pays 48.00","hidden":true}],"require":[],"forbid":[],"more":[{"task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"line = \"Oslo,Norway,709000\\n\"\nparts = line.split(\",\")\nprint(len(parts))\nprint(parts[1])\nprint(int(parts[2]) // 1000)\nprint(parts[2] == \"709000\")\n","solution":"line = \"Oslo,Norway,709000\\n\"\nparts = line.split(\",\")\nprint(len(parts))\nprint(parts[1])\nprint(int(parts[2]) // 1000)\nprint(parts[2] == \"709000\")\n","hints":["The string has two commas, so `split(\",\")` cuts it into three pieces.","`int` ignores the line break around a number, so the third piece converts fine. `//` divides and drops the decimals.","The line was never stripped, so the last piece still ends with `\\n`, and the comparison with `\"709000\"` is false."],"tests":[{"name":"Output","stdin":"","expect":"3\nNorway\n709\nFalse","hidden":false}],"require":[],"forbid":[],"lines":["3","Norway","709","False"]},{"task":"The file `menu.csv` lists the dishes of a café after a header line. Each line has a dish, its price and its course: `starter`, `main` or `dessert`. Write a function `prices_in(filename, course)` that returns a dictionary of the dishes of the given course only. Its keys are the dishes, and its values are their prices as decimal numbers (`float`). When the menu has no dishes of that course, the dictionary is empty.\n\nWith this file\n\n```text\ndish;price;course\nsoup;6.50;starter\npasta;11.90;main\nsalad;8.00;starter\ncake;5.20;dessert\n```\n\n`print(prices_in(\"menu.csv\", \"starter\"))` prints:\n\n```\n{'soup': 6.5, 'salad': 8.0}\n```\n","kind":"code","seed":"def prices_in(filename, course):\n    pass\n\n\nif __name__ == \"__main__\":\n    print(prices_in(\"menu.csv\", \"starter\"))\n","solution":"def prices_in(filename, course):\n    prices = {}\n    with open(filename) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            if parts[0] == \"dish\":\n                continue\n            if parts[2] == course:\n                prices[parts[0]] = float(parts[1])\n    return prices\n\n\nif __name__ == \"__main__\":\n    print(prices_in(\"menu.csv\", \"starter\"))\n","hints":["Start with an empty dictionary, open the file named by the parameter, and split each stripped line at `;`. Skip the header line, whose first field is `dish`.","The course is the last field, `parts[2]`. Strip the line before you split it: otherwise that field ends with `\\n` and is never equal to `\"starter\"`.","Only when `parts[2] == course`, store the dish: `prices[parts[0]] = float(parts[1])`. Return the dictionary after the `with` block."],"tests":[{"name":"print(prices_in(\"menu.csv\", \"starter\"))","stdin":"","call":"print(prices_in(\"menu.csv\", \"starter\"))\n","files":{"menu.csv":"dish;price;course\nsoup;6.50;starter\npasta;11.90;main\nsalad;8.00;starter\ncake;5.20;dessert\n"},"expect":"{'soup': 6.5, 'salad': 8.0}","hidden":false},{"name":"prices_in(\"lunch.csv\", \"dessert\")","stdin":"","call":"prices_in(\"lunch.csv\", \"dessert\")\n","files":{"lunch.csv":"dish;price;course\npancakes;7.40;main\nice cream;3.90;dessert\nburger;12.50;main\nfruit bowl;4.00;dessert\n"},"expect":"{'ice cream': 3.9, 'fruit bowl': 4.0}","hidden":true,"returns":true},{"name":"prices_in(\"lunch.csv\", \"starter\")","stdin":"","call":"prices_in(\"lunch.csv\", \"starter\")\n","files":{"lunch.csv":"dish;price;course\npancakes;7.40;main\nice cream;3.90;dessert\nburger;12.50;main\n"},"expect":"{}","hidden":true,"returns":true},{"name":"prices_in(\"menu.csv\", \"main\")","stdin":"","call":"prices_in(\"menu.csv\", \"main\")\n","files":{"menu.csv":"dish;price;course\nsoup;6.50;starter\npasta;11.90;main\nrisotto;13.00;main\ncake;5.20;dessert\n"},"expect":"{'pasta': 11.9, 'risotto': 13.0}","hidden":true,"returns":true}],"require":[],"forbid":[]}]}