生成所有5张牌扑克牌

时间:2010-09-30 09:46:49

标签: python algorithm permutation combinatorics poker

这个问题乍一看听起来很简单,但事实证明它看起来要复杂得多。这让我很难过。

有52c5 = 2,598,960种方式从52张牌中选择5张牌。然而,由于套装在扑克中是可以互换的,其中许多是相同的 - 手2H 2C 3H 3S 4D相当于2D 2S 3D 3C 4H - 简单地换掉套装。根据{{​​3}},一旦你考虑到可能的套装重新着色,就有134,459个不同的5张牌。

问题是,我们如何有效地生成所有这些可能的牌?我不想生成所有的手,然后消除重复,因为我想将问题应用于更多的卡,以及评估快速螺旋失控的手的数量。我目前的尝试集中在生成深度优先,并跟踪当前生成的卡以确定哪些套装和等级对下一张卡有效,或者广度优先,生成所有可能的下一张卡,然后通过转换每个卡来删除重复通过重新着色来制作“规范”版本。这是我在Python中尝试广度优先的解决方案:

# A card is represented by an integer. The low 2 bits represent the suit, while
# the remainder represent the rank.
suits = 'CDHS'
ranks = '23456789TJQKA'

def make_canonical(hand):
  suit_map = [None] * 4
  next_suit = 0
  for i in range(len(hand)):
    suit = hand[i] & 3
    if suit_map[suit] is None:
      suit_map[suit] = next_suit
      next_suit += 1
    hand[i] = hand[i] & ~3 | suit_map[suit]
  return hand

def expand_hand(hand, min_card):
  used_map = 0
  for card in hand:
    used_map |= 1 << card

  hands = set()
  for card in range(min_card, 52):
    if (1 << card) & used_map:
      continue
    new_hand = list(hand)
    new_hand.append(card)
    make_canonical(new_hand)
    hands.add(tuple(new_hand))
  return hands

def expand_hands(hands, num_cards):
  for i in range(num_cards):
    new_hands = set()
    for j, hand in enumerate(hands):
      min_card = hand[-1] + 1 if i > 0 else 0
      new_hands.update(expand_hand(hand, min_card))
    hands = new_hands
  return hands

不幸的是,这产生了太多的手:

>>> len(expand_hands(set([()]), 5))
160537

任何人都可以建议一种更好的方法来生成独特的手,或指出我在尝试中出错的地方吗?

12 个答案:

答案 0 :(得分:18)

您的整体方法是合理的。我很确定你的make_canonical函数存在问题。您可以尝试使用设置为3或4的num_cards打印出手,并查找您错过的等效项。

我找到了一个,但可能还有更多:

# The inputs are equivalent and should return the same value
print make_canonical([8, 12 | 1]) # returns [8, 13]
print make_canonical([12, 8 | 1]) # returns [12, 9]

供参考,以下是我的解决方案(在查看您的解决方案之前开发)。我使用深度优先搜索而不是广度优先搜索。此外,我写了一个函数来检查一只手是否规范,而不是编写一个函数来将一个手转换为规范形式。如果它不是规范的,我会跳过它。我定义了rank = card%13和suit = card / 13.这些差异都不重要。

import collections

def canonical(cards):
    """
    Rules for a canonical hand:
    1. The cards are in sorted order

    2. The i-th suit must have at least many cards as all later suits.  If a
       suit isn't present, it counts as having 0 cards.

    3. If two suits have the same number of cards, the ranks in the first suit
       must be lower or equal lexicographically (e.g., [1, 3] <= [2, 4]).

    4. Must be a valid hand (no duplicate cards)
    """

    if sorted(cards) != cards:
        return False
    by_suits = collections.defaultdict(list)
    for suit in range(0, 52, 13):
        by_suits[suit] = [card%13 for card in cards if suit <= card < suit+13]
        if len(set(by_suits[suit])) != len(by_suits[suit]):
            return False
    for suit in range(13, 52, 13):
        suit1 = by_suits[suit-13]
        suit2 = by_suits[suit]
        if not suit2: continue
        if len(suit1) < len(suit2):
            return False
        if len(suit1) == len(suit2) and suit1 > suit2:
            return False
    return True

def deal_cards(permutations, n, cards):
    if len(cards) == n:
        permutations.append(list(cards))
        return
    start = 0
    if cards:
        start = max(cards) + 1
    for card in range(start, 52):
        cards.append(card)
        if canonical(cards):
            deal_cards(permutations, n, cards)
        del cards[-1]

def generate_permutations(n):
    permutations = []
    deal_cards(permutations, n, [])
    return permutations

for cards in generate_permutations(5):
    print cards

它会生成正确的排列数:

Cashew:~/$ python2.6 /tmp/cards.py | wc
134459

答案 1 :(得分:3)

