{"id":"reading-files-combine","slug":"combine","title":"Combining data from several files","text":"Data that belongs together is often spread over several files. A shop might keep its product names in one file and the amounts in stock in another, because different people update them. What connects the files is a shared **key**: a value, such as a product code, that appears in both and means the same thing in each.\n\nThe usual way to combine such files is to read each one into a dictionary with the key as the dictionary's key, and then look the keys up:\n\n```file products.csv\ncode;name\nP1;desk lamp\nP2;office chair\nP3;bookshelf\n```\n\n```file stock.csv\ncode;amount\nP2;14\nP1;3\n```\n\n```python\nnames = {}\nwith open(\"products.csv\") as file:\n    for line in file:\n        parts = line.strip().split(\";\")\n        if parts[0] == \"code\":\n            continue\n        names[parts[0]] = parts[1]\n\namounts = {}\nwith open(\"stock.csv\") as file:\n    for line in file:\n        parts = line.strip().split(\";\")\n        if parts[0] == \"code\":\n            continue\n        amounts[parts[0]] = int(parts[1])\n\nfor code, name in names.items():\n    if code in amounts:\n        print(f\"{name}: {amounts[code]} in stock\")\n    else:\n        print(f\"{name}: none in stock\")\n```\n\n```output\ndesk lamp: 3 in stock\noffice chair: 14 in stock\nbookshelf: none in stock\n```\n\nNotice two things. The lines of the two files are in a different order, and that doesn't matter, because the dictionary finds each code wherever it was in the file. And `P3` has no line in `stock.csv` at all: the `in` check handles that, where `amounts[code]` alone would stop the program with a `KeyError`.\n\n### One function, several files\n\nWhen two files have the same shape, one function can read both. Programs also often ask the user which files to read, instead of having the names in the code:\n\n```file names.csv\n101;Ana\n102;Bo\n103;Cy\n```\n\n```file emails.csv\n103;cy@example.com\n101;ana@example.com\n```\n\n```python\ndef read_pairs(filename):\n    pairs = {}\n    with open(filename) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            pairs[parts[0]] = parts[1]\n    return pairs\n\n\nnames = read_pairs(input(\"Names file: \"))\nemails = read_pairs(input(\"Emails file: \"))\nfor number, name in names.items():\n    if number in emails:\n        print(f\"{name}: {emails[number]}\")\n    else:\n        print(f\"{name}: no email\")\n```\n\n```input\nnames.csv\nemails.csv\n```\n\n```output\nNames file: names.csv\nEmails file: emails.csv\nAna: ana@example.com\nBo: no email\nCy: cy@example.com\n```\n\nThe student numbers stay strings here: they are only compared with each other, never calculated with, so there's no need to convert them.\n\n### Many lines with the same key\n\nSometimes a file has many lines for the same key, such as one line for every sale of a product. Then the dictionary can collect a total for each key: the first time a key comes up, its total starts at 0, and every line adds to it.\n\n```python\ntotals = {}\nwith open(\"sales.csv\") as file:\n    for line in file:\n        parts = line.strip().split(\";\")\n        code = parts[0]\n        if code not in totals:\n            totals[code] = 0\n        totals[code] += int(parts[1])\n```\n","task":"The file `prices.csv` has the price of each item in a shop, and `cart.csv` has the items in a customer's cart and how many of each. Fill in the blank so the program prints the total price of the cart. With these files\n\n```text\napple;0.40\nbread;2.10\nmilk;1.15\n```\n\n```text\nbread;1\napple;6\nmilk;2\n```\n\nthe program prints:\n\n```\nTotal: 6.80\n```\n","kind":"fill","seed":"prices = {}\nwith open(\"prices.csv\") as file:\n    for line in file:\n        parts = line.strip().split(\";\")\n        prices[parts[0]] = float(parts[1])\n\ntotal = 0\nwith open(\"cart.csv\") as file:\n    for line in file:\n        parts = line.strip().split(\";\")\n        item = parts[0]\n        count = int(parts[1])\n        total += count * prices[[[item]]]\nprint(f\"Total: {total:.2f}\")\n","solution":"prices = {}\nwith open(\"prices.csv\") as file:\n    for line in file:\n        parts = line.strip().split(\";\")\n        prices[parts[0]] = float(parts[1])\n\ntotal = 0\nwith open(\"cart.csv\") as file:\n    for line in file:\n        parts = line.strip().split(\";\")\n        item = parts[0]\n        count = int(parts[1])\n        total += count * prices[item]\nprint(f\"Total: {total:.2f}\")\n","hints":["The price of an item is in the dictionary `prices`, under the item's name. The blank is the key that goes between the square brackets.","The key is the item's name, which the line before has stored in the variable `item`."],"tests":[{"name":"Test 1","stdin":"","files":{"prices.csv":"apple;0.40\nbread;2.10\nmilk;1.15\n","cart.csv":"bread;1\napple;6\nmilk;2\n"},"expect":"Total: 6.80","hidden":false},{"name":"Test 2","stdin":"","files":{"prices.csv":"apple;0.40\nbread;2.10\nmilk;1.15\n","cart.csv":"milk;4\n"},"expect":"Total: 4.60","hidden":true}],"require":[],"forbid":[],"more":[{"task":"A climbing gym keeps its members in one file (`number;name`) and every visit in another (`number;date`, one line per visit). Write a program that asks for the names of the two files and prints each member's name and number of visits, in the order of the members file. Members who haven't visited get 0.\n\nWith these files, `members.csv` and `visits.csv`\n\n```text\n1;Ana\n2;Bo\n3;Cy\n```\n\n```text\n3;2026-03-02\n1;2026-03-02\n1;2026-03-05\n1;2026-03-09\n```\n\na run looks like this:\n\n```\nMembers: members.csv\nVisits: visits.csv\nAna 3\nBo 0\nCy 1\n```\n","kind":"code","seed":"members_file = input(\"Members: \")\nvisits_file = input(\"Visits: \")\n","solution":"members_file = input(\"Members: \")\nvisits_file = input(\"Visits: \")\n\nnames = {}\nvisits = {}\nwith open(members_file) as file:\n    for line in file:\n        parts = line.strip().split(\";\")\n        names[parts[0]] = parts[1]\n        visits[parts[0]] = 0\n\nwith open(visits_file) as file:\n    for line in file:\n        parts = line.strip().split(\";\")\n        visits[parts[0]] += 1\n\nfor number, name in names.items():\n    print(name, visits[number])\n","hints":["Read the members file into a dictionary from number to name. Open the file whose name the user typed, not a fixed name.","A second dictionary can count the visits. Give every member a count of 0 while you read the members file.","Then each line of the visits file adds one to its member's count. Finally go through the members and print each name with its count."],"tests":[{"name":"Test 1","stdin":"members.csv\nvisits.csv\n","files":{"members.csv":"1;Ana\n2;Bo\n3;Cy\n","visits.csv":"3;2026-03-02\n1;2026-03-02\n1;2026-03-05\n1;2026-03-09\n"},"expect":"Members: members.csv\nVisits: visits.csv\nAna 3\nBo 0\nCy 1","hidden":false},{"name":"Test 2","stdin":"club.csv\nmarch.csv\n","files":{"club.csv":"7;Dee\n8;Eero\n","march.csv":"8;2026-03-01\n8;2026-03-15\n7;2026-03-20\n8;2026-03-28\n"},"expect":"Members: club.csv\nVisits: march.csv\nDee 1\nEero 3","hidden":true}],"require":[],"forbid":[]},{"task":"A football league has two files, each with a header line. `players.csv` tells which team each player is in (`player;team`), and `goals.csv` has a line for each match in which a player scored (`player;goals`). Write a function `team_goals(players_file, goals_file)` that returns a dictionary from each team to its total goals. Every team of the players file is in it, in the order the teams first appear there, also a team whose players scored nothing.\n\nWith these files\n\n```text\nplayer;team\nAna;Owls\nBo;Foxes\nCy;Owls\nDee;Bears\n```\n\n```text\nplayer;goals\nAna;2\nCy;1\nAna;1\nBo;3\n```\n\n`print(team_goals(\"players.csv\", \"goals.csv\"))` prints:\n\n```\n{'Owls': 4, 'Foxes': 3, 'Bears': 0}\n```\n","kind":"code","seed":"def team_goals(players_file, goals_file):\n    pass\n\n\nif __name__ == \"__main__\":\n    print(team_goals(\"players.csv\", \"goals.csv\"))\n","solution":"def team_goals(players_file, goals_file):\n    teams = {}\n    goals = {}\n    with open(players_file) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            if parts[0] == \"player\":\n                continue\n            teams[parts[0]] = parts[1]\n            goals[parts[1]] = 0\n\n    with open(goals_file) as file:\n        for line in file:\n            parts = line.strip().split(\";\")\n            if parts[0] == \"player\":\n                continue\n            team = teams[parts[0]]\n            goals[team] += int(parts[1])\n    return goals\n\n\nif __name__ == \"__main__\":\n    print(team_goals(\"players.csv\", \"goals.csv\"))\n","hints":["You need two dictionaries: one from player to team (to find a scorer's team), and the result, from team to goals.","While reading the players file, store each player's team and give the team 0 goals. Skip the header lines in both files.","For each line of the goals file, find the player's team with the first dictionary and add the goals to that team: `goals[team] += int(parts[1])`."],"tests":[{"name":"print(team_goals(\"players.csv\", \"goals.csv\"))","stdin":"","call":"print(team_goals(\"players.csv\", \"goals.csv\"))\n","files":{"players.csv":"player;team\nAna;Owls\nBo;Foxes\nCy;Owls\nDee;Bears\n","goals.csv":"player;goals\nAna;2\nCy;1\nAna;1\nBo;3\n"},"expect":"{'Owls': 4, 'Foxes': 3, 'Bears': 0}","hidden":false},{"name":"team_goals(\"squad.csv\", \"spring.csv\")","stdin":"","call":"team_goals(\"squad.csv\", \"spring.csv\")\n","files":{"squad.csv":"player;team\nEli;Hawks\nFay;Hawks\nGus;Wolves\n","spring.csv":"player;goals\nGus;1\nFay;2\nGus;2\nEli;1\n"},"expect":"{'Hawks': 3, 'Wolves': 3}","hidden":true,"returns":true}],"require":[],"forbid":[]}]}