У меня очень длинная последовательность данных в форме IEnumerable
, и я хотел бы проверить ее на наличие ряда условий. Каждое условие возвращает значение true или false, и я хочу знать, выполняются ли все условия. Моя проблема в том, что я не могу позволить себе материализовать 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,
// more conditions...
);
К сожалению, это не дает никаких улучшений. 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 минут. Любые идеи о том, как я мог бы эффективно выполнить эти условия в одном потоке?