LINQ交叉联接列表列表?列表数量未知的笛卡尔乘积

问题描述 投票:0回答:3

假定我具有以下类-ItemMenu-具有列表列表。如何使用C#生成具有所有可用组合的交叉联接输出?

对于以下代码,我希望得到3(温度)乘以4(侧面)乘以4(饮料)的结果,例如:

  • 无边无饮的稀有牛排
  • 无边牛排和啤酒
  • 无边牛排和葡萄酒
  • 无边无焦的稀有牛排
  • 带沙拉和饮料的稀有牛排
  • ...(总共48个组合)

很明显,修饰符和修饰符选项的数量是未知的,因此,如果我们有4个修饰符,每个修饰符各有5个选项,我们最终将得到5 * 6 * 6 * 6(第一个是强制性的,其余都是无选项添加)结果。我正在考虑使用LINQ SelectMany来使列表变平,但是在选项数量未知的情况下,我无法产生预期的结果。我正在考虑将所有选项记录为数组中的位标志,并且仅进行计数,但是存在此强制性标志问题。


public class ItemMenu
{
    public string Name { get; set; }
    public List<Modifier> Modifiers { get; set; }
}
public class Modifier
{
    public bool IsMandatory { get; set; }
    public string Name { get; set; }
    public List<ModifierOption> Options { get; set; }
}
public class ModifierOption
{
    public int ID { get; set; }
    public string Name { get; set; }
    public bool Selected { get; set; }
}
public static ItemMenu GetSteakMenu()
{
    return new ItemMenu
    {
        Name = "Beef Steak",
        Modifiers = new List<Modifier> {
            new Modifier {  Name = "Temperature", IsMandatory = true, Options = new List<ModifierOption>
                {
                    new ModifierOption { ID = 1, Name = "Rare" },
                    new ModifierOption { ID = 2, Name = "Medium" },
                    new ModifierOption { ID = 3, Name = "Well done" },
                }
            },
            new Modifier {  Name = "Side", Options = new List<ModifierOption>
                {
                    new ModifierOption { ID = 1, Name = "Salad" },
                    new ModifierOption { ID = 2, Name = "Fries" },
                    new ModifierOption { ID = 3, Name = "Sweet fries" },
                }
            },
            new Modifier {  Name = "Drink", Options = new List<ModifierOption>
                {
                    new ModifierOption { ID = 1, Name = "Beer" },
                    new ModifierOption { ID = 2, Name = "Wine" },
                    new ModifierOption { ID = 3, Name = "Coke" },
                }
            }
        }
    };
}

从输出类型开始,我最好使用将ModifierOptions标志设置为true的ItemMenu对象列表,但是任何类型的输出对象都是可接受的,甚至是字符串。谢谢!

c# linq cross-join
3个回答
1
投票

回答标题中的问题,这是使用LINQ的列表数量未知的结果:

public static class EnumerableExtensions
{
    public static IEnumerable<IEnumerable<T>> CrossProduct<T>(
        this IEnumerable<IEnumerable<T>> source) => 
        source.Aggregate(
            (IEnumerable<IEnumerable<T>>) new[] { Enumerable.Empty<T>() },
            (acc, src) => src.SelectMany(x => acc.Select(a => a.Concat(new[] {x}))));
}

据我了解,您想像这样使用它:

var beefSteak = GetSteakMenu();
var modifiers = beefSteak.Modifiers.Select(m => m.Options);
var results = modifiers.CrossProduct();

