Korištenje LEFT i RIGHT JOIN pomoću LINQ


LINQ je, slobodno možemo reći, uveliko promijenio način programiranja u .NET programskim jezicima na način da je nešto što je prije LINQ-a izgledalo jako komplikovano i vrlo zahtjevno danas sa LINQom jednostavno, lagano i „vrlo prosto“.
O LINQ-u sam najviše napisao blog postova. Skoro da nema posta ovdje koji se nekim dijelom ne dotiče LINQa. Ali, s LINQom uvijek se nešto otkrije i pokuša primjeniti u stvarnom primjeru, a inspiraciju nađemo ili u standradnom SQL jeziku ili je inspiracija pronađena u nekom od postojećih algoritama.
Danas ćemo vidjeti kako se preko LINQa implementira LEFT i RIGHT JOIN, koji predstavljaju osnovne operatore SQL jezika. Šta je JOIN odnosno LEFT i RIGHT JOIN možete pogledati na ovom linku , a jednostavno možemo reći da su to osnovni operatoru spajanja dva ili više skupova podataka.
Obični JOIN vrlo lako možemo implementirati preko LINQ. Npr. pretpostavimo da imamo dva tipa podataka: Vehicle tip i VehicleType tip podataka. Pretpostavimo i to da svaki Vehicle tip sadrži osobinu koja nam govori o kakvom VehicleType tipu je riječ. Prevedeno na domaći jezik možemo kazati da svako Vozilo (eng. Vehicle) pripada nekom tipu vozila (eng. VehicleType). Neka imamo sljedeću deklaraciju tipova podataka:

class VehicleGroup
{
    public int? ID;
    public string Name;

}
class VehicleType
{
    public int? ID;
    public string Name;
    public  int? vehiclegroupID;
}
class Vehicle
{
    public int? ID;
    public string  Name;
    public int? vehicletypeID;
}

Svako vozilo (Vehicle) pripada tipu vozila, a tipovi vozila pripadaju određenoj grupi vozila (VehicleGroup). Kada bi željeli da izlistamo sva vozila sa pripadajućem tipom i grupom, LINQ izraz bi izgedao kao na sljedeći način:

//List all vehicles with coresponded types and groups without LEFT JOIN
var q = from v in vehicles
        from t in types
        where v.vehicletypeID == t.ID
        from g in groups
        where t.vehiclegroupID == g.ID
        select new
        {
            VehicleID = v.ID,
            VehicleName = v.Name,
            VehicleTypeName = t.Name,
            VehicleGroupName = g.Name
        };

Ovdje se može primjetiti da je operator JOIN u LINQ implementiran preko WHERE operatora. Mana ovog izraza jesta ta da u koliko ne postoji Vehicle objekat nekog specifičnog VehicleType tipa odnosno grupe, dotični tip i i grupa se neće pojaviti u rezultatu.
U koliko želimo da izlistamo sve grupe i tipove bez obzira da li imamo Vehicle tip definisan, to nećemo moći gornjom implementacijom. Ista situacija se dešava i u relacijskim bazama podataka pa je nužno koristiti LEFT ili RIGHT JOIN.
Da bi prikazali sve grupe i tipove bez obzira da li za dotične postoji Vehicle tip implementacija LINQa data je na sljedećem listingu:

//List all vehicles with coresponded types and groups with RIGHT JOIN,
//If we want LEFT JOIN from below query order of the collections will be as in the previous query
var q = from grp in groups

        from typ in types
                .Where(t=>t.vehiclegroupID==grp.ID)
                .DefaultIfEmpty()//this is important method for RIGHT JOIN

        from veh in vehicles
                .Where(v=>v.vehicletypeID == typ.ID)
                .DefaultIfEmpty()//this is important method for RIGHT JOIN
        select new
        {
            VehicleID = veh==null?0: veh.ID,
            VehicleName = veh == null ? "unknown" : veh.Name,
            VehicleTypeName = typ==null? "unknown" : typ.Name,
            VehicleGroupName = grp==null? "unknown":grp.Name
        };

Vidimo da je implementacija RIGHT JOIN slična prethodnoj implementaciji. Razlika se sastoji u poretku kolekcija, gdje ovdje idemo od najvišeg tipa po hijearhiji do najnižeg. Druga stvar koju smo ovdje koristili jeste metoda DefaultIfEmpty() koja nam sprečava da LINQ upit “pukne” kada se u procesuiranju dođe do null vrijednosti. Na kraju pravimo novi tip u koji unosimo vrijednosti iz svih kolekcija. Međutim pošto se ovdje može desiti da Vehicle ne postoji za određeni tip ili da tip i vehicle ne postoji za određenu grupu, prilikom inicijalizacije kolekcije sa rezultatom provjeravamo da li je dotični property null. Na ovaj način se također sprečavamo da nam LINQ izraz ne “pukne” u toku procesuiranja. LEFT JOIN se jednostavno implementira na način da ovom upitu zamjenimo poredak kolekcija, odnosno da poredak bude isti kao u prvom upitu,  a ostalo ostaje isto.

Na kraju napravimo jednostavni demo koji će nam demonstrirati prethodno rečeno. Implementirajno prezentirane kolekcije vehicle, types i group sa podacima, ali na taj način da imamo jedan VehicleType koji neće sadržavati ni jedan Vehicle.

private static void InitData(out List groups,out List types,out List vehicles)
        {
            groups = new List()
            {
                new VehicleGroup()
                {
                    ID=0,
                    Name="Unknown"
                },
                new VehicleGroup()
                {
                    ID=1,
                    Name= "Group1"
                },
                new VehicleGroup()
                {
                    ID=2,
                    Name= "Group2"
                }
            };

            types = new List()
            {
                new VehicleType()
                {
                    ID=0,
                    Name="Unknown",
                    vehiclegroupID=0
                },
                new VehicleType()
                {
                    ID=1,
                    Name= "Type1",
                    vehiclegroupID=1

                },
                new VehicleType()
                {
                    ID=2,
                    Name= "Type2",
                    vehiclegroupID=1
                },
                new VehicleType()
                {
                    ID=3,
                    Name= "Type3",
                    vehiclegroupID=2
                },
                new VehicleType()
                {
                    ID=4,
                    Name= "Type4",
                    vehiclegroupID=1
                }
            };
            vehicles = new List()
            {
                new Vehicle()
                {
                    ID=0,
                    Name="Unknown",
                    vehicletypeID=0
                },
                new Vehicle()
                {
                    ID=1,
                    Name= "Vehicle1",
                    vehicletypeID=1

                },
                new Vehicle()
                {
                    ID=2,
                    Name= "Vehicle2",
                    vehicletypeID=2
                },
                new Vehicle()
                {
                    ID=3,
                    Name= "Vehicle3",
                    vehicletypeID=3
                },
                new Vehicle()
                {
                    ID=4,
                    Name= "Vehicle4",
                    vehicletypeID=1
                },
                new Vehicle()
                {
                    ID=5,
                    Name= "Vehicle5",
                    vehicletypeID=2
                },
                new Vehicle()
                {
                    ID=6,
                    Name= "Vehicle6",
                    vehicletypeID=2
                },
                new Vehicle()
                {
                    ID=7,
                    Name= "Vehicle7",
                    vehicletypeID=2
                },
                new Vehicle()
                {
                    ID=8,
                    Name= "Vehicle8",
                    vehicletypeID=2
                },
                new Vehicle()
                {
                    ID=9,
                    Name= "Vehicle9",
                    vehicletypeID=3
                },
                new Vehicle()
                {
                    ID=10,
                    Name= "Vehicle10",
                    vehicletypeID=3
                }
            };

        }

