【问题标题】:LINQ, Lambda Expression Skip n records, while averaging the skipped recordsLINQ,Lambda 表达式跳过 n 条记录,同时平均跳过的记录
【发布时间】:2012-07-14 16:08:04
【问题描述】:

我有一个表情:

Records.OrderBy(o => o.TIME).Where((o, i) => i % interval == 0).ToList();

这在获取大量数据记录并将其缩减为较小的列表方面做得很好。 (interval 是要跳过的记录数)。问题是,我想平均一些字段,而不仅仅是跳过它们。我不知道如何在不产生巨大循环的情况下做到这一点。值得注意的是,每条数据记录大约有 90 个字段。想法?

编辑:我希望能够准确地跳过每第 n 条记录,平均 2 个特定字段(纬度和经度(存储为十进制)),并且很可能保持其他 88 个字段不变。

编辑:我想离开

    timelat longmany other fields
    1   2   3   field1
    2   3   4   field1
    3   4   5   field1
    4   5   6   field1
    5   6   7   field1
    6   7   8   field1
    7   8   9   field1
    8   9   10  field1
    9   10  11  field1
    10  11  12  field1
    11  12  13  field1
    12  13  14  field1

收件人:

    timelat     long    other fields            
    3   3   4   field1
    6   6   7   field1
    9   9   10  field1
    12  12  13  field1

【问题讨论】:

    标签: c# linq lambda


    【解决方案1】:

    如果我理解正确,您希望将大量项目分组到较小数量的同等大小的“桶”中,其中每个桶的一些字段被聚合(例如平均)并且一些字段被跳过(即取自桶中的最后一项)。

    考虑是否可以这样做:

    Records
    .ToBuckets(interval)
    .Select(bucket => new Record {
         Time = bucket.Last().Time,
         Count = bucket.Count,
         Lat = bucket.Average(x => x.Lat), 
         Long = bucket.Average(x => x.Long),
         Other = bucket.First().Other
    }
    .ToList()
    

    如果这是你想要的,你需要做的就是创建 ToBuckets 方法,这是一个更简单(和通用!)的问题:

    public static IEnumerable<IList<T>> ToBuckets<T>(this IEnumerable<T> source, int size)
    {
        var bucket = new List<T>(size);
        foreach (var item in source)
        {
            bucket.Add(item);
            if (bucket.Count == size) {
               yield return bucket;
               bucket = new List<T>(size); // or you can use the same one if you're careful
        }
    
        if (bucket.Count > 0) yield return bucket;
    }
    

    (以上是作为示例的扩展方法给出的,当然这也可以是常规方法)。

    【讨论】:

    • 正是我想要的
    【解决方案2】:

    如果您想在平均值中包含给定记录,您将不得不触及该记录。 某事将不得不循环遍历所有记录,无论您是明确执行此操作还是 Linq 是否在幕后执行此操作。

    给定的 Linq 表达式只能返回一件事。

    您当前拥有的 Linq 表达式将返回过滤后的列表。

    您将需要第二个 Linq 表达式(或您自己的循环)来平均所有记录,例如

    var avg = Records.Average(r => r.FieldToAverage);
    

    我不知道你的意思是什么

    值得注意的是,每条数据记录大约有90个字段

    您是否必须以某种方式对给定记录中的字段进行平均?如果是这样,它们是什么数据类型?是否有一些现有的方法来枚举所有这些字段?如果没有,您将需要显式访问每个字段,或使用反射来枚举(相关)字段。

    【讨论】:

    • 我认为我正在寻找的是每第 n 条记录一组的平均值。字段是多种数据类型(众所周知)
    • 这对我来说不是很清楚。您可以为您的问题添加一个示例吗?
    • 我添加了示例数据。我越来越想我将不得不主要跳过 linq 并使用一个巨大的循环。
    【解决方案3】:

    您应该能够将其粘贴在 Where 子句中。它会有点难看,但是是这样的:

    [编辑:从您的编辑中,我现在了解到您想要一些不同的东西。此代码已相应编辑]。

    decimal latSum = 0;
    decimal longSum = 0;
    int count = 0;
    
    var recordList = Records
        .OrderBy(o => o.TIME)
        .Where((o, i) => {
            if (i % interval == 0)
            {
                // Modify the record in place (hope that's OK)
                o.Lat = (o.Lat + latSum) / (count + 1);
                o.Long = (o.Long + longSum) / (count + 1);
                latSum = longSum = count = 0;
                return true;
            }
    
            latSum += o.Lat;
            longSum += o.Long;
            count++;
            return false;
        })
        .ToList();
    

    【讨论】:

    • 这确实很有趣,但我觉得这有点滥用Where。如果我们知道我们想要处理 N 行的存储桶,为什么不明确地将其添加为一个步骤?我试图在我的回答中这样做。
    • 你的答案肯定比我的更优雅,但我认为我的答案会快一点。我可能会一次性使用我的答案,而对于通用功能,或者如果我必须多次做同样的事情。
    • @Avish:我起初也有同样的想法,但后来意识到自定义循环会做同样的事情。特别是函数式程序员会发现 Where 中引入的副作用令人讨厌,但它确实避免了重复列表。
    • 我的版本没有重复列表;它只会重复每个存储桶来计算聚合,并且差异应该可以忽略不计(1 次操作的 N 次循环与 N 次操作的 1 次循环,您所节省的只是遍历数组的开销)。
    • 由于您调用了Average,它实际上确实重复了整个列表。无论如何,我很好奇,所以我创建了一个测试应用程序来比较我们的两个解决方案。到目前为止,我的解决方案似乎快了大约 2 倍。我看到了 1.9 到 4 的范围,具体取决于配置(间隔和记录数)。代码是here
    猜你喜欢
    • 1970-01-01
    • 2017-08-03
    • 1970-01-01
    • 1970-01-01
    • 2015-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多