I would propose the following pragmatic approach:
from itertools import permutations
def fixed_permutations(values: list, fixed_pos: list[int] | None = None) -> list[list]:
"""
Produces a list of lists with all possible permutations of the given values,
except for those at the given fixed positions, which remain at their indices.
:param values: list of values to be permuted
:param fixed_pos: optional list of positions/indices to remain unpermuted
:return: list of lists of the resulting permutations
"""
fixed_pos = [] if fixed_pos is None else fixed_pos
# Collect the values that are *not* to be kept at a fixed position
permuted_values = [v for i, v in enumerate(values) if i not in fixed_pos]
# Calculate all permutations
all_permutations = [list(p) for p in permutations(permuted_values)]
# Add fixed positions back in (in-place)
for current_permutation in all_permutations:
for fixed_position in fixed_pos:
current_permutation.insert(fixed_position, values[fixed_position])
return all_permutations
print(fixed_permutations(["a", "b", "c", "d", "e"], fixed_pos=[1, 3]))
# [['a', 'b', 'c', 'd', 'e'], ['a', 'b', 'e', 'd', 'c'], ['c', 'b', 'a', 'd', 'e'],
# ['c', 'b', 'e', 'd', 'a'], ['e', 'b', 'a', 'd', 'c'], ['e', 'b', 'c', 'd', 'a']]
The basic idea: Put aside the values that are not supposed to be part of the permutation, permute the others (using itertools.permutations()), then reinsert the unpermuted values.
As it is currently written, the proposed fixed_permutations() function consumes all results of itertools.permutations() eagerly. We could adjust that by not creating the all_permutations list but adding the values back into each individual permutation on-the-fly and then yielding the individual results rather than returning all at once. Also, as it is currently implemented, it might not be the most efficient approach, given that we have to add values back in for each individual result of itertools.permutations(). I do not see an easy solution to the latter, other than completely implementing the permutation approach from scratch.