Sada implementirajmo obični i RIGHT JOIN a rezultat pokretanja demo aplikacije vidimo na sljedećoj slici:

Sa slike se može vidjeti da u prvom primjeru imamo 11 stavki, a u drugoj 12. 12-ta stavka je upravo ona stavka koja nije prikazana u prvom slučaju, odnosno nije prikazana stavka sa VehicleType.Name=”Type4″.
Cjelokupan demo može se preuzeti sa ovog linka.

Advertisement

Detaljni pregled LINQ – Integrirani SQL upiti u .NET programskom jeziku


LINQ ili Language Integrated Query je sastavni dio  .NET programskog jezika prilagođen da izvršava upit nad bilo kojom vrstom izvora podataka na način kako se to radi sa SQL jezikom. LINQ čini skup operatora koji po mnogo čemu sliče SQL komandama. Pomoću LINQ operatora mnogostuko se skraćuje vrijeme sortiranja, grupiranja, filtriranja podataka koji se dosada morao implementirati ili koristiti biblioteke trećih lica. LINQ koristimo kako za memorijske rtko i za eksterne izvore podataka poput DB i XML. Skraćenica LINQ najbolje bi se mogla prevesti kao SQL osobine integrirane u programski jezik.

Uvod

U Posljednjih 20 godina razvoj programskih jezika došao je do stadija kada posjedujemo moćne objektno orijentisane programske jezike poput C++, C# i JAVA. Programeri, koristeći ove jezike danas koriste sve blagodati OOP poput klasa, objekata, nasljeđivanja, polimorfizma, iskorištavanje postojećeg koda. Možemo kazati da je era OOP  došla do svog vrhunca, uz prethodno programeri danas koriste programske jezike koji imaju sposobnost oslobađanja i kontrole curenja memorije u programskim jezicima. U posljednjih nekoliko godina postavlja se pitanje šta dalje i u kom pravcu razvijati programske jezike, na način da pomažu programerima u razvijanju softvera.

Jedan pravac u kojem se programski jezici razvijaju, a koji je posljednjih nekoliko godina sve više zastupljen je razvijanje IDE alata za automatsko generiranje koda tzv. Rapid Application Developmen (RAD).  Danas kad pogledamo softvere za razvoj aplikacija vidimo mnogo ugrađenih alata za generiranje raznih, uglavnom ponavljajući sekvenci izvornog koda. Danas se aplikacije razvijaju na način da imate osjećaj kako radite u nekom od klasičnih softvera za crtanje: povlačite stavke iz kutija sa alatima, dijagramski pišete klase i dodajete metode, članove, nasljeđivate klase itd.

Sagledavajući današnju tehnologiju i pravac u kome se razvija buduća tehnologija, svakako je smanjivanje kompleksnosti pristupa i integracije informacijama koja nije prirodno povezana se OPP. Naime, svaka aplikacije manipuliše određenim podacima koji su po prirodi stvari odvojeni od same aplikacije. Podaci kojim manipulišemo dolaze uglavnom iz dva glavna izvora, a to su relacijske baze podataka i XML.

Manipulacija sa podacima koji dolaze iz ova dva izvora programera više stvara svestranijim nego stručnijim, jer stvara uslov poznavanja drugih tehnologija osim primarne tehnologije razvoja aplikacija u određenom programskom jeziku. LINQ projekat se bazira na problemima manipulacije podataka prethodno opisanih.  Engleski izgovor ove kratice izgovara se kratko LINK.

LINQ kako i sam naziv govori je sastavni dio primarnog programskog jezika, koji sa sastoji od standardnih operatora upita  za manipulaciju, projekciju i filtriranje sa podacima na način sličan kako se to radi pomoću SQL. Izvor podataka nad kojim se vrše upiti ne ograničava se samo na eksterne izvore podataka, nego na generalno svaki izvor podataka kako eksterni tako i interni – aplikacijski izvor podataka. Ovo omogućuje da programer ostaje u svom primarnom programskom jeziku dok manipuliše podacima iz izvora podataka.

LINQ operatori mogu se primjeniti na svaki izvor podataka koji je izveden iz IEnumerable interfejsa. Ovo omogućava razvijanje komponenti trećih strana baziranih na LINQ-u preklapanjem standardnih operatora. Do sada takvih implementacija je urađeno, a najpoznatiji su LINQ to Amazon, LINQ  to Google i td. Važno je spomenuti da se na Open Source zajednicama poput Source Forge pokrenuto više desitina sličnih projekata.

Upoznavanje sa LINQ

Najbolji način kako se približiti LINQ je da se napiše jednostavan primjer napisan u izvornom kodu.

class Program
{
static void Main(string[] args)
{
   //Definisanje polja stringova gradova
   string[] gradovi = {
                        "Sarajevo",
                        "Tuzla",
                        "Mostar",
                        "Banja Luka",
                        "Zenica",
                        "Bihać",
                        "Cazin"
                    };

//Konstrukcija LINQ upita
IEnumerable izraz = from g in gradovi where g.Length == 5 orderby g select g.ToUpper();

//izvršavanje upita na poljem stringova
foreach (string grad in izraz)
Console.WriteLine(grad);

}
}

Ako se ovaj program pokrene pritiskom na F5, dobijamo rezultat kao na sljedećoj slici.

image

Kao što smo naglasili svaki tip koji je izveden iz IEnumerable interfejsa podržava LINQ operatore. U našem primjeru koristili smo 3 standardna operatora upita: select, where, orderby. Isti primjer možemo napisati bez dotičnih operatora na sljedeći način:

var izraz = gradovi.Select(g => g.ToUpper())
.Where(g => g.Length == 5)
.OrderBy(g=>g);

