如何遍历所有组合,例如48选5【重复】

Posted

技术标签:

【中文标题】如何遍历所有组合,例如48选5【重复】【英文标题】:How to loop through all the combinations of e.g. 48 choose 5 [duplicate] 【发布时间】:2012-01-12 14:40:13 【问题描述】:

可能重复:How to iteratively generate k elements subsets from a set of size n in java?

我想建立自己的扑克牌评估器,但在某个特定部分遇到问题。

如果两个玩家拿到两张牌,那么牌组中将剩下 48 张牌。在德州扑克中,接下来会发另外 5 张可能的公共牌(这称为棋盘)。我想枚举/循环遍历所有 48 种选择 5 种可能的棋盘组合,并计算玩家 A 获胜的次数、玩家 B 获胜的次数以及他们平局的时间。

我不确定如何系统地循环遍历每 5 张卡片组合。有没有人有任何想法?卡片表示为 Card 类的数组,但如果这样可以加快速度,我也可以将它们表示为 bitset。

我在 Java 中执行此操作。

非常感谢

【问题讨论】:

“有人有什么想法吗?” 1) 在提出问题之前搜索重复项 2) 提出一个(具体的)问题。 3) 善待你的妈妈。 @Andrew Thompson:除了他的问题 非常具体。它专门用于评估在德州扑克游戏中翻牌前全押(至少一名全押)的两名玩家之间所有 C(48,5) 可能的对决。能比这更具体多少?他甚至继续解释他是如何模拟卡片的,并且他有自己的手牌评估器(显然)。所以他的问题是关于生成 C(48,5) 板。看我的回答。 @user988052 “需求声明”(具体或不具体)不等同于问题。在那篇文章中提出的唯一问题是我引用的那句话。 @Andrew Thompson:除了上下文在问题之前的短语中非常明显,在同一段落中:“我不确定如何系统地循环遍历每个5张卡片组合。有人有什么想法吗?”。我通过回答(明显的)OP 的问题来提供帮助。还可以通过使用您的代表重新提出问题来提供帮助怎么样? 【参考方案1】:

(免责声明:我写了一个非常快的扑克手评估器)

我想枚举/循环遍历所有 48 选择 5 可能 棋盘组合并计算玩家 A 获胜的次数和次数 玩家 B 获胜,当他们打平时。

您不想在每次翻牌前两名玩家之间的对局时评估 C(48,5) (1 712 304) 手牌:大多数程序只是在两名玩家翻牌前所有可能的对局之间使用预先计算的查找表。

例如,假设您有“Ac Ad”与“7c 6c”,您只需查找包含以下内容的查找表:1 333 573, 371 831, 6900(其中 1 333 573 是“Ac Ad”获胜的次数,371 831是“7c 6c”获胜的次数,而 6 900 是平局数(总和为 1 712 304)。要获得一些空间,您可以丢弃 6 900,知道平局数应始终为 C(48,5) - (赢 1 + 赢 2).

(更多关于此答案末尾的查找表)

但要回答你的问题:

我不确定如何系统地循环遍历每 5 张卡片 组合。

如果您真的想在每个组合中循环,您必须知道扑克手评估器通常是需要非常非常快的程序。这些程序通常每秒可以评估数亿手(您没看错:数亿)。

当您需要如此高性能的“数字运算”时,您可以忘记“设计模式”和“OO”。你想要的是原始速度。

例如下面的代码会经过最里面的循环 C(48,5) 次,速度非常快:

    for ( int i = 0; i < n; i++ ) 
        for ( int j = i + 1; j < n; j++ ) 
            for ( int k = j + 1; k < n; k++ ) 
                for (int l = k + 1; l < n; l++) 
                    for (int m = l + 1; m < n; m++) 
                        ...
                    
                
            
        
    

再一次,对于两个玩家来说,这可能是一个非常糟糕的主意:使用查找表会更快。

但是对于三个玩家翻牌前(使用翻牌前牌桌是不切实际的,有太多对局),你可能想要像这样循环,在 C(46,5) 手牌上,使用五个嵌套循环(当然您需要使用 i,j,k,l,m 从剩下的 46 张卡片中取出正确的 5 张卡片)。然后,一旦你得到了 5 张牌,你就可以使用一个快速的手牌评估器,它会给出 7 张牌中最好的牌(棋盘上的 5 张牌 + 每个玩家的两张牌)。

关于查找表:

大多数人使用近似 169 对 169 的查找表(“Ac Kd”、“As Kh”、“Ad Ks”等都变成“AK 非同花”,C(52,2) 可能的起手牌被分组在 169 种起手牌中)。***的文章解释了如何获得 169 个非等价起手牌:

http://en.wikipedia.org/wiki/Texas_hold_%27em_starting_hands

当您考虑 一手 时,它们是不等价的,但是一旦您考虑 一手对手 2,“169 对 169”就是一个近似值(说的很好)。

当然你可以变得更漂亮。只有 C(52,2)(给出 1326)真正不同的德州扑克起手牌,这意味着在现代计算机上构建一个完美的查找表(根本没有近似值)非常实用(C(1326,2) ain不是那么大)如果你真的需要完美的数字。如果您可以接受近似值,请选择 169 vs 169 表(它需要 C(169,2) 或 14 196 个条目)。

【讨论】:

感谢您的精彩回答!很有帮助。事实上,我试图构建这个的原因是我从pokerstove.com/analysis/preflopeq.php 获得了 169 与 169 的查找表,并试图弄清楚这些数字是如何得出的,以及我如何将个人手牌与手牌赢率结合起来值到达范围与范围值。似乎如果我可以构建自己的实现,那么学习经验就会使数学变得清晰。再次为您的回答喝彩。 @Joe:没问题,这就是 *** 的用途 :) 关于 “个人手牌与范围”“范围与范围” ,对于两个玩家:您基本上可以简单地在 169 vs 169 表中进行几次查找。例如,您需要“JJ vs AA、KK 和 QQ 范围”,您只需执行“JJ vs AA”+“JJ vs KK”+“JJ vs QQ”/3。这比这要复杂一些,但基本上是一个要走的路。您可以随时使用 PokerStove 来验证您的结果:) @TacticalCoder,但是上面的代码是静态的。当for 的数量是动态的时,这种方法就行不通了。【参考方案2】:
    将数组 (A) 初始化为前 5 个索引。 (0,1,2,3,4) 将 A 中最后一个可能的索引移动到下一个位置。 A[k]++ 将以下索引移动到以下连续位置。 (A[k+1] = A[k] + 1, ...)。如果某个索引变得太大,请在第 2 步中尝试使用较早的索引。 在A 中的当前索引处生成元素。 如果可能,从第 2 步开始重复。

