środa, 23 maja 2012

Aggregate - One Method to rule them all

LINQ w C# wywarł ma mnie ogromny wpływ. Za pomocą tej technologii mogłem operować na obiektach tak jak za pomocą SQLa. Na przykład przy użyciu metod Where i Sum mogłem prosto i szybko sumować liczby, które są parzyste. Problem pojawia się kiedy chciałbym wykonać coś bardziej skomplikowanego lub złożonego, na przykład obliczyć odchylenie standardowe. Pierwsze rozwiązanie jakie przychodzi mi do głowy to stworzenie metody do obliczenia. Takie rozwiązanie jest dobre, więc kiedyś stworzyłem projekt z samymi extension metods. Projekt można pobrać z codeplex.com. Ale czy za każdym razem należy tworzyć nową metodę? Czy nie możemy zastosować już dostępnych metod do manipulacji kolekcją danych? Z pomocą przychodzi nam niedoceniana metoda Aggregate. Ta funkcja jest do agregacji elementów. Poniżej przedstawiam jej zastosowanie.



Na początku zastąpię proste metody jak suma, count, all i concat za pomocą aggregate.

var ints = Enumerable.Range(0, 10);
var sum1 = ints.Sum();
var sum2 = ints.Aggregate( ( agg, item ) => agg + item );


var count1 = ints.Count();
var count2 = ints.Aggregate(0,(agg, item) => agg + 1, x=>x);

var median1 = ints.ElementAt(ints.Count()/2);
var median2 = ints.Aggregate( 0, ( info, item ) => info = info + 1,
                             x => ints.ElementAt( x / 2 ) );

var all1 = ints.All(x => x > -1);
var all2 = ints.Aggregate( true,
               ( info, item ) =>
               {
               if (info == true && item > -1)
               {
                  return true;
               }
                  return false;
               },
               x => x);


var secondInts = Enumerable.Range(12, 3);
var concat1 = ints.Concat( secondInts );
var concat2 = secondInts.Aggregate( new List<int>( ints ),
              ( list, i )=> { list.Add( i ); return list; },
               list => list );
                     
Ok, to teraz bardziej statystyczne funkcje.
var range1 = ints.Max() - ints.Min();
var range2 = ints.Aggregate( new
{ Max = ints.First(), Min = ints.First() },
( x_range, item ) =>{
     var max = x_range.Max;
     var min = x_range.Min;
     if (max < item)
         max = item;
     
     if (min > item)
         min = item;
     
     return new {Max = max, Min = min}; },
     x_range => x_range.Max - x_range.Min);



var intsRepeatable = ints.Concat( new List<int> { 1, 2, 2, 5, 6 } );
var mode1 = intsRepeatable
            .ToLookup( x => x )
            .OrderByDescending( x => x.Count() )
            .Select( x => x.Key ).First();

var mode2 = intsRepeatable.Aggregate( new Dictionary<int, int>(),
                          ( dic, item ) =>
                          { if (dic.Keys.Contains( item ))
                                dic[item]++; 
                           else
                                dic.Add( item, 1 ); 
                           return dic;
                          },
                          dic => 
                              dic.OrderByDescending( x => x.Value )
                                 .First())
                          .Key;

        

A na koniec ciekawsze smaczki, czyli średnia krocząca dla 3 okresów i odchylenie standardowe.
int movingMeanCounter = 3;
var movingMean3 = ints.Aggregate(
                    new
                    {
                    List = new List<List<int>>(),
                    Counter = 0
                    },
                    ( info, item ) =>
                    {
                        info.List.Add( new List<int>() );
                        for (int i = 0 ; 
                        i < movingMeanCounter 
                            && info.Counter - i >= 0 ; 
                        i++)
                        {
                        var vList = info.List[info.Counter - i];
                        vList.Add( item );
                        }

                        return new
                        {
                        List = info.List,
                        Counter = info.Counter + 1
                        };
                    },
                    x => x.List.Select( item => item.Average() ));

var stdDev = ints.Aggregate( new
                    {
                    Mean = 0,
                    Sum = 0,
                    i = 0
                    },
                    ( info, item ) =>
                    {
                        var i = info.i + 1;
                        var delta = item - info.Mean;
                        var mean = delta / i;
                        var sum = info.Sum + delta * (item - mean);
                        return new
                        {
                            Mean = mean,
                            Sum = sum,
                            i = i
                        };
                    },
                    x => x.i < 2 
                        ? 0 
                        : Math.Sqrt( x.Sum / (x.i - 1) ));            

Możemy wiele ciekawych rzeczy zrobić za pomocą aggregate. Gdybyś miał pomysł na zastosowanie tej metody to jestem otwarty na propozycje. Więcej o niej można poczytać tutaj.

Brak komentarzy:

Prześlij komentarz