Argumenti koji se pojavljuju u metodama standardnih upita zovem lambda izrazi (lambda expressions) , oni obezbjeđuju da se standardni operatori upita definišu posebno kao metode i spajaju lančano korištenjem tačka notacije (dot notation).

Izvršavanje LINQ upita

U prethodnom primjeru značajno je kazati da se evaluacija upita ne dešava u trenutku njegovog deklarisanja. Evaluacija LINQ upita se dešava u onom trenutku kada pristupamo varijabli odnosno u našem primjeru evaluacija upita vrši se u bloku koji je označen naredbom foreach. Ovakva odgođena (defered) evaluacija upita upit čini fleksibilnim i dozvoljava izvršavanje više puta sa jednom definicijom. Pretpostavimo sljedeći primjer:

class Program
{
static void Main(string[] args)
{
   //Definisanje polja stringova gradova
   string[] gradovi = {
                        "Sarajevo",
                        "Tuzla",
                        "Mostar",
                        "Banja Luka",
                        "Zenica",
                        "Bihać",
                        "Cazin"
                    };

  //Konstrukcija LINQ upita
  var izraz = from g in gradovi where g.Length == 5 orderby g select g.ToUpper();

  //izvršavanje upita na poljem stringova
  foreach (string grad in izraz)
    Console.WriteLine(grad);

   Console.WriteLine("-------- Drugo izvršavanje upita----");

   //Modifikacija polja
   gradovi[1] = "Brčko";

   //ponovno izvršavanje upita na poljem stringova
   foreach (string grad in izraz)
    Console.WriteLine(grad);

   Console.ReadLine();
 }
}

Ako gornji primjer kompajliramo i pokrenemo rezultat se dobije kao na sljedećoj slici.

image

Upit se evaluira svaki put kada smo iterirali varijablu izraz. Međutim, odgođeno izvršavanje LINQ upita može dovesti u zabludu, posebno ako se ne poznaje dovoljno način na koji se izvršava LINQ upit. Kao dokaz uzmimo sljedeći primjer:

class Program
{
static void Main(string[] args)
{
   //Definisanje polja stringova gradova
   string[] gradovi = {
                        "Sarajevo",
                        "Tuzla",
                        "Mostar",
                        "Banja Luka",
                        "Zenica",
                        "Bihać",
                        "Cazin"
                    };

   string prvoSlovo = "S";

   //Konstrukcija LINQ upita
   var izraz = from g in gradovi where g.StartsWith(prvoSlovo) select g;

   string ispisNaKOnzolu = "Gradovi koji počinju s prvim slovom '" + prvoSlovo + "'\n";

   //promijenimo varijablu prvoSLovo
   prvoSlovo = "B";

   Console.WriteLine(ispisNaKOnzolu);

  foreach (string grad in izraz)
   Console.WriteLine(grad);

   Console.ReadLine();
}
}

U primjeru imamo definisan LINQ upit sa varijablom prvoSlovo, i gdje smo formirali LINQ upit kada je dotična varijabla imala vrijednost „S“. U normalnim uslovima, onako kako smo naučili da se varijable inicijaliziraju i po logici stvari, LINQ upit bi trebao vratiti sve gradove koji počinju sa prvim slovom S. Medjutim, rezultat upita je vratio gradove koji počinju slovom B, jer smo vrijednos slova B, pridružili vrajiabli prije same evaluacije LINQ upita, što potvrđuje i pokretanje primjera i sljedeća slika.

image

To upravo pokazuje način i vrijeme kada se LINQ upit generiše i evaluira nad izvorom podataka.

LINQ standardni operatori upita

Prethodno smo vidjeli upotrebu nekoliko operatora upita. U ovom poglavlju pozabavit ćemo se više oko ovih operatora, te vidjeti na koji ih način koristiti efikasno u programima. Razvijajući aplikacije, programer  konstantno rješava određene probleme manipulacije sa podacima, riješava i definiše algoritme I pomoćne programe. U tu svrhu koriste se određene preinstalirane biblioteke kao i biblioteke od trećih lica. Nažalost, nismo uvijek u prilici da sa bibliotekama koje koristimo imamo riješene sve probleme.

LINQ operatori upita mnoge naše probleme kojim smo svakodnevno okruženi tokom razvoja aplikacija, mogu zaista efektivno riješiti u samo nekoliko linija koda. Kao što smo kazali LINQ ne predstavlja manipulaciju samo sa eksternim izvorima podataka, jer se mogu koristiti I kako smo ranije kazali sa memorijskim izvorima podataka. U narednom tekstu upoznat ćemo se sa operatorima upita te kroz primjere primjene pokazati njihovu jednostavnost, efikasnost i lakoću.

Operatori sortiranja (orderby, reverse, descending)

Već smo u  prvom dijelu vidjeli upotrebu sortiranja podataka. Prednosti korištenja ovog operatora nad kolekcijama koje nemaju implementiranu ovu mogućnost su vrlo korisne. Uzmimo iz prethodnog primjera polje stringova i primjenimo operator sortiranja. Npr sortirajmo uzlaznim i slizanim redom gradove u BiH:

//sortiranje niza abecedno
var g1 = gradovi.OrderBy(g=>g);
var g2 = gradovi.OrderByDescending(g=>g);

Međutim, sortiranje možemo izvršiti po nekom drugom kriteriju npr.  po broju slova u nazivu.

//sortiranje niza po broju slova u nazivu
var gs1 = gradovi.OrderBy(g=>g.Length);
var gs2 = gradovi.OrderByDescending(g=>g.Length);

Kriterije možemo lančano slagati i sa tačka notacijom. U tom slučaju koristimo ThenBy operator poslije OrderBy operatora. Kombinirajmo prethodna dva primjera i napišimo sljedeći primjer:

//sortiranje niza po broju slova u nazivu i abecedno
var g = gradovi.OrderBy(g => g.Length).ThenBy(g => g);

Reverse operator koristimo kada želimo sortirani niz obrnuti. Sljedeći primjer prikazuje upotrebu operatora Reverse i OrderbyDescending, da bi prikazali suštinsku razliku između ova dva operatora.

//suštinske razlike operatora sortiranja
var g1 = gradovi.OrderBy(g => g.Length);
var g2 = gradovi.OrderByDescending(g=>g.Length);
var g3 = gradovi.OrderBy(g => g.Length).Reverse();

Rezultat prethodnih upita prikazan je na sljedećoj slici:

image

Reverse operator za razliku od OrderBy operatora  oslanja se samo na poredak podataka koji je dobijenih iz izvora podataka.

GroupBy operator

