Come utilizzare correttamente gli elenchi in R?


320

Breve background: molti (la maggior parte?) Linguaggi di programmazione contemporanei in uso diffuso hanno almeno una manciata di ADT [tipi di dati astratti] in comune, in particolare,

  • stringa (una sequenza composta da caratteri)

  • list (una raccolta ordinata di valori) e

  • tipo basato su mappa (un array non ordinato che associa le chiavi ai valori)

Nel linguaggio di programmazione R, i primi due sono implementati come charactere vector, rispettivamente.

Quando ho iniziato a studiare R, due cose erano ovvie quasi dall'inizio: listè il tipo di dati più importante in R (perché è la classe genitore per la R data.frame), e in secondo luogo, non riuscivo a capire come funzionavano, almeno non abbastanza bene da usarli correttamente nel mio codice.

Per prima cosa, mi è sembrato che il listtipo di dati di R fosse un'implementazione diretta della mappa ADT ( dictionaryin Python, NSMutableDictionaryin Obiettivo C, hashin Perl e Ruby, object literalin Javascript e così via).

Ad esempio, li crei proprio come faresti con un dizionario Python, passando coppie chiave-valore a un costruttore (cosa che in Python dictnon lo è list):

x = list("ev1"=10, "ev2"=15, "rv"="Group 1")

E accedi agli elementi di un Elenco R proprio come faresti con quelli di un dizionario Python, ad es x['ev1']. Allo stesso modo, puoi recuperare solo le "chiavi" o solo i "valori" :

names(x)    # fetch just the 'keys' of an R list
# [1] "ev1" "ev2" "rv"

unlist(x)   # fetch just the 'values' of an R list
#   ev1       ev2        rv 
#  "10"      "15" "Group 1" 

x = list("a"=6, "b"=9, "c"=3)  

sum(unlist(x))
# [1] 18

ma gli R listsono anche diversi dagli altri ADT di tipo cartografico (tra le lingue che ho imparato comunque). La mia ipotesi è che questa sia una conseguenza delle specifiche iniziali per S, ovvero l'intenzione di progettare un DSL [linguaggio specifico del dominio] di dati / statistiche da zero.

tre differenze significative tra R liste tipi di mappatura in altre lingue in uso diffuso (ad es. Python, Perl, JavaScript):

in primo luogo , lists in R sono una raccolta ordinata , proprio come i vettori, anche se i valori sono codificati (ovvero, le chiavi possono essere qualsiasi valore hash non solo numeri interi sequenziali). Quasi sempre, il tipo di dati di mappatura in altre lingue non è ordinato .

secondo , lists può essere restituito da funzioni anche se non hai mai passato a listquando hai chiamato la funzione, e anche se la funzione che ha restituito la listnon contiene un listcostruttore ( esplicito) (Ovviamente, puoi gestirlo in pratica da racchiudendo il risultato restituito in una chiamata a unlist):

x = strsplit(LETTERS[1:10], "")     # passing in an object of type 'character'

class(x)                            # returns 'list', not a vector of length 2
# [1] list

Una terza caratteristica peculiare di R list: non sembra che possano essere membri di un altro ADT, e se si tenta di farlo, il contenitore primario viene forzato in a list. Per esempio,

x = c(0.5, 0.8, 0.23, list(0.5, 0.2, 0.9), recursive=TRUE)

class(x)
# [1] list

la mia intenzione qui non è di criticare la lingua o il modo in cui è documentata; allo stesso modo, non sto suggerendo che ci sia qualcosa di sbagliato nella liststruttura dei dati o nel modo in cui si comporta. Tutto quello che sto cercando di correggere è la mia comprensione di come funzionano in modo da poterli usare correttamente nel mio codice.

Ecco il genere di cose che vorrei capire meglio:

  • Quali sono le regole che determinano quando una chiamata di funzione restituirà un list(es. strsplitEspressione sopra citata)?

  • Se non assegno esplicitamente nomi a un list(ad es. list(10,20,30,40)) , I nomi predefiniti sono solo numeri interi sequenziali che iniziano con 1? (Presumo, ma non sono certo che la risposta sia sì, altrimenti non saremmo in grado di forzare questo tipo di listun vettore con una chiamata a unlist.)

  • Perché questi due diversi operatori []e [[]]restituiscono lo stesso risultato?

    x = list(1, 2, 3, 4)

    entrambe le espressioni restituiscono "1":

    x[1]

    x[[1]]

  • perché queste due espressioni non restituiscono lo stesso risultato?

    x = list(1, 2, 3, 4)

    x2 = list(1:4)

