Διαδρομές
/
R
R
/
Ασκήσεις
/
Λέσχη τυριού
Λέσχη τυριού

Λέσχη τυριού

Άσκηση εκμάθησης

Εισαγωγή

Τι είναι ο συναρτησιακός προγραμματισμός;

Για τους σκοπούς μας, αυτά είναι μερικά βασικά χαρακτηριστικά:

  • Καθαρές συναρτήσεις, που αντιστοιχίζουν εισόδους σε εξόδους με συνέπεια και χωρίς παρενέργειες.
  • Αμετάβλητα δεδομένα.
  • Συναρτήσεις πρώτης κλάσης, που μπορούν να αντιμετωπιστούν ως τιμές όπως κάθε άλλος τύπος.
  • Συναρτήσεις ανώτερης τάξης, που παίρνουν άλλες συναρτήσεις ως ορίσματα ή ως τιμές επιστροφής.

Ωστόσο, η R δεν είναι καθαρά συναρτησιακή γλώσσα (σε αντίθεση με, για παράδειγμα, τη Haskell).

Δες την R ως μια πραγματιστική γλώσσα, που (όλο και περισσότερο) παρέχει συναρτησιακές δυνατότητες όπου αυτές αποτελούν έναν καθαρό και εύκολο τρόπο για να γίνει η δουλειά. Γενικά, ο στόχος είναι η ανάλυση δεδομένων, και ο προγραμματισμός είναι απλώς ένα μέσο για αυτόν τον σκοπό.

Σωλήνες

Η χρήση σωλήνων |> για τη σύνδεση συναρτήσεων είναι μια τεχνική που γίνεται όλο και πιο συνηθισμένη σε αρκετές γλώσσες.

Εν συντομία:

  • Ένα αποτέλεσμα από τα αριστερά του σωλήνα γίνεται το πρώτο όρισμα στη συνάρτηση που βρίσκεται στα δεξιά (όπου το "δεξιά" αγνοεί τις προαιρετικές αλλαγές γραμμής).
  • Τυχόν υπόλοιπα ορίσματα μπορούν να συμπεριληφθούν στην κλήση της συνάρτησης.
  • Στην R, πρέπει να διοχετεύουμε σε μια κλήση συνάρτησης, όχι απλώς σε ένα όνομα, οπότε οι παρενθέσεις είναι πάντα απαραίτητες (αυτό διαφέρει από κάποιες άλλες γλώσσες).

Κάποιοι προγραμματιστές βρίσκουν χρήσιμο να προφέρουν τον σωλήνα ως "then" όταν διαβάζουν κώδικα: κάνε το βήμα 1 και ΤΟΤΕ κάνε το βήμα 2...

Το παρακάτω παράδειγμα δείχνει τη σύνταξη.

library(stringr)

"Monday-25-February" |> 
  str_split("-") |>     
  unlist() |>               # [1] "Monday"   "25"       "February"
  str_sub(1, 3) |>          # [1] "Mon" "25"  "Feb"
  str_to_upper() |>         # [1] "MON" "25"  "FEB"
  str_flatten(collapse = ", ")
#> [1] "MON, 25, FEB"

Αυτός ο τύπος σωλήνα προστέθηκε στη Base R στην έκδοση 4.1.0, και μπορεί να τον δεις να αναφέρεται ως "εγγενής σωλήνας".

Ο επεξεργαστής RStudio (μεταξύ άλλων) χρησιμοποιεί το Ctrl-Shift-M ως συντόμευση πληκτρολογίου για να προσθέσει έναν σωλήνα με τα κατάλληλα κενά.

Συναρτήσεις apply

Είδαμε προηγουμένως ότι πολλές συναρτήσεις στην R είναι "διανυσματοποιημένες": λειτουργούν σε ολόκληρα διανύσματα, χωρίς να χρειάζεται να γράψεις ρητούς βρόχους.

Οι συναρτήσεις που χρησιμοποιούν μόνο διανυσματοποιημένες συναρτήσεις είναι πιθανό να διανυσματοποιούνται αυτόματα και οι ίδιες. Σε ένα απλό παράδειγμα:

triple <- function(x) return(3 * x)
triple(1:10)
 [1]  3  6  9 12 15 18 21 24 27 30

Ωστόσο, πιο σύνθετες συναρτήσεις μπορεί να μην δέχονται διανυσματική είσοδο, για παράδειγμα αν περιέχουν βρόχους ή μπλοκ if-else.

Χρειαζόμαστε έναν πιο γενικό τρόπο να εφαρμόζουμε συναρτήσεις σε διανύσματα (παρόμοιο με τη map() σε κάποιες άλλες γλώσσες).