Skup standardni operatora upita čini i operator grupiranja GroupBy, koji uspostavlja podjelu nad sekvencijalnim vrijednostima baziranih na funkcijama ekstrakcije. Ovaj operator vraća skup IGrouping podataka za svaki različiti vrijednosni kriterij. Svakako da je IGrouping izvedeni interfejs od IEnumerable koji dodatno sadrži kriterij koji je korišten za ekstrakciju podataka iz izvora. Jednostavan primjer upotrebe GroupBy operatora možemo predstaviti na sljedećem primjeru:

class Program
{
static void Main(string[] args)
{
   //Definisanje polja stringova gradova
   string[] gradovi = {
                        "Sarajevo",
                        "Tuzla",
                        "Mostar",
                        "Banja Luka",
                        "Zenica",
                        "Bihać",
                        "Cazin"
                    };

   //Grupiranje po dužini riječi
   var grupeGradova = gradovi.GroupBy(g => g.Length);
   foreach (IGrouping grupe in grupeGradova)
     {
        Console.WriteLine("Dužine riječi u nazivima gradova od {0} slova", grupe.Key);
        Console.WriteLine("-----------------------------------------------");

        foreach (string grad in grupe)
           Console.WriteLine("  {0}",grad);
      }

   Console.ReadLine();
 }
}

Poslije pokretanja ovog programa rezultat je prikazan na sljedećoj slici:

image

Agregacijski operatori

Agregacijski operatori koji su podržani u LINQ definišemo na sličan način, kao I prethodne operatore. Napišimo primjer upotrebe agregacijskog operatora Agregate. Ovaj operator vrši određenu kalkulaciju na sekvencom podataka. Operator vrši operacije koristeći  lambda izraze nad svakojm sekvenco podataka. Sljedeći primjer izračunava broj karaktera korištenih u cijelom nizu:

   //Definisanje polja stringova gradova
   string[] gradovi = {
                        "Sarajevo",
                        "Tuzla",
                        "Mostar",
                        "Banja Luka",
                        "Zenica",
                        "Bihać",
                        "Cazin"
                    };
   int brojSlova = gradovi.Aggregate(0, (c, s) => c + s.Length);
   Console.WriteLine("Broj slova svih gradova u nizu iznosi: {0}",brojSlova);

Rezultat pokretanja programa:

image

Agregate operator propisuje  Count  operator i 4 numerička agregacijska operatora(Min, Max, Sum, i Average) minimum, maksimum, suma i srednja vrijednost respektivno. Ovi se numerički operatori procesuiraju nad sekvencama podataka bilo kojeg numeričkog tipa podataka: int, double, decimal i sl. Sljedeći primjer prokazuje upotrebu nekoliko pomenutih operatora:

int[] brojevi = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
int ukupno = brojevi.Sum();
int sumaKvadrata = brojevi.Sum(x=>x*x);
Console.WriteLine("Suma članova niza: {0}",ukupno);
Console.WriteLine("Suma kvadrata članova niza: {0}", sumaKvadrata);

Rezultat pokretanja programa prikazan je na sljedećoj slici:

image

Sintaksa upita u LINQ

U prethodnim paragrafima vidjeli smo načine kako možemo formirati upit nad izvorom podataka. Takodjer se može primjetiti da svaki upit možemo formirati na dva, u suštini ista, a sintaktički različita načina. Naime svaki LINQ upit formiran Lambda izrazom, takodjer  možemo formirati tačka notacijom (Dot Notaton). Ovakav fleksibilam pristup definisanju upita u LINQ rezultat su proširenja koja se pojavljuju u verziji C#3.0, a koje smo spominjali u prethodnim blog postovima postovima. Npr. definišimo LINQ upit tačka notacijom. Imamo:

var izraz= nazivi.Where(s=>s.Lenght==6)
                     .OrderBy(s=>s)
                     .Select(s=>s.ToUpper());

Prethodni upit  možemo formirati Lambda izrazom na sljedeći način:

var izraz= from s in imena
              where s.Lenght ==6
              orderby s
              select s.ToUpper();

Na kraju ovog članaka pobrojat ćemo sve operatore koji se mogu pojaviti u LINQ upitima:

OPERATOR

Opis

Where

Restriktivni operator.

Select / SelectMany

Operator projekcije.

Take/Skip

TakeWhile/SkipWhile

Parcijalni operator baziran na poziciji ili uslovnoj funkciji.

Join/GroupJoin

Operator spajanja u odnosu na zadani uslov.

Concat

Operator spajanja.

OrderBy/ThenBy/

OrderByDescending

ThenByDescending

Sortiranje u uzlaznom, silaznom smijeru u odnosu na zadani uslov.

Reverse

Operator sortiranja sekvence u suprotnom smijeru

GroupBy

Operator grupiranja u odnosu na zadani uslov.

Distinct

Operator uklanjanja duplikata elemenata u skupu.

Union/Intersect

Operatr koji vraća uniju ili podskup zadanih skupova elemenata.

Except

Operator koji vraća komplement zadanog skupa.

ToSequence

Operator konverzije u IEnumerable

ToArray/ToList

Operator konverzije u List

ToDictionary/ToLookup

Operator konverzije u Dictionary ili LookUp u odnosu na zadani ključ.

OfType/Cast

Operator konverzije u Ienumerableu odnosu na filtrirane elemente ili konverzije u tip argumenta.

EqualAll

Operator jednakosti koji vraća jedenake uparene elemente.

First/FirstOrDefault/

Last/LastOrDefault/

Single/SingleOrDefault

Operator vraćanja početne/zadnje/jednog elementa u odnosu na zadanu funkciju.

ElementAt/

ElementAtOrDefault

Operator vraćanja elementa određene pozicije.

DefaultIfEmpty

Operator zamjene prazne vrijednosti sa podrazumijevanom.

Range

Generatorski operator vraćanja broja u opsegu.

Repeat

Generatorski operator vraćanja višestrukih pojavljivanja elementa zadane vrijednosti.

Empty

Generatorski operator vraćanja prazne sekvence.

Any/All

Kvantifikator provjere egzistencije ili univerzalnosti funkcije izraza.

Contains

Kvantifikator provjere postojanja datog elementa.

Count/LongCount

Agregacijski operatori brojanja elemenata.

Sum/Max/Min/Average

Agregacijski oparatori kao opcione funkcije selektora.

Aggregate

Agregacijski oparator , vidi dio III

References

1. The LINQ Project .NET Language Integrated Query May 2006 Don Box, Architect, Microsoft Corporation and Anders Hejlsberg, Technical Fellow, Microsoft Corporation
2. http://forums.microsoft.com/msdn/showforum.aspx?forumid=123&siteid=1
3. http://weblogs.asp.net/scottgu/
4. http://microsoft.com

.NET 4.0 System.Numeric.BigInteger


