skriven av Reinder de Vries den 9 juli 2020 i apputveckling, Swift

mappa, reducera och filtrera i Swift

i Swift använder du map(), reduce() och filter() för att slinga över samlingar som arrayer och ordböcker, utan att använda en For-loop.

kartläggnings -, reducerings-och filterfunktionerna kommer från sfären av funktionell programmering (FP). De kallas högre ordningsfunktioner, eftersom de tar funktioner som inmatning. Du använder en funktion till en array, till exempel för att omvandla dess data.

Swifts karta, minska och filtrera funktioner kan utmanande att linda huvudet runt. Speciellt om du alltid har kodat for in loopar för att lösa iterationsproblem. I den här guiden lär du dig hur du använder funktionerna map(_:), reduce(_:_:) och filter(_:) i Swift.

Låt oss komma igång!

  1. introduktion till karta, minska och filtrera
  2. Snabbstart: Funktioner med högre ordning i Swift
  3. använda kartfunktionen
  4. använda funktionen minska
  5. använda Filterfunktionen
  6. kombinera karta, minska och filtrera
  7. Vidare läsning

introduktion till karta, minska och filtrera

när du bygger iOS appar, du använder vanligtvis procedurell eller objektorienterad programmering. Funktionell programmering är annorlunda: det handlar bara om funktioner. Inga variabler, inget tillstånd, inga för-loopar-bara funktioner.

Swift-programmeringsspråket lämpar sig perfekt för att blanda funktionell programmering med icke-funktionella tillvägagångssätt, som OOP. Du behöver inte strikt skriva funktionell kod, och att anta begrepp från funktionell programmering kan hjälpa dig att lära dig att koda bättre.

funktionerna map(_:), reduce(_:_:) och filter(_:) kallas högre ordningsfunktioner, eftersom de tar en funktion som input och returfunktioner som output. Tekniskt sett returnerar Swift resultaten av en operation (dvs. en transformerad array) när du använder funktioner med högre ordning, medan ett rent funktionellt språk returnerar en samling funktioner. I Swift är ingångarna för dessa funktioner stängningar.

så här fungerar de:

  • funktionen map() tillämpar en funktion på varje objekt i en samling. Tänk på att” kartlägga ” eller omvandla en uppsättning värden till en annan uppsättning värden.
  • funktionen reduce() förvandlar en samling till ett värde. Tänk på det som att kombinera många värden till en, som i genomsnitt en uppsättning siffror.
  • funktionen filter() returnerar helt enkelt värden som passerade ett if-uttalande, och endast om det villkoret resulterade i true.

om du tänker: ”titta, jag behöver inte funktionell programmering eller databehandling, eftersom mina appar inte gör det!”stanna inte här. I de senaste appprojekten har jag använt karta, minska och filtrera vid flera tillfällen:

  • filtrera kostnads – / intäktsvärden med filter(_:), för att möta ett tröskelvärde innan värdena visas i en linjediagram
  • medelvärde av tusentals filmbetyg till ett värde med reduce(_:_:)
  • kartlägga några operationer på en sträng med hashtags, omvandla den till en normaliserad samling, med hjälp av map(_:)

du kunde ha löst alla dessa problem med en for-loop, men du ser att använda map(), reduce() och filter() resulterar i mer koncis, läsbar och performant kod.

Snabbstart: Funktioner med högre ordning i Swift

vi fokuserar på map(), reduce() och filter() i denna handledning. Innan vi går vidare, här är en snabb översikt över de vanligaste högre ordningens funktioner i Swift:

  • map(_:) loopar över varje objekt i en sekvens, tillämpar en funktion på varje element och returnerar det transformerade resultatet
  • reduce(_:_:) loopar över varje objekt i en sekvens, kombinerar dem till ett värde och returnerar det kombinerade resultatet
  • filter(_:) loopar över varje objekt i en sekvens och returnerar en resulterande sekvens som bara innehåller objekt som uppfyller en given filtreringsfunktion
  • flatMap(_:) gör samma sak som map(_:), förutom att den plattar ut den resulterande sekvensen, dvs. kapslade matriser är un-kapslade eller ”tillplattad ut”
  • compactMap(_:) gör detsamma som map(_:), förutom att det tar bort nil värden från den resulterande sekvensen innan du returnerar den