Επειδή η R έχει μεγάλη ποικιλία δομών δεδομένων στη βασική γλώσσα, έχει επίσης μια ολόκληρη οικογένεια συναρτήσεων *apply() για να λειτουργεί πάνω τους. Πιο τεχνικά, οι προγραμματιστές τις αποκαλούν συχνά "συναρτήσεις ανώτερης τάξης" και οι μαθηματικοί "συναρτησιακά", επειδή παίρνουν συναρτήσεις ως ορίσματα.

Για την ώρα, ας δούμε τη lapply() και τη sapply().

lapply()

Η "List-apply" είναι σχεδιασμένη να δουλεύει με εισόδους λίστας, αλλά τα διανύσματα μετατρέπονται σιωπηλά σε λίστες όταν χρειάζεται.

Η έξοδος είναι λίστα, οπότε μπορεί να χρειαστεί ξετύλιγμα για να πάρεις διάνυσμα:

h <- function(size) {
    switch(size,
           "small" = 6,
           "medium" = 8,
           "large" = 10
    )
}

v <- c("small", "large", "medium")

h(v) # bad use of an unvectorized function
#> Error in switch(size, small = 6, medium = 8, large = 10) : 
#>   EXPR must be a length 1 vector

lapply(v, h)  # output is a list
#> [[1]]
#> [1] 6

#> [[2]]
#> [1] 10

#> [[3]]
#> [1] 8

lapply(v, h) |> unlist() # output is a vector
#> [1]  6 10  8

Όσοι έχουν εξοικειωθεί με την έννοια Switch μπορεί να προσέξουν ότι θα μπορούσαμε εναλλακτικά να αντικαταστήσουμε τη βαθμωτή switch με τη διανυσματοποιημένη recode_values() από τη βιβλιοθήκη dplyr. Η διανυσματοποίηση είναι καλή, και η σύγχρονη R παρέχει πολλούς τρόπους για να την πετύχεις.

library(dplyr)

h <- function(size) {
  recode_values(size,
         "small" ~ 6,
         "medium" ~ 8,
         "large" ~ 10
  )
}

v <- c("small", "large", "medium")
h(v)
#> [1]  6 10  8

sapply()

Η "Simplified-apply" κάνει κάτι παρόμοιο με τη lapply, αλλά προσπαθεί επίσης να απλοποιήσει την τιμή επιστροφής: για παράδειγμα, ένα ονομασμένο διάνυσμα αντί για λίστα. Ανάλογα με τα συμφραζόμενα, μπορεί να χρειαστεί η χρήση της unname():

sapply(v, h)
 small  large medium 
     6     10      8 

sapply(v, h) |> unname()
#> [1]  6 10  8

Η βιβλιοθήκη purrr

Οι lapply και sapply είναι απλές, γνωστές στους περισσότερους χρήστες της R, και ακόμα αρκετά δημοφιλείς για διανύσματα και απλές λίστες.

Η Base R έχει επίσης πολλές άλλες συναρτήσεις apply. Η παροχή μιας απλούστερης και πιο συνεπούς εναλλακτικής σε αυτού του είδους τη σύγχυση είναι βασικός στόχος του Tidyverse.

Υπάρχει πλέον το πακέτο purrr, που παρέχει αρκετές επιπλέον επιλογές όπως η map(), βελτιώνοντας παράλληλα τη συνέπεια στον σχεδιασμό και τη σαφήνεια στα μηνύματα σφάλματος.

Λιγότερο περιορισμένο από τη συμβατότητα προς τα πίσω σε σχέση με τη Base R, αυτό το πακέτο φέρνει την R σημαντικά πιο κοντά στον σύγχρονο συναρτησιακό προγραμματισμό όπως τον συναντάς σε άλλες γλώσσες.

Υπάρχουν πολλές και διαφορετικές συναρτήσεις στο purrr, που δεν συνοψίζονται εύκολα. Πόροι που βοηθούν στην πλοήγηση στη βιβλιοθήκη περιλαμβάνουν:

Οι πολλές συναρτήσεις map

Η συνάρτηση map() εφαρμόζει μια συνάρτηση σε κάθε στοιχείο της εισόδου και επιστρέφει πάντα μια λίστα.

Μέχρι εδώ, αυτό ακούγεται σαν τη lapply(), απλώς με νέο όνομα, και ίσως με καλύτερα μηνύματα σφάλματος.

Ωστόσο, η map() είναι μόνο ένα μέλος μιας μεγάλης οικογένειας.

