{"id":"dsa-backtracking-permutations","slug":"permutations","title":"Permutations","text":"A **permutation** is an ordering of items: `abc`, `acb`, `bac`, `bca`, `cab` and `cba` are the six orderings of three letters. Here each level of the decision tree answers another question: which item goes in the next place? Any item that isn't placed yet can go there, so the search keeps a list of **used** flags, one per item:\n\n```python\ndef permute(letters, used, path):\n    if len(path) == len(letters):\n        print(\"\".join(path))\n        return\n    for i in range(len(letters)):\n        if used[i]:\n            continue\n        used[i] = True                  # choose\n        path.append(letters[i])\n        permute(letters, used, path)    # explore\n        path.pop()                      # un-choose\n        used[i] = False\n\n\npermute(\"abc\", [False, False, False], [])\n```\n\n```output\nabc\nacb\nbac\nbca\ncab\ncba\n```\n\n### How it works\n\n- Any letter whose flag is still `False` may take the next place. So the first place has n candidates, the second n - 1, and the last only one: n × (n - 1) × ... × 1 = **n!** orderings. That's 6 for 3 letters, 40,320 for 8 and over 6 billion for 13.\n- The un-choose undoes both changes the choose made: the letter comes off `path`, and its flag goes back to `False`. The next turn of the loop then sees exactly the state the call started with.\n- The letters are tried from left to right, so `\"abc\"` gives its orderings in alphabetical order. A word whose letters aren't in order, such as `\"cab\"`, doesn't.\n- Each ordering is n items long, so listing them all takes O(n · n!) time. The calls are only n + 1 deep.\n\nLeave out the last line, `used[i] = False`, and the same program prints just one line, `abc`. The first path marks a, b and c as used, and nothing ever marks them free again, so every later loop finds all three used and skips them. When a backtracking search finds too few answers, check that every change made before the recursive call is undone after it.\n\n### Swapping instead of flags\n\nAnother classic way needs no flags: it moves the items around in the list itself. To fill place `i`, swap each of the items from place `i` on into place `i`, permute the rest from `i + 1`, then swap back:\n\n```python\ndef permute_swap(items, i):\n    if i == len(items):\n        print(\"\".join(items))\n        return\n    for j in range(i, len(items)):\n        items[i], items[j] = items[j], items[i]    # choose: items[j] goes to place i\n        permute_swap(items, i + 1)\n        items[i], items[j] = items[j], items[i]    # un-choose: swap back\n\n\npermute_swap(list(\"abc\"), 0)\n```\n\n```output\nabc\nacb\nbac\nbca\ncba\ncab\n```\n\nThe same six orderings, in a slightly different order: `cba` comes before `cab`, because the swap that moved `c` to the front put `a` at the back.\n\n### `itertools.permutations`\n\nIn real code, use the standard library. `permutations` gives tuples, in the same order as the flag version, and a second argument asks for orderings of only some of the items:\n\n```pycon\n>>> from itertools import permutations\n>>> list(permutations(\"abc\", 2))\n[('a', 'b'), ('a', 'c'), ('b', 'a'), ('b', 'c'), ('c', 'a'), ('c', 'b')]\n>>> len(list(permutations(range(8))))\n40320\n>>> [\"\".join(p) for p in permutations(\"pop\")]\n['pop', 'ppo', 'opp', 'opp', 'ppo', 'pop']\n```\n\nInterviews still ask you to write the search yourself, and your own search can do what `permutations` can't: skip choices.\n\n### Repeated letters\n\n`permutations(\"pop\")` gives 6 orderings but only 3 different strings: to it, the two p's are different items. `set()` removes the repeats, but only after making all n! orderings. For `\"aaaaaaabbbbbbb\"`, 14 letters, that's over 87 billion orderings (hours of work) for 3,432 different strings.\n\nA better search never makes the repeats at all. Instead of asking \"which position of the word goes next?\", it asks \"which letter goes next?\", and it keeps a count of each letter that's left: `{\"o\": 1, \"p\": 2}` for `\"pop\"`. The next place may take any letter whose count is above 0. Taking a letter lowers its count, the recursive call fills the places after it, and putting the letter back raises the count again. Both p's are now one option, `\"p\"`, so each different string comes out exactly once, and no time goes into strings that would be thrown away.\n","task":"Fill in the blanks so that `arrange` prints every ordering of `k` of the letters, here 2 of the letters of `abcd`, in the order `permutations(\"abcd\", 2)` gives them: the condition that a path is complete, and the un-choose that frees the letter again. The program should print:\n\n```\nab\nac\nad\nba\nbc\nbd\nca\ncb\ncd\nda\ndb\ndc\n```\n","kind":"fill","seed":"def arrange(letters, k, used, path):\n    if [[len(path) == k‖k == len(path)]]:\n        print(\"\".join(path))\n        return\n    for i in range(len(letters)):\n        if used[i]:\n            continue\n        used[i] = True\n        path.append(letters[i])\n        arrange(letters, k, used, path)\n        path.pop()\n        [[used[i] = False]]\n\n\narrange(\"abcd\", 2, [False] * 4, [])\n","solution":"def arrange(letters, k, used, path):\n    if len(path) == k:\n        print(\"\".join(path))\n        return\n    for i in range(len(letters)):\n        if used[i]:\n            continue\n        used[i] = True\n        path.append(letters[i])\n        arrange(letters, k, used, path)\n        path.pop()\n        used[i] = False\n\n\narrange(\"abcd\", 2, [False] * 4, [])\n","hints":["An arrangement of 2 letters is complete when the path holds `k` letters, not all of them.","The un-choose undoes `used[i] = True`, so that letter can be used again in the next branches."],"tests":[{"name":"Output","stdin":"","expect":"ab\nac\nad\nba\nbc\nbd\nca\ncb\ncd\nda\ndb\ndc","hidden":false}],"require":[],"forbid":[],"more":[{"task":"This version of `permute_swap` forgot to swap back. What does it print? Type each line it prints.\n","kind":"predict","seed":"def permute_swap(items, i):\n    if i == len(items):\n        print(\"\".join(items))\n        return\n    for j in range(i, len(items)):\n        items[i], items[j] = items[j], items[i]\n        permute_swap(items, i + 1)\n\n\npermute_swap(list(\"abc\"), 0)\n","solution":"def permute_swap(items, i):\n    if i == len(items):\n        print(\"\".join(items))\n        return\n    for j in range(i, len(items)):\n        items[i], items[j] = items[j], items[i]\n        permute_swap(items, i + 1)\n\n\npermute_swap(list(\"abc\"), 0)\n","hints":["The first two lines are the same as before: `abc` and `acb`. But now the list stays `acb` when those calls return.","Back in the first call, `j = 1` swaps places 0 and 1 of `acb`, which gives `cab`. The calls below print `cab`, then swap its last two letters and print `cba`.","The list is now `cba`, and `j = 2` swaps places 0 and 2. Repeats and missing orderings are what a missing un-choose looks like."],"tests":[{"name":"Output","stdin":"","expect":"abc\nacb\ncab\ncba\nabc\nacb","hidden":false}],"require":[],"forbid":[],"lines":["abc","acb","cab","cba","abc","acb"]},{"task":"Write `unique_permutations(word)`, which returns a list of every different ordering of the letters of `word`, each exactly once, in alphabetical order. It must be fast for 14 letters that have only 3,432 different orderings, so don't make all n! orderings and remove the repeats: choose among the different letters, with a count for each.\n\nFor example, `unique_permutations(\"pop\")` returns:\n\n```\n['opp', 'pop', 'ppo']\n```\n","kind":"code","seed":"def unique_permutations(word):\n    pass\n\n\nif __name__ == \"__main__\":\n    print(unique_permutations(\"pop\"))\n","solution":"def build(counts, length, path, result):\n    if len(path) == length:\n        result.append(\"\".join(path))\n        return\n    for letter in counts:\n        if counts[letter] > 0:\n            counts[letter] -= 1           # choose one of this letter\n            path.append(letter)\n            build(counts, length, path, result)\n            path.pop()                    # un-choose: give it back\n            counts[letter] += 1\n\n\ndef unique_permutations(word):\n    counts = {}\n    for letter in sorted(word):\n        counts[letter] = counts.get(letter, 0) + 1\n    result = []\n    build(counts, len(word), [], result)\n    return result\n\n\nif __name__ == \"__main__\":\n    print(unique_permutations(\"pop\"))\n","hints":["Count the letters first, in a dict. Build it from `sorted(word)`: a dict keeps the order its keys were added in, so looping over it tries the letters in alphabetical order, and the results come out sorted.","Recurse with the counts and the path built so far. When the path is as long as the word, join it into a string and append it to the result.","Loop over the letters in the dict and skip those whose count is 0. Otherwise lower the count, append the letter, recurse, pop the letter and raise the count again.","Making every ordering with used flags and keeping a set of them is correct too, but for 14 letters that's 14!, about 87 billion orderings: far too slow."],"tests":[{"name":"unique_permutations(\"pop\")","stdin":"","call":"unique_permutations(\"pop\")\n","expect":"['opp', 'pop', 'ppo']","hidden":false,"returns":true},{"name":"unique_permutations(\"tea\")","stdin":"","call":"unique_permutations(\"tea\")\n","expect":"['aet', 'ate', 'eat', 'eta', 'tae', 'tea']","hidden":true,"returns":true},{"name":"unique_permutations(\"yo\")","stdin":"","call":"unique_permutations(\"yo\")\n","expect":"['oy', 'yo']","hidden":true,"returns":true},{"name":"unique_permutations(\"mmmm\")","stdin":"","call":"unique_permutations(\"mmmm\")\n","expect":"['mmmm']","hidden":true,"returns":true},{"name":"unique_permutations(\"\")","stdin":"","call":"unique_permutations(\"\")\n","expect":"['']","hidden":true,"returns":true},{"name":"Test 6","stdin":"","call":"result = unique_permutations(\"banana\")\nprint(len(result), result[:3], result[-1])\n","expect":"60 ['aaabnn', 'aaanbn', 'aaannb'] nnbaaa","hidden":true},{"name":"Test 7","stdin":"","call":"result = unique_permutations(\"ab\" * 7)\nprint(len(result), result[0], result[-1])\n","expect":"3432 aaaaaaabbbbbbb bbbbbbbaaaaaaa","hidden":true,"big":true},{"name":"Test 8","stdin":"","call":"result = unique_permutations(\"a\" * 15 + \"b\")\nprint(len(result), result[:2])\n","expect":"16 ['aaaaaaaaaaaaaaab', 'aaaaaaaaaaaaaaba']","hidden":true,"big":true}],"require":[],"forbid":[]}]}