du kan använda dessa funktioner på arrayer, ordböcker, uppsättningar, intervall, sekvenser och alla andra Swift-typer som du kan iterera över. Om du vill lära dig mer om compactMap(_:) och flatMap(_:), kolla in den här handledningen.

flera typer i Swift, som Array och Dictionary, har funktioner som accepterar stängningar som inmatning. Ett snabbt val:

  • contains(where:) loopar över en samling, tillämpar ett predikat (en stängning) till varje objekt, returnerar en true om ett objekt uppfyller predikatet, annars false
  • first(where:) om det uppfyller predikatet
  • firstIndex(where:) gör detsamma som first(where:), förutom att det returnerar indexet istället för värdet

du kan lära dig mer om dessa funktioner i den här handledningen. Hur where används i Swift är också intressant, du kan lära dig mer om det i denna handledning.

du har sett funktionerna ”karta” och ”minska” i Swift skrivna som map(_:) och reduce(_:_:) under hela denna handledning. Understreck och kolon i dessa funktioner är en del av funktionens signatur, vilket är ett speciellt format för att indikera funktionsparametrar. Till exempel har funktionen map(_:) en namnlös parameter, medan funktionen reduce(_:_:) har två. Du kan lära dig mer om det i denna handledning: funktioner i Swift förklarade.

bli anställd som IOS-utvecklare

lär dig att bygga iOS 14-appar med Swift 5

registrera dig för min iOS-utvecklingskurs och lär dig hur du startar din karriär som professionell iOS-utvecklare.

använda kartfunktionen

funktionen map(_:) loopar över varje objekt i en samling och tillämpar en åtgärd på varje element i samlingen. Den returnerar en samling resulterande objekt, som operationen tillämpades på.

Låt oss titta på ett exempel. Vi har en mängd temperaturer i Celsius som du vill omvandla till Fahrenheit.

du kan använda en for-loop, så här:

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

koden fungerar bra, men det är för verbose. Du behöver en muterbar” helper ” – variabel fahrenheit för att lagra de beräknade konverteringarna när du arbetar igenom dem, och du behöver 3 rader kod för själva konverteringen.

så här kan vi göra detsamma med funktionen map(_:) :

låt celsius =
låt fahrenheit = celsius.karta { $0 * (9/5) + 32 }
tryck (fahrenheit)
dölj varningar

du kan till och med göra allt på en rad:

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

vad händer här?

  1. en konstant celsius definieras, en uppsättning dubblar och initieras med några slumpmässiga Celsius-värden.
  2. funktionen map(_:) anropas på matrisen celsius. Funktionen har ett argument, en stängning, som konverterar från Celsius till Fahrenheit.
  3. slutligen skrivs resultatet ut: den konverterade matrisen, från Celsius till Fahrenheit.

funktionen map(_:) omvandlar en array till en annan genom att tillämpa en funktion på varje objekt i arrayen. Stängningen * (9/5) + 32 tar ingångsvärdet i Celsius och returnerar ett värde i Fahrenheit. Den resulterande arrayen av map(_:) är uppbyggd av de konverterade värdena.

Låt oss titta närmare på Stängningen. Om du har arbetat med stängningar tidigare känner du igen syntaxen för stängning med kort hand. Det är ett kortare sätt att koda en stängning, genom att lämna ut mycket av sin syntax.

här är ett mindre kortfattat alternativ:

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

det faktiska map(_:) funktionsanropet och dess stängning är detta:

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

vad händer där? Funktionen map(_:)anropas på matrisen celsius. Det tar ett argument: en stängning av typen (Double) -> Double.