Κάποιες ερωτήσεις να σκεφτείς όταν επιλέγεις συνάρτηση:

  • Τι θέλεις να επιστρέψεις; Η map() επιστρέφει πάντα λίστα, αλλά αν θέλεις έναν συγκεκριμένο τύπο ατομικού διανύσματος, χρησιμοποίησε τη map_chr() για συμβολοσειρές, τη map_int() για ακέραιους, κ.λπ.
  • Χρειάζεσαι κάθε θέση από την είσοδο; Βάλε το i μπροστά από τα ονόματα των συναρτήσεων, όπως στη imap().
  • Θέλεις να επεξεργαστείς πολλαπλές εισόδους παράλληλα;
    • Αντικατέστησε το map με map2 στο όνομα, όπως στη map2_dbl() για δύο εισόδους (π.χ. τιμές και βάρη, για να υπολογίσεις έναν σταθμισμένο μέσο όρο)
    • Αντικατέστησε με pmap για μια λίστα παράλληλων εισόδων, όπως στη pmap_chr(). Και ναι, ο παραλληλισμός σε επίπεδο υλικού είναι εφικτός.
  • Θέλεις να επεξεργαστείς όλα τα στοιχεία της εισόδου; Αν όχι, σκέψου τη map_if() για να εφαρμόσεις ένα φίλτρο με κατηγόρημα (boolean), τη map_at() για να ορίσεις κριτήρια θέσης, τη head_while() για να επεξεργάζεσαι την είσοδο μέχρι ένα στοιχείο να αποτύχει σε ένα κατηγόρημα.
  • Είναι τα δεδομένα εισόδου βαθιά εμφωλευμένα; Υπάρχουν συναρτήσεις για αυτή την περίπτωση.
  • Θέλεις η τιμή επιστροφής να είναι πάντα του ίδιου τύπου με την είσοδο; Χρησιμοποίησε τις συναρτήσεις modify() αντί για τη map, ή τη modify_tree() για αναδρομική εφαρμογή.

Αυτές οι συναρτήσεις έχουν τα δεδομένα εισόδου ως πρώτο όρισμα όποτε είναι δυνατόν, ώστε να δουλεύουν καλά με σωλήνες.

Επίσης, το purrr είναι (σκόπιμα) πιο σχολαστικό από τη Base R. Αν ζητήσεις κάτι συγκεκριμένο, οι συναρτήσεις θα επιστρέψουν ακριβώς αυτό ή θα σταματήσουν με ένα μήνυμα σφάλματος. Σκληρό, αλλά πολύ αποτελεσματικό στη μείωση των δυσεύρετων bugs.

Συναρτήσεις reduce

Οι συναρτήσεις map έχουν συνήθως τιμή επιστροφής με το ίδιο μήκος με την είσοδο.

Κάποιες άλλες συναρτήσεις μειώνουν τη διάσταση των δεδομένων, για παράδειγμα η sum().

sum(1:100)
#> [1] 5050

Στην παραπάνω περίπτωση, μετατρέπουμε ένα διάνυσμα μήκους 100 σε ακέραιο μήκους 1. Θα δούμε σε μια μελλοντική έννοια για μήτρες και πίνακες ότι η έννοια της μείωσης διάστασης είναι πιο γενική.

Η συνάρτηση sum() είναι ενσωματωμένη (όπως και πολλές άλλες στατιστικές συναρτήσεις). Ωστόσο, χρειαζόμαστε έναν τρόπο να εφαρμόζουμε αυθαίρετες συναρτήσεις μείωσης διάστασης σε μια δομή δεδομένων, χρησιμοποιώντας κάποια συνάρτηση ανώτερης τάξης αντίστοιχη με τη map().

Σε αρκετές άλλες γλώσσες χρησιμοποιούνται ονόματα συναρτήσεων όπως fold, foldl και foldr. Στην R, η σχετική συνάρτηση είναι η reduce(), που αντιστοιχεί στο γνωστό (στους λάτρεις των αλγορίθμων) framework MapReduce.

Το πρώτο όρισμα είναι, ως συνήθως, τα δεδομένα εισόδου. Το δεύτερο όρισμα είναι μια συνάρτηση με 2 ορίσματα: συχνά μια ανώνυμη συνάρτηση, αλλά μπορεί να είναι τόσο απλή όσο ένας αριθμητικός τελεστής όπως το +.

# surround an infix operator with backticks, to use as a function
reduce(1:100, `+`)
#> [1] 5050  # same as sum(1:100)

# anonymous function, alternately add and subtract
reduce(1:10, \(accum, nextval) ifelse(nextval %% 2 == 0, accum + nextval, accum - nextval))
#> [1] 7

