Metodi funzionali sulle collezioni

6

Sto imparando Scala e sono un po 'sconcertato da tutti i metodi (funzioni di ordine superiore) disponibili nelle raccolte. Quali producono più risultati della collezione originale, quali producono meno e quali sono più appropriati per un determinato problema? Anche se sto studiando Scala, penso che questo riguarderebbe la maggior parte dei linguaggi funzionali moderni (Clojure, Haskell) e anche di Java 8 che introduce questi metodi sulle raccolte Java.

In particolare, ora mi chiedo sulla mappa con filtro vs. piega / riduzione. Mi ha fatto molto piacere che l'uso di foldRight () possa produrre lo stesso risultato di una mappa (...). Filter (...) con un solo attraversamento della collezione sottostante. Ma un amico ha sottolineato che foldRight () può forzare l'elaborazione sequenziale mentre map () è più amichevole rispetto all'elaborazione da parte di più processori in parallelo. Forse questo è il motivo per cui mapReduce () è così popolare?

Più in generale, a volte sono ancora sorpreso quando concateno molti di questi metodi per recuperare un elenco (List ()) o per passare un elenco (List ()) e recuperare solo un elenco (). Ad esempio, quando dovrei usare:

collection.map(a => a.map(b => ...))

vs.

collection.map(a => ...).map(b => ...)

Il comando for / yield non fa nulla per aiutare questa confusione. Sto chiedendo la differenza tra un'operazione "fold" e "unfold"?

Sto cercando di intromettermi troppe domande in una? Penso che ci possa essere un concetto di fondo che, se ho capito, potrebbe rispondere a tutte queste domande, o almeno legare insieme le risposte.

    
posta GlenPeterson 02.11.2012 - 16:22
fonte

3 risposte

8

Puoi emulare approssimativamente la mappa / filtro usando foldRight (o foldLeft) con cons come la funzione di riduzione data. Ad esempio foldRight(f, L, initial) dove L = [x,y,z] potrebbe essere espanso a

f(x, f(y, f(z, initial)))

Ciò significa che non puoi elaborare x finché non hai elaborato f(z, initial) e poi f(y, ...) . Stai creando una dipendenza che non esiste con map / filter.

Come per l'ordinamento delle mappe ...

(A) collection.map(a => a.map(b => ...))

(A) prende una raccolta e quindi applica la funzione mappa a ciascun elemento, ciò implica che ogni elemento è una raccolta "mappabile". Questa mappa interna restituirà quindi l'elenco elaborato (che è una lista) e quindi ogni elemento di collection rimarrà una raccolta. Questo è il modo in cui mapperesti una funzione su ogni elemento di un elenco di elenchi e il risultato sarebbe di nuovo un elenco di elenchi.

(B) collection.map(a => ...).map(b => ...)

(B) elabora ogni elemento di una lista e forma quei risultati in una nuova lista. Questo elenco viene quindi elaborato di nuovo con una seconda funzione della mappa che fornisce ancora un altro elenco.

(A) serve per elaborare gli elementi interni di una lista ("sottoelementi" se lo si desidera). (B) è per elaborare una lista più volte. Se scriviamo (B) concretamente come

collection.map(a => f(a)).map(b => g(b))

possiamo vedere che è equivalente a

collection.map(a => g(f(a)))

Potrebbe aiutarti a scriverli come per i loop. (A) userà loop incorporati dove As (B) userà due loop sequenziali.

Questa non è la differenza tra fold e unfold. Né (A) né (B) è una piega poiché la struttura della lista presente, tuttavia profondamente annidata, viene preservata. Fold crea uno scalare da una lista, mentre unfold (non troppo familiare con esso) prende uno scalare e produce una lista in base ad alcune regole.

EDIT : @Giorgio aveva ragione a suggerire flatMap. È una variazione interessante sulla mappa. Diciamo che stiamo mappando un elenco di X in un elenco di Ys, quindi passiamo alla mappa una funzione f:X->Y . Supponiamo di avere un altro calcolo g che prende una X ma restituisce più Y% g:X->[Y] . In questo caso useremmo flatMap . map prende i risultati di f e li mette in una lista. flatMap prende i risultati di g e li concatena insieme.

Ex. Supponiamo di avere un elenco di liste L = [[1,2,3],[4,5,6]]

L.map(a => a.map(b => b * 2))

[[2,4,6],[8,10,12]] . Ma diciamo che vogliamo ogni numero raddoppiato in una lista, nessuna sotto-lista. Quindi chiameremmo

L.flatMap(a => a.map(b => b * 2))

che dà [2,4,6,8,10,12] . Nota che la nostra funzione interna a => a.map(b => b * 2) prende e restituisce una lista.

    
risposta data 02.11.2012 - 17:02
fonte
2

La piegatura è più generale della mappa, della riduzione e del filtro. Se ci prendi un minuto per pensarci, la loro implementazione in termini di una piega dovrebbe essere semplice. Considera di usarli prima perché trasmette più intenzioni e limita ciò che potrebbe accadere. Una mappa produrrà sempre un elenco della stessa lunghezza, il filtro non cambierà i valori solo se ne rimuoverà alcuni, ridurrà una lista in un singolo valore, con una piega che tutti e tre potrebbero accadere allo stesso tempo.

Se passi da una lista ad una lista di liste, allora una mappa (o una piega che fa la mappa come cose) sta restituendo una lista da qualche parte. L'elenco delle liste di una lista significa che una riduzione sta arrivando per giocare da qualche parte.

Vorrei anche esaminare lo stack overflow nelle discussioni che li riguardano. Dovrebbe aiutare con casi d'uso per ciascuno.

    
risposta data 02.11.2012 - 16:57
fonte
2

Riguardo alle operazioni che producono un output più piccolo del loro input, un errore comune riguarda map stesso. Per intuizione, map prende ciascuno degli elementi di input, calcola una trasformazione di questo elemento e posiziona il risultato sull'output, in modo che input e output abbiano le stesse dimensioni.

Infatti, in Scala, una mappa di un insieme (diciamo, di interi) è un insieme. Quindi, applicando un predicato (ovvero una funzione booleana) stabilendo se il numero intero è pari o dispari, il risultato sarà un insieme di valori booleani. Pertanto, le uniche dimensioni di output possibili sono 0, 1 e 2, indipendentemente dalla dimensione di input.

val a = Set(1,2,3,4,5)
a.map(_ % 2 == 0)
res0: scala.collection.immutable.Set[Boolean] = Set(false, true)

Nota che è una convenzione di Scala. Ad esempio, in Python, una mappa di un insieme è una lista contenente lo stesso numero di elementi rispetto al set originale.

    
risposta data 03.11.2012 - 13:40
fonte