2009-10-11

LINQ - Przyspieszenie metody Count()

Metoda Count() zlicza nam ilość elementów w enumeracji. Bardzo często chcemy sprawdzić, czy nasz zbiór zawiera określoną ilość elementów. Nie ma wtedy sensu zliczać wszystkich elementów, musimy tylko zliczyć tyle ile nam potrzeba. Oto przykład:
var gr = from n in new int[] { 1, 1, 1, 2, 2 }
         where n == 1
         select n;

bool b1 = gr.Count() == 0;
bool b2 = gr.Count() != 0;
bool b3 = gr.Count() == 2;
bool b4 = gr.Count() != 2;
bool b5 = (gr.Count() >= 2) && (gr.Count() <= 3);
Wraz ze wzrostem ilości elementów w gr całość zaczyna działać coraz wolniej. Oczywiście możemy przekształcić gr w listę lub tablice, wtedy metoda Count() będzie działać błyskawicznie, ale będzie się to wiązało z kosztem czasowym (i pamięciowym) utworzeniem takiego obiektu (umiejętne wykorzystanie metody ToList() i ToArray() aby na pewnym etapie uzyskać wynik, którego już nie trzeba będzie ponownie wyliczać, także pozwala nam na przyspieszenie wyrażeń LINQ). Próbując opierać się tylko tym co dostarcza nam LINQ, powyższy przykład możemy przyspieszyć tak:
var gr = from n in new int[] { 1, 1, 1, 2, 2 }
         where n == 1
         select n;

bool b1 = !gr.Any();
bool b2 = gr.Any();
bool b3 = gr.Take(3).Count() == 2;
bool b4 = gr.Take(3).Count() != 2;
bool b5 = (gr.Take(2).Count() == 2) && (gr.Take(4).Count() <= 3);
Pierwsze dwa testy sprowadzają się do pobrania ze zbioru tylko jednego elementu. Test b3 i b4 wymaga pobrania tylko jednego więcej elementu niż jest wymagane (metoda Take() nie zwraca trzech elementów, zwraca enumerator, który zwróci co najwyżej trzy elementy). Test b5 może być w pesymistycznym przypadku tak wolny jak poprzednio, ale generalnie powinien być szybszy. Ciągle jednak możemy go przyspieszyć rezygnując z dwukrotnej enumeracji po zbiorze za pomocą poniższej metody:
public static bool IsCountInRange<T>(this IEnumerable<T> a_enumerable, int a_min, int a_max)
{
    Debug.Assert(a_min >= 0);
    Debug.Assert(a_max >= a_min);

    int count = 0;
    IEnumerator<T> enumerator = a_enumerable.GetEnumerator();

    while (enumerator.MoveNext())
    {
        count++;
        if (count > a_max)
            return false;
    }

    return (count >= a_min);
}
Przy jej pomocy powyższy przykład możemy zapisać:
var gr = from n in new int[] { 1, 1, 1, 2, 2 }
         where n == 1
         select n;

bool b1 = !gr.Any();
bool b2 = gr.Any();
bool b3 = gr.Take(3).Count() == 2;
bool b4 = gr.Take(3).Count() != 2;
bool b5 = gr.IsCountInRange(2, 3);
Metoda IsCountInRange() sprawdza, czy ilość elementów zawiera się w podanym zakresie. W przeciwieństwie do poprzedniego testu b5 tutaj enumeracja po zbiorze dokonywana jest jednokrotnie.

Brak komentarzy:

Prześlij komentarz