[PYTHON] Modèle de conception #Strategy

J'ai pratiqué les modèles de conception afin de pouvoir écrire du code conscient du design. D'autres modèles de conception seront publiés fréquemment.

Préface

L'objectif principal est de comprendre quand, quoi et comment utiliser les modèles de conception. (Je suis nouveau en Java ou dans un langage à typage statique, et je n'ai pas une longue histoire de python, donc je pense qu'il y a des choses qui ne ressemblent pas à Pythonista. Si vous avez des suggestions, apprenez-moi s'il vous plaît.)

Cette fois, la stratégie de modèle concernant le comportement.

Quelle est la stratégie

La partie qui implémente l'algorithme peut être échangée dans un aperçu. Un modèle qui permet de changer facilement d'algorithme et de résoudre le même problème différemment.

C'est parce que le modèle de stratégie sépare consciemment la partie algorithme des autres parties. Et seule l'interface (API) avec l'algorithme est spécifiée. Ensuite, l'algorithme est utilisé par délégation du programme. Vous pouvez facilement basculer entre les algorithmes en utilisant les liens lâches de la délégation.

Le modèle Stratégie peut être utilisé lorsque de nombreux comportements apparaissent sous la forme de plusieurs instructions conditionnelles.

Aperçu

Le programme d'exemple créé ici est pour exécuter "Janken" sur un ordinateur. J'ai pensé à deux méthodes comme la «stratégie» de Janken. L'un est "Si vous gagnez, vous ferez le même coup la prochaine fois" (WinningStrategy), et l'autre est "Calculez de manière probabiliste le coup suivant à partir du coup précédent" (ProbStrategy).

Diagramme de classe global

hand.py


class Hand():
    HANDVALUE_ROCK = 0
    HANDVALUE_SCISSORS = 1
    HANDVALUE_PAPER = 2
    NAMES = ['Goo', 'Choki', 'Par']
    HANDS = [HANDVALUE_ROCK,
             HANDVALUE_SCISSORS,
             HANDVALUE_PAPER]

    def __init__(self, handvalue):
        self.__handvalue = handvalue

    def get_hand(self, handvalue):
        return self.HANDS[self.__handvalue]

    def is_stronger_than(self, h):
        return self.__fight(h) == 1

    def is_weaker_than(self, h):
        return self.__fight(h) == -1

    def __fight(self, h):
        if self.__handvalue == h.__handvalue:
            return 0
        elif (self.__handvalue + 1) % 3 == h.__handvalue:
            return 1
        else:
            return -1

    def to_string(self):
        return self.NAMES[self.__handvalue]

La classe Hand est une classe qui représente la «main» de Janken. À l'intérieur de la classe, goo vaut 0, choki vaut 1 et le par est 2. Enregistrez-le en tant que champ de valeur manuelle.

Seules trois instances de la classe Hand peuvent être créées. Au départ, trois instances sont créées et stockées dans le tableau HANDS.

Vous pouvez obtenir une instance en utilisant la méthode de classe get_hand. Si vous donnez une valeur de main comme argument, l'instance sera la valeur de retour.

is_stronger_than et is_weaker_than comparent la force de la main. À utiliser lorsque vous avez deux mains, hand1 et hand2.

À l'intérieur de cette classe, c'est une méthode appelée combat qui détermine en fait la force de la main. La valeur de la main est utilisée pour juger de la force. La (self .__ handvalue + 1)% 3 == h .__ handvalue utilisée ici est la valeur de la main de self plus 1 est la valeur de la main de h (si self est goo, h est choki, self Si est choki, h est par, et si self est par, h est goo), alors self est plus fort que h. La raison d'utiliser l'opérateur% pour prendre le reste de 3 est que nous voulons qu'il soit goo (0) lorsque 1 est ajouté à par (2).

strategy.py


from abc import ABCMeta, abstractmethod


class Strategy(metaclass=ABCMeta):

    @abstractmethod
    def next_hand():
        pass

    @abstractmethod
    def study(win):
        pass

L'interface de stratégie est une collection de méthodes abstraites pour la «stratégie» de Janken.