foreach (var resultList in results)
{
    Console.WriteLine($"Steak, {string.Join(", ", resultList.Select(r => r.Name))}");
}
> Steak, Rare, Salad, Beer
> Steak, Medium, Salad, Beer
> Steak, Well done, Salad, Beer
> Steak, Rare, Fries, Beer
> Steak, Medium, Fries, Beer
> Steak, Well done, Fries, Beer
> Steak, Rare, Sweet fries, Beer
> Steak, Medium, Sweet fries, Beer
> Steak, Well done, Sweet fries, Beer
> Steak, Rare, Salad, Wine
> Steak, Medium, Salad, Wine
> Steak, Well done, Salad, Wine
> Steak, Rare, Fries, Wine
> Steak, Medium, Fries, Wine
> Steak, Well done, Fries, Wine
> Steak, Rare, Sweet fries, Wine
> Steak, Medium, Sweet fries, Wine
> Steak, Well done, Sweet fries, Wine
> Steak, Rare, Salad, Coke
> Steak, Medium, Salad, Coke
> Steak, Well done, Salad, Coke
> Steak, Rare, Fries, Coke
> Steak, Medium, Fries, Coke
> Steak, Well done, Fries, Coke
> Steak, Rare, Sweet fries, Coke
> Steak, Medium, Sweet fries, Coke
> Steak, Well done, Sweet fries, Coke

编辑:将累加器更改为使用Enumerable.Empty<T>()而不是实例化数组,因为它避免了分配。


0
投票

尝试以下内容:

    class Program
    {
        static void Main(string[] args)
        {
            ItemMenu menu = GetSteakMenu();

            var results = menu.Modifiers.SelectMany(x => x.Options.Select(y => new {
                modifierName = x.Name,
                modifierMandatory = x.IsMandatory,
                id = y.ID,
                optionName = y.Name,
                selected = y.Selected
            }))
            .GroupBy(x => x.id)
            .Select(x => x.Select(y => y).ToList())
            .ToList();
        }
        public static ItemMenu GetSteakMenu()
        { 
            return new ItemMenu
            {
                Name = "Beef Steak",
                Modifiers = new List<Modifier> {
                    new Modifier {  Name = "Temperature", IsMandatory = true, Options = new List<ModifierOption>
                        {
                            new ModifierOption { ID = 1, Name = "Rare" },
                            new ModifierOption { ID = 2, Name = "Medium" },
                            new ModifierOption { ID = 3, Name = "Well done" },
                        }
                    },
                    new Modifier {  Name = "Side", Options = new List<ModifierOption>
                        {
                            new ModifierOption { ID = 1, Name = "Salad" },
                            new ModifierOption { ID = 2, Name = "Fries" },
                            new ModifierOption { ID = 3, Name = "Sweet fries" },
                        }
                    },
                    new Modifier {  Name = "Drink", Options = new List<ModifierOption>
                        {
                            new ModifierOption { ID = 1, Name = "Beer" },
                            new ModifierOption { ID = 2, Name = "Wine" },
                            new ModifierOption { ID = 3, Name = "Coke" },
                        }
                    }
                }
            };
        }

    }
    public class ItemMenu
    {
        public string Name { get; set; }
        public List<Modifier> Modifiers { get; set; }
    }
    public class Modifier
    {
        public bool IsMandatory { get; set; }
        public string Name { get; set; }
        public List<ModifierOption> Options { get; set; }
    }
    public class ModifierOption
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public bool Selected { get; set; }
    }

0
投票

由于Rosetta Code,我找到了一种整洁的扩展方法:

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 acc in accumulator
        from item in sequence
        select acc.Concat(new[] { item }));
}

以上方法接受列表的列表并产生所有组合:

private static void Main(string[] args)
{
    ItemMenu steak = GetSteakMenu();
    var modArray = steak.Modifiers.ToArray();
    var combinations = steak.Modifiers.Select(mo => mo.Options).CartesianProduct();

    Console.WriteLine($"Total of {combinations.Count()}");
    foreach (var variation in combinations)
    {
        var array = variation.ToArray();
        for (int i = 0; i < array.Length; i++)
        {
            Console.WriteLine($"Modifier: {modArray[i].Name}, Option: {array[i]}");
        }
    }

    Console.WriteLine("Done!");
    Console.ReadKey();
}

不幸的是,这不考虑IsRequired标志,但仍呈现所有组合

© www.soinside.com 2019 - 2024. All rights reserved.