代码之家  ›  专栏  ›  技术社区  ›  user366312

从IEnumerable<T>返回元素计数的最快方法是什么?

  •  -3
  • user366312  · 技术社区  · 2 年前

    我一直在使用 System.Linq.Count() 以获得中的元素计数 IEnumerable<T> 这花费了大量的时间。

    对于instale,对于十万个元素,它需要几秒钟的时间。

    有更好的方法吗?

    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    
    namespace MyLib
    {
    
        public struct Vec3
        {
            // Properties for X, Y, Z components of the vector.
            public double X { get; set; }
            public double Y { get; set; }
            public double Z { get; set; }
    
            public double SumComponents()
            {
                return X + Y + Z;
            }
    
            public double DotProduct(Vec3 other)
            {
                return X * other.X + Y * other.Y + Z * other.Z;
            }
    
            public double Norm()
            {
                return Math.Sqrt(X * X + Y * Y + Z * Z);
            }
    
            public double Cosine(Vec3 other)
            {
                return DotProduct(other) / (Norm() * other.Norm());
            }
    
            // A static read-only property representing a zero vector.
            public static Vec3 Zero { get; } = new Vec3(0, 0, 0);
            public static Vec3 One { get; } = new Vec3(1, 1, 1);
            public static Vec3 Multiply(Vec3 left, Vec3 right)
            {
                return new Vec3(left.X * right.X, left.Y * right.Y, left.Z * right.Z);
            }
    
            // Constructor to initialize the vector with X, Y, Z components.
            public Vec3(double x, double y, double z)
            {
                X = x;
                Y = y;
                Z = z;
            }
    
            // Add two vectors.
            public static Vec3 operator +(Vec3 a, Vec3 b)
            {
                return new Vec3(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
            }
    
            // Subtract two vectors.
            public static Vec3 operator -(Vec3 a, Vec3 b)
            {
                return new Vec3(a.X - b.X, a.Y - b.Y, a.Z - b.Z);
            }
    
            // Negate a vector.
            public static Vec3 operator -(Vec3 a)
            {
                return new Vec3(-a.X, -a.Y, -a.Z);
            }
    
            // Multiply vector by a scalar.
            public static Vec3 operator *(Vec3 a, double scalar)
            {
                return new Vec3(a.X * scalar, a.Y * scalar, a.Z * scalar);
            }
    
            // Multiply scalar by vector (commutative).
            public static Vec3 operator *(double scalar, Vec3 a)
            {
                return a * scalar;
            }
    
            // Divide vector by a scalar.
            public static Vec3 operator /(Vec3 a, double scalar)
            {
                if (scalar == 0)
                    throw new DivideByZeroException("Cannot divide by zero.");
    
                return new Vec3(a.X / scalar, a.Y / scalar, a.Z / scalar);
            }
    
            // Calculate dot product of two vectors.
            public static double operator *(Vec3 a, Vec3 b)
            {
                return Vec3.Dot(a, b);
            }
    
            // Calculate Dot product of two Vec3 objects
            public double Dot(Vec3 other) => X * other.X + Y * other.Y + Z * other.Z;
    
            public static double Dot(Vec3 a, Vec3 b)
            {
                return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
            }
    
            // Calculate cross product of two vectors.
            public static Vec3 operator %(Vec3 a, Vec3 b)
            {
                 return new Vec3(
                    a.Y * b.Z - a.Z * b.Y,
                    a.Z * b.X - a.X * b.Z,
                    a.X * b.Y - a.Y * b.X);
            }
    
            // Check if two vectors are equal.
            public static bool operator ==(Vec3 a, Vec3 b)
            {
                return a.X == b.X && a.Y == b.Y && a.Z == b.Z;
            }
    
            // Check if two vectors are not equal.
            public static bool operator !=(Vec3 a, Vec3 b)
            {
                return !(a == b);
            }
    
            // Override Equals() method for value comparison.
            public override bool Equals(object obj)
            {
                // Instead of direct type check, use 'as' to allow nulls
                Vec3 vec = (Vec3) obj;
                return vec != null && X == vec.X && Y == vec.Y && Z == vec.Z;
            }
    
            // Override GetHashCode() method.
            public override int GetHashCode()
            {
                unchecked // Overflow is fine, just wrap
                {
                    int hash = 17;
                    // Suitable nullity checks etc, of course :)
                    hash = hash * 23 + X.GetHashCode();
                    hash = hash * 23 + Y.GetHashCode();
                    hash = hash * 23 + Z.GetHashCode();
                    return hash;
                }
            }
    
            // Calculate magnitude (length) of the vector.
            public double Magnitude()
            {
                return Math.Sqrt(X * X + Y * Y + Z * Z);
            }
    
            public double LengthSquared()
            {
                return X * X + Y * Y + Z * Z;
            }
    
            // Normalize the vector (make it unit length).
            public Vec3 Normalize()
            {
                double magnitude = Magnitude();
                if (magnitude == 0)
                    throw new InvalidOperationException("Cannot normalize a zero vector.");
                return this / magnitude;
            }
    
            // Calculate distance between two vectors.
            public static double Distance(Vec3 a, Vec3 b)
            {
                return (a - b).Magnitude();
            }
    
            // Calculate the angle between two vectors in radians.
            public static double Angle(Vec3 a, Vec3 b)
            {
                double dot = a * b;
                double magA = a.Magnitude();
                double magB = b.Magnitude();
                // Ensure no division by zero
                if (magA == 0 || magB == 0)
                    throw new InvalidOperationException("Cannot calculate the angle with a zero vector.");
                double cosTheta = dot / (magA * magB);
                // Ensure the value is within -1 and 1 to account for any doubleing point errors
                cosTheta = Math.Max(-1, Math.Min(1, cosTheta));
                return Math.Acos(cosTheta);
            }
    
            // Squared norm of the vector
            public double NormSquared()
            {
                return X * X + Y * Y + Z * Z;
            }
    
            public static double DistanceSquared(Vec3 v1, Vec3 v2)
            {
                double dx = v1.X - v2.X;
                double dy = v1.Y - v2.Y;
                double dz = v1.Z - v2.Z;
                return dx * dx + dy * dy + dz * dz;
            }
    
            // Override ToString() for easy debugging and display.
            public override string ToString()
            {
                // Using String.Format instead of string interpolation for compatibility.
                return String.Format("({0}, {1}, {2})", X, Y, Z);
            }
        }
    
    
        public static class TimeSeriesAnalysisAutoCorrelationMemoryLess
        {
            public static double AutoCorr(int lag, IEnumerable<Vec3> vectorList)
            {
                int n = vectorList.Count();//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    
                if (n == 0 || lag >= n || lag < 0)
                    throw new ArgumentException($"Lag must be between 0 and {n - 1}, and the list cannot be empty.");
    
                double sum = 0;
                for (int i = 0; i < n - lag; i++)
                {
                    var vecA = vectorList.ElementAt(i);
                    var vecB = vectorList.ElementAt(i+lag);
                    sum += vecA.Dot(vecB);
                }
    
                return sum / (n - lag);
            }
    
            public static IEnumerable<double> GetTValues(int maxLag, int vectorCount)
            {
                int validMaxLag = Math.Min(maxLag, vectorCount - 1); // Ensure maxLag does not exceed n-1
                for (int lag = 0; lag <= validMaxLag; lag++)
                {
                    yield return lag;
                }
            }
    
            public static IEnumerable<double> GetCResults(IEnumerable<Vec3> vectorList, int maxLag, double c0)
            {
                for (int lag = 0; lag <= maxLag; lag++)
                {
                    double cValue = 0;
                    try
                    {
                        cValue = AutoCorr(lag, vectorList);
                    }
                    catch (ArgumentException ex)
                    {
                        Console.WriteLine(ex.Message);
                        yield break; // Exit the loop if an invalid lag is encountered
                    }
                    yield return cValue / c0; // Normalization is done here
                }
            }
    
            public static PairReturn<double> GetAutoCorrelationPoints(IEnumerable<Vec3> vectors, int maxLag)
            {
                double c0 = AutoCorr(0, vectors); // This is the normalization factor
                Console.WriteLine($"Normalization factor: {c0}");
    
                return new PairReturn<double>(GetTValues(maxLag, vectors.Count()), GetCResults(vectors, maxLag, c0));
            }
        }
    }
    
    

    代码在标记的语句上花费了很长时间。

    4 回复  |  直到 2 年前
        1
  •  3
  •   mjwills Myles McDonnell    2 年前

    这是XY问题的一个经典例子。你专注于一个特定的问题,而不是问“我在工作中使用正确的工具吗?”。

    代码的主要问题不是 Count() -它是你的用途 ElementAt . ElementAt 打电话 在一个循环内 对于 IEnumerable<T> 可以 根据类型而正常-例如,对于 List<T> -但对于实现的其他类型 IEnumerable<T> 它的表现会很糟糕,甚至更糟糕的是,它不总是返回您期望的值)。

    我强烈建议你阅读MoreLinq的 Lag - https://github.com/morelinq/MoreLINQ/blob/master/MoreLinq/Lag.cs .

    如果您使用 缓慢移动 然后你可以投影原件 IEnumerable<T> 转换为新投影。然后 AutoCorr 可以迭代并返回 sum 而且还要跟踪元素的数量(即 foreach 迭代的结果 缓慢移动 call)并返回那个(所以返回count 和总和 ). 通过这种方式,我们加快了对 Count 通过(卷筒) 将其完全移除 。取消不必要的呼叫确实是最快的方法。

        2
  •  2
  •   Chris Schaller    2 年前

    .Count() 通常是 最高效 计算中元素的方法 IEnumerable<T> .

    如果计算速度比您预期的慢,那么您枚举的对象类型是什么?如果它是一个涉及许多过滤器或函数调用的复杂表达式,那么在我们知道元素是否在最终输出中之前,必须从底层源对它们进行求值。

    IEnumerable<T> 只是一个接口,对于不同的底层具体类型实现和表达式,它们可能具有更高效的特定属性或函数,因此对于个别场景,最好提出一个更具体的问题,重点关注具体类型,而不是接口。

    如果由于重复调用而导致执行速度出现问题,那么您应该考虑 物化 这个 IEnumerable<T> 变成 List<T> ,仍然使用相同的性能命中来解析项目,但现在重复调用 计数 会更有效率。

    • 您可以将其进一步应用于数组,然后长度是集合的物理属性

      var myArray = myItems.ToArray();
      var itemCount = myArray.Length();
      

    不要认为转换为数组然后只访问长度是个好主意,具体化元素只会提高性能 重返大气层 ,它不会优化第一次调用。

    这将是一个糟糕的实现, 请照做 不是 做这个 :

    var itemCount = myItems.ToArray().Length;
    

    当你有这样的逻辑时,通常会提出这个问题:

    if (myItems.Count() > 0) // we forced the iteration of ALL elements
    {
        // TODO: Something...
    }
    

    在这种情况下 .Any() 会更高效,因为我们只需要解决中的第一个元素 IEnumerable<T>

    if (myItems.Any()) // only iterate the first element
    {
        // TODO: Something...
    }
    
        3
  •  1
  •   Joel Coehoorn    2 年前

    这取决于基础项。 并非所有IEnumerable都是有界的 …你可以做一个无限长的 永不停止。

    例如,用一个 BigInteger 键入而不仅仅是 int :

    IEnumerable<int> Fibonacci()
    {
       int n1 = 0;
       int n2 = 1;
    
       yield return 1;
       while (true)
       {
          int n = n1 + n2;
          n1 = n2;
          n2 = n;
          yield return n;
       }
    }
    

    然后您可以使用以下项目 Skip() Take() 制作这种 IEnumerable<T> 仍然做着实际有用的工作。但如果你试着打电话 Fibonacci().Count() ,它将一直运行,直到您的机器断电,您终止进程,或者(很可能)溢出 int 试着把它们都数出来。

    相反,你要记住 IEnumerable<T> 只是一个接口,对象也将始终具有 实型 在那里的某个地方。你能知道或保证这种类型可能会实现 更好的界面 对于此任务。。。可能 IList<T> ?

        4
  •  0
  •   Ivan Petrov    2 年前

    编辑:答案是问题的第一个版本,该版本不包含任何源代码,我可以XY方式接收已接受的答案。

    最有效的方式是官方方式,即 Count 上的扩展方法 IEnumerable<T>.

    这是 source code ,我相信微软没有错过任何东西。

    public static int Count<TSource>(this IEnumerable<TSource> source) {
        if (source is null) {
            ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
        }
    
        if (source is ICollection<TSource> collectionoft) {
            return collectionoft.Count;
        }
    
    #if !OPTIMIZE_FOR_SIZE
        if (source is Iterator<TSource> iterator) {
            return iterator.GetCount(onlyIfCheap: false);
        }
    #endif
    
        if (source is ICollection collection) {
            return collection.Count;
        }
    
        int count = 0;
        using (IEnumerator<TSource> e = source.GetEnumerator()) {
            checked {
                while (e.MoveNext()) {
                    count++;
                }
            }
        }
    
        return count;
    }