Если у вас есть два набора, которые оба отсортированы, вы можете реализовать более быстрое пересечение, чем что-либо, поставляемое из коробки с LINQ.
По сути, оставляйте открытыми два курсора IEnumerator<T>
, по одному для каждого набора.В любой момент, аванс, который имеет меньшее значение.Если они совпадают в любой точке, продвигайте их обоих, и так далее, пока не дойдете до конца любого итератора.
Хорошая вещь в этом состоит в том, что вам нужно только итерировать каждый набор один раз, и вы можете сделатьэто в памяти O (1).
Вот пример реализации - не тестировался, но он компилируется :) Предполагается, что обе входящие последовательности не содержат дубликатов и отсортированы, как в соответствии с предоставленным компаратором (passв Comparer<T>.Default
):
(в конце ответа больше текста!)
static IEnumerable<T> IntersectSorted<T>(this IEnumerable<T> sequence1,
IEnumerable<T> sequence2,
IComparer<T> comparer)
{
using (var cursor1 = sequence1.GetEnumerator())
using (var cursor2 = sequence2.GetEnumerator())
{
if (!cursor1.MoveNext() || !cursor2.MoveNext())
{
yield break;
}
var value1 = cursor1.Current;
var value2 = cursor2.Current;
while (true)
{
int comparison = comparer.Compare(value1, value2);
if (comparison < 0)
{
if (!cursor1.MoveNext())
{
yield break;
}
value1 = cursor1.Current;
}
else if (comparison > 0)
{
if (!cursor2.MoveNext())
{
yield break;
}
value2 = cursor2.Current;
}
else
{
yield return value1;
if (!cursor1.MoveNext() || !cursor2.MoveNext())
{
yield break;
}
value1 = cursor1.Current;
value2 = cursor2.Current;
}
}
}
}
РЕДАКТИРОВАТЬ: Как отмечено в комментариях, в некоторых случаях вы можете иметь один вход, которыйнамного больше, чем другие, и в этом случае вы могли бы потенциально сэкономить много времени, используя бинарный поиск для каждого элемента из меньшего набора в большем наборе.Однако для этого требуется произвольный доступ к большему набору (это просто предпосылка бинарного поиска).Вы даже можете сделать его немного лучше, чем простой двоичный поиск, используя совпадение из результата предыдущий , чтобы дать нижнюю границу для двоичного поиска.Предположим, вы искали значения 1000, 2000 и 3000 в наборе с каждым целым числом от 0 до 19,999.На первой итерации вам нужно будет просмотреть весь набор - ваши начальные нижний / верхний индексы будут 0 и 19,999 соответственно.Однако после того, как вы нашли совпадение с индексом 1000, шаг следующий (где вы ищете 2000) может начинаться с более низкого индекса 2000. По мере продвижения, диапазон, в котором вам нужноискать постепенно сужается.Однако стоит ли это дополнительных затрат на реализацию или нет, это другой вопрос.