生成所有可能的覆盖选项

时间:2017-05-16 21:11:36

标签: c# arrays list linq

假设我有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

2 个答案:

答案 0 :(得分:3)

这是我扔在一起的东西,可能会或可能不会起作用。借用dtb this answer上的WebView作品,大量借鉴。

基本上,我生成所有内容,然后删除那些不符合要求的内容。

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;

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

答案 1 :(得分:2)

因此,让我们使用一些辅助类将数字转换为不同基数的IEnumerable<int>枚举。使用List<>可能更有效,但因为我们正在尝试使用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;
}

现在我们可以创建一个列出所有可能答案(以及一些额外内容)的枚举。我将List转换为Array s以获得索引效率。

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);