Let's say that I am trying to make a BattleShip game in Python. I have a list of lists called board. Each list in board is a list of the spaces in that row of the board, for this example, it will be 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']]
I want to create a function to return a random clear space in the board.
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
Here are my questions: Is it inefficient to have a function call itself until it gets the value it wants (even if the board is almost entirely filled)? Is there a better way I should do this instead?
I couldn't figure out how to use a while statement with this because the statement would always reference 'place' before it was assigned, and I couldn't think of any value for place that wouldn't reference an out-of-range or unassigned 'board' value.
My apologies if this is a duplicate topic, I tried to find a similar one but couldn't. This is also my first time using this site to ask a question instead of answer one.
Is it inefficient to have a function call itself until it gets the value it wants (even if the board is almost entirely filled)? Is there a better way I should do this instead?
Yes and yes. But it's not really the inefficiency that's the issue, so much as the fact that if you get unlucky enough to need to try 1000 times, your program will fail with a recursion error.
I couldn't figure out how to use a while statement with this because the statement would always reference 'place' before it was assigned, and I couldn't think of any value for place that wouldn't reference an out-of-range or unassigned 'board' value.
Just use while True:, and you can break or return to get out of the loop:
while True:
    place = [randrange(0,4),randrange(0,4)] #randrange is inclusive
    if board[place[0]][place[1]] == "clear":
        return place
As a side note, as inspectorG4dget points out in the comments, randrange is not inclusive; this will only return the numbers 0, 1, 2, and 3.
Also, putting the x and y coordinates into a list just so you can use [0] and [1] repeatedly makes things a bit harder to read. Just use x, y = [randrange(5), randrange(5)] (fixing the other problem as well), then board[x][y], and return x, y.
If this is too slow, then yes, there is a more optimal way to do it. First make a list of all of the clear slots, then pick one at random:
clearslots = [(x, y) for x in range(5) for y in range(5) if board[x][y] == "clear"]
return random.choice(clearslots)
That will probably be slower when the board is mostly empty, but it won't get any worse as the board fills up. Also, unlike your method, it has guaranteed worst-case constant time; instead of being incredibly unlikely for the routine to take years, it's impossible.
If you don't understand that list comprehension, let me write it out more explicitly:
clearslots = []
for x in range(5):
    for y in range(5):
        if board[x][y] == "clear":
            clearslots.append((x, y))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With