【问题标题】:Generate all possible coverage options生成所有可能的覆盖选项
【发布时间】:2017-05-16 21:11:36
【问题描述】:

假设我有 2 个列表:一个包含字符串,一个包含整数,它们的长度不同。我正在构建的应用程序将使用这些列表来生成车辆和覆盖区域的组合。字符串表示区域名称,整数表示车辆 ID。

我的目标是生成用于进一步调查的所有可能的独特组合的列表。一辆车可以服务多个区域,但一个区域不能由多辆车服务。每个区域都必须接受服务,每辆车都必须使用。

所以总结一下约束:

  • 每个区域只使用一次
  • 每辆车至少使用一次
  • 不能遗漏任何区域。
  • 不能遗漏任何车辆

这是一个例子:

public class record = {
    public string areaId string{get;set;}
    public int vehicleId int {get;set;}
}

List<string> areas = new List<string>{ "A","B","C","D"};
List<int> vehicles = new List<int>{ 1,2};

List<List<record>> uniqueCombinationLists = retrieveUniqueCombinations(areas,vehicles);

我只是不知道如何制作retrieveUniqueCombinations 函数。也许我只是看起来错了或想得太难了。我一直在思考大规模循环和其他蛮力方法。一个更好的方法的解释将不胜感激。

结果应该类似于这样,我认为这包含了此示例的所有可能性。

A1;B1;C1;D2
A1;B1;C2;D1
A1;B2;C1;D1
A2;B1;C1;D1
A2;B2;C2;D1
A2;B2;C1;D2
A2;B1;C2;D2
A1;B2;C2;D2
A2;B1;C1;D2
A1;B2;C2;D1
A2;B2;C1;D1
A1;B1;C2;D2
A2;B1;C2;D1
A1;B2;C1;D2

【问题讨论】:

  • 你能提供一个示例输出吗
  • 不,反之亦然。一辆车可以服务多个区域,一个区域不能由多辆车服务。
  • 检查我的编辑。我想我最好把你的问题表述清楚,但如果我搞砸了任何细节,请edit你的问题。
  • 你的例子有点混乱。为什么没有A1 B1 C1 D1
  • 这看起来很像stackoverflow.com/q/10519619/215552,除了约束。确实,您的图块有点误导,因为它都是可能的独特组合;这是最可能的独特组合...

标签: c# arrays list linq


【解决方案1】:

这是我总结的一些东西,可能有用也可能没用。大量借鉴dtbthis answer 上的工作。

基本上都是我自己生成,然后把不符合要求的去掉。

List<string> areas = new List<string> { "A", "B", "C", "D" };
List<int> vehicles = new List<int> { 1, 2 };

var result = retrieveUniqueCombinations(areas, vehicles);
result.ToList().ForEach((recordList) => { 
    recordList.ToList().ForEach((record) => 
        Console.Write("{0}{1};", record.areaId, record.vehicleId)); 
    Console.WriteLine(); 
});

public IEnumerable<IEnumerable<record>> retrieveUniqueCombinations(IEnumerable<string> areas, IEnumerable<int> vehicles)
{
    var items = from a in areas
                from v in vehicles
                select new record { areaId = a, vehicleId = v };
    var result = items.GroupBy(i => i.areaId).CartesianProduct().ToList();
    result.RemoveAll((records) => 
        records.All(record => 
            record.vehicleId == records.First().vehicleId));
    return result;
}

public class record
{
    public string areaId { get; set; }
    public int vehicleId { get; set; }
}


static class Extensions
{
    public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(
      this IEnumerable<IEnumerable<T>> sequences)
    {
        IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() };
        return sequences.Aggregate(
          emptyProduct,
          (accumulator, sequence) =>
            from accseq in accumulator
            from item in sequence
            select accseq.Concat(new[] { item }));
    }
}

这会产生以下内容:

A1;B1;C1;D2;
A1;B1;C2;D1;
A1;B1;C2;D2;
A1;B2;C1;D1;
A1;B2;C1;D2;
A1;B2;C2;D1;
A1;B2;C2;D2;
A2;B1;C1;D1;
A2;B1;C1;D2;
A2;B1;C2;D1;
A2;B1;C2;D2;
A2;B2;C1;D1;
A2;B2;C1;D2;
A2;B2;C2;D1;

请注意,这些顺序与您的顺序不同,但我会将验证留给您。此外,可能还有更好的方法来做到这一点(例如,通过将逻辑放在CartesianProduct 函数中的RemoveAll 步骤中),但是嘿,你得到你所支付的;)。

【讨论】:

  • 甜蜜!非常好的解决方案,但这肯定会遇到OutOfMemoryException,比如 20 个区域和 5 辆车。任何解决方法的想法?
  • @mohammedlok 不是。这更像是“朝着正确的方向前进”的答案,而不是“这适用于所有事情!”种答案。我认为OP将能够从这里拿走它。他在 cmets 中提到,他稍后会解决并行性和内存消耗等问题。
  • 不错的答案。我认为您可以像这样更轻松地创建itemsitems = from a in areas select from v in vehicles select new { a, v }
【解决方案2】:

因此,让我们使用一些辅助类将数字转换为不同基数的IEnumerable&lt;int&gt; 枚举。使用List&lt;&gt; 可能更有效,但由于我们正在尝试使用 LINQ:

public static IEnumerable<int> LeadingZeros(this IEnumerable<int> digits, int minLength) {
    var dc = digits.Count();
    if (dc < minLength) {
        for (int j1 = 0; j1 < minLength - dc; ++j1)
            yield return 0;
    }
    foreach (var j2 in digits)
        yield return j2;
}

public static IEnumerable<int> ToBase(this int num, int numBase) {
    IEnumerable<int> ToBaseRev(int n, int nb) {
        do {
            yield return n % nb;
            n /= nb;
        } while (n > 0);
    }

    foreach (var n in ToBaseRev(num, numBase).Reverse())
        yield return n;
}

现在我们可以创建一个枚举,列出所有可能的答案(以及一些额外的答案)。我将Lists 转换为Arrays 以​​提高索引效率。

var areas = new List<string> { "A", "B", "C", "D" };
var vehicles = new List<int> { 1, 2 };

var areasArray = areas.ToArray();
var vehiclesArray = vehicles.ToArray();
var numVehicles = vehiclesArray.Length;
var numAreas = areasArray.Length;
var NumberOfCombos = Convert.ToInt32(Math.Pow(numVehicles, numAreas));
var ansMap = Enumerable.Range(0, NumberOfCombos).Select(n => new { n, nd = n.ToBase(numVehicles).LeadingZeros(numAreas)});

鉴于可能的组合的枚举,我们可以转换为areasvehicles,并排除不使用所有vehicles的组合。

var ans = ansMap.Select(nnd => nnd.nd).Select(m => m.Select((d, i) => new { a = areasArray[i], v = vehiclesArray[d] })).Where(avc => avc.Select(av => av.v).Distinct().Count() == numVehicles);

【讨论】:

  • 这是一个非常完整且超快速的解决方案。非常感谢。我有很多东西要学,我会尝试剖析你的代码。
  • 我比我更喜欢这个:)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-31
相关资源
最近更新 更多