napsal Reinder de Vries dne 9. července 2020 v App Development, Swift

Mapujte, zmenšujte a filtrujte ve Swiftu

v Swiftu používáte map(), reduce() a filter() k opakování kolekcí, jako jsou pole a slovníky, bez použití for-loop.

funkce map, reduce a filter pocházejí z oblasti funkcionálního programování (FP). Říká se jim funkce vyššího řádu, protože berou funkce jako vstup. Používáte funkci na pole, například k transformaci jeho dat.

funkce mapy, redukce a filtrování Swiftu mohou být náročné na Omotání hlavy. Zvláště pokud jste vždy kódovali smyčky for in, abyste vyřešili iterační problémy. V této příručce se dozvíte, jak používat funkce map(_:), reduce(_:_:) a filter(_:) ve službě Swift.

začněme!

  1. Úvod do mapy, redukce a filtrování
  2. rychlý Start: Funkce vyššího řádu v systému Swift
  3. pomocí funkce mapy
  4. pomocí funkce Reduce
  5. pomocí funkce filtru
  6. kombinací Map, Reduce a Filter
  7. další čtení

Úvod do Map, Reduce a Filter

při vytváření aplikací pro iOS obvykle používáte procedurální nebo objektově orientované programování. Funkční programování je jiné: zabývá se pouze funkcemi. Žádné proměnné, žádný stav, Žádné pro-smyčky-jen funkce.

programovací jazyk Swift se dokonale hodí pro míchání funkčního programování s nefunkčními přístupy, jako je OOP. Nemusíte striktně psát funkční kód a přijetí konceptů z funkčního programování vám pomůže naučit se lépe kódovat.

funkce map(_:), reduce(_:_:) a filter(_:) se nazývají funkce vyššího řádu, protože berou funkci jako vstupní a návratové funkce jako výstup. Technicky Swift vrací výsledky operace (tj. transformované pole) při použití funkcí vyššího řádu, zatímco čistý funkční jazyk vrátí sbírku funkcí. Ve Swiftu jsou vstupy pro tyto funkce uzávěry.

zde je návod, jak fungují:

  • funkce map() aplikuje funkci na každou položku v kolekci. Přemýšlejte o „mapování“ nebo transformaci jedné sady hodnot na jinou sadu hodnot.
  • funkce reduce() změní kolekci na jednu hodnotu. Přemýšlejte o tom jako o kombinaci mnoha hodnot do jedné, jako je zprůměrování sady čísel.
  • funkce filter() jednoduše vrací hodnoty, které prošly příkazem if, a pouze pokud tato podmínka vyústila v true.

v případě, že si myslíte: „podívejte se, nepotřebuji funkční programování nebo zpracování dat, protože moje aplikace to nedělají!“tak tady nezastavuj. V posledních projektech aplikací jsem použil mapu, snížit a filtrovat při více příležitostech:

  • filtrování hodnot nákladů a výnosů pomocí filter(_:) pro splnění prahové hodnoty, před zobrazením hodnot v řádkovém grafu
  • Zprůměrování tisíců hodnocení filmů do jedné hodnoty pomocí reduce(_:_:)
  • mapování několika operací na řetězci pomocí hashtagů, jejich přeměna na normalizovanou sbírku, s map(_:)

všechny tyto problémy jste mohli vyřešit pomocí for-loop, ale uvidíte, že použití map(), reduce() a filter() má za následek stručnější, čitelnější a výkonnější kód.

Rychlý Start: Funkce vyššího řádu v Swift

v tomto tutoriálu se zaměříme na map(), reduce() a filter(). Než půjdeme dál, zde je rychlý přehled nejběžnějších funkcí vyššího řádu ve službě Swift:

  • map(_:) smyčky nad každou položkou v sekvenci, použije funkci pro každý prvek a vrátí transformovaný výsledek
  • reduce(_:_:) smyčky nad každou položkou v sekvenci, spojí je do jedné hodnoty a vrátí kombinovaný výsledek
  • filter(_:) smyčky nad každou položkou v sekvenci a vrátí výslednou sekvenci, která obsahuje pouze položky, které splňují danou filtrační funkci
  • flatMap(_:), je stejná jako map(_:), kromě toho, že zplošťuje výslednou sekvenci, tj. vnořená pole jsou un-vnořená nebo „zploštělá“
  • compactMap(_:) dělá to samé jako map(_:), kromě toho, že odstraní nil hodnoty z výsledné sekvence před vrácením

