8

I'm new to programming, and I'm trying to write a Python function to find the inverse of a permutation on {1,2,3,...,n} using the following code:

def inv(str):
    result = []
    i = list(str).index(min(list(str)))
    while min(list(str)) < len(list(str)) + 1:
        list(str)[i : i + 1] = [len(list(str)) + 1]
        result.append(i + 1)
    return result

However, when I try to use the function, inv('<mypermutation>') returns []. Am I missing something? Is Python skipping over my while loop for some syntactical reason I don't understand? None of my google and stackoverflow searches on topics I think of are returning anything helpful.

Wooble
  • 87,717
  • 12
  • 108
  • 131
Fingolfin
  • 93
  • 1
  • 1
  • 5

7 Answers7

17

Other answers are correct, but for what it's worth, there's a much more performant alternative using numpy:

inverse_perm = np.argsort(permutation)

EDIT: and the fourth function below is even faster.

Timing code:

def invert_permutation_list_scan(p):
    return [p.index(l) for l in range(len(p))]

def invert_permutation_list_comp(permutation):
    return [i for i, j in sorted(enumerate(permutation), key=lambda i_j: i_j[1])]

def invert_permutation_numpy(permutation):
    return np.argsort(permutation)

def invert_permutation_numpy2(permutation):
    inv = np.empty_like(permutation)
    inv[permutation] = np.arange(len(inv), dtype=inv.dtype)
    return inv

x = np.random.randn(1000)
perm = np.argsort(x)
permlist = list(perm)
assert np.array_equal(invert_permutation_list_scan(permlist), invert_permutation_numpy(perm))
assert np.array_equal(invert_permutation_list_comp(perm), invert_permutation_numpy(perm))
assert np.array_equal(invert_permutation_list_comp(perm), invert_permutation_numpy2(perm))
%timeit invert_permutation_list_scan(permlist)
%timeit invert_permutation_list_comp(perm)
%timeit invert_permutation_numpy(perm)
%timeit invert_permutation_numpy2(perm)

Results:

82.2 ms ± 7.28 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
479 µs ± 9.19 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
18 µs ± 1.17 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)
4.22 µs ± 388 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
d.b
  • 403
  • 5
  • 10
  • 2
    Well, if you're going for speed, then you can do less work: . ``` def invert_permutation_numpy2(permutation): x = np.empty_like(permutation) x[permutation] = np.arange(len(permutation), dtype=permutation.dtype) return x ``` . On the mini-benchmarks it beats numpy sorting 10-20x – VF1 Sep 01 '20 at 22:42
  • @VF1 Nice. I've edited my answer to include this even faster function. – d.b Sep 10 '20 at 05:51
  • I think `np.arange(len(permutation))[np.argsort(permutation)]` equals `np.argsort(permutation)`. – abukaj Nov 19 '20 at 15:08
  • Good catch. Fixed! – d.b Apr 30 '21 at 23:16
11

If you only want the inverse permutation, you can use

def inv(perm):
    inverse = [0] * len(perm)
    for i, p in enumerate(perm):
        inverse[p] = i
    return inverse

perm = [3, 0, 2, 1]
print(inv(perm))
for i in perm:
    print(inv(perm)[i])

[1, 3, 2, 0]
0
1
2
3
Reinstate Monica
  • 4,568
  • 1
  • 24
  • 35
4

I believe the best way to invert a permutation perm is

pinv = sorted(range(len(perm)), key=perm.__getitem__)

This avoids repeated calls to .index() (as in the answer by SeF), which may not be very efficient (quadratic time complexity, while sorting should only take O(n log n)).

Note, however, that this yields as a result a permutation of {0,1,...n-1}, regardless of whether the input was a permutation of {0,1,...,n-1} or of {1,2,...,n} (the latter is what is stated in the question). If the output is supposed to be a permutation of {1,2,...,n}, each element of the result has to be increased by one, for example, like this:

pinv = [i+1 for i in sorted(range(len(perm)), key=perm.__getitem__)]
1

A "functional style" version:

def invert_permutation(permutation):
    return [i for i, j in sorted(enumerate(permutation), key=lambda (_, j): j)]

Basically, sorting the indices i of the permutation by their values j in the permutation yields the desired inverse.

p = [2, 1, 5, 0, 4, 3]

invert_permutation(p)
# [3, 1, 0, 5, 4, 2]

# inverse of inverse = identity
invert_permutation(invert_permutation(p)) == p
# True
kjo
  • 33,683
  • 52
  • 148
  • 265
1

Maybe there is a shorter way:

def invert(p):
    return [p.index(l) for l in range(len(p))] 

so that:

perm = [3, 0, 2, 1]; print(invert(perm))

returns

[1,3,2,0]

SeF
  • 3,864
  • 2
  • 28
  • 41
1

Just since no one has recommended it here yet, I think it should be mentioned that SymPy has an entire combinatorics module, with a Permutation class:

from sympy.combinatorics import Permutation
o = [3, 0, 2, 1]
p = Permutation(o)
inv = p.__invert__()
print(inv.array_form) # [1, 3, 2, 0]

Using the SymPy class gives you access to a whole lot of other useful methods, such as comparison between equivalent permutations with ==.

You can read the sympy.combinatorics.Permutation source code here.

Other than that, I would recommend the answer on this page using np.arange and argsort.

Louis Maddox
  • 5,226
  • 5
  • 36
  • 66
1

Correct me if I have this wrong, but I think the problem with my code comes when I change str to a list: str is a string, and list(str) is a list of string elements. However, since string elements can't be numerically compared to numbers, the code fails to produce a result (other than []).

Fingolfin
  • 93
  • 1
  • 1
  • 5