Code Review: Scalanie pętli za pomocą LINQ

Czasami można zaobserwować następujący kod:

int[] firstArray = Enumerable.Range(1, 1000).ToArray();
int[] secondArray = Enumerable.Range(1, 1000).ToArray();

foreach (int item in firstArray)
{
    Process(item);
}

foreach (int item in secondArray)
{
    Process(item);
}

Mam na myśli sytuacje kiedy mamy kilka osobnych tablic, ale przetwarzanie ich jest takie same lub bardzo podobne. Inny przykład to przetworzenie tablicy, a potem pojedynczego elementu pochodzącego z innego źródła:

int[] firstArray = Enumerable.Range(1, 1000).ToArray();
var singleItem=GetItem(...);

foreach (int item in firstArray)
{
 Process(item);
}

Process(singleItem);

W skrócie, w kodzie takim występujące kilka źródeł danych albo pojedyncze elementy.  Tworzenie kilku pętli, jak wyżej jest brzydkie i niewygodne. Lepiej skorzystać z LINQ i złączyć kilka źródeł w jedno. Możemy to zrobić za pomocą concat albo union:


int[] firstArray = Enumerable.Range(1, 1000).ToArray();
int[] secondArray = Enumerable.Range(1, 1000).ToArray();

foreach (int item in firstArray.Union(secondArray))
{
    Process(item);
}

foreach (int item in firstArray.Concat(secondArray))
{
    Process(item);
}

Jaka jest różnica między union a concat? Union złączy tylko unikalne elementy czyli to tak jakby wywołać concat a potem na końcu Distinct(), który zwraca wyłącznie unikalne liczby. Oczywiście ma to ogromny wpływ na wydajność ponieważ należy w przypadku union wywoływać Equals oraz GetHash:

private static void Main(string[] args)
{

  int[] firstArray = Enumerable.Range(1, n).ToArray();
  int[] secondArray = Enumerable.Range(1, n).ToArray();

  TestConcat(firstArray,secondArray);
  TestUnion(firstArray,secondArray);

}
private static void TestUnion(int[] array1, int[] array2)
{           
  for (int i = 0; i < Tests; i++)
  {
      Stopwatch stopwatch = Stopwatch.StartNew();

      foreach (var item in array1.Union(array2))
      {
      }

      Console.WriteLine("Union: {0}", stopwatch.ElapsedTicks);
  }
}

private static void TestConcat(int[] array1, int[] array2)
{           
  for (int i = 0; i < Tests; i++)
  {
      Stopwatch stopwatch = Stopwatch.StartNew();

      foreach (var item in array1.Concat(array2))
      {
      }

      Console.WriteLine("Concat: {0}",stopwatch.ElapsedTicks);
  }
}

Wynik:

image

Sprawa może wydawać się łatwa ale czasami, gdy kod jest bardziej skomplikowany nie jest zbyt oczywista. W praktyce, nie zawsze mamy dokładnie te same typy i wtedy należy dokonać np. transformacji aby ujednolicić przetwarzane dane.

One thought on “Code Review: Scalanie pętli za pomocą LINQ”

  1. Czy nie lepiej zmodyfikować (lub stworzyć metodę enkapsulującą) metodę Process() aby przyjmowała zmienną liczbę argumentów (deklaracja params)?

    Dzięki temu ograniczymy nadmierny kod, a przy okazji będzie go można wykorzystać zarówno do tablic jak i pojedynczych obiektów

Leave a Reply

Your email address will not be published.