问题
I'm trying to shuffle only elements of a list on 3rd till last position so the 1st two will always stay in place e.g.
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):
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.
回答1:
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
回答2:
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:
>>> mylist = [1,2,3,4,5,6]
>>> slice = MutableSlice(mylist, 2)
>>> import random
>>> random.shuffle(slice)
>>> mylist
[1, 2, 4, 3, 5, 6]
回答3:
You can create your own shuffle function that will allow you to shuffle a slice within a mutable sequence. It handles sampling the slice copy and reassigning back to the slice. You must pass slice()
arguments instead of the more familiar [2:]
notation.
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']
回答4:
To shuffle a slice of the list in place, without copies, we can use a 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:
>>> a = [0, 1, 2, 3, 4, 5]
>>> shuffle_slice(a, 0, 3)
>>> a
[2, 0, 1, 3, 4, 5]
回答5:
l[2:]
constructs a new list, and random.shuffle
tries to change the list "in-place," which has no effect on l
itself.
You could use random.sample
for this:
l[2:] = random.sample(l[2:], len(l)-2)
回答6:
Using the fact that a list has fast remove and insert and exteding a previous solution (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:
#!/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()
回答7:
I copied the shuffle function from random.shuffle and adapted it, so that it will shuffle a list only on a defined range:
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, i.e. no copy of the list is created.
回答8:
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:
create a new list of only the elements you want to shuffle.
shuffle the new list.
remove those elements you wanted to shuffle from your original list.
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)
来源:https://stackoverflow.com/questions/9557182/python-shuffle-only-some-elements-of-a-list