tyto funkce můžete použít na polích, slovnících, sadách, rozsazích, sekvencích a jakémkoli jiném typu Swift, který můžete iterovat. Pokud se chcete dozvědět více o compactMap(_:) a flatMap(_:), podívejte se na tento tutoriál.

několik typů ve Swiftu, například pole a slovník, má funkce, které přijímají uzávěry jako vstup. Rychlý výběr:

  • contains(where:) smyčky nad kolekcí, použije predikát (Uzávěr) na každou položku, vrátí true , pokud položka vyhovuje predikátu, jinak false
  • first(where:) použije predikát (Uzávěr) na každou položku a vrátí položku, pokud splňuje predikát
  • firstIndex(where:) dělá to samé jako first(where:), kromě toho, že vrací index místo hodnoty

více o těchto funkcích se dozvíte v tomto tutoriálu. Jak se where používá ve Swiftu, je také zajímavé, o tom se můžete dozvědět více v tomto tutoriálu.

v tomto tutoriálu jste viděli funkce“ map „A“ reduce “ v programu Swift napsané jako map(_:) a reduce(_:_:). Podtržítka a dvojtečky v těchto funkcích jsou součástí podpisu funkce, což je speciální formát pro označení parametrů funkce. Například funkce map(_:) má jeden nepojmenovaný parametr, zatímco funkce reduce(_:_:) má dva. Více se o tom dozvíte v tomto tutoriálu: funkce ve Swift vysvětleny.

získejte najat jako vývojář iOS

Naučte se vytvářet aplikace pro iOS 14 pomocí Swift 5

přihlaste se do mého kurzu vývoje iOS a naučte se, jak začít svou kariéru jako profesionální vývojář iOS.

pomocí funkce Map

funkce map(_:) smyčka nad každou položkou v kolekci a použije operaci pro každý prvek v kolekci. Vrací sbírku výsledných položek, na které byla operace použita.

podívejme se na příklad. Máme tu řadu teplot ve stupních Celsia, které chcete změnit na Fahrenheit.

můžete použít for-loop, jako je tento:

let celsius = var fahrenheit: = for value in celsius { fahrenheit += }print(fahrenheit)// Output: 

kód funguje dobře, ale je příliš podrobný. Potřebujete proměnnou „pomocník“ fahrenheit pro uložení vypočtených konverzí při práci s nimi a pro samotnou konverzi potřebujete 3 řádky kódu.

zde je návod, jak můžeme udělat totéž s funkcí map(_:) :

nechť celsius =
nechť fahrenheit = celsius.mapa { $0 * (9/5) + 32 }
tisk(fahrenheit)
skrýt varování

můžete to udělat i na jednom řádku:

.map {  * (9/5) + 32 }

co se tady děje?

  1. je definována konstanta celsius, pole dvojic a inicializováno několika náhodnými hodnotami Celsia.
  2. funkce map(_:) je volána na poli celsius. Funkce má jeden argument, uzávěr, který převádí z Celsia na Fahrenheita.
  3. nakonec je výsledek vytištěn: převedené pole, od Celsia po Fahrenheita.

funkce map(_:) transformuje jedno pole do druhého použitím funkce na každou položku v poli. Uzávěr * (9/5) + 32 vezme vstupní hodnotu ve stupních Celsia a vrátí hodnotu ve stupních Fahrenheita. Výsledné pole map(_:) je vytvořeno z těchto převedených hodnot.

podívejme se blíže na uzavření. Pokud jste již dříve pracovali s uzávěry, poznáte syntaxi uzavření Krátké ruky. Je to kratší způsob, jak kódovat uzavření, vynecháním velké části jeho syntaxe.

zde je méně stručná alternativa:

let celsius = let fahrenheit = celsius.map({ (value: Double) -> Double in return value * (9/5) + 32})print(fahrenheit)

skutečné volání funkce map(_:) a její uzavření je toto:

··· = celsius.map({ (value: Double) -> Double in return value * (9/5) + 32})

co se to tam děje? Funkce map(_:) je volána na poli celsius. Trvá jeden argument: uzavření typu (Double) -> Double.