Έχει σημασία αν εφαρμόζουμε τη συνάρτηση από την αρχή ή από το τέλος της εισόδου;

Όχι με προσεταιριστικούς τελεστές όπως το + ή το *. Προφανώς, (1 + 2) + 3 == 1 + (2 + 3).

Αντίθετα, το - και το / δεν είναι προσεταιριστικά:

(1 - 2) - 3
#> [1] -4
1 - (2 - 3)
#> [1] 2

Για συναρτήσεις όπου η κατεύθυνση έχει σημασία, υπάρχει ένα προαιρετικό όρισμα .dir.

# default is .dir = "forward"
reduce(1:10, `-`)
#> [1] -53
reduce(1:10, `-`, .dir = "backward")
#> [1] -5

Όπως με τη map() και τη map2(), υπάρχει επίσης μια συνάρτηση reduce2() για να λειτουργεί σε δύο διανύσματα εισόδου παράλληλα.

Στην R, μας αρέσει να δουλεύουμε με διανύσματα. Ας υποθέσουμε ότι, αντί για μία μόνο τελική τιμή, θέλεις να δεις όλα τα ενδιάμεσα βήματα.

Για αυτό, οι αντίστοιχες συναρτήσεις είναι η accumulate() και η accumulate2(). Η σύνταξη είναι παρόμοια με τη reduce(), με κάποια επιπλέον προαιρετικά ορίσματα για τον καθορισμό του τύπου εξόδου.

accumulate(1:10, `+`)
#> [1]  1  3  6 10 15 21 28 36 45 55

Κάποιοι συνηθισμένοι τύποι accumulate έχουν τις δικές τους συναρτήσεις. Η έννοια Vector Functions συζήτησε τη cumsum() και τη cumprod() στη Base R, και η βιβλιοθήκη dplyr έχει τη cummean() για αριθμητικούς μέσους όρους, τη cumall() και τη cumany() για λογικά διανύσματα.

Συναρτήσεις φιλτραρίσματος

Συχνά, θέλουμε να συμπεριλάβουμε (ή να αποκλείσουμε) στοιχεία της εισόδου που ταιριάζουν με κάποιο κατηγόρημα.

Στην έννοια Vector Filtering είδαμε έναν ιδιωματικό τρόπο να το κάνουμε αυτό.

v <- 1:3
v[v >= 2]
#> [1] 2 3

Αυτό χρησιμοποιείται πολύ στην παραδοσιακή R, αλλά ταιριάζει άσχημα στις σύγχρονες συναρτησιακές σωληνώσεις.

Κατά συνέπεια, το purrr παρέχει ένα σύνολο συναρτήσεων κατηγορήματος, που ακολουθούν τη συνήθη σύμβαση ορισμάτων f(input, func, ...) για να δουλεύουν με σωλήνες.

Τα ονόματα είναι λίγο διαφορετικά από ό,τι στις περισσότερες άλλες γλώσσες (ονόματα όπως filter και drop σημαίνουν ήδη κάτι εντελώς διαφορετικό στην R).

Οι αντίστοιχες συναρτήσεις του purrr περιλαμβάνουν τη keep() και τη discard(), και αρκετές άλλες.

Έχουμε επίσης τη every(), τη some() και τη none(), για να αντικαταστήσουν τη all() και τη any() όταν επιστρέφεται μία μόνο λογική τιμή.

Οδηγίες

Ξεκινάμε μια λέσχη τυριού, η οποία θα χρησιμοποιεί μηχανική μάθηση για να επιλέγει νέα τυριά και να τα προσφέρει στους πελάτες που αγαπούν το τυρί, με βάση το ιστορικό και τα γούστα τους.

Οι νέοι πελάτες καλούνται να συμπληρώσουν μια αρχική έρευνα, ώστε να συγκεντρώσουμε κάποια βασικά δεδομένα για αρχή. Μέσα από αυτήν, διαπιστώθηκε ότι υπάρχει ένα υποσύνολο εμφατικών πελατών που δεν έχουν λεπτότητα στις κριτικές τους. Καθώς αυτό μπορεί να προκαλέσει ανεπανόρθωτη μεροληψία σε έναν πιο εκλεπτυσμένο αλγόριθμο, έχει στηθεί ένας ξεχωριστός αλγόριθμος για να καλύπτει τις ανάγκες τους. Σου ζητείται να παρέχεις κάποιες βοηθητικές συναρτήσεις για να τακτοποιήσεις τα δεδομένα τους.

Note

