我正在创建一个很大的对象列表,这些对象是一组以上集合中的最小值,最大值和增量的所有可能组合。
但是它起作用了,但我的问题是,如果不重写LINQ查询,就无法轻易排除一个或多个项目集合。从传递的“循环”列表中的顺序可以推断出查询中“ from”语句的顺序。在此示例中,对“步骤模型”列表的查询和最终投影需要处理三个项目集。尽管'from'语句是LINQ,但它看起来仍然像是重复代码,可将自身插入For循环。考虑到在单个查询中结合了投影,我无法弄清楚该如何做。 注意:我可能会添加更多集合,使问题更加复杂!
public static IEnumerable<StepModel> CreateSteps(List<MinToMax> loops)
{
var steps =
from item1 in Enumerable
.Repeat(loops[0].Minimum, (loops[0].Maximum - loops[0].Minimum) / loops[0].Increment + 1)
.Select((tr, ti) => tr + loops[0].Increment * ti)
from item2 in Enumerable
.Repeat(loops[1].Minimum, (loops[1].Maximum - loops[1].Minimum) / loops[1].Increment + 1)
.Select((tr, ti) => tr + loops[1].Increment * ti)
from item3 in Enumerable
.Repeat(loops[2].Minimum, (loops[2].Maximum - loops[2].Minimum) / loops[2].Increment + 1)
.Select((tr, ti) => tr + loops[2].Increment * ti)
select new StepModel
{
ItemValues1 = new Step { Value = item1, IsActive = true },
ItemValues2 = new Step { Value = item2, IsActive = true },
ItemValues3 = new Step { Value = item3, IsActive = true },
};
return steps;
}
public class MinToMax
{
public int Minimum { get; set; }
public int Maximum { get; set; }
public int Increment { get; set; }
public bool IsActive { get; set; } = true;
}
public class Step
{
public int Value { get; set; }
public bool IsActive { get; set; } = true;
}
public class StepModel
{
public Step ItemValues1 { get; set; }
public Step ItemValues2 { get; set; }
public Step ItemValues3 { get; set; }
}
public class ItemSteps
{
public MinToMax ItemStep1 { get; } = new MinToMax();
public MinToMax ItemStep2 { get; } = new MinToMax();
public MinToMax ItemStep3 { get; } = new MinToMax();
}
public static List<MinToMax> GetValuesIntoSteps()
{
var list = new List<MinToMax>();
var itemValues = new ItemSteps();
itemValues.ItemStep1.Minimum = 10;
itemValues.ItemStep1.Maximum = 100;
itemValues.ItemStep1.Increment = 10;
if (itemValues.ItemStep1.IsActive)
{
list.Add(itemValues.ItemStep1);
}
itemValues.ItemStep2.Minimum = 3;
itemValues.ItemStep2.Maximum = 30;
itemValues.ItemStep2.Increment = 3;
if (itemValues.ItemStep2.IsActive)
{
list.Add(itemValues.ItemStep2);
}
itemValues.ItemStep3.Minimum = 15;
itemValues.ItemStep3.Maximum = 75;
itemValues.ItemStep3.Increment = 5;
if (itemValues.ItemStep3.IsActive)
{
list.Add(itemValues.ItemStep3);
}
return list;
}
答案 0 :(得分:1)
您要在此处进行的操作可以概括地描述为任意数量的序列的交叉联接。
当您在LINQ中执行多个from
子句时,这就是交叉联接。除了第一个以外,其他所有内容都是对SelectMany
的调用。为了支持任意数量的输入,您需要遍历它们。不过,一个简单的foreach
循环将无法正常工作,因为第一个序列的处理方式有所不同。
如何?两件事情。首先,没有可调用的SelectMany
。如果在一个空列表上调用它,则会得到另一个空列表。因此,loops
中的第一个元素将建立基本列表。我在下方中提供的实现以空序列开头,但是只有在loops
中找不到任何元素的情况下,它才存在。找到第一个元素后,它将被替换。其次,至少在此实现中,您需要从第一个元素发出新列表,而随后的联接需要发出下一个元素并构造一个新列表。
要记住的另一件事是,您需要一个可以处理任意数量元素的结果类型。为此,我选择了IEnumerable<List<Step>>
-我选择List<Step>
作为元素类型,因为每个列表的每个索引都将与loops
参数的索引相关联,因此您可以将它们都索引直。例如,每个结果的元素[5]
将来自loops[5]
。
为实现这一点,我直接使用foreach
编写了一个IEnumerator<T>
循环的等效内容。对于loops
中的第一个元素,它为要返回的每个结果构造一个Step
对象的列表。
对于loops
中的每个后续项,它使用SelectMany
进行交叉连接,并将它们聚合到包含左侧元素和右侧元素的新列表中。
枚举器公开Current
属性。这样可以将迭代从绑定到给定的索引中解放出来。您会注意到current
曾经在loops[n]
处使用过。
值得注意的是,必须强制调用ToList
,因为current
变量不会被lambda捕获,就像foreach
循环中的范围变量会被捕获一样。
这是它的外观:
public static IEnumerable<List<Step>> CreateSteps(IEnumerable<MinToMax> loops)
{
IEnumerable<List<Step>> sequence = Enumerable.Empty<List<Step>>();
using (IEnumerator<MinToMax> enumerator = loops.GetEnumerator())
{
if (enumerator.MoveNext())
{
MinToMax current = enumerator.Current;
sequence = Enumerable
.Repeat(current.Minimum, (current.Maximum - current.Minimum) / current.Increment + 1)
.Select((tr, ti) => new List<Step>() { new Step() { Value = tr + current.Increment * ti, IsActive = true } })
.ToList();
while (enumerator.MoveNext())
{
current = enumerator.Current;
sequence = sequence
.SelectMany(
ctx => Enumerable
.Repeat(current.Minimum, (current.Maximum - current.Minimum) / current.Increment + 1)
.Select((tr, ti) => new Step() { Value = tr + current.Increment * ti, IsActive = true }),
(list, value) => new List<Step>(list) { value }
)
.ToList();
}
}
}
return sequence;
}
我也将loops
参数更改为IEnumerable<MinToMax>
,因为关于该方法如何使用的任何要求都不需要将其作为列表。另一方面,我将返回序列中的项目保留为List
,因为它使将它们的元素与源列表相关联变得更加容易,如我之前提到的。
还有更多的重构可以完成,例如将传递给Enumerable.Repeat
的表达式提取到一种方法中,以便可以重用。
我没有看到您的使用方式,因此我将其快速地投入使用。对于您提供的loops
中的三个元素,这两种实现方式都显示出相同的结果,并且对于我的两个和四个输入,似乎都显示出正确的结果。
static void Main(string[] args)
{
List<MinToMax> loops = GetValuesIntoSteps();
foreach (List<Step> loop in CreateSteps(loops))
{
foreach (Step step in loop)
{
Console.Write($"{step.Value} ");
}
Console.WriteLine();
}
}
我希望有帮助。