Microsoft je već u beta fazi razvoja VS 2008, prezentirao razvoj prostora imena Numeric, međutim poslije se ovaj prostor nije našao u finalnim verzijama. Ista situacija je i sa VS2010, gdje se ponovo pojavio prostor imana Numeric, s tim da će on sigurno biti uključen u finalnu verziju. Ovaj prostor za sada sadrži dvije strukture:

BigInteger
Complex

Prva stukture obezbjeđuje rad sa velikim cjelobrojnim brojevima, dok drugra sa kompleksnim brojevima. Obe strukture vrlo su slične klasičnim strukturama brojeva koje su već podržane u .NET Frameworku, tako da je rad sa ovim strukturama identičan sa ostalim. U ovom postu bavićemo se samo strukturom BigInteger, dok ćemo drugu strukturu ostaviti za neki naredni post.

Šta je veliki broj?

Posmatrano u domeni programiranja velikim brojevima se nazivaju oni brojevi koji se ne mogu zapisati u izvornom obliku sa klasičnim brojevnim tipovima. Naime, ako bi htjeli da zapišemo cjelobrojni tip od 50 cifara, to nismo u mogućnosti uraditi ni sa bilo kojim cjelobrojnim tipom, jer ako se pogleda specifikacija cjelobrojnih tipova u C#, vidimo da najveća cjelobrojna vrijednost za tip int64 iznosi 20 cifara. Više informacija možete pogledati u MSDN dokumentaciji.

Operacija sa velikim brojevima zahtjeva posebnu implementaciju, o kojoj sam pisao u nekoliko prethodnih postova. Novina u .NET 4.0 o ovom problemu je pojava strukture BigInteger, koja omogućava operacije sa velikim cjelobrojnim brojevima. U narednom tekstu pregledaćemo značajnije osobine ove strukture i na kraju uraditi nekoliko primjera koji na najbolji način opisuje primjenu ove strukture.

BigInteger struktura posjeduje 8 konstrukktora pomoću kojih se može formiratu ova struktura. Ne zaboravimo da je ovo value tip, kao i svaki drugi brojevni tip podataka.

public BigInteger(byte[] value);
public BigInteger(decimal value);
public BigInteger(double value);
public BigInteger(float value);
public BigInteger(int value);
public BigInteger(long value);
public BigInteger(uint value);
public BigInteger(ulong value);

U biti ovo znači da se BigInteger možete formirati uz pomoć bilo kojeg postojećeg brojevnog tipa. Medjutim kada formirate veliki cjelobrojni broj ponekad je potrebno da mu na samom počeku definišete vrijednost koja je veliki broj. U tom pogledu koristite klasične Parse ili TryParse metode definisanja vrijednosti. Ove metode su kao i u klasičnim tipovima statičke pa se definisanje svodi na vrlo jednostavnu konstrukciju pretvaranja stringa u BigInteger tip. Npr:

BigInteger bigNumber1=BigInteger.Parse("37107287533902102798797998220837590246510135740250");

TryParse metodom imamo bolju kontrolu koda u slučaju kad vaš string može sadržavati i karaktere koji nisu cifre.

BigInteger bigNumber2;
if(!BigNumber.TryParse("46376937677490009712648124896970078050417018260538",out bigNumber2))
     bigNumber=0;

Ovo gore znači ako naš veliki broj nije uspješno definisan, TryParse vraća false, pa u tom slučaju broj postavljmo npr. na vrijednost 0. Pored konstruktora i metoda za definisanje vrijednosti, postoji nekoliko osobina koje su vrlo korisne u analizi BigInteger broja. Donji tekst sadrži 5 osobina BigInteger broja, čiji nazivi govore sami za sebe.

public bool IsEven {get; }// true ako je paran broj, inaee false
public bool IsOne { get; }//true ako je 1, inače false
public bool IsPowerOfTwo {get; } //true ako je broj potencija broja 2, inaee false
public bool IsZero {get; }//true ako je 0, inače false
public static BigInteger MinusOne {get; }// vraća –1 vrijednost
public static </span>BigInteger One { get;}// vraća 1 vrijednost
public int Sign {get; }//1 ako je pozitivan, –1 za negativan broj
public static BigInteger Zero {get;}//Vraća 0 vrijednost

Operacija nad BigInteger tipom

Sve klasične operacija koje su zastupljene u drugim tipovima mogu se naći ovdje, pogodnim preklamanjem operatora ova struktura nema nikakvih ograničenja ili posebno definisanih metoda za operacije. Npr:

BigInteger bigNumber2 = bigNumber1 *456+ bigNumber2 + 2345;

Operacije kompariranja također su zastupljene, kako sa BigInteger tipom tako i u kombinaciji sa long i ulong tipom. Npr:

if(bigNumber2>123456789)
    //uradi nešto

Operacije logaritmiranja, i eksponenta takodjer su prisutne u ovoj strukturi. Struktura posjeduje još nekoliko vrlo pogodnih metoda ali čitaocu se ostavlja da sam istraži ove metode.

Primjerna klase BigInteger

Za ovaj post i primjenu BigInteger strukture prikazaćemo nekoliko rješenja problema sa Project Euler stranice.

Problem 13: Potrebno je naći prvih 10 cifara sume 100, 50-to cifrenih brojeva.

Rješenje: Ovaj problem se svodi na najjednostavnije sumiranje 100 brojeva koji su dati u tekstu problema. Kratka uputa rješavanja se svodi na for petlju koja uzima string od 10 karaktera, pretvara ga u BigInteger tip, i klasično sabira. Kada se izračuna suma, uzme se prvih 10 cifara. Medjutim rješavanje ovog problema nećemo ići klasičnim putem nego uz primjenu LINQ-a, kao i u postu u kojem sam rješavao nekoliko prvih problema. Source code rješenja problema je sljedeći:

Prvo definišemo string u kojem se nalaze brojevi iz problema:

string numbers =
@"37107287533902102798797998220837590246510135740250
46376937677490009712648124896970078050417018260538
74324986199524741059474233309513058123726617309629
91942213363574161572522430563301811072406154908250
23067588207539346171171980310421047513778063246676
89261670696623633820136378418383684178734361726757
28112879812849979408065481931592621691275889832738
44274228917432520321923589422876796487670272189318
47451445736001306439091167216856844588711603153276
70386486105843025439939619828917593665686757934951
62176457141856560629502157223196586755079324193331
64906352462741904929101432445813822663347944758178
92575867718337217661963751590579239728245598838407
58203565325359399008402633568948830189458628227828
80181199384826282014278194139940567587151170094390
35398664372827112653829987240784473053190104293586
86515506006295864861532075273371959191420517255829
71693888707715466499115593487603532921714970056938
54370070576826684624621495650076471787294438377604
53282654108756828443191190634694037855217779295145
36123272525000296071075082563815656710885258350721
45876576172410976447339110607218265236877223636045
17423706905851860660448207621209813287860733969412
81142660418086830619328460811191061556940512689692
51934325451728388641918047049293215058642563049483
62467221648435076201727918039944693004732956340691
15732444386908125794514089057706229429197107928209
55037687525678773091862540744969844508330393682126
18336384825330154686196124348767681297534375946515
80386287592878490201521685554828717201219257766954
78182833757993103614740356856449095527097864797581
16726320100436897842553539920931837441497806860984
48403098129077791799088218795327364475675590848030
87086987551392711854517078544161852424320693150332
59959406895756536782107074926966537676326235447210
69793950679652694742597709739166693763042633987085
41052684708299085211399427365734116182760315001271
65378607361501080857009149939512557028198746004375
35829035317434717326932123578154982629742552737307
94953759765105305946966067683156574377167401875275
88902802571733229619176668713819931811048770190271
25267680276078003013678680992525463401061632866526
36270218540497705585629946580636237993140746255962
24074486908231174977792365466257246923322810917141
91430288197103288597806669760892938638285025333403
34413065578016127815921815005561868836468420090470
23053081172816430487623791969842487255036638784583
11487696932154902810424020138335124462181441773470
63783299490636259666498587618221225225512486764533
67720186971698544312419572409913959008952310058822
95548255300263520781532296796249481641953868218774
76085327132285723110424803456124867697064507995236
37774242535411291684276865538926205024910326572967
23701913275725675285653248258265463092207058596522
29798860272258331913126375147341994889534765745501
18495701454879288984856827726077713721403798879715
38298203783031473527721580348144513491373226651381
34829543829199918180278916522431027392251122869539
40957953066405232632538044100059654939159879593635
29746152185502371307642255121183693803580388584903
41698116222072977186158236678424689157993532961922
62467957194401269043877107275048102390895523597457
23189706772547915061505504953922979530901129967519
86188088225875314529584099251203829009407770775672
11306739708304724483816533873502340845647058077308
82959174767140363198008187129011875491310547126581
97623331044818386269515456334926366572897563400500
42846280183517070527831839425882145521227251250327
55121603546981200581762165212827652751691296897789
32238195734329339946437501907836945765883352399886
75506164965184775180738168837861091527357929701337
62177842752192623401942399639168044983993173312731
32924185707147349566916674687634660915035914677504
99518671430235219628894890102423325116913619626622
73267460800591547471830798392868535206946944540724
76841822524674417161514036427982273348055556214818
97142617910342598647204516893989422179826088076852
87783646182799346313767754307809363333018982642090
10848802521674670883215120185883543223812876952786
71329612474782464538636993009049310363619763878039
62184073572399794223406235393808339651327408011116
66627891981488087797941876876144230030984490851411
60661826293682836764744779239180335110989069790714
85786944089552990653640447425576083659976645795096
66024396409905389607120198219976047599490197230297
64913982680032973156037120041377903785566085089252
16730939319872750275468906903707539413042652315011
94809377245048795150954100921645863754710598436791
78639167021187492431995700641917969777599028300699
15368713711936614952811305876380278410754449733078
40789923115535562561142322423255033685442488917353
44889911501440648020369068063960672322193204149535
41503128880339536053299340368006977710650566631954
81234880673210146739058568557934581403627822703280
82616570773948327592232845941706525094512325230608
22918802058777319719839450180888072429661980811197
77158542502016545090413245809786882778948721859617
72107838435069186155435662884062257473692284509516
20849603980134001723930671666823555245252804609722
53503534226472524250874054075591789781264330331690"

Jednostavnim kopiranjem sa stranice i stavljanjem ispred početka stringa kakakter et (@) kompajler će nam kopirano prebaciti u string varijablu numbers. Sada je potrebeno sve brojeve dobiti pogodnom podjelom ovog stringa, pretvoriti ih u BigInteger tipove i sabrati ih. Cijela ova operacija se sastoji od jedne linije LINQ upita:

string first10digit = numbers.Split(new char[] { '\r','\n',' '},StringSplitOptions.RemoveEmptyEntries)
                    .Aggregate((BigInteger)0, (poc, nn) => poc += BigInteger.Parse(nn)).ToString().Substring(0,10);

Sada je potrebeno samo first10digit ispisati na konzolu. Ovdje je korišten operator agregacije, kojim se u svakoj pojedinčnoj sekvencij sabiraju brojevi, a na kraju vraća suma, koja se pretvara u string, a potom vraća samo prvih 10 karaktera.

Problem 20: Potrebno izračunati sumu cifara 100! (100 faktorijel).

Rješenje: Ovaj problem BigInteger rješava u djeliću sekunde na sljedeći način:

var sumofDigit = Enumerable.Range(1,100).
Aggregate((BigInteger)1, (prev, numb) => prev *= numb).ToString().
Aggregate(0, (sum, digit) => sum += int.Parse(digit.ToString()));

Ovdje smo preko operatora agregata, pomnožili sve brojeve od 1 do 100, a zatim ponovo preko agregacijskog operatora, vrijednost 100! koji je pretvoren u string, svaku cifru datog broja sumirali.

Problem 16: Koliko iznosi zbir cifara broja 21000?

Rješenje: Na isti vrlo jednostavan način korištenja BigInteger i agregatora imamo:

var sumofDigit=BigInteger.Pow(2,1000).ToString().
Aggregate(0, (sum, digit) => sum += int.Parse(digit.ToString()));

Problem 25: Pronađi prvi član Fibonaccijevog niza koji sadrži 1000 cifara.

Rješenje: Ovaja problem rješićemo kada metodu koja generira članove Finonaccijevog niza, prikazanu u prethodnim postovima, modifikujemo preko BigInteger tipa, na sljedeći način:

static IEnumerable<BigInteger> FibonacciNiz()
{
//Prva dva člana, odnosno prethodna dva člana
BigInteger a = 1;
BigInteger b = 1;
//i-ti clan
BigInteger c = 0;
yield return a;
yield return b;

while(true)
{
yield return c = a + b;
//nakon proracuna i-tog člana
// prethodna dva člana postaju
a = b;
b = c;
}
}

Ovu metodu implemenitirali smo u prethodnom postu, samo je sada u pitanji BigInteger tip. Rješenje se svodi na jednu liniju koda:

var termNumber = FibonacciNiz().TakeWhile(x =>; x.ToString().Length<1000).Count() +1;

TakeWhile operator uzima članove niza sve dok je broj cifara člana manji od 1000. Kada se to ostvari, operatorom Count vratimo broj članova i uvećamo ga za jedan.

Problem 48: Pronađi zadnjih 10 cifara sume: 11 + 22 + 33 + … + 10001000.