první část uzávěru, začínající {, označuje, že tento uzávěr má jeden parametr value typu Double a uzávěr vrací hodnotu typu Double. Uzavírací těleso, začínající return, jednoduše vrátí výsledek výpočtu Celsia na Fahrenheita.

pokud porovnáte syntaxi uzavření Krátké ruky s rozšířeným kódem výše, uvidíte, že:

  • závorky funkce ( a ) jsou vynechány, protože je můžete vynechat, když je posledním parametrem volání funkce uzavření.
  • část () -> in může být vynechána, protože Swift může odvodit, že jako vstup používáte jeden parametr Double a očekává se, že vrátí Double. Nyní, když jste vynechali proměnnou value, můžete použít zkratku .
  • příkaz return lze také vynechat, protože se očekává, že toto uzavření stejně vrátí výsledek výrazu.

i když výše uvedený vzorek kódu používá typy Double, nejste omezeni pouze na tyto typy. Výsledný typ funkce map() může mít jiný typ, než jaký jste do ní vložili, a můžete použít map() i na Dictionary.

pojďme na reduce(_:_:)!

pomocí funkce Reduce

funkce reduce(_:_:) smyčky nad každou položkou v kolekci a redukuje je na jednu hodnotu. Přemýšlejte o tom jako o kombinaci více hodnot do jedné.

funkce redukce je možná nejtěžší z mapy, redukce, filtrování pochopit. Jak můžete přejít ze sbírky hodnot na jednu hodnotu?

několik příkladů:

  • vytvoření součtu více hodnot, tj. 3 + 4 + 5 = 12
  • zřetězení kolekce řetězců, tj. = "Zaphod, Trillian, Ford"
  • Zprůměrování sady hodnot, tj. (7 + 3 + 10) / 3 = 7/3 + 3/3 + 10/3 = 6.667

při zpracování dat si můžete představit spoustu scénářů, kdy se takové jednoduché operace hodí. Stejně jako dříve můžete vyřešit některý z těchto problémů pomocí for-loop, ale reduce(_:_:) je prostě kratší a rychlejší.

zde je návod:

let values =
let sum = values.snížit (0, +)
tisk (součet)
skrýt varování

funkce reduce(_:_:) má dva argumenty, počáteční hodnotu a uzávěr. Ve výše uvedeném kódu poskytujeme operátor +, to je také funkce se dvěma parametry.

samozřejmě můžete také zajistit vlastní uzavření:

nechť hodnoty =
nechť průměr = hodnoty.snížit (0.0) { $0 + $1 } / Double (hodnoty.počet)
tisk (průměr)
skrýt varování

ve výše uvedeném příkladu vypočítáváte průměr tří čísel. Typy hodnot v kódu jsou všechny Double. Nejprve sečteme všechna čísla a pak je vydělíme počtem čísel.

funkce reduce(_:_:) se liší dvěma způsoby:

  1. funkce reduce(_:_:) má dva nepojmenované parametry; počáteční hodnotu a uzávěr
  2. uzávěr, který je poskytován reduce(_:_:), má také dva parametry; aktuální výsledek redukce a nová hodnota, která se chystá snížit

zde, Podívejte se na toto:

let values =
let sum = values.zmenšit (0) {
tisk(„\($0) + \($1) = \($0 + $1)“)
návrat $0 + $1
}
tisk (suma)
skrýt varování

ve výše uvedeném příkladu můžete jasně vidět a , 2 parametry uzávěrů. Když spustíte kód, toto je výstup, který získáte:

0 + 7 = 77 + 3 = 1010 + 10 = 2020

podívejte se, jak Začínáme s 0 a pak přidáme 7? V dalším kroku bereme 7 – aktuální hodnotu redukce – a přidáme 3, hodnotu „další“ v redukci.

zde je další způsob, jak se na to dívat:

0 + 7(0 + 7) + 3((0 + 7) + 3) + 10

to také objasňuje, proč potřebujete počáteční hodnotu pro reduce(_:_:), protože to je první první parametr uzavření.

redukce může být obtížné pochopit. Je důležité pochopit, že iterativně aplikujete operaci, například +, na sadu hodnot, dokud Vám nezůstane pouze jedna hodnota. Doslova snižujete množství hodnot.

pojďme na filter(_:)!

pomocí funkce filtru

funkce filter přeskočí každou položku v kolekci a vrátí kolekci obsahující pouze položky, které splňují podmínku zahrnout.

je to jako použít if – příkaz na kolekci a zachovat pouze hodnoty, které předávají podmínku.

zde se podívejte na toto:

let values =
let even = values.filtr { $0.isMultiple (z: 2) }
tisk (i)
skrýt varování

ve výše uvedeném příkladu filtrujete čísla z values, která jsou sudá. Funkce isMultiple(of:) vrací true, když lze dělit 2 a false jinak.

na rozdíl od map(_:) a reduce(_:_:) musí uzávěr filter(_:) vrátit boolean, takže buď true nebo false. Když uzávěr vrátí true, hodnota je zachována a když je vrácena false, hodnota je vynechána. Takto filter(_:) filtruje vstupní pole.

zde je o něco jasnější příklad s rozšířeným uzávěrem:

let values = let even = values.filter({ (value:Int) -> Bool in return value.isMultiple(of: 2)})print(even) // Output: 

v příkladu uzávěr vrátí booleovskou hodnotu označenou -> Bool. Je k dispozici jeden parametr, položka v kolekci, a vrátí výsledek isMultiple(of:). Paráda!

kombinace mapy, redukce a filtrování

můžete kombinovat funkce map(), reduce() a filter()? Jasně, že můžeš!

řekněme, že máme třídu studentů. Znáte rok, kdy se každý student narodil. Chcete vypočítat kombinovaný věk všech studentů narozených v roce 2000 nebo později.

zde je návod, jak to udělat:

let now = 2020
let years =
let sum = years.filtr({ $0 >= 2000 }).mapa ({ now – $ 0 }).snížit (0, +)
tisk (součet)
skrýt varování

výše uvedený vzorek kódu používá řetězení. Používá výsledek jedné funkce jako vstup pro jinou, kombinující map-reduce-filter. Funkce map() je volána na poli výsledků funkce filter() a funkce reduce() je volána na výsledku funkce map(). Úžasné!

samotný kód je jednoduchý:

  1. Vytvořte konstantu now a years a přiřaďte jí spoustu let.
  2. odfiltrujte roky, které jsou nižší než 2000, tj. ponechte si ty, pro které >= 2000 je true
  3. Transformujte každý rok na věk odečtením roku od now
  4. přidejte všechny věky nahoru snížením pomocí +

Vezměme si zajímavější příklad. Podívejte se na následující kód, převzatý z tutoriálu o FizzBuzz:

nechť fizzbuzz: (Int) -> String = { i v
switch (I % 3 = = 0, i % 5 == 0)
{
case (true, false):
return „Fizz“
case (false, true):
return“Buzz“
case (true,true):
return“FizzBuzz“
default:
návrat “ \(i)“
}
}
nechť výsledek = Array (2…100).mapa (fizzbuzz).snížit(„1“, { $0 + „, “ + $1 })
tisk (výsledek)
skrýt varování

podívejte se, co se děje? Transformujeme pole s čísly od 2 do 100 na „Fizz“, „Buzz“ nebo „FizzBuzz“ s map(_:), na základě pravidel hry. Nakonec redukujeme toto pole řetězců na jeden velký řetězec s reduce(_:_:), který kombinuje každou hodnotu. Paráda!

další čtení

co kdybyste to všechno museli kódovat smyčkami for in? Použil bys mnohem víc kódu. A to je síla map-reduce-filter: je to stručnější, často čitelnější a-přiznejte to-docela zatraceně cool!

chcete se dozvědět více? Podívejte se na tyto zdroje:

  • jak používat „kde“ v Swift
  • FlatMap a CompactMap vysvětleno v Swift
  • The Ultimate Guide to Closures in Swift
  • jak: najít položku v poli v Swift
  • Hrajte s kódem: Binární vyhledávání v Swift
  • Začínáme s Xcode Playgrounds

získejte najat jako vývojář iOS

Naučte se vytvářet aplikace pro iOS 14 pomocí Swift 5

přihlaste se do mého kurzu vývoje iOS a naučte se, jak začít svou kariéru jako profesionální vývojář iOS.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.