简体   繁体   English

Python - 仅对列表中的某些元素进行洗牌

[英]Python - shuffle only some elements of a list

I'm trying to shuffle only elements of a list on 3rd till last position so the 1st two will always stay in place eg 我试图将第3个列表中的元素洗牌到最后一个位置,所以前两个将始终保持原位,例如

list = ['a?','b','c','d','e']

into

list = ['a?','b','d','e','c']

and for some reason this doesn't work: 由于某种原因,这不起作用:

list = ['a?','b','c','d','e']
import random
random.shuffle(list[2:])    
print list

Any know what am I doing wrong?? 谁知道我做错了什么?

The only thing that works for me is so far this (EDITED): 对我来说唯一有用的是迄今为止(EDITED):

lists = [['a?','b','c','d','e'],['1?','2','3','4','5','6','7']]
import random

for list in lists:
    copy = list[2:]
    random.shuffle(copy)
    list[2:] = copy

print lists

Think this is exactly what I needed. 认为这正是我所需要的。

What you do is this: 你做的是这样的:

copy = list[2:]
random.shuffle(copy)    

which does not do much to the original list. 这对原始列表没有太大作用。 Try this: 尝试这个:

copy = list[2:]
random.shuffle(copy)
list[2:] = copy # overwrite the original

If you want to shuffle without copying, you may try to write your own mutable slice class, like follows (that's a rough implementation sketch, no boundary checks etc): 如果你想在不复制的情况下进行随机播放,你可以尝试编写自己的可变切片类,如下所示(这是一个粗略的实现草图,没有边界检查等):

class MutableSlice(object):
    def __init__(self, baselist, begin, end=None):
        self._base = baselist
        self._begin = begin
        self._end = len(baselist) if end is None else end

    def __len__(self):
        return self._end - self._begin

    def __getitem__(self, i):
        return self._base[self._begin + i]

    def __setitem__(self, i, val):
        self._base[i + self._begin] = val

Then wrap the original list into it and feed to the standard shuffle: 然后将原始列表包装到其中并输入标准shuffle:

>>> mylist = [1,2,3,4,5,6]
>>> slice = MutableSlice(mylist, 2)
>>> import random
>>> random.shuffle(slice)
>>> mylist
[1, 2, 4, 3, 5, 6]

You can create your own shuffle function that will allow you to shuffle a slice within a mutable sequence. 您可以创建自己的shuffle函数,以允许您在可变序列中对切片进行洗牌。 It handles sampling the slice copy and reassigning back to the slice. 它处理切片副本的采样并重新分配回切片。 You must pass slice() arguments instead of the more familiar [2:] notation. 您必须传递slice()参数而不是更熟悉的[2:]表示法。

from random import sample
def myShuffle(x, *s):
    x[slice(*s)] = sample(x[slice(*s)], len(x[slice(*s)]))

usage: 用法:

>>> lst = ['a?','b','c','d','e']   #don't use list as a name
>>> myShuffle(lst, 2)              #shuffles lst[:2]
>>> lst
['b', 'a?', 'c', 'd', 'e']
>>> myShuffle(lst, 2, None)        #shuffles lst[2:]
>>> lst
['b', 'a?', 'd', 'e', 'c']

To shuffle a slice of the list in place, without copies, we can use a Knuth shuffle : 要在没有副本的情况下对列表中的一部分进行洗牌 ,我们可以使用Knuth shuffle

import random
def shuffle_slice(a, start, stop):
    i = start
    while (i < stop-1):
        idx = random.randrange(i, stop)
        a[i], a[idx] = a[idx], a[i]
        i += 1

It does the same thing as random.shuffle, except on a slice: 它与random.shuffle完全相同,除了在切片上:

>>> a = [0, 1, 2, 3, 4, 5]
>>> shuffle_slice(a, 0, 3)
>>> a
[2, 0, 1, 3, 4, 5]

l[2:] constructs a new list, and random.shuffle tries to change the list "in-place," which has no effect on l itself. l[2:]构造一个新列表, random.shuffle尝试“就地”更改列表,这对l本身没有影响。

You could use random.sample for this: 你可以使用random.sample

l[2:] = random.sample(l[2:], len(l)-2)

Using the fact that a list has fast remove and insert and exteding a previous solution ( https://stackoverflow.com/a/25229111/3449962 ): 使用列表快速删除并插入和删除以前的解决方案这一事实( https://stackoverflow.com/a/25229111/3449962 ):

List item 项目清单

  • enumerate fixed elements and copy them and their index 枚举固定元素并复制它们及其索引
  • delete fixed elements from list 从列表中删除固定元素
  • shuffle remaining sub-set 洗牌剩下的子集
  • put fixed elements back in 将固定元素放回去

This will use in-place operations with memory overhead that depends on the number of fixed elements in the list. 这将使用具有内存开销的就地操作,这取决于列表中固定元素的数量。 Linear in time. 线性时间。 A possible more general implementation of shuffle_subset: 一个可能更普遍的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>]

Exclude certain postions from the shuffle.  For example, exclude the
first two elements:

>>> fix = [0, 1]
>>> 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>]
>>> for i in range(4):                # doctest: +NORMALIZE_WHITESPACE
...     shuffle_subset(lst, fix)
...     print([lst[i] for i in fix], end=" ")
...
[<CA: 3>, <CA: 2>] [<CA: 3>, <CA: 2>] [<CA: 3>, <CA: 2>] [<CA: 3>, <CA: 2>]

