本篇閱讀的程式碼實現了隨機打亂列表元素的功能,將原有列表亂序排列,並返回一個新的列表(不改變原有列表的順序)。
本篇閱讀的程式碼片段來自於30-seconds-of-python。
shuffle
from copy import deepcopy
from random import randint
def shuffle(lst):
temp_lst = deepcopy(lst)
m = len(temp_lst)
while (m):
m -= 1
i = randint(0, m)
temp_lst[m], temp_lst[i] = temp_lst[i], temp_lst[m]
return temp_lst
# EXAMPLES
foo = [1,2,3]
shuffle(foo) # [2,3,1], foo = [1,2,3]
Python實際上提供了和shuffle
功能類似的標準庫函式random.shuffle
,不過這個函式會在原列表上進行打亂,改變原列表的元素順序。現在我們還是看下上面這段程式碼,如何實現亂序排列,如何實現返回新列表而不改變原列表內容。
copy.deepcopy(x[, memo])
shuffle
函式使用了深拷貝將原始列表拷貝了一份傳遞給了臨時列表,後續的所有操作都是基於臨時列表進行的,所以不會影響到原有的列表。
那麼為什麼不直接使用賦值語句呢?temp_lst = lst
或者temp_lst = lst[:]
?因為Python的賦值語句實際上是將一個name
繫結到了物件上。上面的兩種賦值語句實際上使得多個name
繫結到了一個物件上。那麼對於可變型別的物件而言,自身的改變會反映到不同的name
上。所以使用上面兩種賦值語句都不能做到不影響原始列表。
>>> from copy import deepcopy
>>> o = [[1,2,3],4,5]
>>> c1 = o
>>> c2 = o[:]
>>> c3 = deepcopy(o)
>>> c1[2] = 6
>>> c2[0][0] = 0
>>> c3[0][1] = 0
>>> c3[1] = 6
>>> o
[[0, 2, 3], 4, 6]
Fisher-Yates algorithm
Fisher-Yates algorithm
演算法實現了洗牌演算法。對於長度為n的列表,它的現代實現可以描述為以下虛擬碼:
-- To shuffle an array a of n elements (indices 0..n-1):
for i from n−1 downto 1 do
j ← random integer such that 0 ≤ j ≤ i
exchange a[j] and a[i]
這個演算法在數學上的基本思想是不放回的隨機從原始列表中取出元素,並將取出的元素按順序放入新的列表中。使用這種方式就實現了新列表元素順序的隨機。這個演算法的虛擬碼實現,優化了整體的效率,將元素的取出和放入新的陣列,替換成了在原陣列上將元素進行交換。