Per favore, non indicarmi la Documentazione R ( ?list, R-intro): l'ho letto attentamente e non mi aiuta a rispondere al tipo di domande che ho appena esposto.

(infine, di recente ho appreso e iniziato a utilizzare un pacchetto R (disponibile su CRAN) chiamato hashche implementa il comportamento convenzionale del tipo di mappa tramite una classe S4; posso sicuramente consigliare questo pacchetto.)


3
Con x = list(1, 2, 3, 4), entrambi questi NON restituiscono lo stesso risultato:, x[1]e x[[1]]. Il primo restituisce un elenco e il secondo restituisce un vettore numerico. Scorrendo sotto mi sembra che Dirk sia stato l'unico rispondente a rispondere correttamente a questa domanda.
IRTFM,

2
Non ho notato nessuno espandere il tuo elenco di modi che listin R non è come un hash. Ne ho uno in più che ritengo degno di nota. listin R possono avere due membri con lo stesso nome di riferimento. Considera che obj <- c(list(a=1),list(a=2))è valido e restituisce un elenco con due valori nominati di 'a'. In questo caso, una chiamata per obj["a"]restituirà solo il primo elemento dell'elenco corrispondente. Puoi ottenere un comportamento simile (forse identico) a un hash con un solo oggetto per nomi di riferimento usando ambienti in R. es.x <- new.env(); x[["a"]] <- 1; x[["a"]] <- 2; x[["a"]]
russellpierce

1
Ho riletto questo post con le risposte tre volte negli ultimi 6 mesi e ogni volta ho trovato più illuminazione. Grande domanda e alcune grandi risposte. Grazie.
Rich Lysakowski PhD

Risposte:


150

Solo per affrontare l'ultima parte della tua domanda, dal momento che questo indica davvero la differenza tra a liste vectorin R:

Perché queste due espressioni non restituiscono lo stesso risultato?

x = elenco (1, 2, 3, 4); x2 = elenco (1: 4)

Un elenco può contenere qualsiasi altra classe come ogni elemento. Quindi puoi avere un elenco in cui il primo elemento è un vettore di caratteri, il secondo è un frame di dati, ecc. In questo caso, hai creato due elenchi diversi. xha quattro vettori, ciascuno della lunghezza 1. x2ha 1 vettore della lunghezza 4:

> length(x[[1]])
[1] 1
> length(x2[[1]])
[1] 4

Quindi questi sono elenchi completamente diversi.

Gli elenchi R sono molto simili a una struttura di dati della mappa hash in quanto ogni valore di indice può essere associato a qualsiasi oggetto. Ecco un semplice esempio di un elenco che contiene 3 classi diverse (inclusa una funzione):

> complicated.list <- list("a"=1:4, "b"=1:3, "c"=matrix(1:4, nrow=2), "d"=search)
> lapply(complicated.list, class)
$a
[1] "integer"
$b
[1] "integer"
$c
[1] "matrix"
$d
[1] "function"

Dato che l'ultimo elemento è la funzione di ricerca, posso chiamarlo così:

> complicated.list[["d"]]()
[1] ".GlobalEnv" ...

Come ultimo commento su questo: va notato che a data.frameè davvero un elenco (dalla data.framedocumentazione):

Un frame di dati è un elenco di variabili dello stesso numero di righe con nomi di riga univoci, data la classe '"data.frame"'

Ecco perché le colonne in a data.framepossono avere tipi di dati diversi, mentre le colonne in una matrice non possono. Ad esempio, qui provo a creare una matrice con numeri e caratteri:

> a <- 1:4
> class(a)
[1] "integer"
> b <- c("a","b","c","d")
> d <- cbind(a, b)
> d
 a   b  
[1,] "1" "a"
[2,] "2" "b"
[3,] "3" "c"
[4,] "4" "d"
> class(d[,1])
[1] "character"

Nota come non posso cambiare il tipo di dati nella prima colonna in numerico perché la seconda colonna ha caratteri:

> d[,1] <- as.numeric(d[,1])
> class(d[,1])
[1] "character"

4
Questo aiuta, grazie. (A proposito, il tuo esempio è "elenco complicato", come potresti già sapere, è il modo standard per replicare l'istruzione "switch" in C ++, Java, ecc. In linguaggi che non ne hanno uno; probabilmente un buon modo per fare questo in R quando ne ho bisogno). +1
Doug

8
Giusto, sebbene switchin R vi sia una funzione utile che può essere usata a tale scopo (vedi help(switch)).
Shane,

63

Per quanto riguarda le tue domande, fammi rispondere in ordine e fornire alcuni esempi:

1 ) Viene restituito un elenco se e quando l'istruzione return ne aggiunge uno. Tener conto di

 R> retList <- function() return(list(1,2,3,4)); class(retList())
 [1] "list"
 R> notList <- function() return(c(1,2,3,4)); class(notList())
 [1] "numeric"
 R> 

2 ) I nomi semplicemente non sono impostati:

R> retList <- function() return(list(1,2,3,4)); names(retList())
NULL
R> 

3 ) Non restituiscono la stessa cosa. Il tuo esempio dà

R> x <- list(1,2,3,4)
R> x[1]
[[1]]
[1] 1
R> x[[1]]
[1] 1

dove x[1]restituisce il primo elemento di x- che è lo stesso di x. Ogni scalare è un vettore di lunghezza uno. D'altra parte x[[1]]restituisce il primo elemento dell'elenco.

4 ) Infine, i due sono diversi tra loro creano, rispettivamente, un elenco contenente quattro scalari e un elenco con un singolo elemento (che risulta essere un vettore di quattro elementi).


1
Molto utile, grazie. (Per quanto riguarda l'articolo n. 1 nella tua risposta, sono d'accordo, ma quello che avevo in mente erano incorporati come 'strsplit', non funzioni create dall'utente). In ogni caso, +1 da me.
Doug

2
@doug Informazioni sull'articolo n. 1 Penso che l'unico modo sia controllare l'aiuto per una funzione specifica, sezione Value. Come in ?strsplit: "Un elenco della stessa lunghezza di x". Ma dovresti considerare che può esserci una funzione che restituisce valori diversi dipendenti dagli argomenti (es. Sapply può restituire una lista o un vettore).
Marek,

34

Solo per prendere un sottoinsieme delle tue domande:

Questo articolo sull'indicizzazione affronta la questione della differenza tra []e [[]].

In breve [[]] seleziona un singolo elemento da un elenco e []restituisce un elenco degli elementi selezionati. Nel tuo esempio, l' x = list(1, 2, 3, 4)'elemento 1 è un singolo intero ma x[[1]]restituisce un singolo 1 e x[1]restituisce un elenco con un solo valore.

> x = list(1, 2, 3, 4)
> x[1]
[[1]]
[1] 1

> x[[1]]
[1] 1

A proposito, A = array( 11:16, c(2,3) ); A[5]è 15, nella matrice piatta ?!
denis,

13

Uno degli elenchi dei motivi che funzionano (ordinati) è quello di rispondere alla necessità di un contenitore ordinato che può contenere qualsiasi tipo in qualsiasi nodo, cosa che i vettori non fanno. Gli elenchi vengono riutilizzati per vari scopi in R, incluso formare la base di a data.frame, che è un elenco di vettori di tipo arbitrario (ma della stessa lunghezza).

Perché queste due espressioni non restituiscono lo stesso risultato?

x = list(1, 2, 3, 4); x2 = list(1:4)

Per aggiungere alla risposta di @ Shane, se si desidera ottenere lo stesso risultato, provare:

x3 = as.list(1:4)

Il che costringe il vettore 1:4in un elenco.


11

Solo per aggiungere un altro punto a questo:

R ha una struttura dati equivalente al dict Python il hashpacchetto . Puoi leggerlo in questo post del blog da Open Data Group . Ecco un semplice esempio:

> library(hash)
> h <- hash( keys=c('foo','bar','baz'), values=1:3 )
> h[c('foo','bar')]
<hash> containing 2 key-value pairs.
  bar : 2
  foo : 1

In termini di usabilità, la hashclasse è molto simile a un elenco. Ma le prestazioni sono migliori per set di dati di grandi dimensioni.


1
Sono a conoscenza del pacchetto hash - è menzionato nella mia domanda originale come proxy adatto al tipo di hash tradizionale.
Doug,

Si noti inoltre che l'uso di hash :: hash è di utilità discutibile rispetto agli ambienti con hash, rpubs.com/rpierce/hashBenchmarks .
Russellpierce,

9

Tu dici:

Per un altro, gli elenchi possono essere restituiti da funzioni anche se non si è mai passati in un Elenco quando si è chiamata la funzione e anche se la funzione non contiene un costruttore Elenco, ad es.

x = strsplit(LETTERS[1:10], "") # passing in an object of type 'character'
class(x)
# => 'list'

E immagino che tu suggerisca che questo è un problema (?). Sono qui per dirti perché non è un problema :-). Il tuo esempio è un po 'semplice, in quanto quando fai la suddivisione in stringhe, hai un elenco con elementi che sono lunghi 1 elemento, quindi sai che x[[1]]è lo stesso di unlist(x)[1]. Ma cosa succede se il risultato di strsplitrisultati restituiti di diversa lunghezza in ogni cestino. Restituire semplicemente un vettore (rispetto a un elenco) non lo farà affatto.

