使用列表具有快速删除和插入的事实:
- 枚举固定元素并复制它们及其索引
- 从列表中删除固定元素
- 洗牌剩余的子集
- 将固定元素放回
有关更通用的解决方案,请参阅https://stackoverflow.com/a/25233037/3449962 。
这将使用内存开销取决于列表中固定元素的数量的就地操作。时间线性。一个可能的实现shuffle_subset:
#!/usr/bin/env python
"""Shuffle elements in a list, except for a sub-set of the elments.
The sub-set are those elements that should retain their position in
the list.  Some example usage:
>>> from collections import namedtuple
>>> class CAnswer(namedtuple("CAnswer","x fixed")):
...             def __bool__(self):
...                     return self.fixed is True
...             __nonzero__ = __bool__  # For Python 2. Called by bool in Py2.
...             def __repr__(self):
...                     return "<CA: {}>".format(self.x)
...
>>> val = [3, 2, 0, 1, 5, 9, 4]
>>> fix = [2, 5]
>>> lst = [ CAnswer(v, i in fix) for i, v in enumerate(val)]
>>> print("Start   ", 0, ": ", lst)
Start    0 :  [<CA: 3>, <CA: 2>, <CA: 0>, <CA: 1>, <CA: 5>, <CA: 9>, <CA: 4>]
Using a predicate to filter.
>>> for i in range(4):  # doctest: +NORMALIZE_WHITESPACE
...     shuffle_subset(lst, lambda x : x.fixed)
...     print([lst[i] for i in fix], end=" ")
...
[<CA: 0>, <CA: 9>] [<CA: 0>, <CA: 9>] [<CA: 0>, <CA: 9>] [<CA: 0>, <CA: 9>]
>>> for i in range(4):                # doctest: +NORMALIZE_WHITESPACE
...     shuffle_subset(lst)           # predicate = bool()
...     print([lst[i] for i in fix], end=" ")
...
[<CA: 0>, <CA: 9>] [<CA: 0>, <CA: 9>] [<CA: 0>, <CA: 9>] [<CA: 0>, <CA: 9>]
"""
from __future__ import print_function
import random
def shuffle_subset(lst, predicate=None):
    """All elements in lst, except a sub-set, are shuffled.
    The predicate defines the sub-set of elements in lst that should
    not be shuffled:
      + The predicate is a callable that returns True for fixed
      elements, predicate(element) --> True or False.
      + If the predicate is None extract those elements where
      bool(element) == True.
    """
    predicate = bool if predicate is None else predicate
    fixed_subset = [(i, e) for i, e in enumerate(lst) if predicate(e)]
    fixed_subset.reverse()      # Delete fixed elements from high index to low.
    for i, _ in fixed_subset:
        del lst[i]
    random.shuffle(lst)
    fixed_subset.reverse()      # Insert fixed elements from low index to high.
    for i, e in fixed_subset:
        lst.insert(i, e)
if __name__ == "__main__":
    import doctest
    doctest.testmod()