笛卡尔积的任意数量的物体

时间:2018-04-10 16:50:47

标签: c#

我希望在c#中获得任意数量对象的笛卡尔积。我的情况有点不寻常 - 我的输入不是基类型列表,而是具有属性列表的对象。

我的输入和输出对象如下:

public class Input
{
    public string Label;
    public List<int> Ids;
}

public class Result
{
    public string Label;
    public int Id;
}

一些示例输入数据:

var inputs = new List<Input>
{
    new Input { Label = "List1", Ids = new List<int>{ 1, 2 } },
    new Input { Label = "List2", Ids = new List<int>{ 2, 3 } },
    new Input { Label = "List3", Ids = new List<int>{ 4 } }
};

我期望的输出对象:

var expectedResult = new List<List<Result>>
{
    new List<Result>
    {
        new Result{Label = "List1", Id = 1},
        new Result{Label = "List2", Id = 2},
        new Result{Label = "List3", Id = 4}
    },
    new List<Result>
    {
        new Result{Label = "List1", Id = 1},
        new Result{Label = "List2", Id = 3},
        new Result{Label = "List3", Id = 4}
    },
    new List<Result>
    {
        new Result{Label = "List1", Id = 2},
        new Result{Label = "List2", Id = 2},
        new Result{Label = "List3", Id = 4}
    },
    new List<Result>
    {
        new Result{Label = "List1", Id = 2},
        new Result{Label = "List2", Id = 3},
        new Result{Label = "List3", Id = 4}
    }
};

如果我知道&#39;输入中的项目数量&#39;提前我可以这样做:

var knownInputResult = 
    from id1 in inputs[0].Ids
    from id2 in inputs[1].Ids
    from id3 in inputs[2].Ids
    select
    new List<Result>
    {
        new Result { Id = id1, Label = inputs[0].Label },
        new Result { Id = id2, Label = inputs[1].Label },
        new Result { Id = id3, Label = inputs[2].Label },
    };

我正在努力使其适应任意数量的输入 - 有可能这样做吗?

2 个答案:

答案 0 :(得分:1)

我认为这个问题的副本在评论中被链接,但由于它被重新打开并且你很难将这个问题适应你的情况,所以这是如何。

首先抓住Eric Lippert从重复问题中获取的功能(如何解释):

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

然后压平你的输入。基本上只需将相应的标签附加到每个id:

var flatten = inputs.Select(c => c.Ids.Select(r => new Result {Label = c.Label, Id = r}));

然后运行笛卡尔积并完成:

// your expected result
var result = flatten.CartesianProduct().Select(r => r.ToList()).ToList();            

答案 1 :(得分:0)

我并不为自己花费大量时间而感到骄傲,但它确实有效。 它基本上是黑魔法,我会在你获得的第一次机会取代它。

public static List<List<Result>> Permutate(IEnumerable<Input> inputs)
{
    List<List<Result>> results = new List<List<Result>>();

    var size = inputs.Select(inp => factorial_WhileLoop(inp.Ids.Count)).Aggregate((item, carry) => item + carry) - 1;

    for (int i = 0; i < size; i++) results.Add(new List<Result>());

    foreach (var input in inputs)
    {
        for (int j = 0; j < input.Ids.Count; j++)
        {
            for (int i = 0; i < (size / input.Ids.Count); i++)
            {
                var x = new Result() { Label = input.Label, Id = input.Ids[j] };

                results[(input.Ids.Count * i) + j].Add(x);
            }
        }
    }

    return results;
}

public static int factorial_WhileLoop(int number)
{
    var result = 1;
    while (number != 1)
    {
        result = result * number;
        number = number - 1;
    }
    return result;
}