A tak vzniklo devatero zastavení na jejich konci jeden řádek jest.
Zadání
Vytvořte konzolovou aplikaci, která vypíše pozice a počet výskytů hledaného znaku v textu. Text i znak je zadán obsluhou z konzole.
Drobný úvod
...aneb, abyste si nemysleli, že jsem debil.
Parciální třídy a parciální metody, Extension methods, Lambda expressions, Automatic properties, Anonymní typy, atd., atp. jsou už s námi dosti dlouhou dobu, ale zatím se mi je nějak nepodařilo strávit a začít rutině používat.
V poslední době mi pomohlo několikanásobné shlédnutí Michalova videa (viz. Další informace) a pořízení ReSharperu od Jet BRAINS, který, mimo jiné, nabízí převod klasických konstrukcí for-next na lambda výrazy. Stačí jen kliknout.
Takže takto vybaven jsem se jal upravovat klasický program ze Zadání.
Malá poznámka
Ne zcela důvěřuji SyntaxHighlighteru, který používám na této stránce pro zobrazení zdrojových kódů, tak pokud máte pochybnosti, stáhněte si z odkazu na konci článku testovací projekt, který je prověřen.Generace 1
Zde je, dá se říct, program klasické konstrukce. Pěkně se porovnáváme znak za znakem a v případě shody něco vypíšeme na konzoli a zvýšíme počítadlo výskytů.
private static void Gen1(string text, string znak) { int pocet = 0; //počítadlo výskytů for (int pozice = 0; pozice < text.Length; pozice++) if (text.Substring(pozice, 1) == znak) { Console.WriteLine("Výskyt na pozici: {0}", pozice); pocet++; } Console.WriteLine("Počet výskytů: {0}\n", pocet); }
Generace 2
V této generaci se počet výskytu znaků stanoví pomocí LINQ (poslední řádek kódu). Tohle dám ještě z hlavy a je to celkem pochopitelné.
private static void Gen2(string text, char znak) { for (int pozice = 0; pozice < text.Length; pozice++) if (text[pozice] == znak) Console.WriteLine("Výskyt na pozici: {0}", pozice); Console.WriteLine("Počet výskytů: {0}\n", text.Count(x => x == znak)); }
Generace 3
Tady jsem vytvořil strukturu ZnakApozice, kdy struktura nese pozici znaku a znak, a celý text vložil do typového Listu. V návaznosti na to jsem upravil procházení kódu. Zatím to tedy kyne, ale očekávám lepší zítřky.
Poznámka: tu hrůzu ve špičatých závorkách na konci zdrojáku tam dodal SyntaxHighlighter, který si nedokáže poradit s definicí generického typu ZnakApozice a tak vyplodil na konec ukončovací značku.
private static void Gen3(string text, char znak) { var temp = new List(); for (var pozice = 0; pozice < text.Length; pozice++) temp.Add(new ZnakApozice { Znak = text[pozice], Pozice = pozice }); foreach (var znakApozice in temp) if (znakApozice.Znak == znak) Console.WriteLine("Výskyt na pozici: {0}", znakApozice.Pozice); Console.WriteLine("Počet výskytů: {0}\n", text.Count(x => x == znak)); } private struct ZnakApozice { public char Znak; public int Pozice; }
Generace 4
Stále máme typový List, ale ten už nevzniká přes for-each, ale už pomocí lambda výrazu. Tohle navrhl ReSHARPER.
private static void Gen4(string text, char znak) { var temp = text.Select((t, pozice) => new ZnakApozice { Znak = t, Pozice = pozice }).ToList(); foreach (var znakApozice in temp) if (znakApozice.Znak == znak) Console.WriteLine("Výskyt na pozici: {0}", znakApozice.Pozice); Console.WriteLine("Počet výskytů: {0}\n", text.Count(x => x == znak)); } private struct ZnakApozice { public char Znak; public int Pozice; }
Generace 5
Teď už nevypisuji pozice postupně, ale vytvářím si vypis, který pošlu na konzolu až nakonec. Tuším, že mi ReSHARPER opět napoví nějaké pěkné řešení.
private static void Gen5(string text, char znak) { var temp = text.Select((t, pozice) => new ZnakApozice { Znak = t, Pozice = pozice }).ToList(); var vypis = String.Empty; foreach (var znakApozice in temp) if (znakApozice.Znak == znak) vypis += String.Format("Výskyt na pozici: {0}\n", znakApozice.Pozice); Console.WriteLine(vypis); Console.WriteLine("Počet výskytů: {0}\n", text.Count(x => x == znak)); } private struct ZnakApozice { public char Znak; public int Pozice; }
Generace 6
Takže vypis už vzniká lambda výrazem.
private static void Gen6(string text, char znak) { var temp = text.Select((t, pozice) => new ZnakApozice { Znak = t, Pozice = pozice }).ToList(); var vypis = temp.Where(znakApozice => znakApozice.Znak == znak) .Aggregate(String.Empty, (current, znakApozice) => current + String.Format("Výskyt na pozici: {0}\n", znakApozice.Pozice)); Console.WriteLine(vypis); Console.WriteLine("Počet výskytů: {0}\n", text.Count(x => x == znak)); } private struct ZnakApozice { public char Znak; public int Pozice; }
Generace 7
Teď jsem odstranil proměnnou temp a její lambda výraz vložil přímo do vypisu.
private static void Gen7(string text, char znak) { var vypis = text.Select((t, pozice) => new ZnakApozice { Znak = t, Pozice = pozice }).ToList() .Where(znakApozice => znakApozice.Znak == znak) .Aggregate(String.Empty, (current, znakApozice) => current + String.Format("Výskyt na pozici: {0}\n", znakApozice.Pozice)); Console.WriteLine(vypis); Console.WriteLine("Počet výskytů: {0}\n", text.Count(x => x == znak)); } private struct ZnakApozice { public char Znak; public int Pozice; }
Generace 8
Vzpomněl jsem si na Michalovo video a nahradil strukturu ZnakApozice anonymní třídou (konstrukce new {Znak=t, Pozice=pozice}) a taky jsem k tomu rovnou připočetl řetězec s počtem výskytů.
private static void Gen8(string text, char znak) { var vypis = text.Select((t, pozice) => new { Znak = t, Pozice = pozice }) .Where(znakApozice => znakApozice.Znak == znak) .Aggregate(String.Empty, (current, znakApozice) => current + String.Format("Výskyt na pozici: {0}\n", znakApozice.Pozice)) + String.Format("Počet výskytů: {0}\n", text.Count(x => x == znak)); Console.WriteLine(vypis); }
Generace 9
Metodu finalizuji tak, že vše vkládám do Console.WriteLine(), čímž se zbavuji proměnné vypis a mám vše v jednom příkazu. Slovy klasika: Kód o kterém většina programátorů nebude nevěřit, že to ten kompilátor vůbec zkompiluje, natož aby to pak něco dělalo.
private static void Gen9(string text, char znak) { Console.WriteLine(text.Select((t, pozice) => new { Znak = t, Pozice = pozice }) .Where(zp => zp.Znak == znak) .Aggregate(String.Empty, (current, zp) => current + String.Format("Výskyt na pozici: {0}\n", zp.Pozice)) + String.Format("Počet výskytů: {0}\n", text.Count(x => x == znak)) ); }
Další generace
Teď do toho čučím, snažím se kód pochopit a hlavně se dostat do stavu, kdy bych byl schopen něco takového zplodit sám. A případně to ještě zestručnit.Porovnání
Z programátorského hlediska je použití lambda výrazů & spol. nesporně čitelnější, než nějaké for-each konstrukce, ale jak je to s náročností na procesorový čas?
Abych získal odpověď na tuto otázku, mírně jsem upravil program tak, aby jej nebrzdil výpis na konzolu pro metodu Gen1 a Gen9 prohnal 100 000 až 1 000 000 iteracemi a pro délky řetězců 23, 46, 92, 184 a 368, abych odhalil případné nelineární závislosti.
Žádné komentáře:
Okomentovat