这是一个Python解决方案,它利用numpy并生成规范的交易以及它们的多样性。我使用Python的itertools模块创建了4种套装的所有24种可能的排列,然后迭代所有2,598,960种可能的5种卡片交易。每笔交易都被置换,并在5行中转换为规范ID。它非常快,因为循环只经过10次迭代来覆盖所有交易,只需要管理内存需求。除了使用itertools.combinations之外,所有繁重的工作都是在numpy中有效完成的。很遗憾,这不是直接支持numpy。

import numpy as np
import itertools

# all 24 permutations of 4 items
s4 = np.fromiter(itertools.permutations(range(4)), dtype='i,i,i,i').view('i').reshape(-1,4)

c_52_5 = 2598960 # = binomial(52,5) : the number of 5-card deals in ascending card-value order
block_n = c_52_5/10
def all5CardDeals():
    '''iterate over all possible 5-card deals in 10 blocks of 259896 deals each'''
    combos = itertools.combinations(range(52),5)
    for i in range(0, c_52_5, block_n):
        yield np.fromiter(combos, dtype='i,i,i,i,i', count=block_n).view('i').reshape(-1,5)

canon_id = np.empty(c_52_5, dtype='i')
# process all possible deals block-wise.
for i, block in enumerate(all5CardDeals()):
    rank, suit = block/4, block%4     # extract the rank and suit of each card
    d = rank[None,...]*4 + s4[:,suit] # generate all 24 permutations of the suits
    d.sort(2)                         # re-sort into ascending card-value order
    # convert each deal into a unique integer id
    deal_id = d[...,0]+52*(d[...,1]+52*(d[...,2]+52*(d[...,3]+52*d[...,4])))
    # arbitrarily select the smallest such id as the canonical one 
    canon_id[i*block_n:(i+1)*block_n] = deal_id.min(0)
# find the unique canonical deal ids and the index into this list for each enumerated hand
unique_id, indices = np.unique(canon_id, return_inverse=True)
print len(unique_id) # = 134459
multiplicity = np.bincount(indices)
print multiplicity.sum() # = 2598960 = c_52_5

答案 2 :(得分:2)

你的问题听起来很有趣,所以我很简单地尝试通过循环遍历所有可能的牌来实现它。我没有详细查看你的代码,但似乎我的实现与你的完全不同。猜猜我的剧本找到了什么手数:160537

  • 我的手总是排序,例如2 3 4 4 D
  • 如果有2张相同的牌,颜色也会被排序(颜色只称为0,1,2,3)
  • 第一张卡总是颜色为0,第二张颜色为0或1
  • 一张卡片只能使用前一张卡片的颜色或下一个更大的号码,例如如果卡1 + 2的颜色为0,则卡3的颜色只能为0或1

您确定,维基百科上的数字是否正确?

count = 0
for a1 in range(13):
    c1 = 0
    for a2 in range(a1, 13):
        for c2 in range(2):
            if a1==a2 and c1==c2:
                continue
            nc3 = 2 if c1==c2 else 3
            for a3 in range(a2, 13):
                for c3 in range(nc3):
                    if (a1==a3 and c1>=c3) or (a2==a3 and c2>=c3):
                        continue
                    nc4 = nc3+1 if c3==nc3-1 else nc3
                    for a4 in range(a3, 13):
                        for c4 in range(nc4):
                            if (a1==a4 and c1>=c4) or (a2==a4 and c2>=c4) or (a3==a4 and c3>=c4):
                                continue
                            nc5 = nc4+1 if (c4==nc4-1 and nc4!=4) else nc4
                            for a5 in range(a4, 13):
                                for c5 in range(nc5):
                                    if (a1==a5 and c1>=c5) or (a2>=a5 and c2>=c5) or (a3==a5 and c3>=c5) or (a4==a5 and c4>=c5):
                                        continue
                                    #print([(a1,c1),(a2,c2),(a3,c3),(a4,c4),(a5,c5)])
                                    count += 1
print("result: ",count)

答案 3 :(得分:1)

我不是扑克玩家,因此手牌优先的细节超出了我的范围。但似乎问题在于,当你应该穿越“不同扑克手”的空间时,你通过从牌组中产生集合来穿越“5张牌组”的空间。

不同手的空间需要新的语法。重要的是准确捕获与手优先相关的信息。例如,只有4只手是皇室冲洗,因此这些手可以被描述为符号“RF”加上套装指示符,如“RFC”用于俱乐部的皇家冲洗。一个10高的心脏冲洗可能是“FLH10”(不确定是否有其他优先冲洗特征,但我认为这是你需要知道的全部)。如果我找不到您的初始问题陈述,那么“2C 2S AH 10C 5D”的手将是更长的表达,类似于“PR2 A 10 5”。

