在前面的问题中,我展示了我的代码,用于计算扫雷器板上每个字段的排雷概率。但这并不止于此。在同一个Minesweeper Analyze项目中,还存在一种用于计算每个字段的每个数字的概率的算法。

动机

扫雷艇叫Minesweeper Flags,您必须注意不要向对手透露太多信息。这通常是将优秀玩家与经验不足的玩家区分开的原因。如果您有80%的机会成为地雷,但有20%的机会公开地雷(这可能会向您的对手展示10个明显的地雷),您会冒险吗?计算期望值(本身是一个完全不同的主题)告诉我们,这可能是个坏主意。 6枚地雷:



将字段分组为FieldGroups时(发现字段组的定义,请参见前面的问题),我们发现: />
3个字段仅连接到1(b
4个字段同时连接到1和3(c
3个字段仅连接到3(d
44字段均未连接(a



在此板上执行防雷概率分析时,我们发现有两个解决方案组: />
4c = 1,44a = 3,3b = 0,3d = 2,158928组合(c组中的4个字段应具有一个地雷,a组中的44个字段应具有3个地雷,等等) /> 4c = 0、44a ​​= 2、3b = 1、3d = 3、2838个组合

现在,数字“ 2”直接位于“ 1”上方的概率是多少?让我们将1上方的字段称为x。通过向x2a + 3d + 1b = 2)的邻居的现有董事会添加FieldRule,以及对该字段不是地雷的规则(x = 0),可以计算出该规则。但是,如果我们要对董事会的每个领域都这样做,那将花费大量时间。更不用说我们必须对每个数字(0-8)执行此操作。

因此,我们可以将字段分组为与之相邻的字段组:

Field Groups     Probability Groups
  aaaaaaaa           abbbbbba
  aaaaaaaa           bcdefghb
  aabccdaa           bijklmnb
  aab13daa           bop13qrb
  aabccdaa           bijklmnb
  aaaaaaaa           bcdefghb
  aaaaaaaa           abbbbbba

k是我们的字段x所属的位置,它与字段组3*a, 2*b, 1*c相邻。此信息可以存储在上一个问题中介绍的GroupValues结构中,它实际上是Map<FieldGroup, Integer>。为了提高查询速度,它的hashCode值是在首次计算后存储的,以提高性能。

我们可以看到在概率组图上实际上有两个k ,因此我们将它们组合在一起可以获得一些性能,因为它们具有相同的详细概率。在地图上也有22 b,因此我们可以通过让他们共享几率来获得很多性能。请注意,概率组b中的所有字段都与FieldGroup a的5个相邻。

计算概率组的详细概率44a = 3,3b = 0,3d = 2,158928个组合

我们知道x字段(在概率组k中)具有以下邻居:3 * a,2 * b,1 * c。

首先我们处理1c(因为这是Eclipse调试器选择的)。那个特定的邻居有一个地雷,或者没有。假设它没有地雷。然后有2种组合,因为距c场有2个x场,其中一个场必须有一个地雷。

其次,3a。 a组共有44个场,在此解决方案中应具有3个地雷。该字段与这些字段中的3个相邻。假设这些邻居中有1个是我的,那么(根据超几何分布)有\ $ \ binom {3} {1} * \ binom {44-3} {3-1-1} = 2460 \ $个组合。

然后邻居2b。从我们的解决方案中我们知道,尽管b中没有地雷,所以这很简单:一个组合。

我们不与任何d相邻,因此有3个组合(3个字段,2地雷)。

因此,对于c中没有地雷,a中没有地雷,b中没有地雷,总共有一个地雷,并且\ $ 2 * 2460 * 1 * 3 = 14760 \ $组合。由于目前在x字段周围未发现地雷,因此将14760添加到字段x的组合中,成为1。处理完所有邻居组和解后,将其除以整个地图的组合总数即可得出实际概率。

最后,我们得到此double[]我们领域的概率x
[0.4004549781783564, 0.2998343285980985, 0.05172285894440117, 0.0023552538852416455, 1.854530618300508E-5, 0.0, 0.0, 0.0, 0.0]

也就是说,有40%的机会成为开放领域,有29.9%的机会为1,5.1%为2,等等...

速度反射

在概率组上有一个迭代,一个用于解,一个用于邻近的FieldGroup,然后一个递归循环用于特定分配。因此,类似于\ $ O(probabilityGroups * solutions * K ^ {neighboringGroups})\ $此外,还使用二项式系数和超几何分布对实际组合进行了一些计算。

这导致当拥有许多解决方案,许多领域组和/或许多概率组时,就会出现巨大的性能问题,例如在“超级死亡委员会”中,导致此类委员会实际上无法解决,因为解决它们将花费太多时间。幸运的是,这种情况很少发生。当机器人使用此算法玩游戏时,该机器人会简化进行地雷挖掘所需的分析。在使用此算法的其他情况下,它通常用于分析玩家比较聪明的游戏,因此游戏很少变得过于复杂。而且...该算法比我所知道的所有其他算法/方法要快。

典型值:


字段组通常介于10- 50,在极少数情况下可达100。
解决方案组通常在2至50之间,在某些游戏中,如果两个坏玩家在互相比赛,则很容易达到1500(甚至更高)。 />概率组通常约为50,在某些情况下最高为100。

当这些值太大时,此算法会超时。板(至少目前可能会支持更大的板),“字段组”和“概率组”的绝对最大值为256。对于解决方案组,没有已知的最大值,因为具有大量解决方案组的游戏花费的时间太长即使使用“简单”分析也可以进行分析(仅适用于挖掘概率)。

类摘要(7个文件)


DetailAnalyze:入口点,其中包含一种静态方法执行分析
DetailedResults:用于访问结果的接口
DetailedResultsImpl:上述接口的实现
FieldProxy:单个字段的详细概率的容器
NeighborFind:执行邻居检查并确定当前已知值的接口在某个字段周围进行挖掘
知识点:FieldProxy类的公共接口
ProxyProvider:创建结果以访问其他字段的数据时使用的接口

依赖项

分析结果,组合对象,FieldGroup,GroupValues,RuntimeTimeoutException,我的Minesweeper Analyze项目其他部分的解决方案。在我的Minesweeper Analyze github存储库中找到

首先,修改后的版本GroupValues

public class GroupValues<T> extends HashMap<FieldGroup<T>, Integer> {
    private static final long   serialVersionUID    = -107328884258597555L;
    private int bufferedHash = 0;

    public GroupValues(GroupValues<T> values) {
        super(values);
    }

    public GroupValues() {
        super();
    }

    @Override
    public int hashCode() {
        if (bufferedHash != 0) {
            return this.bufferedHash;
        }

        int result = super.hashCode();
        this.bufferedHash = result;
        return result;
    }

    public int calculateHash() {
        this.bufferedHash = 0;
        return this.hashCode();
    }

}


然后,其余代码: />
DetailAnalyze.java:(62行,2139字节)

/**
 * Creator of {@link DetailedResults} given an {@link AnalyzeResult} and a {@link NeighborFind} strategy
 * 
 * @author Simon Forsberg
 */
public class DetailAnalyze {
    public static <T> DetailedResults<T> solveDetailed(AnalyzeResult<T> analyze, NeighborFind<T> neighborStrategy) {
        // Initialize FieldProxies
        final Map<T, FieldProxy<T>> proxies = new HashMap<T, FieldProxy<T>>();
        for (FieldGroup<T> group : analyze.getGroups()) {
            for (T field : group) {
                FieldProxy<T> proxy = new FieldProxy<T>(group, field);
                proxies.put(field, proxy);
            }
        }

        // Setup proxy provider
        ProxyProvider<T> provider = new ProxyProvider<T>() {
            @Override
            public FieldProxy<T> getProxyFor(T field) {
                return proxies.get(field);
            }
        };

        // Setup neighbors for proxies
        for (FieldProxy<T> fieldProxy : proxies.values()) {
            fieldProxy.fixNeighbors(neighborStrategy, provider);
        }

        double totalCombinations = analyze.getTotal();
        Map<GroupValues<T>, FieldProxy<T>> bufferedValues = new HashMap<GroupValues<T>, FieldProxy<T>>();
        for (FieldProxy<T> proxy : proxies.values()) {
            // Check if it is possible to re-use a previous value
            FieldProxy<T> bufferedValue = bufferedValues.get(proxy.getNeighbors());
            if (bufferedValue != null && bufferedValue.getFieldGroup() == proxy.getFieldGroup()) {
                proxy.copyFromOther(bufferedValue, totalCombinations);
                continue;
            }

            // Setup the probabilities for this field proxy
            for (Solution<T> solution : analyze.getSolutionIteration()) {
                proxy.addSolution(solution);
            }
            proxy.finalCalculation(totalCombinations);
            bufferedValues.put(proxy.getNeighbors(), proxy);
        }

        int proxyCount = bufferedValues.size();

        return new DetailedResultsImpl<T>(analyze, proxies, proxyCount);
    }
}


DetailedResults.ja va:(46行,1162字节)

/**
 * Interface for retreiving more detailed probabilities, for example 'What is the probability for a 4 on field x?'
 * 
 * @author Simon Forsberg
 *
 * @param <T> The field type
 */
public interface DetailedResults<T> {

    Collection<ProbabilityKnowledge<T>> getProxies();

    /**
     * Get the number of unique proxies that was required for the calculation. As some can be re-used, this will always be lesser than or equal to <code>getProxyMap().size()</code>
     * 
     * @return The number of unique proxies
     */
    int getProxyCount();

    /**
     * Get a specific proxy for a field
     * 
     * @param field
     * @return
     */
    ProbabilityKnowledge<T> getProxyFor(T field);

    /**
     * Get the underlying analyze that these detailed results was based on
     * 
     * @return {@link AnalyzeResult} object that is the source of this analyze
     */
    AnalyzeResult<T> getAnalyze();

    /**
     * @return The map of all probability datas
     */
    Map<T, ProbabilityKnowledge<T>> getProxyMap();

}


DetailedResultsImpl.java :( 46行,1211字节)

public class DetailedResultsImpl<T> implements DetailedResults<T> {

    private final AnalyzeResult<T> analyze;
    private final Map<T, ProbabilityKnowledge<T>> proxies;
    private final int proxyCount;

    public DetailedResultsImpl(AnalyzeResult<T> analyze, Map<T, FieldProxy<T>> proxies, int proxyCount) {
        this.analyze = analyze;
        this.proxies = Collections.unmodifiableMap(new HashMap<T, ProbabilityKnowledge<T>>(proxies));
        this.proxyCount = proxyCount;
    }

    @Override
    public Collection<ProbabilityKnowledge<T>> getProxies() {
        return Collections.unmodifiableCollection(proxies.values());
    }

    @Override
    public int getProxyCount() {
        return proxyCount;
    }

    @Override
    public ProbabilityKnowledge<T> getProxyFor(T field) {
        return proxies.get(field);
    }

    @Override
    public AnalyzeResult<T> getAnalyze() {
        return analyze;
    }

    @Override
    public Map<T, ProbabilityKnowledge<T>> getProxyMap() {
        return Collections.unmodifiableMap(proxies);
    }
}


FieldProxy.java:(182行,5711字节)

public class FieldProxy<T> implements ProbabilityKnowledge<T> {

    private static int minK(int N, int K, int n) {
        // If all fields in group are neighbors to this field then all mines must be neighbors to this field as well
        return (N == K) ? n : 0;
    }

    private double[] detailedCombinations;
    private double[] detailedProbabilities;
    private final T field;
    private int found;
    private final FieldGroup<T> group;
    private final GroupValues<T> neighbors;

    public FieldProxy(FieldGroup<T> group, T field) {
        this.field = field;
        this.neighbors = new GroupValues<T>();
        this.group = group;
        this.found = 0;
    }

    void addSolution(Solution<T> solution) {
        recursiveRemove(solution.copyWithoutNCRData(), 1, 0);
    }

    /**
     * This field has the same values as another field, copy the values.
     * 
     * @param copyFrom {@link FieldProxy} to copy from
     * @param analyzeTotal Total number of combinations
     */
    void copyFromOther(FieldProxy<T> copyFrom, double analyzeTotal) {
        for (int i = 0; i < this.detailedCombinations.length - this.found; i++) {
            if (copyFrom.detailedCombinations.length <= i + copyFrom.found) {
                break;
            }
            this.detailedCombinations[i + this.found] = copyFrom.detailedCombinations[i + copyFrom.found];
        }

        this.finalCalculation(analyzeTotal);
    }

    /**
     * Calculate final probabilities from the combinations information
     * 
     * @param analyzeTotal Total number of combinations
     */
    void finalCalculation(double analyzeTotal) {
        this.detailedProbabilities = new double[this.detailedCombinations.length];
        for (int i = 0; i < this.detailedProbabilities.length; i++) {
            this.detailedProbabilities[i] = this.detailedCombinations[i] / analyzeTotal;
        }
    }

    /**
     * Setup the neighbors for this field
     * 
     * @param neighborStrategy {@link NeighborFind} strategy
     * @param proxyProvider Interface to get the related proxies
     */
    void fixNeighbors(NeighborFind<T> neighborStrategy, ProxyProvider<T> proxyProvider) {
        Collection<T> realNeighbors = neighborStrategy.getNeighborsFor(field);
        this.detailedCombinations = new double[realNeighbors.size() + 1];
        for (T neighbor : realNeighbors) {
            if (neighborStrategy.isFoundAndisMine(neighbor)) {
                this.found++;
                continue; // A found mine is not, and should not be, in a fieldproxy
            }

            FieldProxy<T> proxy = proxyProvider.getProxyFor(neighbor);
            if (proxy == null) {
                continue;
            }

            FieldGroup<T> neighborGroup = proxy.group;
            if (neighborGroup != null) {
                // Ignore zero-probability neighborGroups
                if (neighborGroup.getProbability() == 0) {
                    continue;
                }

                // Increase the number of neighbors
                Integer currentNeighborAmount = neighbors.get(neighborGroup);
                if (currentNeighborAmount == null) {
                    neighbors.put(neighborGroup, 1);
                }
                else neighbors.put(neighborGroup, currentNeighborAmount + 1);
            }
        }
    }

    @Override
    public T getField() {
        return this.field;
    }

    @Override
    public FieldGroup<T> getFieldGroup() {
        return this.group;
    }

    @Override
    public int getFound() {
        return this.found;
    }

    @Override
    public double getMineProbability() {
        return this.group.getProbability();
    }

    @Override
    public GroupValues<T> getNeighbors() {
        return this.neighbors;
    }

    @Override
    public double[] getProbabilities() {
        return this.detailedProbabilities;
    }

    private void recursiveRemove(Solution<T> solution, double combinations, int mines) {
        if (Thread.interrupted()) {
            throw new RuntimeTimeoutException();
        }

        // Check if there are more field groups with values
        GroupValues<T> remaining = solution.getSetGroupValues();
        if (remaining.isEmpty()) {
            this.detailedCombinations[mines + this.found] += combinations;
            return;
        }

        // Get the first assignment
        Entry<FieldGroup<T>, Integer> fieldGroupAssignment = remaining.entrySet().iterator().next();
        FieldGroup<T> group = fieldGroupAssignment.getKey();
        remaining.remove(group);
        solution = Solution.createSolution(remaining);

        // Setup values for the hypergeometric distribution calculation. See http://en.wikipedia.org/wiki/Hypergeometric_distribution
        int N = group.size();
        int n = fieldGroupAssignment.getValue();
        Integer K = this.neighbors.get(group);
        if (this.group == group) {
            N--; // Always exclude self becuase you can't be neighbor to yourself
        }

        if (K == null) {
            // This field does not have any neighbors to that group.
            recursiveRemove(solution, combinations * Combinatorics.nCr(N, n), mines);
            return;
        }

        // Calculate the values and then calculate for the next group
        int maxLoop = Math.min(K, n);
        for (int k = minK(N, K, n); k <= maxLoop; k++) {
            double thisCombinations = Combinatorics.NNKK(N, n, K, k);
            recursiveRemove(solution, combinations * thisCombinations, mines + k);
        }
    }

    @Override
    public String toString() {
        return "Proxy(" + this.field.toString() + ")"
                + "\n neighbors: " + this.neighbors.toString()
                + "\n group: " + this.group.toString()
                + "\n Mine prob " + this.group.getProbability() + " Numbers: " + Arrays.toString(this.detailedProbabilities);
    }
}


NeighborFind.java:(30行,718字节)

/**
 * Interface strategy for performing a {@link DetailAnalyze}
 * 
 * @author Simon Forsberg
 *
 * @param <T> The field type
 */
public interface NeighborFind<T> {
    /**
     * Retrieve the neighbors for a specific field.
     * 
     * @param field Field to retrieve the neighbors for
     * 
     * @return A {@link Collection} of the neighbors that the specified field has
     */
    Collection<T> getNeighborsFor(T field);

    /**
     * Determine if a field is a found mine or not
     * 
     * @param field Field to check
     * 
     * @return True if the field is a found mine, false otherwise
     */
    boolean isFoundAndisMine(T field);
}


ProbabilityKnowledge.java:(39行,1031字节)

public interface ProbabilityKnowledge<T> {

    /**
     * @return The field that this object has stored the probabilities for
     */
    T getField();

    /**
     * @return The {@link FieldGroup} for the field returned by {@link #getField()}
     */
    FieldGroup<T> getFieldGroup();

    /**
     * @return How many mines has already been found for this field
     */
    int getFound();

    /**
     * @return The mine probability for the {@link FieldGroup} returned by {@link #getFieldGroup()}
     */
    double getMineProbability();

    /**
     * @return {@link GroupValues} object for what neighbors the field returned by {@link #getField()} has
     */
    GroupValues<T> getNeighbors();

    /**
     * @return The array of the probabilities for what number this field has. The sum of this array + the value of {@link #getMineProbability()} will be 1.
     */
    double[] getProbabilities();

}


ProxyProvider.java:(7行,132字节)

public interface ProxyProvider<T> {

    FieldProxy<T> getProxyFor(T field);

}


用法/测试

在Github上可用:https://github.com/Zomis/Minesweeper-Analyze/blob/master/src /test/java/net/zomis/minesweeper/analyze/DetailAnalyzeTest.java

要查看实际分析的结果,请执行以下步骤:


转到我的扫雷标志统计页面上的一个随机游戏

转到该游戏中的随机情况

单击地图
单击底部的分析链接
,然后点击地图上的随机字段。

弹出元素将显示该字段的详细概率。这个存在吗?有没有现有的库?


关于此代码和/或此方法的任何一般性评论都欢迎
可以使其更快吗? (除了一些优化之外,我本人对此表示怀疑,但如果可以对其进行重大改进,我将非常喜欢它)。

评论

我知道这是一个古老的问题,但是“ stats.minesweeperflags.net”的链接已断开。是否有另一个链接可以遵循这些步骤?

@dustytrash不幸的是暂时没有,但希望它将在明年的某个时候回来。

#1 楼

专注于优化:GroupValues中的



@Override
public int hashCode() {
    if (bufferedHash != 0) {
        return this.bufferedHash;
    }

    int result = super.hashCode();
    this.bufferedHash = result;
    return result;
}




@Override
public int hashCode() {
    if (bufferedHash == 0) {
        this.bufferedHash = super.hashCode();
    }

    return this.bufferedHash;
}


1个本地商店已删除。单个出口点可能会有所帮助。


DetailAnalyse中:

Map<GroupValues<T>, FieldProxy<T>> bufferedValues = new HashMap<GroupValues<T>, FieldProxy<T>>();


bufferedValues可能是final


FieldProxy中:

for (int i = 0; i < this.detailedCombinations.length - this.found; i++) {
    if (copyFrom.detailedCombinations.length <= i + copyFrom.found) {
        break;


简体

for (int i = 0; i < a - b; i++) {
    if (a2 <= i + b2) {
        break;


我们可以将iMax分配为Math.min(a - b, a2 - b2)

这样可以节省对常量值的不断重新评估:

final int iMax = Math.min(this.detailedCombinations.length - this.found, copyFrom.detailedCombinations.length - copyFrom.found);
for (int i = 0; i < iMax; i++) {


它最可能更快(一个人永远不知道微优化是什么,查看代码,在这种情况下进行分析会更好。)

新代码段:

final int iMax = Math.min(this.detailedCombinations.length - this.found, copyFrom.detailedCombinations.length - copyFrom.found);
for (int i = 0; i < iMax; i++) {
    this.detailedCombinations[i + this.found] = copyFrom.detailedCombinations[i + copyFrom.found];
}


我建议运行测试并替换System.arrayCopy整个for循环。

System.arrayCopy(copyFrom.detailedCombinations, copyFrom.found, this.detailedCombinations, this.found, Math.min(this.detailedCombinations.length - this.found, copyFrom.detailedCombinations.length - copyFrom.found));


也许更快,因为它可以一次性完成整个程序块。也许速度较慢,因为整个本机复制一些项目会增加很多开销。重新进行分析并找出。


再次,FieldProxy

private void recursiveRemove(Solution<T> solution, double combinations, int mines) {
    if (Thread.interrupted()) {
        throw new RuntimeTimeoutException();
    }

    // Check if there are more field groups with values
    GroupValues<T> remaining = solution.getSetGroupValues();
    if (remaining.isEmpty()) {
        this.detailedCombinations[mines + this.found] += combinations;
        return;
    }

    // Get the first assignment
    Entry<FieldGroup<T>, Integer> fieldGroupAssignment = remaining.entrySet().iterator().next();
    FieldGroup<T> group = fieldGroupAssignment.getKey();
    remaining.remove(group);
    solution = Solution.createSolution(remaining);

    // Setup values for the hypergeometric distribution calculation. See http://en.wikipedia.org/wiki/Hypergeometric_distribution
    int N = group.size();
    int n = fieldGroupAssignment.getValue();
    Integer K = this.neighbors.get(group);
    if (this.group == group) {
        N--; // Always exclude self becuase you can't be neighbor to yourself
    }

    if (K == null) {
        // This field does not have any neighbors to that group.
        recursiveRemove(solution, combinations * Combinatorics.nCr(N, n), mines);
        return;
    }

    // Calculate the values and then calculate for the next group
    int maxLoop = Math.min(K, n);
    for (int k = minK(N, K, n); k <= maxLoop; k++) {
        double thisCombinations = Combinatorics.NNKK(N, n, K, k);
        recursiveRemove(solution, combinations * thisCombinations, mines + k);
    }
}


CTRL + F solution
<
1. private void recursiveRemove(Solution<T> solution, double combinations, int mines) {
2. GroupValues<T> remaining = solution.getSetGroupValues();
3. solution = Solution.createSolution(remaining);
4. recursiveRemove(solution, combinations * Combinatorics.nCr(N, n), mines);
5. recursiveRemove(solution, combinations * thisCombinations, mines + k);


Solution仅用于函数头中,用于获取组值并将其传递回函数中。它还构造一次...

这会改变组值吗?

br />
,然后

public static <T> Solution<T> createSolution(GroupValues<T> values) {
    return new Solution<T>(values).nCrPerform();
}


,它调用

private Solution(GroupValues<T> values) {
    this.setGroupValues = values;
}


,链的末端。

因此不会更改组值。

以下更改将因此提高性能:

替换旧版本的带有新功能的功能

private Solution<T> nCrPerform() {
    double result = 1;
    for (Entry<FieldGroup<T>, Integer> ee : this.setGroupValues.entrySet()) {
        result = result * Combinatorics.nCr(ee.getKey().size(), ee.getValue());
    }
    this.nCrValue = result;
    return this;
}




public static double nCr(int n, int r) {
    if (r > n || r < 0) {
        return 0;
    }
    if (r == 0 || r == n) {
        return 1;
    }

    double value = 1;

    for (int i = 0; i < r; i++) {
        value = value * (n - i) / (r - i);
    }

    return value;
}


这将大大提高性能。我不知道它是否仍然超时?


另外,由于最近造成混淆,我建议您将Solution.getSetGroupValues()重命名为Solution.getCopyOfGroupValues()或类似名称。这是导致我的答案的较早版本出现错误的原因。

评论


\ $ \ begingroup \ $
通常,本地调用通常比非本地调用要快,因为它们不通过JIT运行。也许有足够的运行可以启动热点优化,但是通常由于转换不准确,本机代码比Java字节码更快。
\ $ \ endgroup \ $
–Vogel612♦
2014年12月1日下午13:46

#2 楼

@Pimgd在答案的最后部分肯定是重要的,但还不够。仅仅将Solution<T>更改为GroupValues<T>并没有多大帮助。并循环访问列表而无需进行任何修改。方法是这样的:

private void recursiveRemove(List<Entry<FieldGroup<T>, Integer>> solution, double combinations, int mines, int listIndex) {
    if (Thread.interrupted()) {
        throw new RuntimeTimeoutException();
    }

    // Check if there are more field groups with values
    if (listIndex >= solution.size()) {
        // TODO: or if combinations equals zero ?
        this.detailedCombinations[mines + this.found] += combinations;
        return;
    }

    // Get the assignment
    Entry<FieldGroup<T>, Integer> fieldGroupAssignment = solution.get(listIndex);
    FieldGroup<T> group = fieldGroupAssignment.getKey();

    // Setup values for the hypergeometric distribution calculation. See http://en.wikipedia.org/wiki/Hypergeometric_distribution
    int N = group.size();
    int n = fieldGroupAssignment.getValue();
    Integer K = this.neighbors.get(group);
    if (this.group == group) {
        N--; // Always exclude self becuase you can't be neighbor to yourself
    }

    if (K == null) {
        // This field does not have any neighbors to that group.
        recursiveRemove(solution, combinations * Combinatorics.nCr(N, n), mines, listIndex + 1);
        return;
    }

    // Calculate the values and then calculate for the next group
    int maxLoop = Math.min(K, n);
    for (int k = minK(N, K, n); k <= maxLoop; k++) {
        double thisCombinations = Combinatorics.NNKK(N, n, K, k);
        recursiveRemove(solution, combinations * thisCombinations, mines + k, listIndex + 1);
    }
}


并这样调用:

recursiveRemove(new ArrayList<Entry<FieldGroup<T>, Integer>>(solution.copyWithoutNCRData().getSetGroupValues().entrySet()), 1, 0, 0);


迭代而不是连续复制和修改。

这意味着为什么在地球上我从来没有想到过?

这意味着在“超级死亡委员会”上进行如此大的分析现在只需大约3.5分钟而不是35分钟!!

#3 楼

对于您的用例(一种玩扫雷的AI),使用近似的快速算法而不是精确但较慢的算法可能更合适。根据您先前的问题,我了解您对如何枚举所有具有确切概率的解决方案有完全的了解:您只需在每个FieldGroup上随机分布地雷。因此,您可以编写一种算法来提取所有可能解中具有相等概率的随机解。

如果这样做,那么蒙特卡洛方法很简单:随机提取N个解,并计算出您的随机变量的结果值(即,在一个领域中相邻地雷的数量),并用比率:positive_cases / total_cases。

这样做的好处是,您可以使用相同的随机样本同时评估所有排名。您还可以决定在计算上花费多少时间:花费的时间越多,近似结果越好。

评论


\ $ \ begingroup \ $
我感谢您的建议,当可能发生超时(由于大量的解决方案/字段组/概率组)时,可以采用这种方法。但是在9999例10000中,这种方法足够有效。
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014年9月9日14:09

\ $ \ begingroup \ $
由于我现在已经设法将速度提高了九倍,所以我不太可能使用蒙特卡洛方法。
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014年12月15日12:31