【问题标题】:Generating all possible x amount of targets from target list从目标列表中生成所有可能的 x 数量的目标
【发布时间】:2015-09-07 10:26:51
【问题描述】:

我有一个卡片列表(目标),其中包含卡片游戏中卡片的所有可能目标。但是,我需要拥有 TargetCount 卡的所有可能组合来创建一个极小极大树。我应该如何在 C# 中解决这个问题?

我尝试使用网站上的 CartesianProduct 扩展,但它似乎不起作用(尝试使用时出现编译错误)。

我只是将目标存储在List<Card> 中,并且已经拥有所有可能的目标。现在,对于 X 卡的每个可能组合,我想将包含每个 X 可能目标的List<Card> 添加到我的搜索树中,如下所示。我应该如何处理?

List<GameDecision> Out = new List<GameDecision>();

foreach(Card SpellCard in PlayerSpellbook(Player))
{
    List<Card> PossibleTargets = GetPossibleTargets(SpellCard);

    foreach(all combinations of possible targets)
    {
        List<Card> Targets = new list of Card.TargetCount targets;
        Out.Add(new GameDecision() { Type = GameDecisionType.PlayCard, TheCard = SpellCard, Targets = Targets });
    }
}

【问题讨论】:

  • 你能展示一些你目前拥有的代码吗?
  • 你的数学计算出来了吗?即你能在纸上解决问题吗?就目前而言,解决这个问题的唯一方法是从头开始计算所有内容并构建代码,这不太可能发生。
  • 如果你有一个包含所有卡片的List&lt;Card&gt; allCardsList,并且每个Card 都有一个属性,比如List&lt;Card&gt; PossibleTargets { get; set; },你可以使用LINQ 来展平结构并获取所有可能的目标:allCardsList.SelectMany(card =&gt; card.PossibleTargets) .
  • 基本上,我想做以下事情:pastebin.com/MX9Ej3TH
  • 也许以一种每个人都会理解的方式来解释事情,如果我有一个所有目标的列表,那么可能有一张卡有两个或更多目标(因为卡需要 x 个目标),改变顺序导致不同的结果。例如,如果我有一张牌,上面写着“对一个目标造成 1 点伤害,对另一个目标造成 2 点伤害,对另一个目标造成 3 点伤害”,我会根据目标的顺序得到不同的结果。我不认为我以错误的方式处理这个问题 - 我正在为极小极大树构建所有可能的移动。我只是很难弄清楚如何。

标签: c#


【解决方案1】:

你的问题有点不清楚。

你说组合。组合只是每个可用选项的列表。例如,您有数组 { 1, 2, 3 },并且您希望组合长度为 2,您将获得数组:

  1. { 1, 2 }
  2. { 1, 3 }
  3. { 2, 3 }

您可能指的是排列。排列是每个顺序中的每个组合。因此,从{ 1, 2, 3 } 之前的相同数组开始,所有长度为 2 的排列是:

  1. { 1, 2 }
  2. { 1, 3 }
  3. { 2, 1 }
  4. { 2, 3 }
  5. { 3, 1 }
  6. { 3, 2 }

要获得所有排列而不考虑数组长度,您需要先找到组合并计算排列。

无论您指的是哪个选项,这里有几个有用的扩展方法。

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

public static class IEnumerableExtensions
{
    // Can be used to get all combinations at a certain level
    // Source: http://stackoverflow.com/questions/127704/algorithm-to-return-all-combinations-of-k-elements-from-n#1898744
    public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int k)
    {
        return k == 0 ? new[] { new T[0] } :
            elements.SelectMany((e, i) =>
            elements.Skip(i + 1).Combinations(k - 1).Select(c => (new[] { e }).Concat(c)));
    }

    private static IEnumerable<TSource> Prepend<TSource>(this IEnumerable<TSource> source, TSource item)
    {
        if (source == null)
            throw new ArgumentNullException("source");

        yield return item;

        foreach (var element in source)
            yield return element;
    }

    // This one came from: http://stackoverflow.com/questions/774457/combination-generator-in-linq#12012418
    public static IEnumerable<IEnumerable<TSource>> Permutations<TSource>(this IEnumerable<TSource> source)
    {
        if (source == null)
            throw new ArgumentNullException("source");

        var list = source.ToList();

        if (list.Count > 1)
            return from s in list
                   from p in Permutations(list.Take(list.IndexOf(s)).Concat(list.Skip(list.IndexOf(s) + 1)))
                   select p.Prepend(s);

        return new[] { list };
    }
}

获取排列

听起来您只想获取组合的所有排列(在您的情况下为 PossibleTargets)。

