Python中的自调用函数

4 投票
3 回答
6667 浏览
提问于 2025-04-18 17:21

假设我正在用Python制作一个战舰游戏。我有一个叫做board的列表,里面包含了多个列表。board中的每个列表代表棋盘的一行,在这个例子中,棋盘是5x5的:

[['clear', 'clear', 'clear', 'clear', 'clear'],
['clear', 'clear', 'clear', 'clear', 'clear'],
['clear', 'clear', 'clear', 'clear', 'clear'],
['clear', 'clear', 'clear', 'clear', 'clear'],
['clear', 'clear', 'clear', 'clear', 'clear']]

我想创建一个函数,能够返回棋盘上一个随机的空位。

def pl_point(board):
    place = [randrange(0,5),randrange(0,5)] #heh, found out these aren't inclusive.
    if board[place[0]][place[1]] == "clear": return place
    else:
        return pl_point() #calls itself until a clear place is found

我有几个问题:让一个函数自己调用自己,直到得到想要的值,这样做效率低吗?特别是当棋盘几乎被填满的时候?有没有更好的方法可以做到这一点?

我不知道怎么用while语句来实现这个,因为这个语句总是会在'place'被赋值之前就引用它,我也想不出一个不会引用超出范围或者未赋值的'board'值的'place'的值。

如果这个话题重复了,我很抱歉,我试着找过类似的,但没找到。这也是我第一次在这个网站上提问,而不是回答问题。

3 个回答

0

生成器是一个非常实用的Python工具,可以帮助你解决问题。简单来说,你想要在一组随机的空白空间中逐个返回一个项目,对吧?

你首先需要创建一个函数,这个函数会生成一个包含所有空白空间的列表,就像一些已有的答案所提到的那样,然后从这个列表中随机选择一个项目返回。不过,之后你可以通过调用你创建的函数的next()来继续获取需要的选择。

import random

def board_randomizer():
    clear_spaces = [...] # list comprehension or your method of choosing
    while clear_spaces:
           coord = random.choice(clear_spaces)
           clear_spaces.remove(coord)
           yield coord # returns a random location, one item at a time

clear_positions = board_randomizer() # initialize a generator
# keeps picking random locations until a StopIteration error is raised
clear_positions.next()
clear_positions.next()
1

当然有更好的方法。试试这个:

def pl_point(board):
    for y,row in enumerate(board):
        for x,col in row:
            if col == 'clear':
                return (x,y)

有没有什么特别的原因需要它是随机的?如果有的话……

def pl_point_random(board):
    places = {(x,y) for x in range(len(board)) for y in range(len(board[0]))}
    # the above is a set comprehension of every spot on the board
    #   (assuming the board is rectangular...)
    while True:
        point = random.choice(places)
        # choose a random spot in places
        x,y = point
        if board[x][y] != 'clear':
            return point
        else:
            places.remove(point)

或者更好的方法是:

def pl_point_random_v2(board):
    places = [(x,y) for y,row in enumerate(board) for x,col in rows if col=='clear']
    return random.choice(places)
5

让一个函数自己调用自己,直到得到想要的值,这样做效率低吗?(即使棋盘几乎填满了)有没有更好的方法可以做到这一点?

是的,确实如此。不过,问题不在于效率,而是如果你运气不好,需要尝试1000次,你的程序就会因为递归错误而崩溃。

我不知道怎么用while语句来处理这个,因为这个语句总是会在'place'被赋值之前引用它,我想不出一个不会引用超出范围或未赋值的'board'值的'place'值。

你可以直接用 while True:,然后用 breakreturn 来跳出循环:

while True:
    place = [randrange(0,4),randrange(0,4)] #randrange is inclusive
    if board[place[0]][place[1]] == "clear":
        return place

顺便提一下,正如inspectorG4dget在评论中指出的,randrange不包含 的;这只会返回数字 0123

另外,把x和y坐标放到一个列表里,只是为了能重复使用 [0][1],这样会让代码看起来更难懂。你可以直接用 x, y = [randrange(5), randrange(5)](这样也解决了之前的问题),然后用 board[x][y],最后 return x, y


如果这样做太慢了,那确实有更好的方法。首先,列出所有空位,然后随机选择一个:

clearslots = [(x, y) for x in range(5) for y in range(5) if board[x][y] == "clear"]
return random.choice(clearslots)

当棋盘大部分是空的时候,这种方法可能会慢一点,但随着棋盘的填满,它的速度不会变得更慢。而且,与你的方法不同,这种方法在最坏情况下是有保证的常量时间;也就是说,程序花费数年时间的可能性是不存在的。

如果你不理解那个列表推导式,我可以更明确地写出来:

clearslots = []
for x in range(5):
    for y in range(5):
        if board[x][y] == "clear":
            clearslots.append((x, y))

撰写回答