{"id":"dsa-sorting-insertion","slug":"insertion-sort","title":"Insertion sort","text":"Python sorts a list for you with `sorted` and `sort`, and in your own programs they're almost always the right tool. So why learn how sorting works? The ideas inside the classic sorting algorithms come back in many other problems: growing a part that is already solved, splitting a problem in halves, putting one item in its final place. Interviewers ask about them, and knowing what each one costs tells you which tool fits. This module builds the classic sorts one by one and ends with Python's own.\n\n### Sorting cards in your hand\n\n**Insertion sort** sorts a list the way many people sort a hand of cards: take the cards one at a time, and slide each new card left past the bigger ones until it sits in its place. The cards to the left of the new one are always in order, a sorted part that grows by one card each round.\n\nIn a list, sliding left means moving each bigger item one place to the right, which opens a gap, and then dropping the new item into the gap. Each item moved is one **move**. This program prints the list after each round, with the number of moves the round made:\n\n```python\nvalues = [4, 8, 1, 6, 3]\nprint(values)\nfor i in range(1, len(values)):\n    item = values[i]\n    j = i\n    while j > 0 and values[j - 1] > item:\n        values[j] = values[j - 1]\n        j -= 1\n    values[j] = item\n    print(values, \"moves:\", i - j)\n```\n\n```output\n[4, 8, 1, 6, 3]\n[4, 8, 1, 6, 3] moves: 0\n[1, 4, 8, 6, 3] moves: 2\n[1, 4, 6, 8, 3] moves: 1\n[1, 3, 4, 6, 8] moves: 3\n```\n\nThe 8 already sits after the 4, so the first round leaves the list as it was. The 1 is the smallest item so far: the 8 and the 4 both step right, and the 1 lands at the front. The 6 slides past the 8 alone, because the 4 before it is smaller. The 3 slides past 8, 6 and 4 and stops behind the 1.\n\nWhy is the result sorted? Before round `i`, the items `values[:i]` are in order. The round puts `values[i]` among them in its right place, so after the round `values[:i + 1]` is in order. After the last round, that's the whole list.\n\n### What it costs\n\nEach move puts right exactly one pair of items that was in the wrong order, a bigger item before a smaller one. Such a pair is an **inversion**, and insertion sort makes exactly as many moves as the list has inversions:\n\n- A list in reverse order has every pair the wrong way round. Round `i` makes `i` moves, n(n - 1) / 2 in all: O(n²). For 100,000 values that's about 5 billion moves, many minutes in Python.\n- A sorted list has no inversions. Each round compares once and stops: n - 1 comparisons, O(n).\n- A **nearly sorted** list, where every item is at most a few places from where it belongs, is O(n) too: each round makes only a few moves.\n\nSo insertion sort is slow in general but quick on data that is almost in order. It needs no extra memory, O(1), since it works inside the list. And equal items never pass each other, because `>` stops at an equal item.\n\n### The mistake that throws the speed away\n\nThe `while` loop stops at the first item that isn't bigger, and that's what makes the nearly sorted case fast. A common way to write insertion sort swaps neighbors in a `for` loop that always runs to the front of the list:\n\n```python\ndef stops_early(values):\n    comparisons = 0\n    for i in range(1, len(values)):\n        item = values[i]\n        j = i\n        while j > 0:\n            comparisons += 1\n            if values[j - 1] <= item:\n                break\n            values[j] = values[j - 1]\n            j -= 1\n        values[j] = item\n    return comparisons\n\n\ndef never_stops(values):\n    comparisons = 0\n    for i in range(1, len(values)):\n        for j in range(i, 0, -1):\n            comparisons += 1\n            if values[j - 1] > values[j]:\n                values[j - 1], values[j] = values[j], values[j - 1]\n    return comparisons\n\n\nprint(stops_early(list(range(1000))), never_stops(list(range(1000))))\nprint(stops_early(list(range(1000, 0, -1))), never_stops(list(range(1000, 0, -1))))\n```\n\n```output\n999 499500\n499500 499500\n```\n\nBoth sort correctly, and on a list in reverse order they do the same work. But `never_stops` keeps comparing after the item has found its place, so a sorted list costs it n(n - 1) / 2 comparisons too: O(n²) always. For 200,000 sorted values that's 20 billion comparisons, many minutes, where `stops_early` needs 199,999. The inner loop has to stop: with the condition in the `while`, or with a `break`.\n","task":"The function below sorts a list from the largest value to the smallest with insertion sort. Fill in the condition, so that the item moves left past the items that are smaller than it. The program should print:\n\n```\n[95, 95, 72, 60, 40, 18]\n```\n","kind":"fill","seed":"def sort_descending(values):\n    for i in range(1, len(values)):\n        item = values[i]\n        j = i\n        while j > 0 and [[values[j - 1] < item]]:\n            values[j] = values[j - 1]\n            j -= 1\n        values[j] = item\n\n\nscores = [40, 95, 72, 95, 18, 60]\nsort_descending(scores)\nprint(scores)\n","solution":"def sort_descending(values):\n    for i in range(1, len(values)):\n        item = values[i]\n        j = i\n        while j > 0 and values[j - 1] < item:\n            values[j] = values[j - 1]\n            j -= 1\n        values[j] = item\n\n\nscores = [40, 95, 72, 95, 18, 60]\nsort_descending(scores)\nprint(scores)\n","hints":["In the lesson's version, the item moves past the items before it that are bigger: `values[j - 1] > item`. For the largest first, which items should it move past?","The item moves left while the item before it is smaller than it: `values[j - 1] < item`."],"tests":[{"name":"Output","stdin":"","expect":"[95, 95, 72, 60, 40, 18]","hidden":false}],"require":[],"forbid":[],"more":[{"task":"What does this program print? Type each line it prints.\n","kind":"predict","seed":"values = [5, 6, 1, 6, 2]\ntotal = 0\nfor i in range(1, len(values)):\n    item = values[i]\n    j = i\n    while j > 0 and values[j - 1] > item:\n        values[j] = values[j - 1]\n        j -= 1\n    values[j] = item\n    print(i - j)\n    total += i - j\nprint(values, total)\n","solution":"values = [5, 6, 1, 6, 2]\ntotal = 0\nfor i in range(1, len(values)):\n    item = values[i]\n    j = i\n    while j > 0 and values[j - 1] > item:\n        values[j] = values[j - 1]\n        j -= 1\n    values[j] = item\n    print(i - j)\n    total += i - j\nprint(values, total)\n","hints":["Each of the first lines is the number of moves of one round: how many items before the new item are bigger than it.","In round 3 the second 6 doesn't move at all: `6 > 6` is false, so the loop stops at once. In round 4, the 2 passes 6, 6 and 5."],"tests":[{"name":"Output","stdin":"","expect":"0\n2\n0\n3\n[1, 2, 5, 6, 6] 5","hidden":false}],"require":[],"forbid":[],"lines":["0","2","0","3","[1, 2, 5, 6, 6] 5"]},{"task":"A shop keeps its list of prices sorted, and each day a few prices change a little, so the list is only nearly sorted. Write `insertion_sort(values)`, which sorts the list in place with insertion sort and returns the number of moves it made (each item moved one place to the right is one move). It must be fast for a nearly sorted list of 200,000 prices, so each round has to stop as soon as the item is in its place. Don't use `sorted` or `sort`.\n\nFor example, this program:\n\n```python\nvalues = [19, 25, 22, 30, 28, 27, 40]\nprint(insertion_sort(values))\nprint(values)\n```\n\nprints:\n\n```\n4\n[19, 22, 25, 27, 28, 30, 40]\n```\n","kind":"code","seed":"def insertion_sort(values):\n    pass\n\n\nif __name__ == \"__main__\":\n    values = [19, 25, 22, 30, 28, 27, 40]\n    print(insertion_sort(values))\n    print(values)\n","solution":"def insertion_sort(values):\n    moves = 0\n    for i in range(1, len(values)):\n        item = values[i]\n        j = i\n        while j > 0 and values[j - 1] > item:\n            values[j] = values[j - 1]\n            j -= 1\n            moves += 1\n        values[j] = item\n    return moves\n\n\nif __name__ == \"__main__\":\n    values = [19, 25, 22, 30, 28, 27, 40]\n    print(insertion_sort(values))\n    print(values)\n","hints":["Start from the lesson's insertion sort and add a counter that grows by one each time an item moves one place to the right.","Count `moves += 1` inside the `while` loop, next to `values[j] = values[j - 1]`, and return the counter at the end.","The speed comes from the `while` condition: `while j > 0 and values[j - 1] > item:` stops at the first item that isn't bigger. A `for` loop that always runs to the front of the list makes every round long, even when nothing moves."],"tests":[{"name":"Test 1","stdin":"","call":"values = [19, 25, 22, 30, 28, 27, 40]\nprint(insertion_sort(values))\nprint(values)\n","expect":"4\n[19, 22, 25, 27, 28, 30, 40]","hidden":false},{"name":"Test 2","stdin":"","call":"values = [10, 20, 30, 40, 50]\nprint(insertion_sort(values), values)\n","expect":"0 [10, 20, 30, 40, 50]","hidden":true},{"name":"Test 3","stdin":"","call":"values = [50, 40, 30, 20, 10]\nprint(insertion_sort(values), values)\n","expect":"10 [10, 20, 30, 40, 50]","hidden":true},{"name":"Test 4","stdin":"","call":"values = [3, 1, 3, 1]\nprint(insertion_sort(values), values)\n","expect":"3 [1, 1, 3, 3]","hidden":true},{"name":"Test 5","stdin":"","call":"values = []\nprint(insertion_sort(values), values)\n","expect":"0 []","hidden":true},{"name":"Test 6","stdin":"","call":"values = [7]\nprint(insertion_sort(values), values)\n","expect":"0 [7]","hidden":true},{"name":"Test 7","stdin":"","call":"values = list(range(200_000))\nfor i in range(0, 200_000, 2):\n    values[i], values[i + 1] = values[i + 1], values[i]\nprint(insertion_sort(values), values == list(range(200_000)))\n","expect":"100000 True","hidden":true,"big":true},{"name":"Test 8","stdin":"","call":"values = [i + 1 if i % 3 < 2 else i - 2 for i in range(199_998)]\nprint(insertion_sort(values), values[:4], values[-2:])\n","expect":"133332 [0, 1, 2, 3] [199996, 199997]","hidden":true,"big":true}],"require":[],"forbid":[{"pattern":"\\bsorted\\s*\\(|\\.sort\\s*\\(","message":"Write insertion sort yourself on this step, without `sorted` or `sort`."}]}]}