next_hand est une méthode pour "obtenir le coup suivant". Lorsque cette méthode est appelée, la classe qui implémente l'interface de stratégie décide du "prochain mouvement".

l'étude est une méthode pour apprendre "si oui ou non vous avez gagné par la main que vous venez de lancer". Si l'appel de la méthode next_hand précédent l'emporte, appelez-le comme study (True). Si vous perdez, appelez cela étude (Faux). Par conséquent, la classe qui implémente l'interface Strategy change son état interne et l'utilise comme matériau pour déterminer la valeur de retour de la méthode next_hand à partir de la prochaine fois.

winning_strategy.py


import random
from hand import Hand
from strategy import Strategy


class WinningStrategy(Strategy):

    __won = False
    __prev_hand = 0

    def __init__(self, seed):
        self.__rand = seed

    def next_hand(self):
        if not(self.__won):
            self.__prev_hand = Hand(self.__rand).get_hand(random.randint(0, 3))
        return self.__prev_hand

    def study(self, win):
        self.__won = win

La classe WinningStrategy est l'une des classes qui implémente l'interface Strategy. Implémenter l'interface de stratégie signifie implémenter deux méthodes, next_hand et study.

Dans cette classe, si vous gagnez la partie précédente, vous adopterez la même stratégie la prochaine fois (goo pour goo, par pour par). Si vous perdez la partie précédente, le prochain coup sera décidé en utilisant des nombres aléatoires.

Le champ rand contient les nombres aléatoires que cette classe utilise lorsqu'elle en a besoin.

Le champ gagné contient le résultat du match précédent. Si vous gagnez, ce sera Vrai, si vous perdez, ce sera Faux.

Le champ prev_hand contient la main que vous avez lancée dans le jeu précédent.

prob_strategy.py


import random
from hand import Hand
from strategy import Strategy


class ProbStrategy(Strategy):

    __prev_hand_value = 0
    __current_hand_value = 0
    __history = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]

    def __init__(self, seed):
        self.__rand = seed

    def next_hand(self):
        bet = random.randint(0, self.__get_sum(self.__current_hand_value))
        hand_value = 0
        if bet < self.__history[self.__current_hand_value][0]:
            hand_value = 0
        elif bet < self.__history[self.__current_hand_value][0] + \
                self.__history[self.__current_hand_value][1]:
            hand_value = 1
        else:
            hand_value = 2

        self.__prev_hand_value = self.__current_hand_value
        self.__current_hand_value = hand_value

        return Hand(hand_value).get_hand(hand_value)

    def __get_sum(self, hv):
        total = 0
        for i in range(0, 3):
            total += self.__history[hv][i]
        return total

    def study(self, win):
        if win:
            self.__history[self.__prev_hand_value][self.__current_hand_value] \
                += 1
        else:
            self.__history[self.__prev_hand_value][(self.__current_hand_value + 1) % 3] \
                += 1
            self.__history[self.__prev_hand_value][(self.__current_hand_value + 2) % 3] \
                += 1

La classe ProbStrategy est une autre «stratégie» concrète. Le coup suivant est toujours décidé par un nombre aléatoire, mais l'historique des victoires et des pertes passées est utilisé pour changer la probabilité de chaque coup.

Le champ historique est un tableau de calcul de probabilité qui reflète les victoires et les pertes passées. L'histoire est un tableau à deux dimensions d'entiers, et les indices de chaque dimension ont les significations suivantes.

histoire [Dernière main] [Cette fois] Plus la valeur de cette formule est élevée, plus le taux de victoire passé est élevé.

Si vous écrivez en détail. historique [0] [0] Goo, le nombre de victoires passées lorsque vous le sortez avec Goo histoire [0] [1] Goo, Choki et le nombre de victoires passées lorsque vous sortez historique [0] [2] Nombre de victoires passées quand Goo, Par et moi

Supposons que vous ayez mis une pâte la dernière fois. À ce moment-là, ce que je vais donner ensuite est calculé avec la probabilité à partir des valeurs ci-dessus de histroy [0] [0], history [0] [1], hisstroy [0] [2]. En bref, ajoutez les valeurs de ces trois expressions (méthode get_sum), calculez le nombre à partir de 0, puis décidez du coup suivant en fonction de celui-ci (méthode next_hand).

