2010-04-28 18 views
2

J'apprends encore Python. Je viens d'écrire cette méthode pour déterminer si un joueur a gagné un jeu de tic-tac-toe encore, étant donné un état de société comme: '[['o','x','x'],['x','o','-'],['x','o','o']]'Python: est-il sensé de refactoriser cette vérification dans sa propre méthode?

def hasWon(board): 
    players = ['x', 'o'] 
    for player in players: 
    for row in board: 
     if row.count(player) == 3: 
     return player 
    top, mid, low = board 
    for i in range(3): 
     if [ top[i],mid[i],low[i] ].count(player) == 3: 
     return player 
    if [top[0],mid[1],low[2]].count(player) == 3: 
     return player 
    if [top[2],mid[1],low[0]].count(player) == 3: 
     return player 
    return None 

Il me est apparu que je vérifie les listes de 3 caractères plusieurs fois et pourrait factoriser le contrôle de sa propre méthode comme ceci:

def check(list, player): 
    if list.count(player) == 3: 
    return player 

... mais alors réalisé que tout ce qui fait vraiment est des lignes de changement comme:

if [ top[i],mid[i],low[i] ].count(player) == 3: 
    return player 

à:

if check([top[i],mid[i],low[i]], player): 
    return player 

... ce qui ne semble franchement pas beaucoup améliorer. Voyez-vous une meilleure façon de refactoriser cela? Ou en général une option plus Pythonic? J'aimerais l'entendre!

Répondre

5

Je pourrais utiliser

def check(somelist, player): 
    return somelist.count(player) == 3 

Modifier: comme suggéré dans un @ Andrew commentaire (! Tx @ Andrew), vous pouvez faire encore mieux, par exemple:

def check(somelist, player): 
    return somelist.count(player) == len(somelist) 

sans hardcoding la 3 - ce qui suggère également une autre bonne alternative:

def check(somelist, player): 
    return all(x==player for x in somelist) 

qui lit très directement "tous les éléments dans la liste égale player". Le point général est qu'en refactorisant à une méthode séparée, vous pouvez alors jouer avec la mise en œuvre de cette méthode - maintenant, bien sûr, le code est très simple, donc l'avantage est tout aussi modeste, mais c'est un excellent point à garder à l'esprit. code plus compliqué.

Comme vous l'avez remarqué, vous avez seulement besoin d'un bool de toute façon, donc cela permet une approche beaucoup plus simple - il suffit de retourner l'expression bool plutôt que de faire if dessus. Il est important de ne jamais utiliser un nom intégré comme list pour vos propres identifiants - une «nuisance attrayante» de la langue ... ;-). Par ce que je veux dire, Python utilise beaucoup de noms attrayants comme list, bool, sum et ainsi de suite, donc il est facile de se trouver accidentellement en utilisant un de ces noms pour une variable de votre propre, et rien de mal ne semble se produire ... jusqu'à ce que vous avez besoin de tourner, disons, un tuple dans une liste, utilisez la meilleure solution, x = list(thetuple) ... et de passer notre temps à essayer de comprendre et de résoudre les erreurs venez parce que vous avez utilisé list pour signifier autre chose que le type intégré de ce nom.

Alors, juste prendre l'habitude de ne pas en utilisant ces belles noms intégrés à des fins autres que d'indiquer les builtins respectifs, et vous vous éviterez beaucoup d'avenir aggravation -)

Retour à votre code, vous pourriez considérer la concision offerte par pas déballant board (une décision difficile, puisque votre code est très lisible ...mais peut sembler un peu bavard):

for i in range(3): 
    if check([row[i] for row in board], player): 
    return player 
if check([row[i] for i, row in enumerate(board)], player): 
    return player 
if check([row[2-i] for i, row in enumerate(board)], player): 
    return player 

En fin de compte, je pense que je tiens à votre choix - plus lisible et juste légèrement plus bavard, voire pas du tout - mais il est agréable d'être au courant de la alternatives, je pense - ici, liste des compréhensions et enumerate pour générer les listes à vérifier comme une alternative à "codage manuel" les trois possibilités.

+2

Un autre avantage de transmettre la liste à une fonction est que vous ne besoin de hard-code '3', vous pouvez utiliser la longueur de la liste à la place. –

+0

@Andrew, +1, excellent point - laissez-moi modifier en conséquence! –

+0

Et vous ne devez pas passer dans le joueur non plus; vous pourriez juste comparer au premier élément de somelist - bien que cela ait un certain impact sur la structure externe, aussi. Mais la petite méthode pourrait devenir un test de toutTheSame (liste), dont le résultat pourrait alors être testé contre les vrais joueurs. –

