我的数据序列很长,是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分钟。关于如何在单个线程中高效运行这些条件的任何想法?
如果您只想在一个枚举中检查单个线程上的这三个条件,我就不会使用LINQ并手动汇总检查:
Barrier
如果您计划经常进行这些检查,但看起来可以满足您的所有要求,则可以更通用。
如果需要确保仅对序列进行一次枚举,则对整个序列进行操作的条件将无用。我想到的一种可能性是拥有一个针对序列中每个元素调用的接口,并针对您的特定条件以不同的方式实现此接口:
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;