Algoritmy a programování IV

Týden 7

Přidání cen ve špičce

U konečné funkce chce mýtný úřad přidat časově citlivé ceny ve špičce. V ranních a večerních špičkách se placené poplatky zdvojnásobí. Toto pravidlo má vliv pouze na provoz v jednom směru: příchozí do města ráno a odchozí ve večerní špičce. V jiných časech během pracovního dne se placené poplatky zvyšují o 50 %. Pozdě v noci a brzy ráno je placená daň snížena o 25 %. O víkendu je to normální sazba bez ohledu na čas. K vyjádření tohoto problému můžete použít řadu if příkazů a else pomocí následujícího kódu:


public decimal PeakTimePremiumIfElse(DateTime timeOfToll, bool inbound)
{
    if ((timeOfToll.DayOfWeek == DayOfWeek.Saturday) ||
        (timeOfToll.DayOfWeek == DayOfWeek.Sunday))
    {
        return 1.0m;
    }
    else
    {
        int hour = timeOfToll.Hour;
        if (hour < 6)
        {
            return 0.75m;
        }
        else if (hour < 10)
        {
            if (inbound)
            {
                return 2.0m;
            }
            else
            {
                return 1.0m;
            }
        }
        else if (hour < 16)
        {
            return 1.5m;
        }
        else if (hour < 20)
        {
            if (inbound)
            {
                return 1.0m;
            }
            else
            {
                return 2.0m;
            }
        }
        else // Overnight
        {
            return 0.75m;
        }
    }
}

Předchozí kód funguje správně, ale není čitelný. Abyste mohli o kódu uvažovat, musíte projít všechny vstupní případy a vnořené if příkazy. Místo toho pro tuto funkci použijete porovnávání vzorů, ale integrujete ji s jinými technikami. Mohli byste vytvořit jeden výraz shody se vzorem, který by zohlednil všechny kombinace směru, dne v týdnu a času. Výsledkem by byl složitý výraz. Bylo by těžké ho číst a těžko pochopit. To ztěžuje zajištění správnosti. Místo toho zkombinujte tyto metody a vytvořte řazenou kolekci hodnot, která výstižně popisuje všechny tyto stavy. Pak použijte porovnávání vzorů k výpočtu multiplikátoru pro placenou linku. Řazená kolekce členů obsahuje tři samostatné podmínky:

  • Den je buď pracovní den, nebo víkend.
  • Časové pásmo, kdy se vybírá placená platba.
  • Směr je do města nebo mimo město

Následující tabulka uvádí kombinace vstupních hodnot a násobitele cen ve špičce:

DateTime strukturu pro čas, kdy byla placená platba vybrána. Sestavte členské metody, které vytvářejí proměnné z předchozí tabulky. Následující funkce používá výraz switch odpovídající vzor k vyjádření toho, jestli představuje DateTime víkend nebo den v týdnu:



    timeOfToll.DayOfWeek switch
    {
        DayOfWeek.Monday    => true,
        DayOfWeek.Tuesday   => true,
        DayOfWeek.Wednesday => true,
        DayOfWeek.Thursday  => true,
        DayOfWeek.Friday    => true,
        DayOfWeek.Saturday  => false,
        DayOfWeek.Sunday    => false
    };
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">private static bool IsWeekDay(DateTime timeOfToll) =>
    timeOfToll.DayOfWeek switch
    {
        DayOfWeek.Monday    => true,
        DayOfWeek.Tuesday   => true,
        DayOfWeek.Wednesday => true,
        DayOfWeek.Thursday  => true,
        DayOfWeek.Friday    => true,
        DayOfWeek.Saturday  => false,
        DayOfWeek.Sunday    => false
    };

