C# 将空值设置为列表中最近的最后一个非空值-LINQ
我有一个只读的数据点对象列表,其中一些对象有值,而其他对象为空。我想生成一个新的DataPoint对象列表,其中任何空DataPoint都被设置为最接近左侧前面的非空值。如果空值之前没有非空值,则默认为0 在下面的示例中,前两个空值变为0,因为前面没有非空值,最后两个空值变为5,因为5是最接近其左侧的非空值C# 将空值设置为列表中最近的最后一个非空值-LINQ,c#,linq,C#,Linq,我有一个只读的数据点对象列表,其中一些对象有值,而其他对象为空。我想生成一个新的DataPoint对象列表,其中任何空DataPoint都被设置为最接近左侧前面的非空值。如果空值之前没有非空值,则默认为0 在下面的示例中,前两个空值变为0,因为前面没有非空值,最后两个空值变为5,因为5是最接近其左侧的非空值 public class DataPoint { public DataPoint(int inputValue) {
public class DataPoint
{
public DataPoint(int inputValue)
{
this.Value = inputValue;
}
public int Value {get;}
}
Input:
List<DataPoint> inputList = new List<DataPoint>
{null,
null,
new DataPoint(1),
new DataPoint(2),
new DataPoint(3),
null,
null,
new DataPoint(4),
new DataPoint(5),
null,
null};
Expected Output:
foreach (var item in outputList)
{
Console.WriteLine(item.Value);
}
{0, 0, 1, 2, 3, 3, 3, 4, 5, 5, 5}
我能了解一下如何在LINQ中以优雅的方式实现这一点吗?谢谢
更新:为了避免歧义,我将inputList更新为contains null,而不是使用null值的DataPoint实例。您可以尝试以下方法:
static void Main(string[] args)
{
List<int?> inputList = new List<int?>() { null, null, 1, 2, 3, null, null, 4, 5, null, null };
var result = Enumerable.Range(0, inputList.Count - 1)
.Select(i => inputList[i] ?? GetPrevious(i))
.ToList();
int GetPrevious(int index)
=> index == 0 ? 0 : inputList[index - 1] ?? GetPrevious(index - 1);
}
您可以尝试以下方法:
static void Main(string[] args)
{
List<int?> inputList = new List<int?>() { null, null, 1, 2, 3, null, null, 4, 5, null, null };
var result = Enumerable.Range(0, inputList.Count - 1)
.Select(i => inputList[i] ?? GetPrevious(i))
.ToList();
int GetPrevious(int index)
=> index == 0 ? 0 : inputList[index - 1] ?? GetPrevious(index - 1);
}
假设DataPoint.Value的实际属性类型为int?而不是int这样的东西应该可以工作
var outputList = inputList.Select((l,i)=> new DataPoint()
{
Value = l?.Value ?? inputList.Take(i).LastOrDefault(t=>t?.Value.HasValue ?? false)?.Value ?? 0
});
我还没有检查过,但我确信性能特征很糟糕
全林帕德-
void Main()
{
var inputList = new List<DataPoint>()
{
null, null, 1, 2, 3, null, null, 4, 5, null, null
};
var outputList = inputList.Select((l,i)=> new DataPoint()
{
Value = l?.Value ?? inputList.Take(i).LastOrDefault(t=>t?.Value.HasValue ?? false)?.Value ?? 0
});
outputList.Dump();
}
public class DataPoint
{
public int? Value { get; set; }
//added to make building the inputList easier
public static implicit operator DataPoint(int? value) =>
new DataPoint(){ Value = value };
}
假设DataPoint.Value的实际属性类型为int?而不是int这样的东西应该可以工作
var outputList = inputList.Select((l,i)=> new DataPoint()
{
Value = l?.Value ?? inputList.Take(i).LastOrDefault(t=>t?.Value.HasValue ?? false)?.Value ?? 0
});
我还没有检查过,但我确信性能特征很糟糕
全林帕德-
void Main()
{
var inputList = new List<DataPoint>()
{
null, null, 1, 2, 3, null, null, 4, 5, null, null
};
var outputList = inputList.Select((l,i)=> new DataPoint()
{
Value = l?.Value ?? inputList.Take(i).LastOrDefault(t=>t?.Value.HasValue ?? false)?.Value ?? 0
});
outputList.Dump();
}
public class DataPoint
{
public int? Value { get; set; }
//added to make building the inputList easier
public static implicit operator DataPoint(int? value) =>
new DataPoint(){ Value = value };
}
使用助手扩展方法,该方法是APL扫描运算符(如聚合)的my LINQ实现的变体,但返回使用助手函数启动结果流的中间结果:
// First PrevResult is TRes seedFn(T FirstValue)
// TRes combineFn(TRes PrevResult, T CurValue)
public static IEnumerable<TRes> Scan<T, TRes>(this IEnumerable<T> items, Func<T, TRes> seedFn, Func<TRes, T, TRes> combineFn) {
using (var itemsEnum = items.GetEnumerator()) {
if (itemsEnum.MoveNext()) {
var prev = seedFn(itemsEnum.Current);
while (itemsEnum.MoveNext()) {
yield return prev;
prev = combineFn(prev, itemsEnum.Current);
}
yield return prev;
}
}
}
注意:如果您不想使用helper方法,并且愿意通过使用外部状态(例如helper变量)来稍微滥用LINQ,您可以简单地执行以下操作:
var prevNonNull = new DataPoint(0);
var ans2 = InputList.Select(n => prevNonNull = n ?? prevNonNull).ToList();
使用助手扩展方法,该方法是APL扫描运算符(如聚合)的my LINQ实现的变体,但返回使用助手函数启动结果流的中间结果:
// First PrevResult is TRes seedFn(T FirstValue)
// TRes combineFn(TRes PrevResult, T CurValue)
public static IEnumerable<TRes> Scan<T, TRes>(this IEnumerable<T> items, Func<T, TRes> seedFn, Func<TRes, T, TRes> combineFn) {
using (var itemsEnum = items.GetEnumerator()) {
if (itemsEnum.MoveNext()) {
var prev = seedFn(itemsEnum.Current);
while (itemsEnum.MoveNext()) {
yield return prev;
prev = combineFn(prev, itemsEnum.Current);
}
yield return prev;
}
}
}
注意:如果您不想使用helper方法,并且愿意通过使用外部状态(例如helper变量)来稍微滥用LINQ,您可以简单地执行以下操作:
var prevNonNull = new DataPoint(0);
var ans2 = InputList.Select(n => prevNonNull = n ?? prevNonNull).ToList();
最后两个空值转换为5不符合您的规则,它们之前没有非空值。编辑-等等,我可能读错了。是 啊没关系。您应该添加到目前为止尝试过的内容。为什么必须使用linq来完成?使用它似乎不是一个场景^+1,为什么要使用LINQ?您是否需要延迟执行,或者您只是好奇如何将LINQ转换为这种用例?在LINQ中无法优雅地实现这一点,我指的是现有的内置LINQ方法或语法。最后两个null转换为5不符合您的规则,它们之前没有非null值。编辑-等等,我可能读错了。是 啊没关系。您应该添加到目前为止尝试过的内容。为什么必须使用linq来完成?使用它似乎不是一个场景^+1,为什么要使用LINQ?您是否需要延迟执行,或者您只是好奇如何将LINQ应用于此用例?在LINQ中无法优雅地实现这一点,我指的是现有的内置LINQ方法或语法。您的ScanPairWithHelper非常适合此类应用程序。竖起大拇指。“我的答案中的.Takei的规模将非常大。”asawyer简化了我的答案:经过思考,我意识到一个助手太过分了——标准的扫描携带了之前的结果,就像所需要的聚合一样。我们都需要更多的APL在我们的生活中。@Buntouba有变异状态的linq方法被认为是一种不好的做法,这有点违背linq设计理念。但是这里没有性能下降或类似的情况。@buntouba是的,第一个linq的例子是一个很糟糕的实践,没有引用。我同意Eric的观点-想要改变状态,使用循环。并不是因为引入linq,循环突然变得过时。然而,在您的示例中,这并不是那么简单,因为您希望按照linq的精神生成一个新集合,恰好选择算法需要一个临时变量,至少在最简单的方法中是如此。您的ScanPairWithHelper非常适合这种应用。竖起大拇指。“我的答案中的.Takei的规模将非常大。”asawyer简化了我的答案:经过思考,我意识到一个助手太过分了——标准的扫描携带了之前的结果,就像所需要的聚合一样。我们都需要更多的APL在我们的生活中。@Buntouba有变异状态的linq方法被认为是一种不好的做法,这有点违背linq设计理念。但是这里没有性能下降或类似的情况。@buntouba是的,第一个linq的例子是一个很糟糕的实践,没有引用。我同意Eric的观点-想要改变状态,使用循环。并不是因为引入linq,循环突然变得过时。然而,在您的示例中,这并不是那么简单,因为您希望按照linq的精神生成一个新集合,恰好选择算法需要一个临时变量
至少在最直截了当的情况下是这样的回答很好,先生!谢谢,好主意!回答得好,先生!谢谢,好主意!感谢您添加初始化代码以使其有效,并支持边缘案例场景!感谢您添加初始化代码以使其有效,并支持边缘案例场景!