一旦你定义了不同手的语法,你就可以将它表达为正则表达式,并告诉你如何生成不同手的整个空间。听起来很有趣!

答案 4 :(得分:1)

你可以简单地给所有人一个规范的价值排序(A到K),然后根据他们按顺序首次出现的顺序分配抽象的套装字母。

示例:JH 4C QD 9C 3D将转换为3a 4b 9b Jc Qa。

生成应该最适合作为动态编程:

  • 从一组空单开始,
  • 制作一套新剧集:
    • 对于旧套装中的每只手,通过添加剩余的一张牌生成每只可能的牌
    • 规范所有新手
    • 删除重复项

答案 5 :(得分:1)

初始输入:

H 0 0 0 0 0 0 0 0 0 0 0 0 0
C 1 0 0 0 0 0 0 0 0 0 0 0 0
D 1 0 0 0 0 0 0 0 0 0 0 0 0
S 1 1 0 0 0 0 0 0 0 0 0 0 0
+ A 2 3 4 5 6 7 8 9 T J Q K

步骤1:对于大于或等于所使用的最高等级的每个等级,将该等级中的所有套装设置为0.只能检查较高的牌,因为较低的起点将检查较低的组合。

H 0 0 0 0 0 0 0 0 0 0 0 0 0
C 1 0 0 0 0 0 0 0 0 0 0 0 0
D 1 0 0 0 0 0 0 0 0 0 0 0 0
S 1 0 0 0 0 0 0 0 0 0 0 0 0
+ A 2 3 4 5 6 7 8 9 T J Q K

第2步:折叠到不同的行

0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0 0 0 0
A 2 3 4 5 6 7 8 9 T J Q K

步骤3:爬回来确定匹配每个不同行的第一个套装,并选择与不同行匹配的套装(由*标识)

H 0 * 0 0 0 0 0 0 0 0 0 0 0
C 1 0 0 0 0 0 0 0 0 0 0 0 0
D 1 * 0 0 0 0 0 0 0 0 0 0 0
S 1 1 0 0 0 0 0 0 0 0 0 0 0
+ A 2 3 4 5 6 7 8 9 T J Q K

现在显示等级3的重复

H 0 0 0 0 0 0 0 0 0 0 0 0 0
C 1 0 0 0 0 0 0 0 0 0 0 0 0
D 1 0 0 0 0 0 0 0 0 0 0 0 0
S 1 1 0 0 0 0 0 0 0 0 0 0 0
+ A 2 3 4 5 6 7 8 9 T J Q K

0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0 0 0 0
1 1 0 0 0 0 0 0 0 0 0 0 0
A 2 3 4 5 6 7 8 9 T J Q K

H 0 0 * 0 0 0 0 0 0 0 0 0 0
C 1 0 0 0 0 0 0 0 0 0 0 0 0
D 1 0 * 0 0 0 0 0 0 0 0 0 0
S 1 1 * 0 0 0 0 0 0 0 0 0 0
+ A 2 3 4 5 6 7 8 9 T J Q K

步骤4:一旦有5个单元格设置为1,将可能的套装抽象手数增加1并递归。

可能提起的诉讼总数为134,459。这是我编写的代码来测试它:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication20
{
    struct Card
    {
        public int Suit { get; set; }
        public int Rank { get; set; }
    }
    
    class Program
    {
        static int ranks = 13;
        static int suits = 4;
        static int cardsInHand = 5;

        static void Main(string[] args)
        {
            List<Card> cards = new List<Card>();
            //cards.Add(new Card() { Rank = 0, Suit = 0 });
            int numHands = GenerateAllHands(cards);
    
            Console.WriteLine(numHands);
            Console.ReadLine();
        }
  
        static int GenerateAllHands(List<Card> cards)
        {
            if (cards.Count == cardsInHand) return 1;
    
            List<Card> possibleNextCards = GetPossibleNextCards(cards);
    
            int numSubHands = 0;
    
            foreach (Card card in possibleNextCards)
            {
                List<Card> possibleNextHand = cards.ToList(); // copy list
                possibleNextHand.Add(card);
                numSubHands += GenerateAllHands(possibleNextHand);
            }
    
            return numSubHands;
        }
    
        static List<Card> GetPossibleNextCards(List<Card> hand)
        {
            int maxRank = hand.Max(x => x.Rank);
            
            List<Card> result = new List<Card>();
    
            // only use ranks >= max
            for (int rank = maxRank; rank < ranks; rank++)
            {
                List<int> suits = GetPossibleSuitsForRank(hand, rank);
                var possibleNextCards = suits.Select(x => new Card { Rank = rank, Suit = x });
                result.AddRange(possibleNextCards);
            }
    
            return result;
        }
    
        static List<int> GetPossibleSuitsForRank(List<Card> hand, int rank)
        {
            int maxSuit = hand.Max(x => x.Suit);
    
            // select number of ranks of different suits
            int[][] card = GetArray(hand, rank);
    
            for (int i = 0; i < suits; i++)
            {
                card[i][rank] = 0;
            }
    
            int[][] handRep = GetArray(hand, rank);
    
            // get distinct rank sets, then find which ranks they correspond to
            IEnumerable<int[]> distincts = card.Distinct(new IntArrayComparer());
    
            List<int> possibleSuits = new List<int>();
    
            foreach (int[] row in distincts)
            {
                for (int i = 0; i < suits; i++)
                {
                    if (IntArrayComparer.Compare(row, handRep[i]))
                    {
                        possibleSuits.Add(i);
                        break;
                    }
                }
            }
    
            return possibleSuits;
        }
    
        class IntArrayComparer : IEqualityComparer<int[]>
        {
            #region IEqualityComparer<int[]> Members
    
            public static bool Compare(int[] x, int[] y)
            {
                for (int i = 0; i < x.Length; i++)
                {
                    if (x[i] != y[i]) return false;
                }
    
                return true;
            }
    
            public bool Equals(int[] x, int[] y)
            {
                return Compare(x, y);
            }
    
            public int GetHashCode(int[] obj)
            {
                return 0;
            }

            #endregion
        }

        static int[][] GetArray(List<Card> hand, int rank)
        {
            int[][] cards = new int[suits][];
            for (int i = 0; i < suits; i++)
            {
                cards[i] = new int[ranks];
            }

            foreach (Card card in hand)
            {
                cards[card.Suit][card.Rank] = 1;
            }
    
            return cards;
        }
    }
}