Rješenje: Ovaj problem vrlo jednostavno rješavamo sa ovim tipom i aggregate operatorom:

var = Enumerable.Range(1,1000).
Aggregate((BigInteger)0, (sum, num) => sum += BigInteger.Pow(num, num)).
ToString().Reverse().Take(10).Reverse().ToArray();

Nad intervalom od 1-1000 stepenujemo i ujedno i sabiramo sekvence. Na kraju rezultat pretvorimo u string, napravimo niz u suprotnom poretku, uzmemo prvih 10 cifara, vratimo poredak, te napravimo ponovo string od tih 10 cifara. Vrlo interesantan problem je da se nađe zadnjih 10 cifara najvećeg prostog broja pronađenog do 2004 godine.

Problem 97: Pronaći zadnjih 10 cifara najvećeg prostog broja: 28433×27830457+1.

Rješenje: Na sličan način kao i u prethodnim slučajevima zadnjih 10 cifara pronalazimo sa jednom linijom koda:

var last10Digit = (28433 * BigInteger.Pow(2, 7830457) + 1).ToString().Reverse().Take(10).Reverse().ToArray();

Do rješenja se dolazi poslije nekoliko minuta procesuiranja.

Problem 99: Pronaći najveći broj koji su pohranjeni u datoteku, pri kojem prvi broj označava basni broj a drugi eksponent.

Rješenje: Trik sa ovim problemom je u tome što bi za izračunavanje 632382518061 i komparacija sa sličnim brojem trajala nekoliko minuta, a ukupno i nekoliko sati.Rješenje se svodi na logaritmiranje i kompariranje što zadatak čini vrlo jednostvnim. Rješenje je prikazano u sljedećem tekstu:

StreamReader reader = null;
// open selected file
reader = System.IO.File.OpenText("base_exp.txt");
//read data in to buffer
string buffer = reader.ReadToEnd();
reader.Close();

var resultLine = buffer.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Aggregate(new { maxNumber = 0.0, lineForGreatesNumber = 1, currentLine = 0 },
(prevLine, stringLine) =>
 {
    //Fromiranje brojeva iz string linije
    int baseNumber = int.Parse(stringLine.Split(',')[0]);
    int exponent = int.Parse(stringLine.Split(',')[1]);
    //Umjesto izračunavanja potencije jednostavno računamo
    //logaritam od basnog broja pomnožen sa eksponentom
    double result = exponent * Math.Log10(baseNumber);

   //Komparacija sa prethodnim največim brojem
   if (result > prevLine.maxNumber)
       return new { maxNumber = result, lineForGreatesNumber = prevLine.currentLine + 1, currentLine = prevLine.currentLine + 1};
  else
      return new { maxNumber = prevLine.maxNumber, lineForGreatesNumber = prevLine.lineForGreatesNumber, currentLine = prevLine.currentLine + 1 };

 }).lineForGreatesNumber;

Kombinacija LINQ, sa BigInteger tipom podataka, pojedine ProjectEuler  probleme zaista pojednostavljuje do trivijalnosti, odnosno savršeno jednostavnih rješenja.

C# 4.0 LINQ to Object – Zip operator


U prethodnim postovima bilo je dosta govora o novinama u C# 4.0 i VS 2010. Interesantna stvar se pojavila i u LINQ to Object, sa novim operatorom Zip. Ovaj operator nema nikakve veze sa kompresijom, ali sa spajanjem sekvenci ima. Uzmimo jednostavan primjer.

Pretpostavimo da imamo dva polja objekata. Namjerno kažem objekata jer polja mogu biti različiti u smislu da jedno polje ima neki numerički tip, a drugi string ili apstraktni tip ili obrnuto. Npr…

int[] size = { 157, 452 };
string[] fileNames = { "WordDokument1", "WordDokument2" };

Rezultat zipovanja ova dva polja možemo uraditi na sljedeći način:

var res=fileNames.Zip(size, (fname, fsize) => "Velicina datoteke "+fname+" iznosi: " +fsize+" KB.");
foreach(string str in res)
Console.WriteLine(str);

Rezultat zip operacije vidimo na sljedećoj slici:

Kako operator Zip radi?Napišimo deklaraciju operatora odnosno proširene metode:

IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector);

Ako pogledamo deklaraciju ovog operatora vidimo da on uzima dva argumenta, objekat prvog i drugog polja istog indeksa, koji se prosljeđuju Fun delegatu. Fun delegat vraća rezultat koji je u stvari rezultat Zip operatora.

Zip operator ima mnogo primjena u kojima će implementacija vašeg koda biti elegantna i jednostavna. Za ovaj operator izabrao sam primjenu koja na vrlo jednostavan način implementira sabiranje i oduzimanje velikih brojeva. Oko velikih brojeva i izračunavanja decimal broja PI pisao sam u ovom blog postu, u kojem se može pročitati na koji način se veliki brojevi sabiraju i oduzimaju. Metoda koja sabira dva broja prikazana je sljedećim listingom:

public static List<int> Add(List<int> bigNumber1, List<int> bigNumber2)
{
   if (bigNumber1[0] < 0)
      throw new Exception("Number 1 must be a positive!");
   if (bigNumber2[0] < 0)
      throw new Exception("Number 2 must be a positive!");
   //reverse digits
   bigNumber1.Reverse();
   bigNumber2.Reverse();
   var sumBigNumbers = bigNumber1.Zip(bigNumber2, (pp1, pp2) => pp1 + pp2).
   Aggregate(
               new { Digits = new List<int>(), CarryOver = 0 },
               (prev, number) =>
                   {
                     var cumulativeNumber = number + prev.CarryOver;
                     var digit = cumulativeNumber % 10;
                     var carryOver = cumulativeNumber / 10;
                     prev.Digits.Add(digit);
                     return new { Digits = prev.Digits, CarryOver = carryOver };
                   },
               (finalOperation) =>
                  {
                    if (finalOperation.CarryOver != 0)
                     finalOperation.Digits.Add(finalOperation.CarryOver);
                   finalOperation.Digits.Reverse();
                   return new { Digits = finalOperation.Digits, CarryOver = 0 };
                 }
             );
  //reverse digits
  bigNumber1.Reverse();
  bigNumber2.Reverse();
  return sumBigNumbers.Digits;
 }

Agrumenti ove metoda su jednostavne liste brojeva čije vrijednosti prikazuju cifre. Npr. bigNumber={1,3,4,8,9,2} zači da on predstavlja broj 13489, svaka stavka u listi predstavlja cifru. Pozicija stavki u listi je takva da je zadnja stavka predstavlja cifru jedinica, predzadnja stavka cifru desetica itd…