Tato metoda je správná, ale je opakovaná. Můžete ho zjednodušit, jak je znázorněno v následujícím kódu:



    timeOfToll.DayOfWeek switch
    {
        DayOfWeek.Saturday => false,
        DayOfWeek.Sunday => false,
        _ => true
    };
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">private static bool IsWeekDay(DateTime timeOfToll) =>
    timeOfToll.DayOfWeek switch
    {
        DayOfWeek.Saturday => false,
        DayOfWeek.Sunday => false,
        _ => true
    };

Dále přidejte podobnou funkci, která čas kategorizuje do bloků:



    timeOfToll.Hour switch
    {
        < 6 or > 19 => TimeBand.Overnight,
        < 10 => TimeBand.MorningRush,
        < 16 => TimeBand.Daytime,
        _ => TimeBand.EveningRush,
    };
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">private enum TimeBand
{
    MorningRush,
    Daytime,
    EveningRush,
    Overnight
}private static TimeBand GetTimeBand(DateTime timeOfToll) =>
    timeOfToll.Hour switch
    {
        < 6 or > 19 => TimeBand.Overnight,
        < 10 => TimeBand.MorningRush,
        < 16 => TimeBand.Daytime,
        _ => TimeBand.EveningRush,
    };

Přidáte privátní enum pro převod každého časového rozsahu na diskrétní hodnotu. GetTimeBand Pak metoda používá relační vzory a konjunktivní or vzory, které jsou přidané v jazyce C# 9.0. Relační vzor umožňuje otestovat číselnou hodnotu pomocí <><=nebo >=. Vzor or testuje, jestli výraz odpovídá jednomu nebo více vzorům. Můžete také použít and vzor, který zajistí, aby výraz odpovídal dvěma odlišným not vzorům, a vzor, který otestuje, že výraz neodpovídá vzoru.

Po vytvoření těchto metod můžete k výpočtu cenové úrovně Premium použít jiný switch výraz se vzorem řazené kolekce členů . Mohli byste vytvořit výraz se switch všemi 16 rameny:



    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.MorningRush, true) => 2.00m,
        (true, TimeBand.MorningRush, false) => 1.00m,
        (true, TimeBand.Daytime, true) => 1.50m,
        (true, TimeBand.Daytime, false) => 1.50m,
        (true, TimeBand.EveningRush, true) => 1.00m,
        (true, TimeBand.EveningRush, false) => 2.00m,
        (true, TimeBand.Overnight, true) => 0.75m,
        (true, TimeBand.Overnight, false) => 0.75m,
        (false, TimeBand.MorningRush, true) => 1.00m,
        (false, TimeBand.MorningRush, false) => 1.00m,
        (false, TimeBand.Daytime, true) => 1.00m,
        (false, TimeBand.Daytime, false) => 1.00m,
        (false, TimeBand.EveningRush, true) => 1.00m,
        (false, TimeBand.EveningRush, false) => 1.00m,
        (false, TimeBand.Overnight, true) => 1.00m,
        (false, TimeBand.Overnight, false) => 1.00m,
    };
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">public decimal PeakTimePremiumFull(DateTime timeOfToll, bool inbound) =>
    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.MorningRush, true) => 2.00m,
        (true, TimeBand.MorningRush, false) => 1.00m,
        (true, TimeBand.Daytime, true) => 1.50m,
        (true, TimeBand.Daytime, false) => 1.50m,
        (true, TimeBand.EveningRush, true) => 1.00m,
        (true, TimeBand.EveningRush, false) => 2.00m,
        (true, TimeBand.Overnight, true) => 0.75m,
        (true, TimeBand.Overnight, false) => 0.75m,
        (false, TimeBand.MorningRush, true) => 1.00m,
        (false, TimeBand.MorningRush, false) => 1.00m,
        (false, TimeBand.Daytime, true) => 1.00m,
        (false, TimeBand.Daytime, false) => 1.00m,
        (false, TimeBand.EveningRush, true) => 1.00m,
        (false, TimeBand.EveningRush, false) => 1.00m,
        (false, TimeBand.Overnight, true) => 1.00m,
        (false, TimeBand.Overnight, false) => 1.00m,
    };