Using a selector with the same number of elements as lst:

>>> fix = [0, 1]
>>> lst = [CAnswer(v, i in fix) for i, v in enumerate(val)]
>>> sel = [(i in fix) for i, _ in enumerate(val)]
>>> print("Start   ", 0, ": ", lst)
Start    0 :  [<CA: 3>, <CA: 2>, <CA: 0>, <CA: 1>, <CA: 5>, <CA: 9>, <CA: 4>]
>>> for i in range(4):                # doctest: +NORMALIZE_WHITESPACE
...     shuffle_subset(lst, sel)
...     print([lst[i] for i in fix], end=" ")
...
[<CA: 3>, <CA: 2>] [<CA: 3>, <CA: 2>] [<CA: 3>, <CA: 2>] [<CA: 3>, <CA: 2>]

A generator as selector works fine too:

>>> fix = [0, 1]
>>> 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>]
>>> for i in range(4):                # doctest: +NORMALIZE_WHITESPACE
...     sel = ((i in fix) for i, _ in enumerate(val))
...     shuffle_subset(lst, sel)
...     print([lst[i] for i in fix], end=" ")
...
[<CA: 3>, <CA: 2>] [<CA: 3>, <CA: 2>] [<CA: 3>, <CA: 2>] [<CA: 3>, <CA: 2>]

"""
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.

      + The predicate is an iterable that is True for fixed elements
      or len(predicate) == len(lst).

      + The predicate is a list of indices of fixed elements in lst
      with len(predicate) < len(lst).

    """
    def extract_fixed_elements(pred, lst):
        try:
            if callable(pred) or pred is None:
                pred = bool if pred is None else pred
                fixed_subset = [(i, e) for i, e in enumerate(lst) if pred(e)]
            elif (hasattr(pred, '__next__') or len(pred) == len(lst)):
                fixed_subset = [(i, lst[i]) for i, p in enumerate(pred) if p]
            elif len(pred) < len(lst):
                fixed_subset = [(i, lst[i]) for i in pred]
            else:
                raise TypeError("Predicate {} not supported.".format(pred))
        except TypeError as err:
            raise TypeError("Predicate {} not supported. {}".format(pred, err))
        return fixed_subset
    #
    fixed_subset = extract_fixed_elements(predicate, lst)
    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()

I copied the shuffle function from random.shuffle and adapted it, so that it will shuffle a list only on a defined range: 我从random.shuffle复制了shuffle函数并对其进行了调整,以便它只在定义的范围内对列表进行洗牌:

import random
a = range(0,20)
b = range(0,20)

def shuffle_slice(x, startIdx, endIdx):
    for i in reversed(xrange(startIdx+1, endIdx)):
       # pick an element in x[:i+1] with which to exchange x[i]
       j = random.randint(startIdx, i)
       x[i], x[j] = x[j], x[i]

#Shuffle from 5 until the end of a
shuffle_slice(a, 5, len(a))    
print a

#Shuffle b from 5 ... 15
shuffle_slice(b, 5, 15)
print b

The code above only shuffles the elements within the specified range. 上面的代码只是对指定范围内的元素进行洗牌。 The shuffle is done inplace, ie no copy of the list is created. shuffle在原地完成,即不创​​建列表的副本。

Try this ..it's much simpler and does not make any copies of the list. 试试这个..它更简单,不会制作列表的任何副本。
You can keep any of the elements fixed by just playing with the list indices. 只需使用列表索引即可保持任何元素的固定。

working: 工作:

  1. create a new list of only the elements you want to shuffle. 创建一个仅包含要随机播放的元素的新列表。

  2. shuffle the new list. 洗牌新名单。

  3. remove those elements you wanted to shuffle from your original list. 从原始列表中删除您想要随机播放的元素。

  4. insert the newly created list into the old list at the proper index 将新创建的列表插入到适当索引的旧列表中

import random
    list = ['a?', 'b', 'c', 'd', 'e']

    v = []
    p = [v.append(list[c]) for c in range(2,len(list))] #step 1
    random.shuffle(v)  #step 2
    for c in range(2,len(list)):
        list.remove(list[c])  #step 3
        list.insert(c,v[c-2]) #step 4    #c-2 since the part to be shuffled begins from this index of list

    print(list)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM