假定我具有以下类-ItemMenu-具有列表列表。如何使用C#生成具有所有可用组合的交叉联接输出?
对于以下代码,我希望得到3(温度)乘以4(侧面)乘以4(饮料)的结果,例如:
很明显,修饰符和修饰符选项的数量是未知的,因此,如果我们有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对象列表,但是任何类型的输出对象都是可接受的,甚至是字符串。谢谢!
回答标题中的问题,这是使用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>()
而不是实例化数组,因为它避免了分配。
尝试以下内容:
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; }
}
由于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标志,但仍呈现所有组合