den första delen av stängningen, som börjar med {, indikerar att denna stängning har en parameter value av typen Double, och stängningen returnerar ett värde av typen Double. Stängningskroppen, som börjar med return, returnerar helt enkelt resultatet av Celsius till Fahrenheit-beräkningen.

om du jämför den korta stängningssyntaxen med den expanderade koden ovan ser du det:

  • funktionsparenteserna ( och ) utelämnas, eftersom du kan lämna dem ute när den sista parametern för ett funktionsanrop är en stängning.
  • () -> in – delen kan utelämnas, eftersom Swift kan dra slutsatsen att du använder en Double – parameter som inmatning och förväntas returnera en Double. Nu när du har utelämnat variabeln value kan du använda korthand .
  • return – uttalandet kan också utelämnas, eftersom denna stängning förväntas returnera resultatet av ett uttryck ändå.

även om ovanstående kodexempel använder Double typer, är du inte begränsad till dessa typer. Den resulterande typen av en map() – funktion kan ha en annan typ än vad du lägger in i den, och du kan också använda map() på en Dictionary.

Låt oss gå vidare till reduce(_:_:)!

använda funktionen minska

funktionen reduce(_:_:) loopar över varje objekt i en samling och reducerar dem till ett värde. Tänk på det som att kombinera flera värden till en.

minska funktionen är kanske den svåraste av karta, minska, filter för att förstå. Hur kan du gå från en samling värden till ett värde?

några exempel:

  • skapa en summa av flera värden, dvs. 3 + 4 + 5 = 12
  • sammanfoga en samling strängar, dvs. = "Zaphod, Trillian, Ford"
  • medelvärde av en uppsättning värden, dvs. (7 + 3 + 10) / 3 = 7/3 + 3/3 + 10/3 = 6.667

i databehandling kan du föreställa dig många scenarier när enkla operationer som dessa kommer till nytta. Som tidigare kan du lösa något av dessa problem med en for-loop, men reduce(_:_:) är helt enkelt kortare och snabbare.

så här gör du:

let values =
let sum = värden.minska (0, +)
Skriv ut (summa)
dölj varningar

funktionen reduce(_:_:) tar två argument, ett initialvärde och en stängning. I ovanstående kod tillhandahåller vi operatören +, det är också en funktion med två parametrar.

du kan naturligtvis också ge din egen stängning:

let values =
let average = värden.minska (0.0) { $0 + $1 } / dubbel (värden.räkna)
Skriv ut (genomsnitt)
dölj varningar

i exemplet ovan beräknar du genomsnittet av tre siffror. Typerna av värdena i koden är alla Double. Vi lägger först upp alla siffror och delar sedan upp det med antalet siffror.

funktionen reduce(_:_:) är annorlunda på två sätt:

  1. funktionen reduce(_:_:) har två namnlösa parametrar; initialvärdet och stängningen
  2. Stängningen som tillhandahålls till reduce(_:_:) har också två parametrar; det nuvarande resultatet av minskningen och det nya värdet som håller på att minska

här, kolla in det här:

let values =
let sum = värden.minska (0) {
tryck(”\($0) + \($1) = \($0 + $1)”)
tillbaka $0 + $1
}
Skriv ut (summa)
dölj varningar

i exemplet ovan kan du tydligt se och , de 2 parametrarna för stängningarna. När du kör koden är det den utgång du får:

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

se hur vi börjar med 0 och lägg sedan till 7? I nästa steg tar vi 3, ”nästa” – värdet i reduktionen.

här är ett annat sätt att se på det:

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

detta gör det också klart varför du behöver ett initialvärde för reduce(_:_:), eftersom det är den första första parametern för Stängningen.

minskning kan vara svårt att förstå. Det är viktigt att förstå att du iterativt tillämpar en operation, som +, till en uppsättning värden tills du bara har ett värde. Du minskar bokstavligen mängden värden.

Låt oss gå vidare till filter(_:)!

använda Filterfunktionen

funktionen filter loopar över varje objekt i en samling och returnerar en samling som endast innehåller objekt som uppfyller ett inkluderingsvillkor.

det är som att tillämpa ett if-uttalande till en samling och bara behålla de värden som passerar villkoret.

här, kolla in det här:

låt värden =
låt även = värden.filtrera { $0.isMultiple (av: 2) }
tryck (jämnt)
dölj varningar

i exemplet ovan filtrerar du Nummer från values som är jämna. Funktionen isMultiple(of:) returnerar true när kan delas med 2 och false annars.

till skillnad från map(_:) och reduce(_:_:) måste Stängningen filter(_:) returnera en boolesk, så antingen trueeller false. När Stängningen returnerar true behålls värdet och när false returneras utelämnas värdet. Det är så filter(_:) filtrerar inmatningsmatrisen.

här är ett något tydligare exempel, med Stängningen utökad:

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

i exemplet returnerar Stängningen ett booleskt värde, indikerat med -> Bool. Det finns en parameter, objektet i samlingen, och returnerar resultatet av isMultiple(of:). Snyggt!

kombinera karta, minska och filtrera

kan du kombinera funktionerna map(), reduce() och filter()? Visst kan du!

låt oss säga att vi har en klass av studenter. Du vet året varje elev föddes. Du vill beräkna den kombinerade åldern för alla studenter födda i eller efter 2000.

så här gör du det:

låt nu = 2020
låt år =
låt sum = år.filter({ $0 >= 2000 }).karta ({nu – $0 }).minska (0, +)
Skriv ut (summa)
dölj varningar

ovanstående kodexempel använder kedja. Den använder resultatet av en funktion som ingång för en annan, som kombinerar map-reduce-filter. Funktionen map() anropas på resultatmatrisen för funktionen filter() och funktionen reduce() anropas på resultatet av funktionen map(). Häftig!

själva koden är enkel:

  1. gör en konstant now och years och tilldela en massa år till den.
  2. filtrera bort de år som ligger under 2000, dvs. håll de för vilka >= 2000 är true
  3. omvandla varje år till en ålder, genom att subtrahera året från now
  4. Lägg till alla åldrar upp, genom att minska med +

låt oss ta ett mer intressant exempel. Kolla in följande kod, hämtad från handledningen om FizzBuzz:

låt fizzbuzz: (Int) -> String = { i i
växla (i % 3 = = 0, i % 5 == 0)
{
fall (sant, falskt):
returnera ”Fizz”
fall (falskt, sant):
returnera ”Buzz”
fall (sant, sant):
returnera ”FizzBuzz”
standard:
returnera ” \(i)”
}
}
låt resultat = Array (2…100).karta (fizzbuzz).minska(”1”, { $0 + ”, ” + $1 })
Skriv ut (resultat)
dölj varningar

se vad som händer? Vi omvandlar en array med siffror från 2 till 100 till antingen ”Fizz”, ”Buzz” eller ”FizzBuzz” med map(_:), baserat på spelets regler. Slutligen reducerar vi den uppsättningen strängar till en stor sträng med reduce(_:_:), som kombinerar varje värde. Snyggt!

Vidare läsning

vad händer om du var tvungen att koda allt detta med for in loopar? Du skulle använda mycket mer kod. Och det är map-reduce-filters kraft: det är mer kortfattat, ofta lättare att läsa, och — erkänna det — ganska jävligt coolt!

vill du lära dig mer? Kolla in dessa resurser:

  • hur man använder” var ” i Swift
  • FlatMap och CompactMap förklaras i Swift
  • den ultimata guiden till nedläggningar i Swift
  • så här hittar du ett objekt i en Array i Swift
  • spela med kod: Binär sökning i Swift
  • Kom igång med Xcode Playgrounds

bli anställd som IOS-utvecklare

lär dig att bygga iOS 14-appar med Swift 5

registrera dig för min iOS-utvecklingskurs och lär dig hur du startar din karriär som professionell iOS-utvecklare.

Lämna ett svar

Din e-postadress kommer inte publiceras.