Par exemple La valeur de l'historique [0] [0] est 3 La valeur de l'historique [0] [1] est 5 La valeur de l'historique [0] [2] est 7 dans le cas de. À ce moment, le rapport entre goo, choki et par est réglé à 3: 5: 7 et le prochain mouvement est décidé. Obtenir une valeur aléatoire entre 0 et moins de 15 (15 est une valeur de 3 + 5 + 7), Goo si 0 ou plus et moins de 3 Choki si 3 ou plus et moins de 8 Par si 8 ou plus et moins de 15 ça ira.

La méthode d'étude met à jour le contenu du champ d'historique en fonction du résultat de la main renvoyée par la méthode next_hand.

player.py


class Player():

    __wincount = 0
    __losecount = 0
    __gamecount = 0

    def __init__(self, name, strategy):
        self.__name = name
        self.__strategy = strategy

    def next_hand(self):
        return self.__strategy.next_hand()

    def win(self):
        self.__strategy.study(True)
        self.__wincount += 1
        self.__gamecount += 1

    def lose(self):
        self.__strategy.study(False)
        self.__losecount += 1
        self.__gamecount += 1

    def even(self):
        self.__gamecount += 1

    def to_stirng(self):
        return '[{0}: {1} games {2} win {3} lose]'.format(self.__name,
                                                          self.__gamecount,
                                                          self.__wincount,
                                                          self.__losecount)

La classe Player est une classe qui représente la personne qui lit les fichiers indésirables. La classe Player reçoit un "nom" et une "stratégie" pour créer une instance. La méthode next_hand sert à obtenir le coup suivant, mais c'est votre «stratégie» qui détermine en fait le coup suivant. La valeur de retour de la méthode next_hand de la stratégie sera la valeur de retour de la méthode next_hand du joueur. La méthode next_hand "délègue" ce qu'elle doit faire à Strategy.

La classe Player appelle la méthode d'étude à travers le champ startegy afin d'utiliser les résultats des jeux gagnants (gagner), perdants (perdants) et tirés (pair) dans le prochain match. Utilisez la méthode d'étude pour modifier l'état interne de la stratégie. wincount, losecount et geamecount enregistrent le nombre de victoires d'un joueur.

main.py


import random
import sys
from winning_strategy import WinningStrategy
from prob_strategy import ProbStrategy
from player import Player
from hand import Hand


def main():
    try:
        if int(sys.argv[1]) >= 3:
            seed1 = random.randint(0, 2)
        else:
            seed1 = int(sys.argv[1])

        if int(sys.argv[2]) >= 3:
            seed2 = random.randint(0, 2)
        else:
            seed2 = int(sys.argv[2])

        player1 = Player('Taro', WinningStrategy(seed1))
        player2 = Player('Hana', ProbStrategy(seed2))
        for i in range(0, 10):  # 10000
            next_hand1 = Hand(player1.next_hand())
            next_hand2 = Hand(player2.next_hand())
            if next_hand1.is_stronger_than(next_hand2):
                print('Winner : {0}'.format(player1.to_stirng()))
                player1.win()
                player2.lose()
            elif next_hand2.is_stronger_than(next_hand1):
                print('Winner : {0}'.format(player2.to_stirng()))
                player1.lose()
                player2.win()
            else:
                print('Even ...')
                player1.even()
                player2.even()

        print('Total result:')
        print(player1.to_stirng())
        print(player2.to_stirng())

    except IndexError:
        print('Check args size, does not work')
        print('usage: python main random_seed1 random_seed2')
        print('Example: python main.py 314 15')

if __name__ == "__main__":
    main()

Résultat d'exécution

python main.py 21  3
Winner : [Hana: 0 games 0 win 0 lose]
Even ...
Winner : [Hana: 2 games 1 win 0 lose]
Winner : [Taro: 3 games 0 win 2 lose]
Even ...
Winner : [Taro: 5 games 1 win 2 lose]
Even ...
Winner : [Hana: 7 games 2 win 2 lose]
Winner : [Taro: 8 games 2 win 3 lose]
Winner : [Hana: 9 games 3 win 3 lose]
Total result:
[Taro: 10 games 3 win 4 lose]
[Hana: 10 games 4 win 3 lose]

