C# 将空值设置为列表中最近的最后一个非空值-LINQ

C# 将空值设置为列表中最近的最后一个非空值-LINQ,c#,linq,C#,Linq,我有一个只读的数据点对象列表,其中一些对象有值,而其他对象为空。我想生成一个新的DataPoint对象列表,其中任何空DataPoint都被设置为最接近左侧前面的非空值。如果空值之前没有非空值,则默认为0 在下面的示例中,前两个空值变为0,因为前面没有非空值,最后两个空值变为5,因为5是最接近其左侧的非空值 public class DataPoint { public DataPoint(int inputValue) {

我有一个只读的数据点对象列表,其中一些对象有值,而其他对象为空。我想生成一个新的DataPoint对象列表,其中任何空DataPoint都被设置为最接近左侧前面的非空值。如果空值之前没有非空值,则默认为0

在下面的示例中,前两个空值变为0,因为前面没有非空值,最后两个空值变为5,因为5是最接近其左侧的非空值

    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的精神生成一个新集合,恰好选择算法需要一个临时变量
至少在最直截了当的情况下是这样的回答很好,先生!谢谢,好主意!回答得好,先生!谢谢,好主意!感谢您添加初始化代码以使其有效,并支持边缘案例场景!感谢您添加初始化代码以使其有效,并支持边缘案例场景!