{"id":"comprehensions-instead-of-loops","slug":"instead-of-loops","title":"Comprehensions instead of loops","text":"Many loops you've written follow the same recipe: start with an empty list, go through a series, maybe check a condition, and append one value per round. Every loop like that can become a comprehension. Each part of the loop has its place:\n\n```python\nlong_names = []\nfor name in names:\n    if len(name) > 4:\n        long_names.append(name.upper())\n\nlong_names = [name.upper() for name in names if len(name) > 4]\n```\n\nWhat was appended moves to the front, the `for` line stays in the middle, and the `if` goes to the end.\n\n### Your own functions inside\n\nWhen the expression needs several lines of work, write a function for it and call the function in the comprehension:\n\n```python\ndef grade(points):\n    if points >= 90:\n        return \"A\"\n    if points >= 75:\n        return \"B\"\n    return \"C\"\n\n\npoints = [95, 80, 62, 77]\nprint([grade(p) for p in points])\n```\n\n```output\n['A', 'B', 'C', 'B']\n```\n\nThe comprehension stays short and readable, and the function can be tested on its own.\n\n### Lists, tuples and objects as items\n\nThe items can be anything, and so can the values you make from them. Here the items are lists and the new items are tuples:\n\n```pycon\n>>> table = [[3, 8, 1], [5], [9, 2]]\n>>> [len(row) for row in table]\n[3, 1, 2]\n>>> [max(row) for row in table]\n[8, 5, 9]\n>>> [(n, n * n) for n in range(1, 4)]\n[(1, 1), (2, 4), (3, 9)]\n```\n\nItems that are objects work the same way: `[book.title for book in books]` makes a list of the books' titles.\n\n### Two lists side by side\n\nTo combine two lists of the same length item by item, go through the indexes with `range(len(...))`:\n\n```python\nprices = [3, 10, 2]\namounts = [4, 1, 5]\ntotals = [prices[i] * amounts[i] for i in range(len(prices))]\nprint(totals)\n```\n\n```output\n[12, 10, 10]\n```\n\n### Counting and adding up\n\nA comprehension makes a list, and the functions you know for lists work on it right away. `len` of a filtered list counts the items that pass, and `sum` adds the values up:\n\n```pycon\n>>> scores = [72, 45, 90, 38, 66]\n>>> len([s for s in scores if s >= 50])\n3\n>>> sum([s for s in scores if s >= 50])\n228\n```\n\n### When to keep the loop\n\nA comprehension is for building a list. When a loop does something else, such as printing, asking for input or updating several variables at once, keep the loop. And if a comprehension grows so long that you have to read it three times, a loop or a helper function is kinder to the next reader.\n","task":"The function below builds its list with a loop. Rewrite it so that it returns a list comprehension instead, without `append`. It should still return the names longer than four letters in capital letters, so the program prints:\n\n```\n['GRACE', 'LINUS']\n```\n","kind":"code","seed":"def long_names_upper(names):\n    result = []\n    for name in names:\n        if len(name) > 4:\n            result.append(name.upper())\n    return result\n\n\nif __name__ == \"__main__\":\n    print(long_names_upper([\"Ada\", \"Grace\", \"Linus\", \"Tim\"]))\n","solution":"def long_names_upper(names):\n    return [name.upper() for name in names if len(name) > 4]\n\n\nif __name__ == \"__main__\":\n    print(long_names_upper([\"Ada\", \"Grace\", \"Linus\", \"Tim\"]))\n","hints":["What the loop appends goes to the front of the comprehension: `name.upper()`.","The loop's `if` becomes a filter at the end: `if len(name) > 4`.","The whole function body can be one line: `return [... for name in names if ...]`."],"tests":[{"name":"print(long_names_upper([\"Ada\", \"Grace\", \"Linus\", \"Tim\"]))","stdin":"","call":"print(long_names_upper([\"Ada\", \"Grace\", \"Linus\", \"Tim\"]))\n","expect":"['GRACE', 'LINUS']","hidden":false},{"name":"long_names_upper([\"Margaret\", \"Tim\", \"Barbara\", \"Ken\"])","stdin":"","call":"long_names_upper([\"Margaret\", \"Tim\", \"Barbara\", \"Ken\"])\n","expect":"['MARGARET', 'BARBARA']","hidden":true,"returns":true},{"name":"long_names_upper([\"Bo\", \"Al\"])","stdin":"","call":"long_names_upper([\"Bo\", \"Al\"])\n","expect":"[]","hidden":true,"returns":true}],"require":[{"pattern":"\\[[^\\n]*\\bfor\\b","message":"Return a list comprehension."}],"forbid":[{"pattern":"\\.append\\s*\\(","message":"The list comprehension builds the list for you, so there's no need for `append`."}],"more":[{"task":"A quiz has a list of the right answers, and each player gives a list of answers in the same order. Write a function `quiz_score(answers, key)` that returns the number of answers that match the right answer in the same position. Use a list comprehension.\n\nFor example, this call:\n\n```python\nprint(quiz_score([\"a\", \"c\", \"b\", \"d\"], [\"a\", \"b\", \"b\", \"d\"]))\n```\n\nprints:\n\n```\n3\n```\n","kind":"code","seed":"def quiz_score(answers, key):\n    pass\n\n\nif __name__ == \"__main__\":\n    print(quiz_score([\"a\", \"c\", \"b\", \"d\"], [\"a\", \"b\", \"b\", \"d\"]))\n","solution":"def quiz_score(answers, key):\n    return len([i for i in range(len(answers)) if answers[i] == key[i]])\n\n\nif __name__ == \"__main__\":\n    print(quiz_score([\"a\", \"c\", \"b\", \"d\"], [\"a\", \"b\", \"b\", \"d\"]))\n","hints":["The positions matter, so go through the indexes with `for i in range(len(answers))`.","Keep only the indexes where `answers[i] == key[i]`.","`len` of that filtered list is the number of right answers."],"tests":[{"name":"print(quiz_score([\"a\", \"c\", \"b\", \"d\"], [\"a\", \"b\", \"b\", \"d\"]))","stdin":"","call":"print(quiz_score([\"a\", \"c\", \"b\", \"d\"], [\"a\", \"b\", \"b\", \"d\"]))\n","expect":"3","hidden":false},{"name":"quiz_score([\"b\", \"b\", \"b\"], [\"a\", \"b\", \"c\"])","stdin":"","call":"quiz_score([\"b\", \"b\", \"b\"], [\"a\", \"b\", \"c\"])\n","expect":"1","hidden":true,"returns":true},{"name":"quiz_score([\"d\", \"d\"], [\"a\", \"b\"])","stdin":"","call":"quiz_score([\"d\", \"d\"], [\"a\", \"b\"])\n","expect":"0","hidden":true,"returns":true}],"require":[{"pattern":"\\[[^\\n]*\\bfor\\b","message":"Use a list comprehension to pick out the right answers."}],"forbid":[]},{"task":"Write a function `price_tags(items)` that gets a list of tuples, each with a product's name and its price, and returns a list of price tags as strings. A tag has the name, a colon, a space and the price with two decimals. Use a list comprehension.\n\nFor example, this program:\n\n```python\ntags = price_tags([(\"tea\", 2.5), (\"bread\", 3), (\"jam\", 4.25)])\nfor tag in tags:\n    print(tag)\n```\n\nprints:\n\n```\ntea: 2.50\nbread: 3.00\njam: 4.25\n```\n","kind":"code","seed":"def price_tags(items):\n    pass\n\n\nif __name__ == \"__main__\":\n    tags = price_tags([(\"tea\", 2.5), (\"bread\", 3), (\"jam\", 4.25)])\n    for tag in tags:\n        print(tag)\n","solution":"def price_tags(items):\n    return [f\"{name}: {price:.2f}\" for name, price in items]\n\n\nif __name__ == \"__main__\":\n    tags = price_tags([(\"tea\", 2.5), (\"bread\", 3), (\"jam\", 4.25)])\n    for tag in tags:\n        print(tag)\n","hints":["Each item is a tuple, so you can take it apart in the `for`: `for name, price in items`.","An f-string makes the tag. `{price:.2f}` shows a number with two decimals.","The expression is `f\"{name}: {price:.2f}\"`."],"tests":[{"name":"Test 1","stdin":"","call":"tags = price_tags([(\"tea\", 2.5), (\"bread\", 3), (\"jam\", 4.25)])\nfor tag in tags:\n    print(tag)\n","expect":"tea: 2.50\nbread: 3.00\njam: 4.25","hidden":false},{"name":"price_tags([(\"milk\", 1.2)])","stdin":"","call":"price_tags([(\"milk\", 1.2)])\n","expect":"['milk: 1.20']","hidden":true,"returns":true},{"name":"price_tags([])","stdin":"","call":"price_tags([])\n","expect":"[]","hidden":true,"returns":true}],"require":[{"pattern":"\\[[^\\n]*\\bfor\\b","message":"Build the list of tags with a list comprehension."}],"forbid":[{"pattern":"\\.append\\s*\\(","message":"Build the list with a list comprehension instead of `append`."}]}]}