Résumé

Le modèle de stratégie sépare consciemment la partie de l'algorithme des autres parties. Comme il utilise une connexion lâche appelée délégation, il est facile de changer d'algorithme si vous ne modifiez pas la partie interface.

référence

Recommended Posts

Modèle de conception #Strategy
Modèle de conception #Builder
Modèle de conception #Adapter
Modèle de conception #Decorator
Modèle de conception #Observer
Modèle de conception #Singleton
Modèle de conception #Proxy
Apprenez le modèle de conception «Stratégie» avec Python
[Gang of Four] Apprentissage des modèles de conception - Stratégie
Design Pattern #Factory, méthode
Modèle de conception Oreore: variable glocale
Python Design Pattern - Méthode de modèle
[Gang of Four] Apprentissage des modèles de conception
Résumé du modèle de conception Java GoF
Apprenez le modèle de conception "Prototype" avec Python
Apprenez le modèle de conception "Builder" avec Python
[Gang of Four] Apprentissage des modèles de conception --Singleton
[Gang of Four] Apprentissage des modèles de conception - Décorateur
[Gang of Four] Apprentissage des modèles de conception - Visiteur
Modèle de conception-Adaptateur
[Gang of Four] Apprentissage des modèles de conception - Médiateur
Apprenez le modèle de conception "Observer" en Python
J'ai étudié les modèles de conception (mémo personnel) Partie 4 (modèle AbstractFactory, modèle de pont, modèle de stratégie)
Apprenez le modèle de conception "Proxy" en Python
Apprenez le modèle de conception "Commande" en Python
[Gang of Four] Apprentissage des modèles de conception - Itérateur
Modèle de conception du GoF à partir du problème 2. Structure
Apprenez le modèle de conception "Visiteur" avec Python
Apprenez le modèle de conception "Bridge" avec Python
Apprenez le modèle de conception "Mediator" avec Python
Apprenez le modèle de conception "Décorateur" avec Python
[Gang of Four] Apprentissage des modèles de conception - Façade
[Gang of Four] Apprentissage des modèles de conception - Composite
[Gang of Four] Apprentissage des modèles de conception - Prototype
Modèle de conception du GoF à partir du problème 1. Génération
[Gang of Four] Apprentissage des modèles de conception --Mémento
[Gang of Four] Apprentissage des modèles de conception - État
[Gang of Four] Apprentissage des modèles de conception - Interprétation
[Gang of Four] Apprentissage des modèles de conception - Constructeur
[Gang of Four] Apprentissage des modèles de conception - Pont
Apprenez le modèle de conception "Composite" avec Python
Apprenez le modèle de conception "Singleton" avec Python
Apprenez le modèle de conception "État" en Python
Apprenez le modèle de conception "Adapter" avec Python
[Gang of Four] Apprentissage des modèles de conception - Proxy
[Gang of Four] Apprentissage des modèles de conception - Adaptateur
Apprenez le modèle de conception "Façade" avec Python
[Gang of Four] Apprentissage des modèles de conception --Observer
[Gang of Four] Apprentissage des modèles de conception - Commande
Modèle de conception GoF à partir du problème 3. Comportement
[Gang of Four] Apprentissage des modèles de conception - Poids du vol
[Gang of Four] Apprentissage des modèles de conception - Usine abstraite
Apprenez le modèle de conception "Abstract Factory" avec Python
Apprenez le modèle de conception "Méthode de modèle" en Python
[Gang of Four] Apprentissage des modèles de conception - Méthode d'usine
Apprenez le modèle de conception "Méthode d'usine" en Python
J'ai écrit un modèle de conception dans l'édition Kotlin Prototype
[Gang of Four] Apprentissage des modèles de conception - Chaîne de responsabilité
[Gang of Four] Apprentissage des modèles de conception - Méthode du modèle