Реализовал метод по своему описанию :
Код | static int[] Exclude(int[] source, int[] filter, out int ifCount) { // Assume the source and filter arrays are SORTED in ASCENDING order.
int[] indices = new int[filter.Length + 2]; indices[0] = -1; indices[filter.Length + 1] = source.Length;
// Here to sum the comparisions count. ifCount = 0;
int beginIndex = 0; for (int filterIndex = 0; filterIndex < filter.Length; filterIndex++) { int endIndex = source.Length - 1, centerIndex = endIndex;
int diffrent = source[centerIndex] - filter[filterIndex]; while (diffrent != 0) { int newCenterIndex = beginIndex + (endIndex - beginIndex) / 2; if (centerIndex == newCenterIndex) throw new ArgumentException("Filter value " + filter[filterIndex] + " not found."); centerIndex = newCenterIndex; diffrent = source[centerIndex] - filter[filterIndex]; if (diffrent < 0) beginIndex = centerIndex; else endIndex = centerIndex; ifCount++; } beginIndex = centerIndex;
indices[filterIndex + 1] = centerIndex; }
// Eliminate elements with known indices. int[] result = new int[source.Length - filter.Length]; int resultIndex = 0; for (int indicesIndex = 0; indicesIndex < indices.Length - 1; indicesIndex++) for (int index = indices[indicesIndex] + 1; index < indices[indicesIndex + 1]; index++) { result[resultIndex] = source[index]; resultIndex++; } return result; }
|
Поскольку больше интересовала скорость поиска, не стал заморачиваться и аккумулировал в промежуточный массив индексы элементов, которые должны быть удалены (пропущены при перестроении). В идеале, это можно делать "на лету".
Код | static void Main(string[] args) { int ifCount;
int[] a = new int[] { 2, 9, 27, 34, 47, 52, 77, 83 }; int[] b = new int[] { 52, 83 };
int[] c = Exclude(a, b, out ifCount);
for (int index = 0; index < c.Length; index++) Console.Write(c[index] + " "); Console.WriteLine(); Console.WriteLine(ifCount * 2 + " (" + ifCount + ") comparisions used."); Console.ReadKey(); }
|
Код | 2 9 27 34 47 77 4 (2) comparisons used.
|
В данном случае метод использовал 4 сравнения, по два на итерацию цикла. В принципе, можно напрячься и сделать одно сранвнение на итерацию за счёт замены битовыми и арифметическими операциями. В любом случае, так как сравнивается одно и то же,
Код | while (diffrent != 0) ... if (diffrent < 0)
|
то два сравнения можно считать почти за одно.
Код | static void Main(string[] args) { int ifCount;
int[] a = new int[] { 1, 3, 4, 5, 7, 9, 11, 17 }; int[] b = new int[] { 3, 7, 17 };
int[] c = Exclude(a, b, out ifCount);
for (int index = 0; index < c.Length; index++) Console.Write(c[index] + " "); Console.WriteLine(); Console.WriteLine(ifCount * 2 + " (" + ifCount + ") comparisions used."); Console.ReadKey(); }
|
Код | 1 4 5 9 11 6 (3) comparisons used.
|
Количество сравнений выглядит выгодно. Но пара тестов - это ерунда. Для отладки косяков метода был использован агрессивный тест, генерящий серию случайных массиов a и b (см. проект). Побочным результатом явилось то, что на таком псевдослучайном генераторе (System.Random) метод в среднем проигрывает по числу сравнений. Так что, если любите статистику, то можно попробовать поискать условия, при которых он работает эффективнее прямого сравнения. А может, это вообще никуда не годится Всё же, полагаю, у него сложность = log2(f(a.Length, b.Length)), что не обязательно хуже, чем у прямого сравнения, сложность которого определяется индексом наибольшего из элементов b в массиве a.
А на счёт List-ов - это конечно особенность, в чём-то даже преимущество .NET : любой вычислительный алгоритм, который неохота (некогда, ненужно, ...) писать эффективно можно реализовать стадесятью неэффективными способами, особо не думая. Не каждый язык+средства может предложить такую свободу. В этом смысле .NET - рай, если требуется что-то сделать "на коленке" |