有7! = 5040
7 个不同对象的序列的排列。因此,就时间复杂度 (O(n!*n)) 和空间复杂度 (O(n!*n)) 而言,生成所有排列都是非常昂贵的。然而,从排列序列中选择随机排列很容易。让我们看一下choice
from的代码random.py
。
def choice(self, seq):
"""Choose a random element from a non-empty sequence."""
return seq[int(self.random() * len(seq))] # raises IndexError if seq is empty
如您所见,索引的计算是 O(1),因为len(seq)
对于任何序列都是 O(1),self.random()
也是 O(1)。在 Python 中从列表类型中获取元素也是 O(1),因此整个函数是 O(1)。
另一方面,使用random.shuffle
将就地交换包中的元素。因此它将使用 O(1) 空间复杂度。然而,就时间复杂度而言,它并不是那么有效。让我们看一下shuffle
from的代码random.py
def shuffle(self, x, random=None, int=int):
"""x, random=random.random -> shuffle list x in place; return None.
Optional arg random is a 0-argument function returning a random
float in [0.0, 1.0); by default, the standard random.random.
"""
if random is None:
random = self.random
for i in reversed(xrange(1, len(x))):
# pick an element in x[:i+1] with which to exchange x[i]
j = int(random() * (i+1))
x[i], x[j] = x[j], x[i]
random.shuffle
实现了费舍尔-耶茨洗牌,“类似于从帽子中随机挑选编号的票,或者从一副牌中随机挑选一张牌,直到没有更多的牌。” 然而,计算次数明显大于第一种方法,因为必须进行len(x)-1
调用,并且还需要交换操作。每个交换操作需要从列表中提取 2 次,并生成一个 2 元组用于解包和分配。random()
len(x)-1
基于所有这些信息,我猜想提到的第一种方法会占用大量内存来存储排列并且需要 O(n!*n) 时间复杂度开销,但从长远来看,它可能效率更高与第二种方法相比,并且可能会在俄罗斯方块游戏的实际实现中保持帧速率稳定,因为在实际游戏循环期间要做的计算更少。甚至可以在显示器初始化之前生成排列,这很好地给人一种你的游戏没有执行很多计算的错觉。
在这里,我使用 Tim Peters 对生成器和循环缓冲区的建议发布了最终代码。由于循环缓冲区的大小在缓冲区创建之前是已知的,并且永远不会改变,因此我没有实现循环缓冲区通常具有的所有功能(您可以在Wikipedia 文章中找到)。在任何情况下,它都非常适合 Random Generator 算法。
def random_generator():
import itertools, random
bag = "IJLOSTZ"
bigbag = list(itertools.permutations(bag))
while True:
for ost in random.choice(bigbag):
yield ost
def popleft_append(buff, start_idx, it):
""" This function emulates popleft and append from
collections.deque all in one step for a circular buffer
of size n which is always full,
The argument to parameter "it" must be an infinite
generator iterable, otherwise it.next() may throw
an exception at some point """
left_popped = buff[start_idx]
buff[start_idx] = it.next()
return (start_idx + 1) % len(buff), left_popped
def circular_peek(seq, start_idx):
return seq[start_idx:len(seq)] + seq[:start_idx]
# Example usage for peek queue of size 5
rg = random_generator()
pqsize = 5
# Initialize buffer using pqsize elements from generator
buff = [rg.next() for _ in xrange(pqsize)]
start_idx = 0
# Game loop
while True:
# Popping one OST (one-sided tetromino) from queue and
# adding a new one, also updating start_idx
start_idx, left_popped = popleft_append(buff, start_idx, rg)
# To show the peek queue currently
print circular_peek(buff, start_idx)