【问题标题】:Retrieve all possible combinations of dictionary recursively递归检索字典的所有可能组合
【发布时间】:2015-09-10 17:19:32
【问题描述】:

我看到了很多类似的问题,但是在这里没有找到我真正需要的东西。任何帮助表示赞赏。

我有一组键 (K[1..M]),每个键 K[i] 可以映射到此特定键 K[i]:V[i,1 的一组可用值中的任何值..Ni]

K[1]: V[1,1] | V[1,2] ... | V[1,N1]
K[2]: V[2,1] | V[2,2] ... | V[1,N2]
...
K[M]: V[M,1] | V[M,2] ... | V[1,NM]

我需要实现 RECURSIVE 函数,返回 K-V 映射的所有可能组合的可枚举

例如: 给定集合:

K1: 1 | 2 | 3
K2: 4 | 1

组合如下:

(K1:1, K2:4)
(K1:2, K2:4)
(K1:3, K2:4)
(K1:1, K2:1)
(K1:2, K2:1)
(K1:3, K2:1)

理想的函数应该是这样的:

IEnumerable<Dictionary<TKey, TValue>> EnumerateAllPossibleCombinations(IEnumerable<TKey> keys, Func<TKey, IEnumerable<TValue>> getAvailableValuesForKey)
{
    ...
    yield return ...;
}

函数的使用(代码草案):

var allCombinations = EnumerateAllPossibleCombinations<string, int>(new[]{"K1","K2"}, k=>{
   switch k
   {
      case "K1": return new[]{1,2,3};
      case "K2": return new[]{4,1};
   }
   ThrowException("Unknown key");
});

例如上面的结果应该是 6 个字典,每个字典有 2 个键值对

我试图避免使用笛卡尔积,因为我需要在字典之后接收字典(allCombinations.ElementAt(1)、allCombinations.ElementAt(2)),而笛卡尔必须完全执行所有组合才能执行返回第一个字典。

【问题讨论】:

  • 你需要 Func 做什么?这不是您的主要功能中的另一个循环吗?还是您打算传入谓词?
  • 因此对于您的示例,您希望它返回 6 个字典,每个字典有两个条目?
  • 我根据您的 cmets 更新了问题文本并添加了更多说明。
  • 为什么人们不赞成这个问题?有什么问题??

标签: c# algorithm combinations combinatorics


【解决方案1】:

我相信这就是你要找的。​​p>

public static IEnumerable<IDictionary<TKey, TSource>> EnumerateAllPossibleCombinations<TKey, TSource>(
    IEnumerable<TKey> keys,
    Func<TKey, IEnumerable<TSource>> getAvailableValuesForKey)
{
    if (keys == null)
    {
        throw new ArgumentNullException("keys");
    }

    if (getAvailableValuesForKey == null)
    {
        throw new ArgumentNullException("getAvailableValuesForKey");
    }

    return keys.Any() ? 
           EnumerateAllPossibleCombinationsImp(keys.Distinct(), getAvailableValuesForKey) :
           Enumerable.Empty<IDictionary<TKey, TSource>>();
}

private static IEnumerable<IDictionary<TKey, TSource>> EnumerateAllPossibleCombinationsImp<TKey, TSource>(
    IEnumerable<TKey> keys,
    Func<TKey, IEnumerable<TSource>> getAvailableValuesForKey)
{
    if (!keys.Any())
    {
        yield return new Dictionary<TKey, TSource>();
        yield break;
    }

    var firstKey = keys.First();
    var values = getAvailableValuesForKey(firstKey) ?? Enumerable.Empty<TSource>();
    bool hasValues = values.Any();

    foreach (var value in values.DefaultIfEmpty())
    {
        foreach (var dictionary in EnumerateAllPossibleCombinationsImp(keys.Skip(1), getAvailableValuesForKey))
        {
            if (hasValues)
            {
                dictionary.Add(firstKey, value);
            }

            yield return dictionary;
        }
    }
}

首先,使用两个方法的原因是,keysgetAvailableValuesForKeyArgumentNullExceptions 将在调用方法时抛出,而不是在迭代结果 IEnumerable 时抛出。接下来我们快速检查keys 是否为空,如果是,我们只返回一个空的IEnumerable。如果它不为空,那么我们调用包含主要实现的第二个方法。

第二种方法是递归的,所以首先我们设置默认情况keys为空,在这种情况下我们产生一个空字典(这就是我们在第一种方法中进行空检查的原因)。然后我们通过从keys 获取第一项并检索该密钥的values 来分解问题。如果getAvailableValuesForKey 为该键返回null,我们将把它当作它返回一个空集来处理(如果需要,这可以被视为异常情况)。然后我们检查是否有任何values 并遍历它们,但如果没有,我们使用DefaultIfEmpty 插入一个默认值。这样做是为了我们将对一组空值进行一次迭代。然后我们通过使用Skip(1) 进行递归调用传递其余键,对于每个返回的字典,我们检查是否有任何键值,如果有,则将该键和值对添加到字典中,或者我们产生字典的方式。这个想法是,递归调用最终会遇到没有键的情况,只返回一个空字典,然后在递归调用展开时将条目添加到其中。

【讨论】:

    【解决方案2】:

    您的解决方案基本上采用(伪代码)的形式:

    foreach (var key in dictionary)
        foreach (var value in dictionary)
            yield return new KeyValuePair(key, value)
    

    如果你需要传入一个谓词来约束值,你可以这样插入它:

    foreach (var key in dictionary)
        foreach (var value in getAvailableValuesForKey)
            yield return new KeyValuePair(key, value)
    

    实际代码看起来更接近这个(未经测试):

    foreach (var x in dictionary)
        foreach (var y in dictionary)
            yield return new KeyValuePair (x.key, y.value);
    

    【讨论】:

    • 为什么不直接迭代 KeysValues 属性呢?
    • 感谢您的回答,但这本质上是笛卡尔积解决方案,我在这里试图避免。我在问题中添加了该注释。
    猜你喜欢
    • 1970-01-01
    • 2014-02-28
    • 2019-08-09
    • 2018-04-21
    • 2020-10-27
    • 1970-01-01
    • 2016-03-20
    • 2019-07-15
    • 2012-03-22
    相关资源
    最近更新 更多