【问题标题】:LINQ - Select the elements from array A depending on values on Array BLINQ - 根据数组 B 上的值从数组 A 中选择元素
【发布时间】:2012-05-17 16:05:28
【问题描述】:

假设我们有两个数组:

DateTime[] wDates = new DateTime[20000];
double[] wValues = new double[20000];

这两个数组都是按顺序排列的,即给定一个int i,wValues[i]代表日期wDates[i]。

假设我们需要获取日期为一月的 wValues 的平均值

使用标准循环将是:

double wAvg = 0.0;
int wDataCount = 0;
for (int i=0; i < 20000; i++)
  if (wDates[i].Month == 1)
  {
    wAvg += wValues[i];
    wDataCount++;
  }

if (wDataCount > 0)
  wAvg /= wDataCount;

我想知道如何在 LINQ 中执行此操作? 我可以创建一个包含两个值的结构/类 DateDouble,然后执行以下操作:

List<DateDouble> wListData = new List<DateDouble>();
Add the items...
double wAvg = (from d in wListData
               where d.Date.Month == 1
               select d.Value).Average();

但是当每天完成数千万次时,创建数千个 DateDouble 对象将是一个巨大的内存开销。临时对象也会发生同样的情况,尝试使用“索引”并在数组上加入索引会产生糟糕的性能。

在 LINQ 中是否有更好的方法来实现这一点?

谢谢, 嗯

【问题讨论】:

    标签: arrays linq select


    【解决方案1】:

    好吧,您可以使用Zip 运算符来简化操作:

    var average = wDates.Zip(wValues, (date, value) => new { date, value })
                        .Where(pair => pair.date.Month == 1)
                        .Average(pair => pair.value);
    

    这仍然会为每对创建一个匿名类型的实例,但我个人会放手并测量性能,然后再假设它太昂贵了。请注意,这将以流方式运行 - 因此尽管会产生大量垃圾,但任何时候所需的总内存都很小。

    您可以通过创建自己的对 struct 来提高效率...这将避免创建额外的对象,但这会更痛苦。不过,还不错:

    // The normal Tuple types are classes.
    public struct TupleValue<T1, T2>
    {
        private readonly T1 item1;
        private readonly T2 item2;
    
        public T1 Item1 { get { return item1; } }
        public T2 Item2 { get { return item2; } }
    
        public TupleValue(T1 item1, T2 item2)
        {
            this.item1 = item1;
            this.item2 = item2;
        }
    }
    
    var average = wDates.Zip(wValues, (date, value) => 
                                       new TupleValue<DateTime, double>(date, value))
                        .Where(pair => pair.Item1.Month == 1)
                        .Average(pair => pair.Item2);
    

    我只会在 证明第一种方法很昂贵之后才这样做。

    【讨论】:

    • 为什么不使用 KeyValuePair
    • @ivowiblo:因为它不是真正的键/值对?是的,它会起作用 - 但它会给人一种印象,即日期是某种关键,我们没有任何证据证明这一点。它可能是一个键,但它可能不是。
    • Zip 运算符很有意义。但是我真正希望避免的内存/分配成本意味着我被困在我猜的循环中。谢谢!
    • 每个日期都有一个值,它看起来像一个键值对。此外,您正在避免创建一个新结构(即使您认为这是您给出的解决方案中最糟糕的部分)。所以...
    • @MichaelMoreno:为什么?底部的形式避免在执行时创建任何新的对象实例......为什么要使用手动循环?
    【解决方案2】:

    IEnumerable.Where() 扩展方法有一个重载版本,它也考虑谓词中的索引。

    double average = wValues.Where((d, i) => wDates[i].Month == 1).Average();
    

    【讨论】:

    • 谢谢。这是一个非常优雅的解决方案!
    【解决方案3】:

    类似:

    double wAvg = wDates.Select((d,i) => new { Month = d.Month, Index = i })
                        .Where(x => x.Month == 1)
                        .Select(x => wValues[i])
                        .Average();
    

    无论如何,在这种情况下,您还将创建 N 个该匿名类型的实例。

    【讨论】:

    • 谢谢。是的,我希望避免创建匿名对象。
    猜你喜欢
    • 2015-08-04
    • 1970-01-01
    • 2020-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-01
    相关资源
    最近更新 更多