如何使用单个枚举在没有缓冲的情况下检查IEnumerable的多个条件?

问题描述 投票:0回答:2

我的数据序列很长,是IEnumerable的形式,我想检查一下是否有许多情况。每个条件都返回true或false值,我想知道所有条件是否为true。我的问题是我无法通过调用IEnumerable实现ToList,因为它太长了(> 10,000,000,000个元素)。我也不愿意多次枚举序列,每种情况一次,因为每次我都会得到不同的序列。我正在寻找一种有效的方法来执行此检查,并尽可能使用现有的LINQ功能。

这是我的序列的虚拟版本:

static IEnumerable<int> GetLongSequence()
{
    var random = new Random();
    for (long i = 0; i < 10_000_000_000; i++) yield return random.Next(0, 100_000_000);
}

这是序列必须满足的条件的示例:

var source = GetLongSequence();
var result = source.Any(n => n % 28_413_803 == 0)
    && source.All(n => n < 99_999_999)
    && source.Average(n => n) > 50_000_001;

不幸的是,此方法调用的是GetLongSequence的三倍,因此它不能满足问题的要求。

我试图编写上面的Linqy扩展方法,希望这可以给我一些想法:

public static bool AllConditions<TSource>(this IEnumerable<TSource> source,
    params Func<IEnumerable<TSource>, bool>[] conditions)
{
    foreach (var condition in conditions)
    {
        if (!condition(source)) return false;
    }
    return true;
}

这是我打算如何使用它:

var result = source.AllConditions
(
    s => s.Any(n => n % 28_413_803 == 0),
    s => s.All(n => n < 99_999_999),
    s => s.Average(n => n) > 50_000_001
);

很遗憾,这没有任何改善。 GetLongSequence再次被调用三次。

在将我的头撞在墙上一个小时之后,没有取得任何进展,我想出了一个可能的解决方案。我可以在单独的线程中运行每个条件,并使它们对序列的单个共享枚举器的访问同步。所以我最终变成了这种怪物:

public static bool AllConditions<TSource>(this IEnumerable<TSource> source,
    params Func<IEnumerable<TSource>, bool>[] conditions)
{
    var locker = new object();
    var enumerator = source.GetEnumerator();
    var barrier = new Barrier(conditions.Length);
    long index = -1;
    bool finished = false;

    IEnumerable<TSource> OneByOne()
    {
        try
        {
            while (true)
            {
                TSource current;
                lock (locker)
                {
                    if (finished) break;
                    if (barrier.CurrentPhaseNumber > index)
                    {
                        index = barrier.CurrentPhaseNumber;
                        finished = !enumerator.MoveNext();
                        if (finished)
                        {
                            enumerator.Dispose(); break;
                        }
                    }
                    current = enumerator.Current;
                }
                yield return current;
                barrier.SignalAndWait();
            }
        }
        finally
        {
            barrier.RemoveParticipant();
        }
    }

    var results = new ConcurrentQueue<bool>();
    var threads = conditions.Select(condition => new Thread(() =>
    {
        var result = condition(OneByOne());
        results.Enqueue(result);
    })
    { IsBackground = true }).ToArray();
    foreach (var thread in threads) thread.Start();
    foreach (var thread in threads) thread.Join();
    return results.All(r => r);
}

为了进行同步,使用了Barrier。这个解决方案实际上比我想象的要好。它每秒可以在我的机器上处理近1,000,000个元素。但是速度不够快,因为处理10,000,000,000个元素的整个序列需要将近3个小时。而且我等不及结果超过5分钟。关于如何在单个线程中高效运行这些条件的任何想法?

c# linq ienumerable
2个回答
0
投票

如果您只想在一个枚举中检查单个线程上的这三个条件,我就不会使用LINQ并手动汇总检查:

Barrier

如果您计划经常进行这些检查,但看起来可以满足您的所有要求,则可以更通用。


0
投票

如果需要确保仅对序列进行一次枚举,则对整个序列进行操作的条件将无用。我想到的一种可能性是拥有一个针对序列中每个元素调用的接口,并针对您的特定条件以不同的方式实现此接口:

bool anyVerified = false;
bool allVerified = true;
double averageSoFar = 0;

foreach (int n in GetLongSequence()) {
    anyVerified = anyVerified || n % 28_413_803 == 0;
    allVerified = allVerified && n < 99_999_999;
    averageSoFar += n / 10_000_000_000;
    // Early out conditions here...
}
return anyVerified && allVerified && averageSoFar > 50_000_001;
© www.soinside.com 2019 - 2024. All rights reserved.