Uslovi da brojevi ne predstavljaju negativne brojeve mora se pretpostaviti jer algoritam sabiranja se bazira čisto na pismenom sabiranju koji smo učili u osnovnoj školi. Pogodnim izborom metoda Add i Substract uvijek je moguće sabrati ili oduzeti bilo koja dva velika broja bili oni negativni ili poitivni.

Naredni dio koda zamjenjuje redoslijed cifara, zbog lakšeg računanja, tako da se brojevi napišu u obrnutom redu.

Sada na scenu stupa operator Zip, koji spaja sekvence na način da svaku cifru prvog broja sabere sa korespodentnom cifrom drugog broja. Naravno zbir mnogih cifara premašuje 9, te se jedinica prenosi u sljedeći rang cifre. Ovaj postupak prenošenja jedinice u sljedeći rang odrađujemo preko operatora Agregate.

Operator Aggregate za prvi argument uzima anonimnu klasu koja sadrži listu cifara i int tip koji treba da se prenese u sljedeci veći rang cifre. Prvi argument je prazan jer počinjemo sa jedinicama, lista je prazna, a broj koji prenosimo (CarryOver) je 0.

Sljedeći argument je (prev , number) agrumenti koji se prenose iz svake sekvence u sljedeću. Prilikom prvog pozivanja drugog delegata oni su jednaki početnim vrijednostima argumenata.

U srednjem delegatu zbrajamo cifru koja je prenešena iz nižeg ranga sa tekućom cifrom. digit varijabla odnosno tekuća cifra se dobija kao ostatak djeljenja kumulattivne cifre sa 10, a broj koji se prenosi u sljedeći rang se dobije kao rezultat djeljenja kumulativne cifre sa 10. Kada su definisane cifre tada tekuće izačunatu cifru stavljamo u listu, a broj koji prenosimo postavljamo u varijablu CarryOver. Na kraju formiramo novu anonimnu klasu kao na početku stim da smo joj pridruđili vrijednosti cifre i ostatka za prenos. Ovaj process se događa dok se sve cifre ne procesuiraju.

Kada se process završi onda se poziva treći delegat koji ima argument naše anonimne klase koju smo vraćali tokom procesuiranja sabiranja. Na kraju je potrebno vidjeti da li je cifra za prenos veća od nule.U potvrdnom slučaju potrebno je dodati još jednu cifru jer je zbir dva broja povećao broj cifara za 1. Na kraju ponovo vraćamo anonimnu klasu koja predstavlja rezultat sabiranja. Na kraju vratimo listu cifara u početni poredak i vratimo listu.

Metoda za oduzimanje dva velika broja slična je prethodnoj samo se koristi logika pismenog oduzimanja. Implementacja metoda Substract izgleda kao na sljedećem listing:

public static List<int> Substract(List<int> bigNumber1, List<int> bigNumber2)
{
   if (bigNumber1 [0] < 0)
      throw new Exception("bigNumber1 must be a positive!");
   if (bigNumber2 [0] < 0)
      throw new Exception("bigNumber2 must be a positive!");

   bool negativeResult = false;
   //Check equality of number of digits
   if (bigNumber1.Count != bigNumber2.Count)
    {
      if (bigNumber1.Count > bigNumber2.Count)
       for (int i = 0; i < bigNumber1.Count - bigNumber2.Count; i++)
        bigNumber2.Insert(0, 0);
     if (bigNumber2.Count > bigNumber1.Count)
      for (int i = 0; i < bigNumber2.Count - bigNumber1.Count; i++)
       bigNumber1.Insert(0, 0);
    }
   //Check if second is grater than first
   for (int i = 0; i < bigNumber1.Count; i++)
    {
       if (bigNumber1[¨i] > bigNumber2[¨i])
        break;
      else if (bigNumber1[ i ] < bigNumber2[ i ])
      {
         List<int> temp = bigNumber1;
         bigNumber1 = bigNumber2;
         bigNumber2 = temp;
         negativeResult = true;
         break;
       }
    }
   bigNumber1.Reverse();
   bigNumber2.Reverse();
   var sumBigNumbers = bigNumber1.Zip(bigNumber2, (pp1, pp2) => pp1 - pp2).
  Aggregate(
               new { Digits = new List<int>(), CarryOver = 0 },
               (prev, number) =>
               {
                 var cumulativeNumber = (number <= 0 ? 20 + (number - prev.CarryOver) : (number - prev.CarryOver));
                 var digit = cumulativeNumber % 10;
                 var carryOver = cumulativeNumber / 10;
                 prev.Digits.Add(digit);
                 return new { Digits = prev.Digits, CarryOver = carryOver };
                },
             (finalOperation) =>
               {
                 Debug.Assert(finalOperation.CarryOver == 0);
                 finalOperation.Digits.Reverse();
                 return new { Digits = finalOperation.Digits, CarryOver = 0 };
               }
             );
   //back reversed digits
   bigNumber1.Reverse();
   bigNumber2.Reverse();
   if (negativeResult)
       sumBigNumbers.Digits[0] = sumBigNumbers.Digits[0] * -1;
   return sumBigNumbers.Digits;
 }

Slično kao u prethodnoj implementaciji Zip operatorom oduzimamo cifre jednu od duge dok agregacijskim operatorom preračunavamo vrijednosti i prenosimo broj u viši rang. Na kraju vraćamo rezultat oduzimanja.

Test za operator Zip možemo implementirati kao na sljedećem listingu:

static void Main(string[] args)
{
   //40 dgit big number
   List<int> bigNumber1 = new List<int>() { 1, 5, 4, 7, 2, 6, 8, 7, 4, 5, 6, 2, 1, 4, 5, 6, 6, 5, 8, 4, 7, 0, 1, 9, 8, 5, 7, 8, 9, 6, 5, 4, 1, 2, 5, 8, 7, 4, 5, 8 };
   //40 dgit big number
   List<int> bigNumber2 = new List<int>() { 5, 2, 4, 5, 3, 4, 7, 0, 2, 4, 6, 1, 7, 3, 4, 0, 8, 7, 0, 1, 3, 2, 3, 5, 4, 6, 4, 3, 2, 2, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1};
   //Result string
   string result = "";
   foreach (int digit in Add(bigNumber1, bigNumber2))
      result += digit.ToString();
   Console.WriteLine("       Result of Add operation is: {0}",result);
   result = "";
   foreach (int digit in Substract(bigNumber1, bigNumber2))
      result += digit.ToString();
   Console.WriteLine("Result of Substract operation is: {0} ", result);
   //Press any key to continue...
   Console.Read();
 }

Formirali smo dva 40-to cifrena broja, te pozvali metodu Add, i ispisali na konzoli rezultat, ista stvar sa metodom Substract.

Ugodno programiranje i nova godina :).