0

Juste une idée

def hasWon(board): 
    players = ['x', 'o'] 
    for player in players: 
    top, mid, low = board 
    game = board + [[ top[i],mid[i],low[i]] for i in range(3)] + [top[0],mid[1],low[2]] +[top[2],mid[1],low[0]] 
    if 3 in [l.count(player) for l in game] : 
     return player 
    return None 
1

Personnellement, je pense que votre meilleur pari pour une meilleure lisibilité est de bulle des fonctions pour vous donner les lignes(), colonnes() et Diags() du conseil d'administration, sous forme de listes de listes. Ensuite, vous pouvez itérer à travers ceux-ci et vérifier uniformément. Vous pouvez même alors définir allTriples(), qui ajoute la sortie de rows(), columns(), et diags(), de sorte que vous pouvez faire votre vérification dans une seule boucle concise. Je ferais probablement aussi de la planche un objet, de sorte que ces fonctions pourraient devenir des méthodes objet.

0

Votre solution est correcte - correcte, lisible et compréhensible. Néanmoins, si vous voulez optimiser la vitesse, j'utiliserais un tableau de chiffres à une dimension, et non des chaînes, et j'essayerais de rechercher chaque nombre aussi peu de fois que possible. Il y aura certainement une solution extrêmement maladroite dans laquelle vous ne contrôlez chaque champ qu'une seule fois. Je ne veux pas construire ça maintenant. :) Des choses comme ça pourraient avoir de l'importance si vous vouliez implémenter une IA jouant contre vous tout en explorant l'arbre de recherche entier des mouvements possibles. Un contrôle gagnant/perdant efficace serait nécessaire là-bas.

2

Il suffit de créer un itérateur personnalisé sur board.

def get_lines(board): 
    nums = range(3) 
    for i in nums: 
    yield (board[i][j] for j in nums) #cols 
    for j in nums: 
    yield (board[i][j] for i in nums) #rows 
    yield (board[i][i] for i in nums) #diag \ 
    yield (board[i][2-i] for i in nums) #diag/

def get_winner(board): #a bit too indented 
    for line in get_lines(board): #more expensive, so go through it only once 
    for player in 'x', 'o': 
     if line == player, player, player: #other way to check victory condition 
     return player 
    return None 

Il est évident que ceux-ci devraient être vraiment méthodes d'une classe board :)

+0

Je me demande si 'get_lines' ne devrait pas plutôt construire des listes. Je suppose que cela dépend de la façon dont les vérifications de déballage et d'égalité sont intelligentes. – badp

2

Vous pouvez utiliser un meilleur nom au lieu de check qui ne dit pas grand-chose. La règle générale est la suivante: si vous pensez à un bon nom pour une paix de code, il peut être utile de le déplacer dans une fonction distincte même s'il ne s'agit que d'une ligne de code. allsame pourrait être l'une des alternatives ici.

def winner(board): 
    main_diag = [row[i] for i, row in enumerate(board)] 
    aux_diag = [row[len(board) - i - 1] for i, row in enumerate(board)] 
    for triple in board + zip(*board) + [main_diag, aux_diag]: 
     if allsame(triple):   
      return triple[0] 

def allsame(lst):  
    return all(x == lst[0] for x in lst) 
1

Et maintenant quelque chose de complètement différent:

représenter le conseil d'une liste de neuf éléments. Chaque élément peut être -1 (X), 1 (O), ou 0 (vide):

WIN_LINES = (
    (0, 1, 2), 
    (3, 4, 5), 
    (6, 7, 8), 
    (0, 3, 6), 
    (1, 4, 7), 
    (2, 5, 8), 
    (2, 4, 6), 
    (0, 4, 8), 
    ) 

def test_for_win(board): 
    for line in WIN_LINES: 
     total = sum(board[point] for point in line) 
     if abs(total) == 3: 
      return total // 3 
    return None 

Raffinement:

WIN_LINES = (
    0, 1, 2, 
    3, 4, 5, 
    6, 7, 8, 
    0, 3, 6, 
    1, 4, 7, 
    2, 5, 8, 
    2, 4, 6, 
    0, 4, 8, 
    ) 

def test_for_win(board): 
    wpos = 0 
    for _unused in xrange(8): 
     total = board[WIN_LINES[wpos]]; wpos += 1 
     total += board[WIN_LINES[wpos]]; wpos += 1 
     total += board[WIN_LINES[wpos]]; wpos += 1 
     if total == 3: return 1 
     if total == -3: return -1 
    return None