Per esempio:

stuff <- c("You, me, and dupree",  "You me, and dupree",
           "He ran away, but not very far, and not very fast")
x <- strsplit(stuff, ",")
xx <- unlist(strsplit(stuff, ","))

Nel primo caso ( x: che restituisce una lista), si può dire ciò che la "parte" 2 ° del 3 ° stringa di stato, ad esempio: x[[3]][2]. Come hai potuto fare lo stesso usando xxora che i risultati sono stati "svelati" ( unlist-ed)?


5
x = list(1, 2, 3, 4)
x2 = list(1:4)
all.equal(x,x2)

non è lo stesso perché 1: 4 è uguale a c (1,2,3,4). Se vuoi che siano uguali, allora:

x = list(c(1,2,3,4))
x2 = list(1:4)
all.equal(x,x2)

4

Questa è una domanda molto antica, ma penso che una nuova risposta possa aggiungere un certo valore poiché, a mio avviso, nessuno ha affrontato direttamente alcune delle preoccupazioni del PO.

Nonostante ciò che suggeriscono le risposte accettate, gli listoggetti in R non sono mappe hash. Se vuoi fare un parallelo con Python, listsono più simili, indovina, Python lists (o tuples in realtà).

È meglio descrivere come la maggior parte degli oggetti R sono memorizzati internamente (è il tipo C di un oggetto R SEXP). Sono composti essenzialmente da tre parti:

  • un'intestazione, che dichiara il tipo R dell'oggetto, la lunghezza e alcuni altri metadati;
  • la parte di dati, che è un array standard allocato in heap C (blocco contiguo di memoria);
  • gli attributi, che sono un elenco collegato denominato di puntatori ad altri oggetti R (o NULLse l'oggetto non ha attributi).

Da un punto di vista interno, ad esempio , c'è poca differenza tra a liste un numericvettore. I valori che memorizzano sono solo diversi. Rompiamo due oggetti nel paradigma che abbiamo descritto prima:

x <- runif(10)
y <- list(runif(10), runif(3))

Per x:

  • L'intestazione dirà che il tipo è numeric( REALSXPnel lato C), la lunghezza è 10 e altro.
  • La parte di dati sarà una matrice contenente 10 doublevalori.
  • Gli attributi sono NULL, poiché l'oggetto non ne ha.

Per y:

  • L'intestazione dirà che il tipo è list( VECSXPnel lato C), la lunghezza è 2 e altro.
  • La parte di dati sarà una matrice contenente 2 puntatori a due tipi SEXP, che punta al valore ottenuto da runif(10)e runif(3)rispettivamente.
  • Gli attributi sono NULL, per quanto riguarda x.

Quindi l'unica differenza tra un numericvettore e a listè che la numericparte di dati è fatta di doublevalori, mentre per la listparte di dati è una matrice di puntatori ad altri oggetti R.

Cosa succede con i nomi? Bene, i nomi sono solo alcuni degli attributi che puoi assegnare a un oggetto. Vediamo l'oggetto qui sotto:

z <- list(a=1:3, b=LETTERS)
  • L'intestazione dirà che il tipo è list( VECSXPnel lato C), la lunghezza è 2 e altro.
  • La parte di dati sarà una matrice contenente 2 puntatori a due tipi SEXP, che punta al valore ottenuto da 1:3e LETTERSrispettivamente.
  • Gli attributi sono ora presenti e sono un namescomponente che è un characteroggetto R con valore c("a","b").

Dal livello R, è possibile recuperare gli attributi di un oggetto con la attributesfunzione.

Il valore-chiave tipico di una mappa hash in R è solo un'illusione. Quando dici:

z[["a"]]

è questo che succede:

  • [[viene chiamata la funzione del sottoinsieme;
  • l'argomento della funzione ( "a") è di tipo character, quindi il metodo è incaricato di cercare tale valore dall'attributo names(se presente) dell'oggetto z;
  • se l' namesattributo non è presente, NULLviene restituito;
  • se presente, il "a"valore viene cercato in esso. Se "a"non è un nome dell'oggetto, NULLviene restituito;
  • se presente, viene determinata la posizione (1 nell'esempio). Quindi viene restituito il primo elemento dell'elenco, ovvero l'equivalente di z[[1]].

La ricerca del valore-chiave è piuttosto indiretta ed è sempre posizionale. Inoltre, utile da tenere a mente:

  • nelle mappe hash l'unico limite che una chiave deve avere è che deve essere hash . namesin R devono essere stringhe ( charactervettori);
  • nelle mappe hash non puoi avere due chiavi identiche. In R, è possibile assegnare namesa un oggetto con valori ripetuti. Per esempio:

    names(y) <- c("same", "same")

    è perfettamente valido in R. Quando si tenta di y[["same"]]recuperare il primo valore. Dovresti sapere perché a questo punto.

In conclusione, la capacità di assegnare attributi arbitrari a un oggetto ti dà l'apparenza di qualcosa di diverso da un punto di vista esterno. Ma R listnon sono mappe di hash in alcun modo.


2

Per quanto riguarda i vettori e il concetto di hash / array di altre lingue:

  1. I vettori sono gli atomi di R. Ad esempio, rpois(1e4,5)(5 numeri casuali), numeric(55)(lunghezza-55 zero vettore su doppi) e character(12)(12 stringhe vuote), sono tutti "di base".

  2. Possono avere elenchi o vettori names.

    > n = numeric(10)
    > n
     [1] 0 0 0 0 0 0 0 0 0 0
    > names(n)
    NULL
    > names(n) = LETTERS[1:10]
    > n
    A B C D E F G H I J 
    0 0 0 0 0 0 0 0 0 0
  3. I vettori richiedono che tutto sia dello stesso tipo di dati. Guarda questo:

    > i = integer(5)
    > v = c(n,i)
    > v
    A B C D E F G H I J           
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
    > class(v)
    [1] "numeric"
    > i = complex(5)
    > v = c(n,i)
    > class(v)
    [1] "complex"
    > v
       A    B    C    D    E    F    G    H    I    J                          
    0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i 0+0i
  4. Gli elenchi possono contenere diversi tipi di dati, come si vede in altre risposte e nella stessa domanda del PO.

Ho visto lingue (ruby, javascript) in cui "array" possono contenere tipi di dati variabili, ma ad esempio in "array" C ++ devono essere tutti lo stesso tipo di dati. Credo che questa sia una cosa velocità / efficienza: se ne hai una numeric(1e6)conosci le sue dimensioni e la posizione di ogni elemento a priori ; se la cosa potrebbe contenere "Flying Purple People Eaters"in qualche fetta sconosciuta, allora devi effettivamente analizzare le cose per conoscerne i fatti di base.

Alcune operazioni R standard hanno anche più senso quando il tipo è garantito. Ad esempio cumsum(1:9)ha senso, mentre cumsum(list(1,2,3,4,5,'a',6,7,8,9))non lo è, senza che il tipo sia garantito per essere doppio.


Per quanto riguarda la tua seconda domanda:

Gli elenchi possono essere restituiti dalle funzioni anche se non hai mai passato un elenco quando hai chiamato la funzione

Le funzioni restituiscono tipi di dati diversi da quelli immessi in ogni momento. plotrestituisce una trama anche se non accetta una trama come input. Argrestituisce a numericanche se ha accettato a complex. Eccetera.

(E per quanto riguarda strsplit: il codice sorgente è qui .)


2

Anche se questa è una domanda piuttosto vecchia, devo dire che sta toccando esattamente la conoscenza che mi mancava durante i miei primi passi in R - cioè come esprimere i dati nella mia mano come oggetto in R o come selezionare da oggetti esistenti. Non è facile per un principiante R pensare "in una scatola R" sin dall'inizio.

Quindi ho iniziato a usare le stampelle qui sotto che mi hanno aiutato molto a scoprire quale oggetto usare per quali dati e fondamentalmente a immaginare l'uso del mondo reale.

Anche se non sto dando risposte esatte alla domanda, il breve testo in basso potrebbe aiutare il lettore che ha appena iniziato con R e sta facendo domande simili.

  • Vettore atomico ... Ho chiamato quella "sequenza" per me stesso, nessuna direzione, solo una sequenza degli stessi tipi. [sottoinsiemi.
  • Vettore ... sequenza con una direzione da 2D, [sottoinsiemi.
  • Matrice ... gruppo di vettori con la stessa lunghezza che formano righe o colonne, [sottoinsiemi di righe e colonne o in sequenza.
  • Matrici ... matrici a strati che formano 3D
  • Dataframe ... una tabella 2D come in Excel, dove posso ordinare, aggiungere o rimuovere righe o colonne o creare arit. operazioni con loro, solo dopo qualche tempo ho davvero riconosciuto che il dataframe è un'implementazione intelligente di listdove posso effettuare il sottoinsieme usando [righe e colonne, ma anche usando [[.
  • Elenco ... per aiutare me stesso ho pensato all'elenco di tree structuredove [i]seleziona e restituisce interi rami e [[i]]restituisce l'elemento dal ramo. E poiché è così tree like structure, puoi anche usare un index sequenceper indirizzare ogni singola foglia su un molto complesso listusando la sua [[index_vector]]. Gli elenchi possono essere semplici o molto complessi e possono combinare vari tipi di oggetti in uno solo.

Quindi per listste puoi finire con più modi come selezionare una leafsituazione a seconda della situazione come nell'esempio seguente.

l <- list("aaa",5,list(1:3),LETTERS[1:4],matrix(1:9,3,3))
l[[c(5,4)]] # selects 4 from matrix using [[index_vector]] in list
l[[5]][4] # selects 4 from matrix using sequential index in matrix
l[[5]][1,2] # selects 4 from matrix using row and column in matrix

Questo modo di pensare mi ha aiutato molto.


1

Se aiuta, tendo a concepire "elenchi" in R come "record" in altre lingue pre-OO:

  • non fanno ipotesi su un tipo generale (o piuttosto è disponibile il tipo di tutti i record possibili di qualsiasi arity e nomi di campo).
  • i loro campi possono essere anonimi (quindi si accede ad essi in ordine di definizione rigoroso).

Il nome "record" si scontrerebbe con il significato standard di "record" (aka righe) nel linguaggio parlamentare, e potrebbe essere questo il motivo per cui il loro nome ha suggerito se stesso: come elenchi (di campi).


1

perché questi due diversi operatori [ ]e [[ ]]restituiscono lo stesso risultato?

x = list(1, 2, 3, 4)
  1. [ ]fornisce un'operazione di subimpostazione. In generale il sottoinsieme di qualsiasi oggetto avrà lo stesso tipo dell'oggetto originale. Pertanto, x[1] fornisce un elenco. Allo stesso modo x[1:2]è un sottoinsieme dell'elenco originale, quindi è un elenco. Ex.

    x[1:2]
    
    [[1]] [1] 1
    
    [[2]] [1] 2
  2. [[ ]]serve per estrarre un elemento dall'elenco. x[[1]]è valido ed estrae il primo elemento dall'elenco. x[[1:2]]non è valido in quanto [[ ]] non fornisce impostazioni secondarie come [ ].

     x[[2]] [1] 2 
    
    > x[[2:3]] Error in x[[2:3]] : subscript out of bounds
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.