W zeszłym tygodniu powiedzieliśmy sobie o funkcjach wyższego rzędu.  W tym tygodniu skoncentruje się na dwóch funkcjach, które do funkcji wyższego rzędu należą – Map i Filter. Są to proste funkcje i raczej nie sprawią problemu ze zrozumieniem. Jednak skoro piszemy o wszystkich to o nich też trzeba.

Z cyklu funkcje wyższego rzędu, do tej pory ukazały się artykuły:

  1. Funkcje wyższego rzędu
  2. Map i Filter
  3. Fold/Reduce
  4. Currying

Map

Map jest funkcją która dla każdego elementu danych wejściowych wykonuje funkcję pojedynczo na wszystkich elementach danych wejściowych, zwracając dane w tej samej kolejności co dane wejściowe. Funkcja, która jest wykonywana na elemencie zazwyczaj zmienia/transformuje dane wejściowe.

Funkcja która jest wykonywana na elementach w celu ich transformacji jest całkowicie dowolna, to może być mnożenie, podnoszenie do kwadratu, czy też zamiana typów danych. Można to dość krótko przedstawić za pomocą obrazka:

Mapowanie pomidora na pokrojony pomidor :)
Mapowanie pomidora na pokrojony pomidor :)

Zrozumiałe? :)

W C# mamy dwie funkcje które nam umożliwiają mapowanie. Pierwsza z nich jest znana od wprowadzenia LINQ – Select. Druga od .NET 4.0 Zip. Dla przykładu mając dane wejściowe w postaci listy osób:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
}

var people = new List<Person> { /****/ };

Możemy z mapować wszystkich ludzi do listy adresów e-mail:

people.Select(x => x.Email);

Albo, możemy podnieść do kwadratu każdą liczbę z przedziału od 1 do 100:

Enumerable.Range(0, 100).Select(x => x * x);

Drugą funkcją C# jest funkcja Zip, która wykonuje funkcję mapującą na wszystkich elementach dwóch list. Listy na której metoda została wykonana, i listy przekazanej w parametrze. Wynikiem jest pojedyncza lista zwierająca tyle elementów ile elementów zawiera najkrótsza z list. Dla przykładu, jeżeli metodę zip wykonamy na liście 6 elementowej, ale do metody przekażemy 3 elementową listę to wynikiem będzie lista 3 elementowa. Ogólnie Zip bierze po tym samym elemencie (po index) z dwóch list i wykonuje na nich funkcję. Jak w jednej z list nie ma takiego indexu, to kończy działanie. Funkcja mapująca zaś, przejmuje dwa parametry, pierwszy to element z listy na której metoda jest wołana, drugi to element z listy która została przekazana do Zip.

Enumerable
    .Range(0, 5)
    .Zip(Enumerable.Range(0,3), (f,s) => f*s)

Wynikiem będzie lista trzy elementowa zawierająca wartości 0, 1, 4.

W F# mamy podobnie, z tą różnicą iż metody zwą się map i map2. Przy czym map2 wyrzuca wyjątek jeżeli listy są o różnych długościach.

let list = [1;2;3]
let map = list |> List.map(fun x -> x*x)
printf "%A" map

let list1 = ["ala";"kota";"a";"ma"]
let list2 = [" ma";", ";" kot";" ale"]
let map = List.map2 (fun f s -> f + s) list1 list2
printf "%A" map

Metody map i map2 są dostępne dla różnych kolekcji (Seq, List, Map itp.).

To tyle jeżeli chodzi o funkcję mapującą.

Filter

Funkcja wyższego rzędu filter to taka funkcja, która dla każdego elementu danych wejściowych wykonuje funkcję która zwraca wartość true lub false. Jeżeli wartość zwrócona przez funkcję to false, dane nie pojawią się w wyniku końcowym. Jeżeli true, do wyniku końcowego dane są dodawane.

Można to zobrazować za pomocą obrazka:

Wybranie kilku pomidorów z wielu
Wybranie kilku pomidorów z wielu

W C# typową funkcją filtrującą jest Where. Dla przykładu, poniższa funkcja zwróci wszystkich ludzi, którzy są starsi niż 10 lat. Jeżeli takich ludzi nie ma, zostanie zwrócona pusta lista.

people.Where(x => x.Age > 10)

W F# istnieje zaś funkcja która się zwie filter. Dla przykładu, w taki o to sposób możemy zwrócić listę wszystkich parzystych liczb z przedziału 1..10:

[1..10]
|> List.filter (fun x -> x%2 = 0)

Podsumowanie

Jak widać, funkcje Map i Filter nie są ciężkie i trudne do zrozumienia. Chodzi jedynie o fakt przekazywania funkcji jako parametru wejściowego. Pewnie z nich korzystaliście nie zastanawiając się nawet, że zwą się one funkcjami wyższego rzędu – i dobrze. Jednak dobrze wiedzieć i znać tę nazwę, gdyż jak się ona pojawi na interview… to nawet wiedząc i znając wszystkie metody możemy nie skumać o co biega.

Cieszę się też, że udało mi się zamknąć te funkcje za jednym razem. Za tydzień postaram się opowiedzieć o najtrudniejszej do zrozumienia funkcji wyższego rzędu, postaram się to też tak opisać, by każdy to zrozumiał. A więc do przeczytania niebawem :)