{"id":"dsa-sorting-merge","slug":"merge-sort","title":"Merge sort","text":"Insertion sort is O(n²) on most lists: for 200,000 values in a random order, about 10 billion moves, far too slow in Python. **Merge sort** does the same job in O(n log n). Its idea is called **divide and conquer**: split the problem into halves, solve each half in the same way, and combine the two answers into one.\n\n### Merging two sorted lists\n\nThe combining step is a **merge**: two lists that are each sorted become one sorted list. The smallest item of all is at the front of one of the two lists, so compare the two front items, take the smaller one, and step past it. Two indexes, `i` and `j`, remember where the front of each list is now:\n\n```python\ndef merge(left, right):\n    merged = []\n    i = 0\n    j = 0\n    while i < len(left) and j < len(right):\n        if left[i] <= right[j]:\n            merged.append(left[i])\n            i += 1\n        else:\n            merged.append(right[j])\n            j += 1\n    merged += left[i:]\n    merged += right[j:]\n    return merged\n\n\nprint(merge([2, 5, 8, 13], [1, 5, 6, 17, 30]))\n```\n\n```output\n[1, 2, 5, 5, 6, 8, 13, 17, 30]\n```\n\nEach round of the loop takes one item, so a merge costs O(len(left) + len(right)). The loop stops as soon as one list runs out, and the other one may still have items, all at least as big as everything taken so far. The last two lines add them. Forget them, and this merge returns `[1, 2, 5, 5, 6, 8, 13]`: no error, just 17 and 30 quietly gone.\n\nTwo more details matter. On a tie, `<=` takes the item from the left list first, so equal items keep their order: merge sort is **stable**. And don't take the front item with `left.pop(0)`. Removing the first item of a list moves all the others one place, which is O(n) each time; the indexes keep each step O(1).\n\n### Splitting and merging\n\nNow merge sort itself. A list of 0 or 1 items is already sorted: that's the base case. Any longer list is split in the middle, each half is sorted by a call of `merge_sort`, and the two sorted halves are merged:\n\n```python\ndef merge(left, right):\n    merged = []\n    i = 0\n    j = 0\n    while i < len(left) and j < len(right):\n        if left[i] <= right[j]:\n            merged.append(left[i])\n            i += 1\n        else:\n            merged.append(right[j])\n            j += 1\n    merged += left[i:]\n    merged += right[j:]\n    return merged\n\n\ndef merge_sort(values):\n    if len(values) <= 1:\n        return values\n    middle = len(values) // 2\n    left = merge_sort(values[:middle])\n    right = merge_sort(values[middle:])\n    return merge(left, right)\n\n\nprint(merge_sort([9, 4, 7, 1, 6, 3, 8]))\n```\n\n```output\n[1, 3, 4, 6, 7, 8, 9]\n```\n\nHere is what happens inside, level by level. Seven items don't halve evenly, so the left half gets 3 and the right half 4:\n\n```text\n[9, 4, 7, 1, 6, 3, 8]\n[9, 4, 7]           [1, 6, 3, 8]            split\n[9]   [4, 7]        [1, 6]     [3, 8]       split\n[9]   [4]  [7]      [1]  [6]   [3]  [8]     single items are sorted\n[9]   [4, 7]        [1, 6]     [3, 8]       merge\n[4, 7, 9]           [1, 3, 6, 8]            merge\n[1, 3, 4, 6, 7, 8, 9]                       merge\n```\n\nWhy is it right? A list of 0 or 1 items is sorted. A longer list splits into two shorter ones, so the calls get smaller and end; and when both halves come back sorted, merging them gives a sorted list.\n\n### What it costs\n\n- **Time.** Halving 200,000 values again and again gets down to single items after 18 levels: about log n levels (log to the base 2). At each level, the merges together take each of the n values once, so each level is O(n). In all, O(n log n): about 3.6 million steps for 200,000 values, where insertion sort needs billions. That holds in the worst case too: no input slows merge sort down.\n- **Memory.** The slices `values[:middle]` and `values[middle:]` are copies, and each merge builds a new list, so merge sort needs O(n) extra memory. Insertion sort needs none.\n- **Recursion depth.** The calls only go about log n deep, 18 levels for 200,000 values, so Python's limit of about 1,000 nested calls is never near.\n","task":"This merge sort prints every list it merges. What does the program print? Type each line it prints.\n","kind":"predict","seed":"def merge(left, right):\n    merged = []\n    i = 0\n    j = 0\n    while i < len(left) and j < len(right):\n        if left[i] <= right[j]:\n            merged.append(left[i])\n            i += 1\n        else:\n            merged.append(right[j])\n            j += 1\n    merged += left[i:]\n    merged += right[j:]\n    print(merged)\n    return merged\n\n\ndef merge_sort(values):\n    if len(values) <= 1:\n        return values\n    middle = len(values) // 2\n    left = merge_sort(values[:middle])\n    right = merge_sort(values[middle:])\n    return merge(left, right)\n\n\nmerge_sort([6, 2, 5, 3, 1])\n","solution":"def merge(left, right):\n    merged = []\n    i = 0\n    j = 0\n    while i < len(left) and j < len(right):\n        if left[i] <= right[j]:\n            merged.append(left[i])\n            i += 1\n        else:\n            merged.append(right[j])\n            j += 1\n    merged += left[i:]\n    merged += right[j:]\n    print(merged)\n    return merged\n\n\ndef merge_sort(values):\n    if len(values) <= 1:\n        return values\n    middle = len(values) // 2\n    left = merge_sort(values[:middle])\n    right = merge_sort(values[middle:])\n    return merge(left, right)\n\n\nmerge_sort([6, 2, 5, 3, 1])\n","hints":["The first split gives `[6, 2]` and `[5, 3, 1]`. The left half is sorted completely, with all its merges, before the right half starts.","`[6, 2]` merges into `[2, 6]`. Then `[5, 3, 1]` splits into `[5]` and `[3, 1]`, and `[3, 1]` has to be merged before `[5]` can be merged with it. The last merge makes the whole list."],"tests":[{"name":"Output","stdin":"","expect":"[2, 6]\n[1, 3]\n[1, 3, 5]\n[1, 2, 3, 5, 6]","hidden":false}],"require":[],"forbid":[],"lines":["[2, 6]","[1, 3]","[1, 3, 5]","[1, 2, 3, 5, 6]"],"more":[{"task":"A quiz app shows its results from the highest score to the lowest, and players with the same score in the order they finished, which is their order in the list. Write `rank_results(results)` with merge sort, where `results` is a list of `(name, score)` tuples. It returns a new list sorted by score from the highest to the lowest, with equal scores kept in their original order. It must be fast for 80,000 results. Don't use `sorted` or `sort`.\n\nFor example, this program:\n\n```python\nresults = [(\"Ada\", 7), (\"Bo\", 9), (\"Cy\", 7), (\"Di\", 10)]\nfor name, score in rank_results(results):\n    print(name, score)\n```\n\nprints:\n\n```\nDi 10\nBo 9\nAda 7\nCy 7\n```\n","kind":"code","seed":"def rank_results(results):\n    pass\n\n\nif __name__ == \"__main__\":\n    results = [(\"Ada\", 7), (\"Bo\", 9), (\"Cy\", 7), (\"Di\", 10)]\n    for name, score in rank_results(results):\n        print(name, score)\n","solution":"def merge(left, right):\n    merged = []\n    i = 0\n    j = 0\n    while i < len(left) and j < len(right):\n        if left[i][1] >= right[j][1]:\n            merged.append(left[i])\n            i += 1\n        else:\n            merged.append(right[j])\n            j += 1\n    merged += left[i:]\n    merged += right[j:]\n    return merged\n\n\ndef rank_results(results):\n    if len(results) <= 1:\n        return results[:]\n    middle = len(results) // 2\n    left = rank_results(results[:middle])\n    right = rank_results(results[middle:])\n    return merge(left, right)\n\n\nif __name__ == \"__main__\":\n    results = [(\"Ada\", 7), (\"Bo\", 9), (\"Cy\", 7), (\"Di\", 10)]\n    for name, score in rank_results(results):\n        print(name, score)\n","hints":["Start from the lesson's `merge` and `merge_sort`. Only the comparison in the merge changes: it compares the scores, `left[i][1]` and `right[j][1]`, and the bigger score goes first.","To keep equal scores in their order, a tie must take the item from the left list, which came first: `if left[i][1] >= right[j][1]:`.","With `>` instead of `>=`, a tie takes the right item first, and Cy would come before Ada.","The first step's insertion sort, with `<` so that it stops at an equal score, gives the same order. But for 80,000 results in a mixed order that's about 1.6 billion moves: far too slow."],"tests":[{"name":"Test 1","stdin":"","call":"results = [(\"Ada\", 7), (\"Bo\", 9), (\"Cy\", 7), (\"Di\", 10)]\nfor name, score in rank_results(results):\n    print(name, score)\n","expect":"Di 10\nBo 9\nAda 7\nCy 7","hidden":false},{"name":"rank_results([(\"Ann\", 5), (\"Ben\", 5), (\"Cid\", 5), (\"Dot\", 5), (\"Eli\", 5)])","stdin":"","call":"rank_results([(\"Ann\", 5), (\"Ben\", 5), (\"Cid\", 5), (\"Dot\", 5), (\"Eli\", 5)])\n","expect":"[('Ann', 5), ('Ben', 5), ('Cid', 5), ('Dot', 5), ('Eli', 5)]","hidden":true,"returns":true},{"name":"rank_results([(\"Zed\", 1), (\"Yan\", 2), (\"Xia\", 3)])","stdin":"","call":"rank_results([(\"Zed\", 1), (\"Yan\", 2), (\"Xia\", 3)])\n","expect":"[('Xia', 3), ('Yan', 2), ('Zed', 1)]","hidden":true,"returns":true},{"name":"rank_results([])","stdin":"","call":"rank_results([])\n","expect":"[]","hidden":true,"returns":true},{"name":"rank_results([(\"Kim\", 4), (\"Lou\", 8), (\"Max\", 4), (\"Ned\", 8), (\"Oz\", 6), (\"Pia\", 4), (\"Quinn\", 8)])","stdin":"","call":"rank_results([(\"Kim\", 4), (\"Lou\", 8), (\"Max\", 4), (\"Ned\", 8), (\"Oz\", 6), (\"Pia\", 4), (\"Quinn\", 8)])\n","expect":"[('Lou', 8), ('Ned', 8), ('Quinn', 8), ('Oz', 6), ('Kim', 4), ('Max', 4), ('Pia', 4)]","hidden":true,"returns":true},{"name":"Test 6","stdin":"","call":"ranked = rank_results([(f\"p{i}\", i * 7919 % 1009) for i in range(80_000)])\nprint(len(ranked), ranked[:3], ranked[-1])\n","expect":"80000 [('p765', 1008), ('p1774', 1008), ('p2783', 1008)] ('p79711', 0)","hidden":true,"big":true}],"require":[],"forbid":[{"pattern":"\\bsorted\\s*\\(|\\.sort\\s*\\(","message":"Write the merge sort yourself on this step, without `sorted` or `sort`."}]},{"task":"An **inversion** is a pair of items in the wrong order: a bigger item before a smaller one. `[4, 6, 2, 5]` has 3 of them: 4 and 2, 6 and 2, 6 and 5. It's the number of moves insertion sort makes, a measure of how far a list is from sorted. Write `count_inversions(values)`, which returns the number of inversions of a list. It must be fast for 60,000 values, so count them while you merge sort, in O(n log n).\n\nThe trick is in the merge. When the front item of the right half is smaller than the front item of the left half, it's smaller than every item still waiting in the left half (that half is sorted), and all of those came before it in the list. So taking it counts `len(left) - i` inversions at once. The inversions inside each half are counted by the calls that sort the halves.\n\nFor example, `count_inversions([4, 6, 2, 5])` returns:\n\n```\n3\n```\n","kind":"code","seed":"def count_inversions(values):\n    pass\n\n\nif __name__ == \"__main__\":\n    print(count_inversions([4, 6, 2, 5]))\n","solution":"def sort_and_count(values):\n    if len(values) <= 1:\n        return values, 0\n    middle = len(values) // 2\n    left, count_left = sort_and_count(values[:middle])\n    right, count_right = sort_and_count(values[middle:])\n    merged = []\n    count = count_left + count_right\n    i = 0\n    j = 0\n    n_left = len(left)\n    n_right = len(right)\n    while i < n_left and j < n_right:\n        if left[i] <= right[j]:\n            merged.append(left[i])\n            i += 1\n        else:\n            merged.append(right[j])\n            j += 1\n            count += n_left - i\n    merged += left[i:]\n    merged += right[j:]\n    return merged, count\n\n\ndef count_inversions(values):\n    return sort_and_count(values)[1]\n\n\nif __name__ == \"__main__\":\n    print(count_inversions([4, 6, 2, 5]))\n","hints":["Write a helper, `sort_and_count(values)`, that returns two things as a tuple: the sorted list and its number of inversions. A list of 0 or 1 items gives `(values, 0)`.","In the helper, split in the middle and call it on each half: `left, count_left = sort_and_count(values[:middle])`. Start the count at `count_left + count_right`, then merge as in the lesson.","In the merge, when `right[j]` is taken because it's smaller than `left[i]`, add `len(left) - i` to the count. On a tie take from the left, as the lesson's merge does: equal items aren't an inversion. `count_inversions` returns the count from the tuple.","A loop inside a loop over all the pairs gives the right answer too, but for 60,000 values that's about 1.8 billion pairs: far too slow."],"tests":[{"name":"count_inversions([4, 6, 2, 5])","stdin":"","call":"count_inversions([4, 6, 2, 5])\n","expect":"3","hidden":false,"returns":true},{"name":"count_inversions([10, 20, 30, 40])","stdin":"","call":"count_inversions([10, 20, 30, 40])\n","expect":"0","hidden":true,"returns":true},{"name":"count_inversions([8, 6, 4, 2, 0])","stdin":"","call":"count_inversions([8, 6, 4, 2, 0])\n","expect":"10","hidden":true,"returns":true},{"name":"count_inversions([7, 7, 7, 7])","stdin":"","call":"count_inversions([7, 7, 7, 7])\n","expect":"0","hidden":true,"returns":true},{"name":"count_inversions([5, 1, 4, 1])","stdin":"","call":"count_inversions([5, 1, 4, 1])\n","expect":"4","hidden":true,"returns":true},{"name":"count_inversions([])","stdin":"","call":"count_inversions([])\n","expect":"0","hidden":true,"returns":true},{"name":"count_inversions([(60_000 - i) * 2 + i * i % 5 for i in range(60_000)])","stdin":"","call":"count_inversions([(60_000 - i) * 2 + i * i % 5 for i in range(60_000)])\n","expect":"1799946000","hidden":true,"big":true,"returns":true},{"name":"count_inversions([i * i % 100_003 for i in range(40_000)])","stdin":"","call":"count_inversions([i * i % 100_003 for i in range(40_000)])\n","expect":"396768492","hidden":true,"big":true,"returns":true}],"require":[],"forbid":[]}]}