differenza tra piega Sinistra e Riduci Sinistra in Scala


196

Ho imparato la differenza di base tra foldLeftereduceLeft

foldLeft:

  • il valore iniziale deve essere passato

reduceLeft:

  • accetta il primo elemento della raccolta come valore iniziale
  • genera un'eccezione se la raccolta è vuota

C'è qualche altra differenza?

Qualche motivo specifico per avere due metodi con funzionalità simili?



Sarebbe bello se modificassi la domanda in "differenza tra piega e riduci in Scala".
Pedram Bashiri,

Risposte:


302

Poche cose da menzionare qui, prima di dare la risposta effettiva:

  • La tua domanda non ha nulla a che fare con left, riguarda piuttosto la differenza tra ridurre e piegare
  • La differenza non è affatto l'implementazione, basta guardare le firme.
  • La domanda non ha nulla a che fare con Scala in particolare, riguarda piuttosto i due concetti di programmazione funzionale.

Torna alla tua domanda:

Ecco la firma di foldLeft(potrebbe anche essere stato foldRightper il punto che farò):

def foldLeft [B] (z: B)(f: (B, A) => B): B

Ed ecco la firma di reduceLeft(di nuovo la direzione non conta qui)

def reduceLeft [B >: A] (f: (B, A) => B): B

Questi due sembrano molto simili e quindi hanno causato confusione. reduceLeftè un caso speciale di foldLeft(che a proposito significa che a volte puoi esprimere la stessa cosa usando uno di essi).

Quando chiami reduceLeftsay su a List[Int], ridurrà letteralmente l'intero elenco di numeri interi in un singolo valore, che sarà di tipo Int(o un supertipo di Int, quindi [B >: A]).

Quando chiami foldLeftdire su un List[Int], piegherà l'intero elenco (immagina di arrotolare un pezzo di carta) in un singolo valore, ma questo valore non deve essere nemmeno correlato a Int(quindi [B]).

Ecco un esempio:

def listWithSum(numbers: List[Int]) = numbers.foldLeft((List.empty[Int], 0)) {
   (resultingTuple, currentInteger) =>
      (currentInteger :: resultingTuple._1, currentInteger + resultingTuple._2)
}

Questo metodo accetta a List[Int]e restituisce a Tuple2[List[Int], Int]o (List[Int], Int). Calcola la somma e restituisce una tupla con un elenco di numeri interi ed è la somma. A proposito, l'elenco viene restituito al contrario, perché abbiamo usato al foldLeftposto di foldRight.

Guarda One Fold per domarli tutti per una spiegazione più approfondita.


Puoi spiegare perché Bè un supertipo di A? Sembra che in Brealtà dovrebbe essere un sottotipo di A, non un supertipo. Ad esempio, supponendo che Banana <: Fruit <: Food, se avessimo un elenco di Fruits, sembra che questo possa contenere alcuni Bananas, ma se contenesse qualche Foods il tipo sarebbe Foodcorretto? Quindi, in questo caso, se Bè un supertipo di Ae c'è un elenco che contiene sia Bs che As, l'elenco dovrebbe essere di tipo B, non A. Puoi spiegare questa discrepanza?
socom1880,

Non sono sicuro di aver compreso correttamente la tua domanda. Quello che dice la mia risposta di 5 anni sulla funzione di riduzione è che a List[Banana]può essere ridotto a un singolo Bananao un singolo Fruito un singolo Food. Perché Fruit :> Bananae "Cibo:> Banana".
Agilesteel,

Sì ... questo ha davvero senso grazie. Inizialmente lo stavo interpretando come "un elenco di tipi Bananapuò contenere un Fruit", il che non ha senso. La tua spiegazione ha un senso: la ffunzione che viene passata reduce()può risultare in a Fruito a Food, il che significa che Bnella firma dovrebbe essere una superclasse, non una sottoclasse.
socom1880,

193

reduceLeftè solo un metodo pratico. È equivalente a

list.tail.foldLeft(list.head)(_)

11
Bene, la risposta concisa :) Potrebbe voler correggere l'ortografia di reducelftse
Hanxue

10
Buona risposta. Ciò evidenzia anche perché foldfunziona su un elenco vuoto mentre reducenon lo fa.
Mansoor Siddiqui,

44

foldLeftè più generico, puoi usarlo per produrre qualcosa di completamente diverso da quello che hai inserito in origine. Considerando che reduceLeftpuò solo produrre un risultato finale dello stesso tipo o super tipo del tipo di raccolta. Per esempio:

List(1,3,5).foldLeft(0) { _ + _ }
List(1,3,5).foldLeft(List[String]()) { (a, b) => b.toString :: a }

Il foldLeftapplicherà la chiusura con l'ultimo risultato ripiegato (prima volta con valore iniziale) e il valore successivo.

reduceLeftd'altra parte combinerà prima due valori dall'elenco e li applicherò alla chiusura. Successivamente combinerà il resto dei valori con il risultato cumulativo. Vedere:

List(1,3,5).reduceLeft { (a, b) => println("a " + a + ", b " + b); a + b }

Se l'elenco è vuoto, è foldLeftpossibile presentare il valore iniziale come risultato legale. reduceLeftd'altra parte non ha un valore legale se non riesce a trovare almeno un valore nell'elenco.


5

La ragione principale per cui si trovano entrambi nella libreria standard Scala è probabilmente perché si trovano entrambi nella libreria standard Haskell (chiamata foldle foldl1). In caso reduceLeftcontrario, sarebbe spesso definito come un metodo di praticità in diversi progetti.


5

Per riferimento, si reduceLeftverificherà un errore se applicato a un contenitore vuoto con il seguente errore.

java.lang.UnsupportedOperationException: empty.reduceLeft

Rielaborazione del codice da utilizzare

myList foldLeft(List[String]()) {(a,b) => a+b}

è un'opzione potenziale. Un altro è quello di utilizzare la reduceLeftOptionvariante che restituisce un risultato a capo Opzione.

myList reduceLeftOption {(a,b) => a+b} match {
  case None    => // handle no result as necessary
  case Some(v) => println(v)
}

2

Dai principi di programmazione funzionale in Scala (Martin Odersky):

La funzione reduceLeftè definita in termini di una funzione più generale, foldLeft.

foldLeftè come reduceLeftma accetta un accumulatore z , come parametro aggiuntivo, che viene restituito quando foldLeftviene chiamato in un elenco vuoto:

(List (x1, ..., xn) foldLeft z)(op) = (...(z op x1) op ...) op x

[al contrario reduceLeft, che genera un'eccezione quando viene chiamato in un elenco vuoto.]

Il corso (vedi lezione 5.5) fornisce definizioni astratte di queste funzioni, che illustrano le loro differenze, sebbene siano molto simili nel loro uso di pattern matching e ricorsione.

abstract class List[T] { ...
  def reduceLeft(op: (T,T)=>T) : T = this match{
    case Nil     => throw new Error("Nil.reduceLeft")
    case x :: xs => (xs foldLeft x)(op)
  }
  def foldLeft[U](z: U)(op: (U,T)=>U): U = this match{
    case Nil     => z
    case x :: xs => (xs foldLeft op(z, x))(op)
  }
}

Nota che foldLeftrestituisce un valore di tipo U, che non è necessariamente lo stesso tipo di List[T], ma riduciLeft restituisce un valore dello stesso tipo dell'elenco).


0

Per capire davvero cosa stai facendo con fold / ridurre, controlla questo: http://wiki.tcl.tk/17983 ottima spiegazione. una volta ottenuto il concetto di piega, ridurre si unirà alla risposta sopra: list.tail.foldLeft (list.head) (_)

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.