Ενώ μπορεί να υπάρχουν διαφορετικοί τρόποι για να λύσεις τις παρακάτω εργασίες, καθεμία μπορεί να λυθεί με το πολύ δύο συναρτήσεις ανώτερης τάξης.

1. Κατηγοριοποίησε τους πελάτες

Το σύστημα αξιολόγησης έχει βάση πέντε αστεριών, η οποία αποτελείται απλώς από τους ακέραιους 1:5. Οι εμφατικοί πελάτες δίνουν μόνο αξιολογήσεις 1 ή 5, και θέλουμε να ξέρουμε αν ένας πελάτης εμφανίζει αυτή τη συμπεριφορά.

Υλοποίησε τη συνάρτηση all_15(), η οποία δέχεται ένα διάνυσμα αξιολογήσεων και επιστρέφει true αν όλες οι αξιολογήσεις είναι 1 ή 5, και false διαφορετικά.

all_15(c(2, 3, 4, 4, 1))
#> [1] FALSE

all_15(c(1, 5, 5, 1, 5))
#> [1] TRUE

2. Ονόμασε τους πελάτες

Πρέπει να συσχετίσουμε το όνομα του πελάτη (ή το ID) με τις αξιολογήσεις του.

Υλοποίησε τη συνάρτηση name_customers(), η οποία δέχεται ένα διάνυσμα ονομάτων και μια λίστα αξιολογήσεων και τα "συμπλέκει" σε μια λίστα από λίστες.

Τα στοιχεία της εσωτερικής λίστας πρέπει να έχουν ονόματα name και rating.

names <- c("c1", "c2")
ratings <- list(c(2, 3, 5), c(1, 1, 5))
name_customers(names, ratings)
#> [[1]]
#> [[1]]$name
#> [1] "c1"

#> [[1]]$rating
#> [1] 2 3 5

#> [[2]]
#> [[2]]$name
#> [1] "c2"

#> [[2]]$rating
#> [1] 1 1 5

3. Ξεχώρισε τους εμφατικούς πελάτες

Πρέπει να ξεχωρίσουμε τους πιο εμφατικούς πελάτες από τους υπόλοιπους.

Υλοποίησε τη συνάρτηση emphatics(), η οποία δέχεται πελάτες και αξιολογήσεις. Επιστρέφει μια λίστα περιορισμένη σε όσους χρησιμοποιούν μόνο αξιολογήσεις με 1 ή 5 αστέρια.`

# names and ratings as in the task 2 example

emphatics(names, ratings)
#> [[1]]
#> [[1]]$name
#> [1] "c2"

#> [[1]]$rating
#> [1] 1 1 5

4. Μετέτρεψε τις αξιολογήσεις σε δυαδικές

Επειδή οι εμφατικοί πελάτες χρησιμοποιούν μόνο αξιολογήσεις 1 και 5, θα είναι πιο βολικό υπολογιστικά αν αυτές μετατραπούν σε 0 και 1.

Υλοποίησε τη συνάρτηση to_binary(), η οποία δέχεται ένα διάνυσμα εμφατικών αξιολογήσεων. Επιστρέφει δυαδικές αξιολογήσεις, όπου το 1 έχει μετατραπεί σε 0 και το 5 έχει μετατραπεί σε 1.

to_binary(c(1, 1, 5, 5, 1))
#> [1] 0 0 1 1 0

5. Υπολόγισε τους μέσους όρους των αξιολογήσεων με την πάροδο του χρόνου

Θέλεις να βελτιώνεις συνεχώς τις προτάσεις για κάθε επιμέρους πελάτη (όχι μόνο για τους εμφατικούς) με την πάροδο του χρόνου, καθώς καταλαβαίνεις καλύτερα τα γούστα τους.

Υλοποίησε τη συνάρτηση satisfactions(), η οποία δέχεται ένα διάνυσμα αξιολογήσεων. Επιστρέφει ένα διάνυσμα με τους αθροιστικούς μέσους όρους των αξιολογήσεων μέχρι αυτό το σημείο. Στρογγυλοποίησε τα αποτελέσματα σε 2 δεκαδικά ψηφία.

Ας ελπίσουμε ότι οι αριθμοί θα συνεχίσουν να μεγαλώνουν, για ευτυχισμένους πελάτες!

satisfactions(c(1, 2, 2, 3))
#> [1] 1.00 1.50 1.67 2.00
Επεξεργασία μέσω GitHub Ο σύνδεσμος ανοίγει σε νέο παράθυρο ή καρτέλα
R Exercism

Έτοιμος να ξεκινήσεις την άσκηση Λέσχη τυριού;

Γράψου στο Exercism για να μάθεις και να κατακτήσεις R με 21 έννοιες111 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.