Sajtklub

Sajtklub

Tanulófeladat

Bevezetés

Technikailag a Higher Order Function egyszerűen egy olyan függvény, amely az alábbiak közül legalább egyet teljesít:

  • Elfogad egy függvényt az egyik argumentumaként.
  • Egy függvényt ad vissza eredményként.

A funkcionális programozás világában a használat általában szűkebb. A kifejezés rendszerint olyan függvényekre utal, mint a filter, a map és a reduce, amelyek egy átadott függvényt alkalmaznak egy gyűjtemény elemeire.

Műveletek gyűjteményeken

A tanterv ezen pontján már láttunk többféle módot arra, hogy egy műveletet egy bejárható gyűjtemény, például egy Vector minden elemére alkalmazzunk:

  • Használj ciklust (ahogy a legtöbb programozási nyelv a digitális számítástechnika hajnala óta).
  • Használj comprehensiont (Python-stílusban).
  • Használj broadcastinget (jellegzetesen Julia-szintaxis, bár sokat köszönhet az R-nek, a Matlabnak és a NumPy-nak).

Ez a fogalom a magasabb rendű függvényekre összpontosít (amelyek bármely funkcionális nyelvből ismerősek, például Haskellből vagy F#-ból).

További lehetséges megközelítések:

  • Rekurzió (ahogy az ML-családba tartozó nyelvekben).
    • A Julia engedi, de farokhívás-optimalizálás nélkül veremtúlcsordulás veszélyével jár.
  • Metaprogramozás makrókkal (hagyományosan Lisp-jellemző).
    • Ezt széles körben használják a haladó Julia-programozásban, de a legtöbb esetben légy óvatos vele.
    • A többi lehetőség valószínűleg könnyebben megírható és sokkal könnyebben debugolható.

Szűrés

A filter() függvény egy átadott függvényt vesz át, amely Boolean értéket ad vissza, és alkalmazza azt egy gyűjteményre. Csak azok az elemek kerülnek bele a visszatérési értékbe, amelyek true-t adnak vissza; a visszatérési érték alapvető típusa megegyezik a bemenetével (lásd alább).

julia> filter(iseven, 1:6)
3-element Vector{Int64}:
 2
 4
 6

# String is a collection of Chars, so String in -> String out
julia> filter(!isascii, "Hrōðgār")
"ōðā"

# tuple input -> tuple output
julia> filter(iseven, (1, 2, 3, 4, 5))
(2, 4)

Többdimenziós tömbök esetén a filter ellapítja a bemenet dimenzióit, és egy Vectort ad vissza: ez a fő kivétel az alól a szabály alól, hogy a kimenet típusa megegyezik a bemenetével.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> filter(isodd, m)
3-element Vector{Int64}:
 1
 5
 3

A fenti példák beépített függvényeket használnak, de ebben a helyzetben nagyon gyakori a névtelen függvények használata.

julia> filter(x -> x % 3 == 0, 1:20)
6-element Vector{Int64}:
  3
  6
  9
 12
 15
 18

Van helyben módosító változat is, filter!(), ahogy e fogalom számos függvényének esetében.

Leképezés

A map() függvény úgy alakít át egy gyűjteményt, hogy egy függvényt alkalmaz minden elemére. Egyszerű esetekben ez hasonlíthat a broadcastinghez, amikor a kimenet alakja megegyezik a bemenetével.

julia> map(√, [1, 4, 9])
3-element Vector{Float64}:
 1.0
 2.0
 3.0

julia> map(x -> x^2 + 1, 1:4)
4-element Vector{Int64}:
  2
  5
 10
 17

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> map(√, m)
2×3 Matrix{Float64}:
 1.0  1.41421  1.73205
 2.0  2.23607  2.44949

A map() több gyűjteményen is működik elemenként.

julia> map(*, [1, 2], [3, 4])
2-element Vector{Int64}:
 3
 8

Fogalmilag ezt tekinthetjük egyenértékűnek azzal, mintha zip()-et futtatnánk a több bemeneti gyűjteményen, majd map()-et a köztes eredmény minden elemén. Ez csak egy durva analógia, semmit sem mond a megvalósításról!

Ahogy a zip() esetében, az eltérő alakú gyűjtemények a legkisebb dimenziójára csonkolódnak.

Néha csak az átadott függvény mellékhatásaira van szükség, például egy adatbázisírásra vagy egy tömbbe irányuló push!-ra. Ilyenkor a foreach() magasabb rendű függvény áll rendelkezésre, amely mindig nothing-ot ad vissza.

Redukálás

A reduce() függvény egy kétargumentumú függvényt vesz át, és alkalmazza azt egy gyűjteményre, ami a dimenziók számának csökkenéséhez vezet.

Ez absztrakt módon zavarónak hangozhat, de gondolj olyan függvényekre, mint a sum() vagy a prod(), amelyek egy gyűjteményt vesznek át, és egyetlen értéket adnak vissza.

julia> sum(1:4) # add
10

julia> prod(1:4) # multiply
24

Ezek a speciális függvények erősen optimalizáltak, és mindig ezeket érdemes használni, ha elérhetők. További példák a maximum() és a minimum(), a logikai all() és any() függvények, valamint számos statisztikai függvény.

Csak szemléltetésképpen nézzük meg ugyanezt a funkciót a jóval általánosabb reduce() segítségével megvalósítva (emlékezz rá, hogy az infix + és * operátorok valójában függvények a háttérben).

julia> reduce(+, 1:4) # add
10

julia> reduce(*, 1:4) # multiply
24

Ahogy a sum() és más aggregáló függvények esetében, a reduce() is fogadhat egy opcionális dims kulcsszóargumentumot, amellyel megadható, mely dimenzió(k) mentén csökkentsen.

julia> m
2×3 Matrix{Int64}:
 1  2  3
 4  5  6

julia> reduce(+, m; dims=1)
1×3 Matrix{Int64}:
 5  7  9

Ezek könnyű példák, mert az összeadás és a szorzás is kommutatív (1+2 == 2+1) és asszociatív ( (1+2)+3 == 1+(2+3) ).

Ez korántsem általános! Még az olyan gyakori műveletek sem asszociatívak, mint a kivonás és az osztás.

További probléma, hogy a lebegőpontos hibák felhalmozódhatnak nagy gyűjtemények esetén, így a balról jobbra haladó reduce némileg más eredményt adhat, mint a jobbról balra haladó.

A Julia reduce függvényének iránya a megvalósítástól függ, és nincs garantálva.

Az irány kifejezett szabályozására ott vannak a foldl() és a foldr() függvények, amelyek elméletileg a „balról”, illetve a „jobbról” kezdenek (egy Vector esetében valójában felülről és alulról).

julia> foldl(-, 1:3) # (1 - 2) - 3
-4

julia> foldr(-, 1:3) # 1 - (2 - 3)
2

Vedd figyelembe, hogy ezeket egydimenziósként kezelhető gyűjteményekhez szánták, és skaláris eredményt adnak vissza. A dims argumentum használata nem támogatott a foldl és a foldr esetében, csak a reduce esetében.

MapReduce

A map művelet és a reduce együttes használata nagyon gyakori a programozás különböző területein.

Sorban lefuttathatnánk a map-et, majd a reduce-ot egy köztes gyűjteményen. Ez azonban a legjobb esetben is hatékonytalan, és nagyon rosszul skálázódik, ahogy a gyűjtemény egyre nagyobb lesz.

Erősen ajánlott helyette a kombinált mapreduce() függvény használata. Ez egy sokkal nagyobb teljesítményű algoritmust tud megvalósítani, amely összefonja a map/reduce műveleteket.

Az első argumentum a leképezésre használt függvény, a második pedig a reduce operátor.

julia> mapreduce(x -> x^2 + 1, +, 1:3)
17

# equivalent to (2 + 5 + 10)
julia> sum(map(x -> x^2 + 1, 1:3))
17

Ahogy várhatnánk, a Juliának vannak mapfoldl() és mapfoldr() függvényei is azokra az esetekre, amikor az irány számít.

Utasítások

Indul egy sajtklub, amely gépi tanulás (ML) segítségével választja ki azokat az új sajtokat, amelyeket a sajtkedvelő vásárlóinknak kínálunk az előzményeik és az ízlésük alapján.

Az új tagoknak ki kell tölteniük egy kezdeti kérdőívet, hogy legyen némi alapadatunk, amivel elindulhatunk. Ebből kiderült, hogy az ügyfeleknek van egy olyan részcsoportja, amelyik hangsúlyosan nyilatkozik, és hiányzik a kritikáiból a finomság. Mivel ez visszafordíthatatlanul elfogulttá tehet egy árnyaltabb algoritmust, külön algoritmust állítottunk fel az igényeik kezelésére. A feladatod, hogy néhány segédfüggvényt írj, amelyekkel rendbe tehetjük az adataikat.

Note

Bár a következő részfeladatokat többféleképpen is meg lehet oldani, mindegyik megoldható egy-egy különálló magasabb rendű függvénnyel.

1. Az ügyfelek osztályozása

Az értékelési rendszer ötcsillagos, ami egyszerűen az 1:5 egész számokból áll. A hangsúlyos ügyfelek csak 1 vagy 5 értékelést adnak, és szeretnénk tudni, hogy egy ügyfél mutatja-e ezt a viselkedést.

Készítsd el az all_15() függvényt, amely egy értékelésekből álló vektort kap, és true-t ad vissza, ha minden értékelés 1 vagy 5, egyébként pedig false.

julia> ratings = [2, 3, 4, 4, 1];

julia> all_15(ratings)
false

julia> ratings = [1, 5, 5, 1, 5];

julia> all_15(ratings)
true

2. A hangsúlyos ügyfelek elkülönítése

A hangsúlyosabb ügyfeleket el kell különítenünk a többiektől.

Készítsd el az emphatics() függvényt, amely az ügyfelek és az értékeléseik szótárát kapja. Egy ugyanilyen szótárat ad vissza azokkal, akik csak 1 vagy 5 csillagos értékelést használnak.

julia> ratings = ([2, 3, 5, 1, 1], [1, 1, 5, 5, 1], [4, 5, 5, 3, 2], [5, 5, 1, 1, 5]);

julia> names = ("c1", "c2", "c3", "c4");

julia> customers = Dict(zip(names, ratings))
Dict{String, Vector{Int64}} with 4 entries:
  "c2" => [1, 1, 5, 5, 1]
  "c1" => [2, 3, 5, 1, 1]
  "c3" => [4, 5, 5, 3, 2]
  "c4" => [5, 5, 1, 1, 5]

julia> emphatics(customers)
Dict{String, Vector{Int64}} with 2 entries:
  "c2" => [1, 1, 5, 5, 1]
  "c4" => [5, 5, 1, 1, 5]

3. Az értékelések átalakítása binárissá

Mivel a hangsúlyos ügyfelek csak 1 és 5 értékelést használnak, számítási szempontból kényelmesebb lesz, ha ezeket 0-ra és 1-re változtatjuk.

Készítsd el a tobinary() függvényt, amely a hangsúlyos értékelések vektorát kapja. Bináris értékeléseket ad vissza, ahol az 1-ből 0, az 5-ből 1 lesz.

julia> ratings = [1, 1, 5, 5, 1];

julia> tobinary(ratings)
5-element Vector{Int64}:
 0
 0
 1
 1
 0

4. Az értékelések mátrixszá alakítása

Az algoritmusaink Matrix bemeneteket használnak, ezért át kell alakítanunk az adatokat mátrixszá.

Készítsd el a tobinarymatrix() függvényt, amely hangsúlyos értékelésvektorok vektorát kapja. Az átalakított adatokból álló Matrix-ot adja vissza, ahol minden értékelésvektor egy sor a mátrixban.

julia> customersratings = [[1, 1, 5, 5, 1],[5, 5, 1, 1, 5]];

julia> tobinarymatrix(customersratings)
2×5 Matrix{Int64}:
 0  0  1  1  0
 1  1  0  0  1
Szerkesztés GitHubon A hivatkozás új ablakban vagy lapon nyílik meg
Julia Exercism

Készen állsz elkezdeni a(z) Sajtklub feladatot?

Iratkozz fel az Exercism-re, hogy megtanuld és elsajátítsd a(z) Julia nyelvet 35 fogalom128 feladat segítségével, valódi emberi mentorálással, mindez ingyen.