希望它足够容易理解。

答案 6 :(得分:1)

这是一个简单而直接的算法,可以根据套装permutatoins将手缩减为规范的。

  1. 将手转换为四个位集,每个套装一个代表诉讼卡
  2. 对bitsets进行排序
  3. 将位集转换回手中
  4. 这就是C ++中的算法,带有一些隐含的Suit和CardSet类。请注意,return语句通过连接位串来转换手。

    CardSet CardSet::canonize () const
    {
      int smasks[Suit::NUM_SUIT];
      int i=0;
      for (Suit s=Suit::begin(); s<Suit::end(); ++s)
        smasks[i++] = this->suitMask (s);
    
      sort (smasks, smasks+Suit::NUM_SUIT);
    
      return CardSet(
        static_cast<uint64_t>(smasks[3])                        |
        static_cast<uint64_t>(smasks[2]) << Rank::NUM_RANK      |
        static_cast<uint64_t>(smasks[1]) << Rank::NUM_RANK*2    |
        static_cast<uint64_t>(smasks[0]) << Rank::NUM_RANK*3);
    }
    

答案 7 :(得分:0)

看看Pokersource。考虑到已经绘制了一些牌,你考虑完成手牌时,问题变得更加严重。

PokerStove背后的人在这方面做得很好,但是消息来源已经披露了。

答案 8 :(得分:0)

为5张牌手生成等价类并非易事。 当我需要时,我通常会使用http://www.vpgenius.com/网页。在http://www.vpgenius.com/video-poker/games/你可以选择你需要的各种扑克游戏,在“编程选项卡”中你有一个关于“独特套装模式”的部分。因此,只需复制并加载到程序中可能比尝试生成自己的更容易。

答案 9 :(得分:0)

看看这里:

http://specialk-coding.blogspot.com/

http://code.google.com/p/specialkpokereval/

这些将5张牌(和7张牌手)视为整数,即各张牌的总和,与牌套无关。正是你需要的。

这是快速排名7和5张牌的计划的一部分,用Objective-C和Java编写。

答案 10 :(得分:0)

如果你只对那些导致不同手牌排名的牌感兴趣,那么实际上只有7462个不同的牌级需要考虑(参见Wikipedia)。

通过创建一个包含每个类的示例及其伴随多重性的表,您可以非常快速地检查所有相关的手加权概率。也就是说,假设没有卡片已知,因此事先已经修好了。

答案 11 :(得分:0)

你真的想要产生不同手的数量,在非等价意义上。在这种情况下,根据维基百科文章,有7462个可能的牌。这是一个python片段,将枚举它们。

逻辑很简单:每组5组都有一手牌;另外,如果所有的等级都是不同的,那么可以通过使所有的套装匹配来形成另一种不同的牌。

count = 0 

for i in range(0,13):
    for j in range (i,13):
        for k in range(j,13):
            for l in range(k,13):
                for m in range(l,13):
                    d = len(set([i,j,k,l,m])) # number of distinct ranks
                    if d == 1: continue    # reject nonsensical 5-of-a-kind
                    count += 1
                    # if all the ranks are distinct then 
                    # count another hand with all suits equal
                    if d == 5: count += 1

print count   # 7462