作为迭代器实现:

public class CombinationIterator<T>
        implements Iterable<List<T>>,
                   Iterator<List<T>> 
    private List<T> items;
    private int choose;
    private boolean started;
    private boolean finished;
    private int[] current;

    public CombinationIterator(List<T> items, int choose) 
        if (items == null || items.size() == 0) 
            throw new IllegalArgumentException("items");
        
        if (choose <= 0 || choose > items.size()) 
            throw new IllegalArgumentException("choose");
        
        this.items = items;
        this.choose = choose;
        this.finished = false;
    

    public Iterator<List<T>> iterator() 
        return this;
    

    public boolean hasNext() 
        return !finished;
    

    public List<T> next() 
        if (!hasNext()) 
            throw new NoSuchElementException();
        

        if (current == null) 
            current = new int[choose];
            for (int i = 0; i < choose; i++) 
                current[i] = i;
            
        

        List<T> result = new ArrayList<T>(choose);
        for (int i = 0; i < choose; i++) 
            result.add(items.get(current[i]));
        

        int n = items.size();
        finished = true;
        for (int i = choose - 1; i >= 0; i--) 
            if (current[i] < n - choose + i) 
                current[i]++;
                for (int j = i + 1; j < choose; j++) 
                    current[j] = current[i] - i + j;
                
                finished = false;
                break;
            
        

        return result;
    

    public void remove() 
        throw new UnsupportedOperationException();
    

在 C# 中等效,使用迭代器方法:

public IEnumerable<IList<T>> Combinations<T>(IEnumerable<T> items, int choose)

    if (items == null) throw new ArgumentNullException("items");

    var itemsList = items.ToList();
    int n = itemsList.Count;

    if (n < 1) throw new ArgumentException("Must contain at least one item.", "items");
    if (choose <= 0 || choose >= n) throw new ArgumentOutOfRangeException("choose");

    var indices = Enumerable.Range(0, choose).ToArray();

    bool moreWork = true;
    while (moreWork)
    
        yield return indices.Select(i => itemsList[i]).ToList();

        moreWork = false;
        for (int i = choose - 1; i >= 0; i--)
        
            if (indices[i] < n - choose + i)
            
                indices[i]++;
                for (int j = i + 1; j < choose; j++)
                
                    indices[j] = indices[i] - i + j;
                
                moreWork = true;
                break;
            
        
    

【讨论】:

感谢您提供有用的代码。 非常有用。我将代码移植到 C# 以与“foreach”一起使用。不过这里的方法略有不同,Boolean MoveNext() 准备下一个结果并返回是否有结果,List&lt;T&gt; Current 属性用于获取该结果。这只是意味着必须存储准备好的结果,但除此之外,几乎没有什么变化。 @Nyerguds 我添加了一个 C# 版本,更好地利用语言约定。 哦,漂亮。我使用这段代码优化了一个包含许多相同 dll 的 4 个应用程序的项目文件夹。安装后的最终结果将在一个文件夹中结束,但为了能够让安装程序选择组件,它们被构建为单独的文件夹。这段代码帮助我优化它并将所有常见文件移动到不同的文件夹:)

以上是关于如何遍历所有组合,例如48选5【重复】的主要内容,如果未能解决你的问题,请参考以下文章

获取R中的所有组合,允许重复

二维数组中长度为 8 的所有可能组合

如何只循环一次组合

如何打印或显示两个字符串列表的所有组合[重复]

在不重复选择的情况下找到所有可能的组合?

为X位数生成0-9的所有唯一组合[重复]