Výše uvedený kód funguje, ale dá se ho zjednodušit. Všech osm kombinací pro víkend má stejné mýto. Všech osm můžete nahradit následujícím řádkem:


 1.0m,
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">(false, _, _) => 1.0m,

Příchozí i odchozí provoz mají stejný násobitel během dne v týdnu i v nočních hodinách. Tato čtyři spínací ramena mohou být nahrazena následujícími dvěma řádky:


 0.75m,
(true, TimeBand.Daytime, _)   => 1.5m,
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">(true, TimeBand.Overnight, _) => 0.75m,
(true, TimeBand.Daytime, _)   => 1.5m,

Kód by měl vypadat jako následující kód po těchto dvou změnách:



    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.MorningRush, true)  => 2.00m,
        (true, TimeBand.MorningRush, false) => 1.00m,
        (true, TimeBand.Daytime,     _)     => 1.50m,
        (true, TimeBand.EveningRush, true)  => 1.00m,
        (true, TimeBand.EveningRush, false) => 2.00m,
        (true, TimeBand.Overnight,   _)     => 0.75m,
        (false, _,                   _)     => 1.00m,
    };
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.MorningRush, true)  => 2.00m,
        (true, TimeBand.MorningRush, false) => 1.00m,
        (true, TimeBand.Daytime,     _)     => 1.50m,
        (true, TimeBand.EveningRush, true)  => 1.00m,
        (true, TimeBand.EveningRush, false) => 2.00m,
        (true, TimeBand.Overnight,   _)     => 0.75m,
        (false, _,                   _)     => 1.00m,
    };

Nakonec můžete odebrat dvě špičky, které platí běžnou cenu. Jakmile tyto paže vyjmete, můžete nahradit false zahozenou (_) v posledním rameni spínače. Budete mít následující dokončenou metodu:



    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.Overnight, _) => 0.75m,
        (true, TimeBand.Daytime, _) => 1.5m,
        (true, TimeBand.MorningRush, true) => 2.0m,
        (true, TimeBand.EveningRush, false) => 2.0m,
        _ => 1.0m,
    };
" style="box-sizing: inherit; outline-color: inherit; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 1em; direction: ltr; border: 0px; padding: 0px; line-height: 1.3571; display: block; position: relative;">public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
    (IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
    {
        (true, TimeBand.Overnight, _) => 0.75m,
        (true, TimeBand.Daytime, _) => 1.5m,
        (true, TimeBand.MorningRush, true) => 2.0m,
        (true, TimeBand.EveningRush, false) => 2.0m,
        _ => 1.0m,
    };

Tento příklad zvýrazňuje jednu z výhod porovnávání vzorů: větve vzorů se vyhodnocují v pořadí. Pokud je přeskupíte tak, aby starší větev zpracovávala některý z pozdějších případů, kompilátor vás upozorní na nedostupný kód. Tato jazyková pravidla usnadnila předchozí zjednodušení s jistotou, že se kód nezměnil.

Porovnávání vzorů dělá některé typy kódu čitelnější a nabízí alternativu k objektům orientovaným technikám, když nemůžete přidat kód do tříd. Cloud způsobuje, že data a funkce se od sebe oddělují. Tvar dat a operace s ním nejsou nutně popsány společně. V tomto kurzu jste využili existující data úplně jinak než jejich původní funkce. Porovnávání vzorů vám umožnilo psát funkce, které tyto typy přetěžují, i když jste je nemohli rozšířit.

Další kroky

Hotový kód si můžete stáhnout z úložiště githubu dotnet/samples . Prozkoumejte vlastní vzory a přidejte tuto techniku do běžných aktivit kódování. Když se tyto techniky naučíte, získáte další způsob, jak přistupovat k problémům a vytvářet nové funkce.

undefined

Existuje 16 různých kombinací těchto tří proměnných. Kombinací některých podmínek zjednodušíte konečný výraz switch.

Systém, který vybírá placené poplatky, používá