diferença entre foldLeft e reduzaLeft em Scala


196

Eu aprendi a diferença básica entre foldLeftereduceLeft

foldLeft:

  • valor inicial deve ser passado

reduzirLeft:

  • toma o primeiro elemento da coleção como valor inicial
  • lança exceção se a coleção estiver vazia

Existe alguma outra diferença?

Algum motivo específico para ter dois métodos com funcionalidade semelhante?



Seria ótimo se você editasse a pergunta como "diferença entre dobrar e reduzir no Scala".
pedram Bashiri

Respostas:


302

Poucas coisas para mencionar aqui, antes de dar a resposta real:

  • Sua pergunta não tem nada a ver left, é sobre a diferença entre reduzir e dobrar
  • A diferença não é a implementação, basta olhar para as assinaturas.
  • A questão não tem nada a ver com Scala em particular, é sobre os dois conceitos de programação funcional.

Voltar à sua pergunta:

Aqui está a assinatura de foldLeft(também poderia ter sido foldRightpara o ponto que eu vou fazer):

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

E aqui está a assinatura de reduceLeft(novamente a direção não importa aqui)

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

Estes dois parecem muito semelhantes e, portanto, causaram confusão. reduceLefté um caso especial de foldLeft(que, a propósito, significa que você às vezes pode expressar a mesma coisa usando qualquer um deles).

Quando você chama reduceLeftsay em a List[Int], literalmente reduz a lista inteira de números inteiros em um único valor, que será do tipo Int(ou um supertipo de Int, portanto [B >: A]).

Quando você chama o foldLeftsay em um, List[Int]ele dobrará a lista inteira (imagine rolar um pedaço de papel) em um único valor, mas esse valor nem precisa estar relacionado aInt (daí [B]).

Aqui está um exemplo:

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

Este método pega um List[Int]e retorna um Tuple2[List[Int], Int]ou (List[Int], Int). Ele calcula a soma e retorna uma tupla com uma lista de números inteiros e sua soma. A propósito, a lista é retornada para trás, porque usamos em foldLeftvez defoldRight .

Assista ao One Fold para dominá-los para uma explicação mais aprofundada.


Você pode explicar por que Bé um supertipo A? Parece Bque na verdade deveria ser um subtipo A, não um supertipo. Por exemplo, supondo que Banana <: Fruit <: Food, se tivéssemos uma lista de Fruits, parece que isso pode conter alguns Bananas, mas se contivesse algum Foods, o tipo seria Foodcorreto? Portanto, nesse caso, se Bfor um supertipo de Ae houver uma lista que contenha Bs e As, a lista deverá ser do tipo B, não A. Você pode explicar essa discrepância?
socom1880

Não tenho certeza se entendi sua pergunta corretamente. O que minha resposta de 5 anos está dizendo sobre a função reduzir é que a List[Banana]pode ser reduzida para um único Bananaou um único Fruitou um único Food. Porque Fruit :> Bananae `Alimentos:> Banana '.
agilesteel

Sim ... isso realmente faz sentido, obrigado. Eu estava interpretando originalmente como "uma lista do tipo Bananapode conter um Fruit", o que não faz sentido. Sua explicação faz sentido - a ffunção que está sendo transmitida reduce()pode resultar em a Fruitou a Food, o que significa que Ba assinatura deve ser uma superclasse, não uma subclasse.
socom1880

193

reduceLefté apenas um método de conveniência. É equivalente a

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

11
Bom, resposta concisa :) Pode querer corrigir a ortografia do reducelftembora
Hanxue

10
Boa resposta. Isso também destaca por que foldfunciona em uma lista vazia enquanto reducenão funciona.
Mansoor Siddiqui

44

foldLefté mais genérico, você pode usá-lo para produzir algo completamente diferente do que você colocou originalmente. Considerando reduceLeftque só pode produzir um resultado final do mesmo tipo ou supertipo do tipo de coleção. Por exemplo:

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

Ele foldLeftaplicará o fechamento com o último resultado dobrado (primeira vez usando o valor inicial) e o próximo valor.

reduceLeftpor outro lado, primeiro combinará dois valores da lista e os aplicará ao fechamento. Em seguida, ele combinará o restante dos valores com o resultado acumulado. Vejo:

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

Se a lista estiver vazia, foldLeftpode apresentar o valor inicial como resultado legal. reduceLeftpor outro lado, não possui um valor legal se não conseguir encontrar pelo menos um valor na lista.


5

A razão básica de ambos estarem na biblioteca padrão Scala é provavelmente porque ambos estão na biblioteca padrão Haskell (chamada foldle foldl1). Caso reduceLeftcontrário, muitas vezes seria definido como um método de conveniência em diferentes projetos.


5

Para referência, reduceLeftocorrerá um erro se aplicado a um contêiner vazio com o seguinte erro.

java.lang.UnsupportedOperationException: empty.reduceLeft

Retrabalhando o código para usar

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

é uma opção em potencial. Outra é usar a reduceLeftOptionvariante que retorna um resultado agrupado em Opção.

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

2

Dos Princípios de Programação Funcional em Scala (Martin Odersky):

A função reduceLefté definida em termos de uma função mais geral foldLeft,.

foldLefté como, reduceLeftmas usa um acumulador z , como um parâmetro adicional, retornado quando foldLefté chamado em uma lista vazia:

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

[ao contrário de reduceLeft, que gera uma exceção quando chamado em uma lista vazia.]

O curso (consulte a aula 5.5) fornece definições abstratas dessas funções, que ilustram suas diferenças, embora sejam muito semelhantes no uso de correspondência e recursão de padrões.

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)
  }
}

Observe que foldLeftretorna um valor do tipo U, que não é necessariamente o mesmo tipo que List[T], mas reduz a esquerda retorna um valor do mesmo tipo que a lista).


0

Para realmente entender o que você está fazendo com dobra / redução, verifique isto: http://wiki.tcl.tk/17983 explicação muito boa. Depois de obter o conceito de dobra, reduzir será acompanhado pela resposta acima: list.tail.foldLeft (list.head) (_)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.