Úloha na procvičení: E-shop s knihami

Vaším cílem je implementovat seřazení knih nabízených na fiktivním e-shopu. Úloha má několik úrovní (s postupně se zvyšující obtížností).

Stáhněte si šablonu Program.cs a váš kód doplňujte do ní.

Můžete také postupně odevzdávat jednotlivé úrovně do ReCodExu. Testy v ReCodExu odpovídají úrovním (nebojte se tedy odevzdávat řešení opakovaně s každou novou úrovní, kterou se vám podaří implementovat).

1. úroveň: název

Seřaďte knihy abecedně podle jejich názvu (Title).

Do třídy Book implementujte interface IComparable<Book> (přidejte ho za IEquatable<Book> a oddělte čárkou). Interface vynutí implementaci metody public int CompareTo(Book other), která slouží pro seřazení dvou prvků a vrací

  • záporné číslo (např. -1), pokud this má být před other,
  • 0, pokud this a other mají stejný název,
  • kladné číslo (např. 1), pokud this má být za other.

Pokud by vás zajímaly podrobnosti, můžete nahlédnout do dokumentace.

Nápověda

Pro textové řetězce bohužel nefunguje porovnání pomocí < a >. Naštěstí typ string implementuje IComparable<string>, můžete tedy zavolat Title.CompareTo(...) (nebo případně String.Compare(..., ...)).

2. úroveň: cena (vzestupně)

Seřaďte knihy podle jejich ceny (Price) od nejlevnější po nejdražší.

Protože přímo ve třídě Book jde definovat jen jeden typ seřazení, musíme si pomoct trochu jinak. Naštěstí metoda Sort může jako parametr dostat funkci pro porovnávání pořadí dvou knih:

int ComparePriceAsc(Book x, Book y)
{
    // TODO: edit this code
    return 0;
}
books.Sort(ComparePriceAsc);

Případně pokud je porovnávání jednoduché (jen jeden příkaz), můžete ho napsat rovnou za šipku:

int ComparePriceAsc(Book x, Book y) => 0;
books.Sort(ComparePriceAsc);

Porovnávací funkce má vracet (obdobně jako CompareTo)

  • záporné číslo (např. -1), pokud x má být před y,
  • 0, pokud x a y mají stejnou cenu,
  • kladné číslo (např. 1), pokud x má být za y.

Více se zase můžete dočíst v dokumentaci.

Zkrácený zápis

Mimochodem, pokud víte, že porovnávací funkci použijete jen jednou a nechcete ji pojmenovávat, jde napsat i přímo do volání Sort:

books.Sort((x, y) => {
    // TODO: edit this code
    return 0;
});

Na druhou stranu, když to uděláte takhle, nemusí být na první pohled jasné, jak má porovnávání fungovat, takže je asi lepší mít funkci pojmenovanou.

3. úroveň: cena (sestupně)

Seřaďte knihy podle jejich ceny (Price) od nejdražší po nejlevnější.

Nápověda

Všimněte si, že teď má porovnání vracet opačná čísla než v předchozí úrovni. Můžete tedy definovat porovnávací funkci, která volá porovnání definované v předchozí úrovni a jen jeho výsledek vynásobí -1.

4. úroveň: cena po slevě

Sleva (Discount) je číslo mezi 0 a 1, které určuje, o kolik procent má být výsledná cena knihy nižší než její původní cena (Price). Seřaďte knihy podle ceny po slevě od nejlevnější po nejdražší.

5. úroveň: autor a název

Seřaďte knihy abecedně podle jména autora (Author). Jméno nechte v celku, nedělte ho na křestní jméno a příjmení. Knihy stejného autora pak seřaďte podle jejich názvu.

Nápověda

Pokud je autor obou knih stejný (výsledek porovnání autorů knih je roven nule), vraťte jako porovnání knih porovnání jejich názvů.

Poznámka

Můžete si všimnout, že na začátku Main metody je nastavení CurrentCulture na InvariantCulture. To je potřeba z toho důvodu, že jinak porovnávání textových řetězců probíhá podle nastavení jazyka ve vašem počítači. Pokud tedy máte vaše prostředí v češtině, bude se vám “Ch” řadit jinam než v angličtině (která je v ReCodExu). Schválně si můžete zkusit, že když nastavíte CurrentCulture na češtinu:

System.Globalization.CultureInfo.CurrentCulture = new System.Globalization.CultureInfo("cs-CZ");

bude výsledné seřazení autorů jiné než pro angličtinu (která je nastavená v původní šabloně takto):

System.Globalization.CultureInfo.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;

Tohle neplatí jen pro textové řetězce, ale týká se to taky třeba vypisování desetinných čísel (desetinná tečka v angličtině, desetinná čárka v češtině).

6. úroveň: vyhledávání na e-shopu

Na vašem e-shopu chcete umožnit uživatelům vyhledávat knihy (podle jejich názvu). Uživatel zadá své hledání jako textový řetězec, který obsahuje několik hledaných slov oddělených mezerou. Seřaďte knihy podle toho, kolik z hledaných slov se vyskytuje v jejich názvu (knihy s největším počtem hledaných slov by měly být první).

Všimněte si, že porovnání knih závisí na parametru, který se může měnit (hledání uživatele). V takovém případě je vhodné definovat porovnání pomocí třídy implementující rozhraní IComparer<Book>:

class BookComparerSearch : IComparer<Book>
{
    public int Compare(Book? x, Book? y)
    {
        // TODO: edit this code
        return 0;
    }
}

Zvolenou hodnotu parametru pak snadno předáte pomocí konstruktoru této třídy (do kódu výše musíte správný konstruktor doplnit):

string searchQuery = "The Great";
books.Sort(new BookComparerSearch(searchQuery));

7. úroveň: vyhledávání a hodnocení

Vylepšete řazení podle vyhledávání tak, že knihy se stejným počtem nalezených slov seřadíte podle jejich průměrného hodnocení. Seznam všech hodnocení každé knihy je v Ratings, jsou to čísla 1 až 5 (“počet hvězdiček”). Z hodnocení spočítejte průměr a pak řaďte knihy s vyšším hodnocením na začátek. Pokud kniha nemá žádná hodnocení, pracujte s ní, jako by měla průměrné hodnocení 3.

Zkuste v této úrovni dodržet zásadu DRY a nekopírovat kód ze 6. úrovně. Místo toho si můžete vytvořit instanci BookComparerSearch a volat její metodu Compare.

Učební výstupy

Učební výstupy podávají zhuštěný souhrn základních konceptů a dovedností, které byste měli umět vysvětlit a/nebo použít po každém cvičení.

  • používat třídy a objekty pro omezení přístupu k datům a povolení jen vybraných operací – rozlišovat veřejné (public) a privátní (private) datové položky a metody
  • umět zdokumentovat třídy, funkce a jejich parametry pomocí dokumentačních komentářů
  • chápat význam klíčového slova static pro označení metod a dat, které patří přímo typu (třídě) a ne konkrétním instancím (objektům)
  • pro vlastní třídy umět definovat rovnost (IEquatable)
  • pro vlastní třídy umět definovat porovnání (IComparable); umět definovat i více způsobů porovnávání (pomocí porovnávacích funkcí nebo rozhraní IComparer)
  • uvědomovat si, že některé operace můžou záviset na jazykovém prostředí (např. porovnávání textových řetězců, formát desetinných čísel), a umět nastavit jazykové prostředí (viz výše)