foreach (var permutation in PossibleTargets.Permutations())
{
    List<Card> Targets = new List<Card>(permutation);
    Out.Add(new GameDecision() { Type = GameDecisionType.PlayCard, TheCard = SpellCard, Targets = Targets });
}

获取组合

如果您实际上是指组合,那么您还需要提供要求解的结果数组的长度。您将其指定为X,但您的代码中没有说明X 可能是什么。要使其正常工作,X 必须小于 Permutations.Count(),否则您将始终得到由列表中每个元素组成的恰好 1 个组合。

var length = X;

foreach (var combination in PossibleTargets.Combinations(length))
{
    List<Card> Targets = new List<Card>(combination);
    Out.Add(new GameDecision() { Type = GameDecisionType.PlayCard, TheCard = SpellCard, Targets = Targets });
}

一种常见的做法是循环遍历所有可能的长度(从 Combinations.Length 到 1),以获得任意长度的所有可能组合。

for (var length = PossibleTargets.Count(); length > 0; length--)
{

    foreach (var combination in PossibleTargets.Combinations(length))
    {
        List<Card> Targets = new List<Card>(combination);
        Out.Add(new GameDecision() { Type = GameDecisionType.PlayCard, TheCard = SpellCard, Targets = Targets });
    }
}

获取每个长度的所有排列

此示例获取所有可能长度的每个排列(以任意顺序组合卡片)。

for (var length = PossibleTargets.Count(); length > 0; length--)
{
    foreach (var combination in PossibleTargets.Combinations(length))
    {
        foreach (var permutation in combination.Permutations())
        {
            List<Card> Targets = new List<Card>(permutation);
            Out.Add(new GameDecision() { Type = GameDecisionType.PlayCard, TheCard = SpellCard, Targets = Targets });
        }
    }
}

【讨论】:

  • 正是我需要的。谢谢!
【解决方案2】:

正如你所说,你想创建一个“最小-最大”,就是这样......这就是解决方案。创建一棵树。每个树节点都有一个:

  1. 卡片 - 告诉这个节点代表什么卡片;
  2. List - 存储下一个可能的目标节点;
  3. 分数 - 添加这个是因为通常min-max trees 有一个分数来保持结果直到那个节点

从根节点开始,将所有可能的起始组合放在根节点的List&lt;Card&gt;中。这个List&lt;Card&gt; 中每个节点的Card 值将告诉如果你选择这张卡可以形成什么树。这样,您将继续构建树,并且在最底层,所有叶子将代表您的纸牌游戏结束的可能方式。 这就是你应该如何解决这个问题


构造最小最大树

为了构造minmax tree,您可以采用两种方式:

  1. 递归和
  2. 迭代。

递归方式中,您可以访问每个子节点并一次构造一个子树。你探索一个分支,直到你到达叶子,然后你去探索下一个分支,直到你到达另一个叶子,依此类推。当我们在 Graph traversal 中采用这种方法时,它被称为 Depth First Traversal。

迭代的方式,你去一个孩子,只处理那个孩子,然后把这个孩子的所有孩子放在一个队列中。然后你出列并处理从这个队列中出来的下一个节点。通过这种方式,您有效地构建了所有分支,一次一个级别。 图遍历中的这种方法称为广度优先遍历。

查找这两种方法的代码非常容易。不过,自己编写代码会更好。

即使对于每个节点的子节点数量适中的小树,递归也可以下降到多个级别。因此,通常首选迭代方法。不过这两种方法都是正确的。


如果您要设计min-max tree,则需要回答更多问题和注意限制。然而,上面给出的答案本身就是完整的。在这个阶段回答额外的问题,尤其是在没有你问的情况下,会使这个答案变得不必要地复杂和令人反感。最好是您创建设计和代码,当您遇到困难时,您会回到这里再次询问。

【讨论】:

    【解决方案3】:

    这看起来主要是一个术语问题。您的问题含糊不清,但您正在寻找的似乎既不是 笛卡尔积 (将集合与自身连接时的 n^2 个元素)也不是 Combinations (n choose r)。

    相反,要填写上面的空白,您需要生成 集合的非空子集的集合。这应该会为您指明某种方向。

    我不会说正确的方向,因为这种方法似乎有缺陷。您的结果的基数将是 2^n - 1。一旦您的牌组开始变得有趣并且您从GetPossibleTargets() 获得超过 20 个左右的结果,事情就会变得毛茸茸的,而且很快。我建议查看List&lt;GameDecision&gt; Out 的用例,并找到一种更高效的方法来解决问题。

    【讨论】:

      猜你喜欢
      • 2012-12-06
      • 2011-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多