Petit exercice sympa inspiré d'un exercice du projet Euler.
Énoncé
L'exercice est à résoudre en Python et est composé des deux questions suivantes.
--------------------------------------------------------------------------------------------------- 1) On vous donne au Poker une main de 5 cartes et le programme doit identifier la nature de la main parmi les neuf combinaisons possibles (quinte, carré, etc, cf. la liste ci-dessous).
2) On vous donne deux mains de 5 cartes issues d'un même jeu de 52 cartes et le programme doit dire quelle est la main qui gagne (ou s'il y a égalité, ce qui est possible). Attention, je ne donnerai pas toutes les règles de gain d'une main sur une autre, se référer à l'article correspondant de wikipedia pour les détails manquants. ---------------------------------------------------------------------------------------------------
Rappels
Au poker, chaque joueur reçoit 5 cartes qui forment ce qu'on appelle une main. Les différentes mains se répartissent en 9 combinaisons, par ordre décroissant de valeur :
1. Quinte
2. Carré
3. Full
4. Couleur
5. Suite
6. Brelan
7. Double paire
8. Paire
9. Carte haute
Attention, bien que cette règle ne soit pas universellement admise, dans le cas de cartes consécutives, l'As a une situation particulière : il peut être supérieur au Roi mais aussi représenter la carte 1 et donc être la carte la plus faible du jeu.
Pour savoir qui l'emporte de deux mains de même niveau (par exemple deux brelans), on regarde «la» carte la plus forte (voir les détails dans l'article de Wikipedia ci-dessus).
Détails
Vous êtes libre de noter les rangs des cartes comme vous le souhaitez. Je suggère 1 pour As, R pour Roi, 7 pour la carte 7, etc.
Pour les couleurs, je suggère
p = pique, c= cœur, k = carreau, t = trèfle
Par conséquent, avec les notations ci-dessus, un valet de pique se notera Vp. Mais je répète, vous pouvez utiliser une notation plus adaptée à Python, par exemple un tuple ('V','p')
Il est souhaitable que ce soient des fonctions (ou à la rigueur des méthodes si vous tenez à écrire votre code avec des classes) qui exécutent chacune des taches des questions 1 et 2.
Pour la génération d'une main, c'est sans importance,
ou bien vous écrivez vous-même une main dans le code,
ou bien vous demandez à l'utilisateur d'entrer une main, genre
Rp 5t Dc Rt 5c
ou bien vous écrivez un générateur aléatoire de mains.
La première forme a ma préférence.
Dans votre solution, veuillez préciser quelle version de Python vous utilisez (tout le monde n'utilise pas la version 3 de Python !)
Il a l'air sympa cet exo (plus difficile qu'il n'en a l'air de prime abord).
J'essayerai d'y consacrer un peu de temps quand j'en aurai l'occasion. On pourrait même envisager de faire évoluer ça en un assistant de jeu ou un truc un peu plus concrêt…
C'est si difficile que ça ? Non, franchement, c'est pas très dur à coder disons "basiquement", c'est tout juste un peu long et un peu technique.
Ce qui est certainement plus difficile c'est de connaître suffisamment Python pour optimiser le code à produire. Par exemple pour le premier exo, si on trouve un carré, a priori on n'a pas à chercher si c'est un carré de Valets ou d'autre chose. Par contre, pour le deuxième exo, il peut arriver qu'on ait besoin de savoir de quel carré il s'agit si l'autre main est aussi un carré. Donc je ne sais pas si c'est faisable, l'idée serait d'utiliser le code du premier exo, en le complétant, le décorant ou je-ne-sais-quoi pour écrire le second.
Déjà, voici un code pour la première question, mais pas testé intensivement :
# -*- coding: utf-8 -*-
# python 2.7
CODES = {'11111': None, '1112': 'Paire', '122': 'Deux paires',
'113': 'Brelan', '23': 'Full', '14': u"Carré"}
def chercherRepet(rangs):
# Renvoie un élément de CODES
s = set(rangs)
mult = []
for c in s:
mult.append(str(rangs.count(c)))
return ''.join(sorted(mult))
def rangs_couleurs(main):
# renvoie
# rangs (As, Roi, etc) de la main
# couleurs (pique, etc) de la main
rangs = []
couleurs = []
for carte in main:
rangs.append(carte[:-1])
couleurs.append(carte[-1])
return (rangs, couleurs)
def estSuite(rangs):
# examine si une liste de rangs est une suite de cartes consécutives
rangs = set(rangs)
for (f, v) in [('V', 11), ('D', 12), ('R', 13)]:
if f in rangs:
rangs.discard(f)
rangs.add(v)
rangs = sorted([int(r) for r in rangs])
debut = int(rangs[0])
return rangs == range(debut, debut + 5) or rangs == [1, 10, 11, 12,
13]
def valeur(main):
# examine ce que vaut la main (une quinte, un carré, etc)
(rangs, couleurs) = rangs_couleurs(main)
resultat = CODES[chercherRepet(rangs)]
if resultat:
return resultat
else:
suite = estSuite(rangs)
if len(set(couleurs)) == 1:
if suite:
return 'Quinte flush'
else:
return 'Couleur'
elif suite:
return 'Suite'
else:
return 'Carte Haute'
test=[
["8c","7c","6c","5c","4c"],
["1c","Rc","Dc","Vc","10c"],
["4p","3p","1p","5p","2p"],
['Rp','Rc','Rt','Rk','3p'],
['1p','1c','1t','1k','3k'],
['Rp','Rc','Rt','3t','3p'],
['Rk','10k','8k','4k','3k'],
['8t','7p','6p','5k','4k'],
['Rp','Rk','Rt','Dp','5t'],
['Rp','Rt','Dt','8t','8k'],
['8t','8k','Rp','Dp','5t'],
['10t','10k','Rp','Dp','5t'],
['Rp','Dp','8c','6k','5t']
]
for main in test:
r = ''
for c in main:
r += '%3s ' % c
print r + ' : %s' % valeur(main)
[Les exemples viennent de Wikipedia cité dans mon énoncé]
8c 7c 6c 5c 4c : Quinte flush
1c Rc Dc Vc 10c : Quinte flush
4p 3p 1p 5p 2p : Quinte flush
Rp Rc Rt Rk 3p : Carré
1p 1c 1t 1k 3k : Carré
Rp Rc Rt 3t 3p : Full
Rk 10k 8k 4k 3k : Couleur
8t 7p 6p 5k 4k : Suite
Rp Rk Rt Dp 5t : Brelan
Rp Rt Dt 8t 8k : Deux paires
8t 8k Rp Dp 5t : Paire
10t 10k Rp Dp 5t : Paire
Rp Dp 8c 6k 5t : Carte Haute
EDIT : pour la comparaison de deux mains, c'est pas très dur en soi mais il y a de quoi s'amuser avec Python en créant une classe Poker et implémenter les méthodes spéciales __le__ et __lt__ .
Disons que ce n'est pas "difficile" en soi à coder de façon basique (la problématique est simple), mais pour ce genre d'exo, je pense que le plus intéressant est de trouver une solution élégante et concise, ce qui n'est pas forcément évident.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
class Main(object):
force = {(1,1,1,1,1):'CARTE',
(2,1,1,1):'PAIRE',
(2,2,1):'DEUX PAIRES',
(3,1,1):'BRELAN',
(3,1,2,0):'SUITE',
(3,1,2,1):'SUITE',
(3,1,2,2):'COULEUR',
(3,2):'FULL',
(4,1):'CARRE',
(5,0):'QUINTE FLUSH',
(5,1):'QUINTE FLUSH'}
def __init__(self,cartes):
valeur,couleur = zip(*cartes)
self.set = sorted([(valeur.count(v),v)for v in set(valeur)],reverse=1)
self.force,_ = zip(*self.set)
if self.force == (1,1,1,1,1):
C = len(set(couleur)) == 1 # teste si c'est une couleur
S = max(valeur)-min(valeur) == 4 # teste si c'est une suite
spe = sorted(valeur) == [2,3,4,5,14] # teste si c'est une suite à hauteur 5
if not C:
if spe or S: self.force = (3,1,2)+((0,) if spe else (1,))
elif spe or S: self.force = (5,)+((0,) if spe else (1,))
else: self.force = (3,1,2,2)
def __repr__(self):
# retourne le nom de la Main
return Main.force[self.force]
def win(self,other):
#Main.win(other) ==> Bool
#retourne True si Main l'emporte sur other
return (self.set > other.set) if self.force == other.force else (self.force > other.force)
VAL = ('','','2','3','4','5','6','7','8','9','10','VALET','DAME','ROI','AS')
COL = ('COEUR','CARREAU','PIQUE','TREFLE')
test=[
[(8,0),(7,0),(6,0),(5,0),(4,0)],
[(14,0),(13,0),(12,0),(11,0),(10,0)],
[(4,2),(3,2),(14,2),(5,2),(2,2)],
[(13,2),(13,0),(13,3),(13,1),(3,2)],
[(14,2),(14,0),(14,3),(14,1),(3,1)],
[(13,2),(13,0),(13,3),(3,3),(3,2)],
[(13,1),(10,1),(8,1),(4,1),(3,1)],
[(8,3),(7,2),(6,2),(5,1),(4,1)],
[(13,2),(13,1),(13,3),(12,2),(5,3)],
[(13,2),(13,3),(12,3),(8,3),(8,1)],
[(8,3),(8,1),(13,2),(12,2),(5,3)],
[(10,3),(10,1),(13,2),(12,2),(5,3)],
[(13,2),(12,2),(8,0),(6,1),(5,3)]
]
for m in test:
for v,c in m: print VAL[v],COL[c],' ',
print ': ',Main(m)
8 COEUR 7 COEUR 6 COEUR 5 COEUR 4 COEUR : QUINTE FLUSH
AS COEUR ROI COEUR DAME COEUR VALET COEUR 10 COEUR : QUINTE FLUSH
4 PIQUE 3 PIQUE AS PIQUE 5 PIQUE 2 PIQUE : QUINTE FLUSH
ROI PIQUE ROI COEUR ROI TREFLE ROI CARREAU 3 PIQUE : CARRE
AS PIQUE AS COEUR AS TREFLE AS CARREAU 3 CARREAU : CARRE
ROI PIQUE ROI COEUR ROI TREFLE 3 TREFLE 3 PIQUE : FULL
ROI CARREAU 10 CARREAU 8 CARREAU 4 CARREAU 3 CARREAU : COULEUR
8 TREFLE 7 PIQUE 6 PIQUE 5 CARREAU 4 CARREAU : SUITE
ROI PIQUE ROI CARREAU ROI TREFLE DAME PIQUE 5 TREFLE : BRELAN
ROI PIQUE ROI TREFLE DAME TREFLE 8 TREFLE 8 CARREAU : DEUX PAIRES
8 TREFLE 8 CARREAU ROI PIQUE DAME PIQUE 5 TREFLE : PAIRE
10 TREFLE 10 CARREAU ROI PIQUE DAME PIQUE 5 TREFLE : PAIRE
ROI PIQUE DAME PIQUE 8 COEUR 6 CARREAU 5 TREFLE : CARTE
Oh, du Java.
Bon, j'avais pas très envie de le faire en Python, donc j'avais commencé en OCaml et puis j'ai arrêté parce que j'avais autre chose à faire, et comme je rajais j'ai supprimé, bref, j'explique vite fait : l'idée cool était une fonction qui était une sorte de groupby améliorée. Elle prenait une liste et renvoyait la liste des listes d'éléments dont l'image par une certaine fonction était identique. À partir de ça, j'identifiais très facilement la couleur, et en regroupant par valeurs et en regardant les longueurs des listes je pouvais aussi identifier les combinaisons.
Ensuite, étant donnée une main, je trouvais la combinaison qu'elle représentait et je triais les cartes selon l'ordre de comparaison (en cas d'égalité) : la liste de listes précédente était très utile pour ça, avec reduce et map on le fait en une ligne pour chaque cas.
Ensuite, il suffit de parcourir linéairement les listes jusqu'à ce qu'on arrive sur une inégalité.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
force = {(1,1,1,1,1):'CARTE',
(2,1,1,1):'PAIRE',
(2,2,1):'DEUX PAIRES',
(3,1,1):'BRELAN',
(3,1,2,0):'SUITE',
(3,1,2,1):'SUITE',
(3,1,2,2):'COULEUR',
(3,2):'FULL',
(4,1):'CARRE',
(5,0):'QUINTE FLUSH',
(5,1):'QUINTE FLUSH'}
def __init__(self,cartes):
valeur,couleur = zip(*cartes)
self.set = sorted([(valeur.count(v),v)for v in set(valeur)],reverse=1)
self.force,_ = zip(*self.set)
if self.force == (1,1,1,1,1):
C = len(set(couleur)) == 1
S = max(valeur)-min(valeur) == 4
spe = sorted(valeur) == [2,3,4,5,14]
if not C:
if spe or S: self.force = (3,1,2)+((0,) if spe else (1,))
elif spe or S: self.force = (5,)+((0,) if spe else (1,))
else: self.force = (3,1,2,2)
def __repr__(self):
# retourne le nom de la Main
return Main.force[self.force]
def win(self,other):
#Main.win(other) ==> Bool
#retourne True si Main l'emporte sur other
return (self.set > other.set) if self.force == other.force else (self.force > other.force)
Désolé mais je n'aime pas trop cette façon de coder : on ne voit pas du tout l'algorithme (peu de découpage), le codage en début de classe est peu compréhensible. C'est certes concis mais pas lisible (enfin, c'est mon point de vue).
Voici mon code pour la question 2. Pas testé intensément, serais pas étonné qu'il subsiste des erreurs. Je n'en suis pas totalement content car j'utilise deux fois la fonction main_triee. On pourrait toutefois y remédier facilement en créant une classe Main (ou Hand plutôt, ce serait moins confusant) et puis tant qu'on y est les méthodes spéciales qui vont bien, mais bon, je trouve que c'est juste de la déco ici (modulo ma redondance de code).
# -*- coding: utf-8 -*-
# python 2.7
ORDRE = ['Carte Haute', 'Paire', 'Deux paires', 'Brelan', 'Suite',
'Couleur', 'Full', u"Carré", 'Quinte flush']
POSITION = dict([(v, i) for (i, v) in enumerate(ORDRE)]) # genre POSITION['Suite']=4
# Pour les combinaisons avec des cartes répétées
CODES = {'11111': None, '2111': ORDRE[1], '221': ORDRE[2],
'311': ORDRE[3], '32': ORDRE[6], '41': ORDRE[7]}
# Convertir les cartes en entiers de 2 à 14 (l'As)
CONV = dict([(str(i), i) for i in range(1, 11)] + zip('VDR1', range(11,
15)))
def main_triee(rangs):
# trie une main en commençant par les répétitions
# les plus nombreuses
s = set(rangs)
mult = []
for c in s:
mult.append((rangs.count(c), CONV[c]))
def comp(x, y):
(m1, v1, m2, v2) = x + y
if m1 != m2:
return -cmp(m1, m2)
else:
return cmp(v1, v2)
return sorted(mult, cmp=comp)
def rangs_couleurs(main):
# renvoie
# rangs (As, Roi, etc) de la main
# couleurs (pique, etc) de la main
rangs = []
couleurs = []
for carte in main:
rangs.append(carte[:-1])
couleurs.append(carte[-1])
return (rangs, couleurs)
def estSuite(rangs):
# examine si une liste de rangs est une suite de cartes consécutives
rangs = set(rangs)
for (f, v) in [('V', 11), ('D', 12), ('R', 13)]:
if f in rangs:
rangs.discard(f)
rangs.add(v)
rangs = sorted([int(r) for r in rangs])
debut = int(rangs[0])
return rangs == range(debut, debut + 5) or rangs == [1, 10, 11, 12,
13]
def valeur(main):
# examine ce que vaut la main (une quinte, un carré, etc)
(rangs, couleurs) = rangs_couleurs(main)
codeRepet = ''.join([str(m) for (m, _) in main_triee(rangs)])
comb_repet = CODES[codeRepet]
if comb_repet:
return comb_repet
else:
suite = estSuite(rangs)
if len(set(couleurs)) == 1:
if suite:
return ORDRE[8]
else:
return ORDRE[5]
elif suite:
return ORDRE[4]
else:
return ORDRE[0]
def gagnant(main1, main2):
# Renvoie la main gagnante (ou les deux en cas d'égalité)
# Si les deux mains sont des combinaisons distinctes (carré vs brelan par exemple)
# on sait qui gagne.
# Sinon, il faut traiter les cas de mains de même combinaison (brelan vs brelan par exemple)
# et il suffit de comparer les mains triées.
(v1, v2) = (valeur(main1), valeur(main2))
if v1 != v2:
return main2 if POSITION[v1] < POSITION[v2] else main1
else:
(rangs1, _) = rangs_couleurs(main1)
(rangs2, _) = rangs_couleurs(main2)
(mt1, mt2) = (main_triee(rangs1), main_triee(rangs2))
if mt1 != mt2:
return main2 if mt1 < mt2 else main1
else:
return (main1, main2)
test=[
(["4c","Rp","6t","1k","3p"],["2c","Rk","1t","5c","4p"]),# mains quelconques
(["4c","Rp","2t","4k","3p"],["4p","Rk","1t","4t","5p"]),# paire vs paire
(["4c","Rp","Rt","4k","3p"],["Rc","Rk","1t","4t","5p"]),# paires vs paire
(["4c","Rp","Rt","4k","3p"],["4p","Rk","Rc","2t","4t"]),# paires vs paires
(["4c","Rp","Rt","4k","3p"],["4p","Rk","Rc","1t","4t"]),# paires vs paires
(["4c","Rp","Rt","4k","1p"],["4p","Rk","Rc","1t","4t"]),# paires vs paires
(["4c","1p","1t","4k","3p"],["Rc","Rk","Rt","4t","5p"]),# paires vs brelan
(["1c","1p","1t","4k","3p"],["Rc","Rk","Rt","4t","5p"]),# brelan vs brelan
(["1c","1p","1t","4k","3p"],["Rc","Rk","Rt","4t","4p"]),# brelan vs full
(["1c","1p","1t","3k","3p"],["Rc","Rk","Rt","4t","4p"]),# full vs full
(["2c","2p","2t","3k","3p"],["Rc","Rk","Rt","4t","4p"]),# full vs full
(["2c","2p","2t","3k","3p"],["3c","5k","Vt","Dt","6p"]),# full vs qualconque
(["2c","2p","2t","3k","3p"],["1c","1k","1t","1k","6p"]),# full vs carre
(["2c","3c","5c","9c","Rc"],["1c","1k","1t","1k","6p"]),# couleur vs carre
(["2c","3c","5c","9c","Rc"],["1t","1k","1p","5k","7p"]),# couleur vs brelan
(["4p","6k","9p","Vk","Dp"],["2c","3c","5c","9c","Rc"]),# suite vs couleur
(["4p","6k","9p","Vk","Dp"],["1t","1k","1p","5k","7p"]),# suite vs brelan
(["1c","1k","1t","1p","6p"],["2c","3c","4c","5c","6c"]),# carre vs quinte
(["7p","6p","3p","4p","5p"],["2c","3c","4c","5c","6c"]),# quinte vs quinte
]
for (main1, main2) in test:
print main1, main2, gagnant(main1, main2)
Citation : Zerda
Bon, j'avais pas très envie de le faire en Python, donc j'avais commencé en OCaml et puis j'ai arrêté parce que j'avais autre chose à faire, et comme je rajais j'ai supprimé, BLA BLA ...
Je n'oserai pas vous montrer mon script, il est pas très joli et codé dans un style plutôt fonctionnel.
A la base : 3 fonctions pour déterminer la figure, on retrouve un peu la structure de josmiley:
La première fonction intervient uniquement sur la valeur des cartes (et non la couleur):
Avec un dictionnaire et la méthode .get(), chaque figure renvoie une valeur correspondante :
Ainsi :
"Vrac" renvoie (1,1,1,1,1)
"Paire" renvoie (2,1,1,1) etc...
De cette manière, je détecte les paires, doubles, brelan, full, carré et autre.
La deuxième fonction, un peu semblable à la première agit sur la couleur(et pas la valeur des cartes).
Là, c'est quand même plus simple, le résultat est binaire : toute les cartes sont de la même couleur, ou non.
La dernière teste la séquence : est-ce égal à un range()? Idem que précédemment, le résultat est binaire.
A partir de là, et en fonction des résultats obtenus, on détermine quelle est la figure en main (stocké dans un tuple).
Pour comparer enfin avec une main adverse, il faut que je décrypte, c'est un bazar immonde.
Dans les grandes lignes, j'ai créé une liste de tuple de référence contenant (le dictionnaire, le booléen de couleur, le booléen de séquence) dans l'ordre croissant de force.
Le tuple de ma main est comparé avec le tuple de la main adverse en cherchant l'index correspondant. En cas d'égalité, je ne sais plus comment j'ai fais.
En espérant avoir été clair.
Bonne soirée.
Edit : C'est moi ou de plus en plus de sujets traitent de problèmes du Projet Euler?
class Main(object):
force = {(1,1,1,1,1):'CARTE',
(2,1,1,1):'PAIRE',
(2,2,1):'DEUX PAIRES',
(3,1,1):'BRELAN',
(3,1,2,0):'SUITE',
(3,1,2,1):'SUITE',
(3,1,2,2):'COULEUR',
(3,2):'FULL',
(4,1):'CARRE',
(5,0):'QUINTE FLUSH',
(5,1):'QUINTE FLUSH'}
def __init__(self,cartes):
valeur,couleur = zip(*cartes)
self.set = sorted([(valeur.count(v),v)for v in set(valeur)],reverse=1)
self.force,_ = zip(*self.set)
if self.force == (1,1,1,1,1):
C = len(set(couleur)) == 1 # teste si c'est une couleur
S = max(valeur)-min(valeur) == 4 # teste si c'est une suite
spe = sorted(valeur) == [2,3,4,5,14] # teste si c'est une suite à hauteur 5
if not C:
if spe or S: self.force = (3,1,2)+((0,) if spe else (1,))
elif spe or S: self.force = (5,)+((0,) if spe else (1,))
else: self.force = (3,1,2,2)
prenons par exemple cette Main:
ROI PIQUE ROI CARREAU ROI TREFLE DAME PIQUE 5 TREFLE
on a donc:
Main.set = (3,roi),(1,dame),(1,5)
en zippant on obtiend:
Main.force = (3,1,1) ==> brelan
_ = (roi,dame,5)
pour comparer 2 Main (simples comparaisons de tuples)
on compare les forces
si elles sont égales alors on compare les set
s 'ils sont égaux ceux sont les mêmes mains.
les lignes 19 à 26 servent seulement à gérer les cas particuliers:
-les suites
-les couleurs
-l'As vaut 1 ou 14
self.set = sorted([(valeur.count(v),v)for v in set(valeur)],reverse=1)
Tiens, genre de code qui m'interpelle. Au sorted près, tu cherches donc les multiplicités des éléments d'une liste. Or, en appelant count sur chaque élément, la complexité devient quadratique (en effet, tout appel à count oblige le parcours de toute la liste, cf. l'implémentation de count dans listobject.c). Je répète, ça n'a pas d'impact ici car les données sont petites et je pense que dans d'autres circonstances tu l'aurais écrit différemment mais juste une petite expérimentation :
from random import randrange
t=[randrange(10) for _ in range(50000)]
print(dict([(x, t.count(x)) for x in t]))
$ time python multiplicite_jos.py
{0: 4968, 1: 4886, 2: 5027, 3: 5115, 4: 4925, 5: 5026, 6: 4929, 7: 4974, 8: 5090, 9: 5060}
real 0m55.037s
user 0m55.007s
sys 0m0.004s
alors qu'une telle recherche devrait prendre quelques ms si codé comme il faut.
Au passage, il n'y a que deux millions six cent mille mains possibles donc il est facile de faire des statistiques. J'ai repris le code de josmiley. On retrouve les statistiques connues, cf ICI.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
class Main(object):
force = {(1,1,1,1,1):'CARTE',
(2,1,1,1):'PAIRE',
(2,2,1):'DEUX PAIRES',
(3,1,1):'BRELAN',
(3,1,2,0):'SUITE',
(3,1,2,1):'SUITE',
(3,1,2,2):'COULEUR',
(3,2):'FULL',
(4,1):'CARRE',
(5,0):'QUINTE FLUSH',
(5,1):'QUINTE FLUSH'}
def __init__(self,cartes):
valeur,couleur = zip(*cartes)
self.set = sorted([(valeur.count(v),v)for v in set(valeur)],reverse=1)
self.force,_ = zip(*self.set)
if self.force == (1,1,1,1,1):
C = len(set(couleur)) == 1 # teste si c'est une couleur
S = max(valeur)-min(valeur) == 4 # teste si c'est une suite
spe = sorted(valeur) == [2,3,4,5,14] # teste si c'est une suite à hauteur 5
if not C:
if spe or S: self.force = (3,1,2)+((0,) if spe else (1,))
elif spe or S: self.force = (5,)+((0,) if spe else (1,))
else: self.force = (3,1,2,2)
def __repr__(self):
# retourne le nom de la Main
return Main.force[self.force]
def win(self,other):
#Main.win(other) ==> Bool
#retourne True si Main l'emporte sur other
return (self.set > other.set) if self.force == other.force else (self.force > other.force)
N=52
jeu=[(fig,col) for fig in range(2,15) for col in range(0,4)]
mains=[]
for a in range(N):
for b in range(a+1,N):
for c in range(b+1,N):
for d in range(c+1,N):
for e in range(d+1,N):
mains.append([jeu[a], jeu[b], jeu[c],jeu[d],jeu[e]])
print len(mains)
resultat= dict.fromkeys([
'CARTE',
'PAIRE',
'DEUX PAIRES',
'BRELAN',
'SUITE',
'COULEUR',
'FULL',
'CARRE',
'QUINTE FLUSH'
],0)
for main in mains:
m=Main(main)
resultat[Main.force[m.force]]+=1
print resultat
Petit exercice sympa inspiré d'un exercice du projet Euler.
Oui, l'exo est effectivement sympa . Je l'ai fait en C++ il y a quelque temps, j'essaierai de le refaire en Python si je trouve le temps.
Citation : candide
Attention, dans le cas de cartes consécutives, l'As a une situation particulière : il peut être devant le Roi mais aussi avant la carte 2.
Cette règle semble ne pas être communément admise.
Citation : Wikipédia
L'as peut cependant aussi être utilisé comme carte de valeur 1, si cette convention est acceptée à la table, permettant ainsi de former des suites de type As, 2, 3, 4, 5.
Je m'étais posé la question en codant pour l'exo de Project Euler, car il m'a semblé d’après la lecture du problème que cette règle n'est pas prise en compte (en tout cas, la réponse est correcte sans cette règle; avec cette règle, je n'en sais rien). Mais bon, ça ajoute un légère difficulté en plus, alors pourquoi pas ?
Je l'ai fait en C++ il y a quelque temps, j'essaierai de le refaire en Python si je trouve le temps.
Effectivement, cette question aurait sa place tant sur le forum Python que sur le forum C ou C++. Je ne sais pas quelle pourrait être la bonne pratique d'annonce :
-- se limiter à poster sur le forum Autres langages, outils et approches avec le risque de ne pas être lu par ceux qui ne le fréquenteraient pas
-- poster dans chaque forum (C, C++, C#, Java, Python)
-- annoncer sur chaque forum et demander à chacun de répondre uniquement sur Autres langages, outils et approches
-- autre ?
Citation : yoch
Citation : candide
Attention, dans le cas de cartes consécutives, l'As a une situation particulière : il peut être devant le Roi mais aussi avant la carte 2.
Cette règle semble ne pas être communément admise.
Effectivement et d'ailleurs j'ignorais cette pratique mais je l'ai proposé pour éviter les ambiguïtés (et c'est vrai que ça rajoute une petite difficulté intéressante). Je vais reformuler mon énoncé.
Citation : yoch
Je m'étais posé la question en codant pour l'exo de Project Euler, car il m'a semblé d’après la lecture du problème que cette règle n'est pas prise en compte
L'énoncé dit
Citation : Project Euler
The cards are valued in the order:
2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, King, Ace.
Ils auraient pu ajouter une phrase du genre "In spite of being marked 1, an Ace never counts as one".
Je regrette qu'un nombre non négligeable d'énoncés du project Euler restent si peu clairs en première lecture, deuxième lecture voire en dixième lecture. Qu'on n'écrive pas un énoncé clair du premier coup, je l'admets volontiers mais qu'on n'arrive pas à l'améliorer en tenant compte des expériences des utilisateurs me rend beaucoup moins tolérant. Hélas, cette pratique de l'énoncé non immédiatement compréhensible est assez fréquente.
Effectivement, cette question aurait sa place tant sur le forum Python que sur le forum C ou C++. Je ne sais pas quelle pourrait être la bonne pratique d'annonce :
-- se limiter à poster sur le forum Autres langages, outils et approches avec le risque de ne pas être lu par ceux qui ne le fréquenteraient pas
-- poster dans chaque forum (C, C++, C#, Java, Python)
-- annoncer sur chaque forum et demander à chacun de répondre uniquement sur Autres langages, outils et approches
-- autre ?
Ça fait longtemps que je pense qu'il devrait y avoir un forum dédié à l'algo. Pour l'instant, une des solutions habituelles est de poster dans le forum autres langages, mais je n'aime pas trop, car ça manque de visibilité.
De toutes manières, ton choix de poster dans le forum de ton langage préféré se justifie amplement.
Citation : candide
Je regrette qu'un nombre non négligeable d'énoncés du project Euler restent si peu clairs en première lecture, deuxième lecture voire en dixième lecture. Qu'on n'écrive pas un énoncé clair du premier coup, je l'admets volontiers mais qu'on n'arrive pas à l'améliorer en tenant compte des expériences des utilisateurs me rend beaucoup moins tolérant. Hélas, cette pratique de l'énoncé non immédiatement compréhensible est assez fréquente.
Je connais ton coté perfectionniste. Personnellement, je trouve que le Project Euler est largement au dessus des certains autres sites d'exo à ce niveau. Dans plusieurs cas les exos ont été reformulés, et la qualité des énoncés est globalement assez élevée.
Bon, trêve de discours, et place au code.
J'ai codé un peu à l'arrache, et bon, c'est ma première classe en Python, aussi ne tapez pas trop fort svp.
Aussi, je n'implémente pas la suite {1,2,3,4,5}.
from collections import namedtuple
Card = namedtuple('Card', 'color value')
Values = {'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9,'J':10,'Q':11,'K':12,'A':13}
HandName = {1:'Card', 2:'Pair', 3:'TwoPairs', 4:'Three', 5:'Straight', 6:'Flush', 7:'Full', 8:'Four', 9:'StraightFlush', 10:'RoyalFlush'}
Levels = {(1,1,1,1,1):1, (2,1,1,1):2, (2,2,1):3, (3,1,1):4, (3,2):7, (4,1):8}
class PokerHand:
def __init__(self, cards):
cards.sort(key=lambda c: Values[c.value])
flush = len(set([c.color for c in cards])) == 1
straight = Values[cards[0].value] + 4 == Values[cards[4].value]
nvals={}
for c in cards:
if c.value not in nvals: nvals[c.value]=1
else: nvals[c.value]+=1
self.cards = sorted(cards, key=lambda c: nvals[c.value], reverse=True)
if straight and flush:
self.level = (10 if self.cards[4]=='A' else 9)
elif flush:
self.level = 6
elif straight:
self.level = 5
else:
self.level = Levels[tuple(sorted(nvals.values(), reverse=True))]
def name(self):
return HandName[self.level]
def __lt__(self, other):
if self.level != other.level:
return self.level < other.level
for i in range(0,5):
if Values[self.cards[i]] != Values[other.cards[i]]:
return Values[self.cards[i]] < Values[other.cards[i]]
return False
def __gt__(self, other):
if self.level != other.level:
return self.level > other.level
for i in range(0,5):
if Values[self.cards[i]] != Values[other.cards[i]]:
return Values[self.cards[i]] > Values[other.cards[i]]
return False
def __eq__(self, other):
if self.level != other.level:
return False
for i in range(0,5):
if Values[self.cards[i]] != Values[other.cards[i]]:
return False
return True
def __repr__(self):
ret = ''
for i in range(0,5): ret += self.cards[i].color + self.cards[i].value + ' '
return ret
main1 = PokerHand([Card('D','5'),Card('D','7'),Card('C','5'),Card('H','K'),Card('S','J')])
main2 = PokerHand([Card('H','4'),Card('D','J'),Card('S','8'),Card('S','5'),Card('C','6')])
print(main1.name(), ';', main2.name())
print(main1, '<' if main1 < main2 else '>' if main1 > main2 else '=', main2)
EDIT : le code a très peu à voir avec mon code C++ original, j'ai trouvé l’écriture en Python beaucoup plus agréable, je dois avouer...
EDIT2 : Amélioration de certaines méthodes :
def __lt__(self, other):
if self.level != other.level:
return self.level < other.level
else:
return [Values[c.value] for c in self.cards] < [Values[c.value] for c in other.cards]
def __gt__(self, other):
if self.level != other.level:
return self.level > other.level
else:
return [Values[c.value] for c in self.cards] > [Values[c.value] for c in other.cards]
def __eq__(self, other):
if self.level != other.level:
return False
else:
return [Values[c.value] for c in self.cards] == [Values[c.value] for c in other.cards]
def __repr__(self):
return ' '.join([c.color + c.value for c in self.cards])
@Yoch:
Si, c'est vraiment ta première classe en python, chapeau!
Le seul truc que moi(pas plus avancé en python que toi), je trouve dommage c'est l'utilisation d'un range dans les for(pour parcourir une liste).
Cet exo je l'ai fait en C(il y a un peu plus d'un an), mais de manière bourrin(vraiment bourrin).
En voyant vos code, je vois que je suis passé à coté de quelque chose.
Pour dire que même si je ne propose rien, je suis très intéressé par les réponses, et j'imagine que pas mal de personnes sont dans le même cas.
Sinon, je trouve aussi que cet exercice manque de visibilité.(et finalement oui, beaucoup d'exos pourraient être communs à tous les langages).
Pour la qualité des énoncés du Euler Project je rejoins Yoch, je trouve qu'ils sont tout de même à l'écoute des critiques.
une tite modif qui affiche des cartes à la place de texte ...
dl l'image, copiez le code et cliquez sur la fenêtre pour passer à la main suivante.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pygame import *
font.init()
scr = display.set_mode((600,123))
font = font.Font(None,16)
class Main(list,object):
tile_set = image.load('cards.png')
valeur = ('','','2','3','4','5','6','7','8','9','10','VALET','DAME','ROI','AS')
couleur = ('TREFLE','CARREAU','COEUR','PIQUE')
force = {(1,1,1,1,1):'CARTE',
(2,1,1,1):'PAIRE',
(2,2,1):'DEUX PAIRES',
(3,1,1):'BRELAN',
(3,1,2,0):'SUITE',
(3,1,2,1):'SUITE',
(3,1,2,2):'COULEUR',
(3,2):'FULL',
(4,1):'CARRE',
(5,0):'QUINTE FLUSH',
(5,1):'QUINTE FLUSH'}
def __init__(self,cartes):
valeur,couleur = zip(*cartes)
self.extend(sorted(cartes,key=lambda v:(valeur.count(v),v),reverse=1)) # tri les cartes
self.force = tuple(sorted([valeur.count(v)for v in set(valeur)],reverse=1)) # donne une representation de la main
if self.force == (1,1,1,1,1): # gestion des cas particuliers
C = len(set(couleur)) == 1 # teste si c'est une couleur
S = max(valeur)-min(valeur) == 4 # teste si c'est une suite
spe = sorted(valeur) == [2,3,4,5,14] # teste si c'est une suite à hauteur 5
if C: self.force = (5,0) if spe else (5,1) if S else (3,1,2,2) # et ...
else: self.force = (3,1,2,0) if spe else (3,1,2,1) if S else self.force # rectification si besoin
if not self.force[-1]: self.append(self.pop(0)) # retification du tri si besoin (si l'AS vaut 1)
def win(self,otherMain):
#Main.win(other) ==> Bool
#retourne True si Main l'emporte sur other
return (self > otherMain) if self.force == otherMain.force else (self.force > otherMain.force)
def __repr__(self):
# retourne le nom de la Main
return Main.force[self.force]
def get_images(self):
# retourne une liste des images des cartes de la main
return [Main.tile_set.subsurface(((v-1)*79,c*123,79,123))for v,c in self]
test=[
[(8,0),(7,0),(6,0),(5,0),(4,0)],
[(14,0),(13,0),(12,0),(11,0),(10,0)],
[(4,2),(3,2),(14,2),(5,2),(2,2)],
[(13,2),(13,0),(13,3),(13,1),(3,2)],
[(14,2),(14,0),(14,3),(14,1),(3,1)],
[(13,2),(13,0),(13,3),(3,3),(3,2)],
[(13,1),(10,1),(8,1),(4,1),(3,1)],
[(8,3),(7,2),(6,2),(5,1),(4,1)],
[(13,2),(13,1),(13,3),(12,2),(5,3)],
[(13,2),(13,3),(12,3),(8,3),(8,1)],
[(8,3),(8,1),(13,2),(12,2),(5,3)],
[(10,3),(10,1),(13,2),(12,2),(5,3)],
[(13,2),(12,2),(8,0),(6,1),(5,3)]
]
for m in test:
m = Main(m)
pos = 0,0
for img in m.get_images(): pos = scr.blit(img,pos).topright
scr.blit(font.render(repr(m),1,(255,255,255)),pos)
display.flip()
while event.wait().type != MOUSEBUTTONUP: pass
scr.fill(0)
Code corrigé, en fait non, c'est pratiquement un nouveau code :
from collections import namedtuple
from functools import cmp_to_key
Card = namedtuple('Card', 'color value')
Values = {'2':1,'3':2,'4':3,'5':4,'6':5,'7':6,'8':7,'9':8,'T':9,'J':10,'Q':11,'K':12,'A':13}
HandName = {1:'Card', 2:'Pair', 3:'TwoPairs', 4:'Three', 5:'Straight', 6:'Flush', 7:'Full', 8:'Four', 9:'StraightFlush', 10:'RoyalFlush'}
Levels = {(1,1,1,1,1):1, (2,1,1,1):2, (2,2,1):3, (3,1,1):4, (3,2):7, (4,1):8}
class PokerHand:
def __init__(self, cards):
# determine la force de la main
nvals={}.fromkeys([c.value for c in cards], 0)
for c in cards:
nvals[c.value]+=1
values = sorted(set([Values[c.value] for c in cards]))
flush = len(set([c.color for c in cards])) == 1
straight = (len(values)==5) and (values[0]+4 == values[4])
if straight and flush:
self.level = (10 if values[4]==Values['A'] else 9)
elif flush:
self.level = 6
elif straight:
self.level = 5
else:
self.level = Levels[tuple(sorted(nvals.values(), reverse=True))]
# classe les cartes
def cmp_fn(a,b):
if nvals[a.value] < nvals[b.value]: return -1
elif nvals[a.value] > nvals[b.value]: return 1
elif Values[a.value] < Values[b.value]: return -1
elif Values[a.value] > Values[b.value]: return 1
else: return 0
self.cards = sorted(cards, key=cmp_to_key(cmp_fn), reverse=True)
def name(self):
return HandName[self.level]
def __lt__(self, other):
if self.level != other.level:
return self.level < other.level
else:
return [Values[c.value] for c in self.cards] < [Values[c.value] for c in other.cards]
def __gt__(self, other):
if self.level != other.level:
return self.level > other.level
else:
return [Values[c.value] for c in self.cards] > [Values[c.value] for c in other.cards]
def __eq__(self, other):
if self.level != other.level:
return False
else:
return [Values[c.value] for c in self.cards] == [Values[c.value] for c in other.cards]
def __repr__(self):
return ' '.join([c.color + c.value for c in self.cards])
Je suis assez déçu des perfs, je me rends compte qu'en cherchant à écrire de façon claire et en même temps concise, on risque de perdre en démarche algorithmique.
Je vois que ce qui prend le + de place dans ton code c'est la redéfinition des opérateurs.
Sinon, en effet c'est vraiment concis.
Hum, a propos ça donne la bonne réponse pour l'exercice du project Euler?
edit:perso j'ai triché, j'avais une main en moins en trop ou en plus d'attribuée.
J'ai testé un cran en dessus ou un cran en dessous et par chance c'est passé.
Je vois que ce qui prend le + de place dans ton code c'est la redéfinition des opérateurs.
Sinon, en effet c'est vraiment concis.
Non, ce que j'ai cherché à rendre vraiment court, c'est le __init__, mais je n'y suis pas arrivé...
Citation : GurneyH
Hum, a propos ça donne la bonne réponse pour l'exercice du project Euler?
Oui. Je viens de tester avec ce code :
counter=0
infile = open('poker.txt','r')
line = infile.readline()
while line != str():
args = line.split(' ')
mainA, mainB = PokerHand(tuple(Card(b,a) for a,b in zip(*zip(*args[:5])))), PokerHand(tuple(Card(b,a) for a,b in zip(*zip(*args[5:]))))
if mainA > mainB: counter+=1
line = infile.readline()
infile.close()
print(counter)
Citation : GurneyH
edit:perso j'ai triché, j'avais une main en moins en trop ou en plus d'attribuée.
J'ai testé un cran en dessus ou un cran en dessous et par chance c'est passé.
Perso, lorsque j'ai testé mon code C++, j'ai mis un temps fou à comprendre que je lisais une ligne de trop, ce qui faussait mon résultat d'un point.
Sinon c'est marrant, je me retrouve avec des habitudes que j'ai volontairement écartées en C, dans le sens ou mon code est absolument immonde (one-liners illisibles [ligne 7]). A croire que c'est une pratique de débutant...
Plus sérieusement, j’espère que le langage Python n'encourage pas trop ces pratiques.
J'ai l'impression que bien que python soit "très propre", il y a une mode du on-liner dégueulasse qui pèse sur la communauté.
Tu fais bien de préciser dégueulasse, car le on-liner n'est censé être utile que pour une bonne lisibilité et compréhension du lecteur.
Hors quand je vois ça
mainA, mainB = PokerHand(tuple(Card(b,a) for a,b in zip(*zip(*args[:5])))), PokerHand(tuple(Card(b,a) for a,b in zip(*zip(*args[5:]))))
Je me dis que ce code devrait rester personnel, et ne devrait pas être montré à des lecteurs potentiels.
Un code concis n'est pas forcément plus lisible et avec le code de yoch on en voit la preuve.
C'est montrer du code pour montrer du code, il n'y a même pas de commentaires.
il y a du values mélangé à du Values, mélangé à du value, enfin bref le seul à pouvoir comprendre ce code est son concepteur.
Citation
Plus sérieusement, j’espère que le langage Python n'encourage pas trop ces pratiques.
chercher zen of python sur google ou en faisant
import this
Mais c'est des recommandations qui peuvent être faites dans tous les autres langages.
Citation
Je suis assez déçu des perfs, je me rends compte qu'en cherchant à écrire de façon claire et en même temps concise, on risque de perdre en démarche algorithmique.
Si tu veux des performances, faut retourner au C ou C++ car il n'y a aucune chance de pouvoir faire mieux en python, même pour un codeur C++ pitoyable.
Le seul code que je vois compréhensif c'est celui de Candide et on comprend bien la démarche, pas besoin de commentaires, mais il en a mis.
Je me dis que ce code devrait rester personnel, et ne devrait pas être montré à des lecteurs potentiels.
...
Le seul code que je vois compréhensif c'est celui de Candide et on comprend bien la démarche, pas besoin de commentaires, mais il en a mis.
perso je trouve le code de Candide trop long, alors que celui de yoch est certe maladroit mais plus dans ma vision des choses.
comme quoi, les goûts et les codeurs ...
Python c'est bon, mangez-en.
Python c'est bon, mangez-en.
Python c'est bon, mangez-en.
Python c'est bon, mangez-en.
Python c'est bon, mangez-en.
Python c'est bon, mangez-en.
Python c'est bon, mangez-en.
Python c'est bon, mangez-en.