różnica między foldLeft i zmniejszLeft w Scali

196

Nauczyłem się podstawowej różnicy między foldLeftireduceLeft

foldLeft:

  • wartość początkowa musi zostać przekazana

zmniejszLeft:

  • przyjmuje pierwszy element kolekcji jako wartość początkową
  • zgłasza wyjątek, jeśli kolekcja jest pusta

Czy jest jakaś inna różnica?

Czy jest jakiś konkretny powód, aby mieć dwie metody o podobnej funkcjonalności?

Rajesh Pitty
źródło
Polecam zobaczyć stackoverflow.com/questions/25158780/…
samthebest
Byłoby wspaniale, gdybyś zredagował pytanie jako „różnica między foldem a zmniejszeniem w Scali”.
pedram bashiri

Odpowiedzi:

302

Kilka rzeczy, o których warto tu wspomnieć, zanim podamy właściwą odpowiedź:

  • Twoje pytanie nie ma nic wspólnego z left, dotyczy raczej różnicy między zmniejszaniem a składaniem
  • Różnica nie polega wcale na implementacji, wystarczy spojrzeć na podpisy.
  • Pytanie nie ma nic wspólnego ze Scalą, chodzi raczej o dwie koncepcje programowania funkcjonalnego.

Powrót do pytania:

Oto podpis foldLeft(mógłbym również zrobić foldRightpunkt, który zamierzam zrobić):

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

A oto podpis reduceLeft(znowu kierunek nie ma tutaj znaczenia)

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

Te dwa wyglądają bardzo podobnie, powodując zamieszanie. reduceLeftjest szczególnym przypadkiem foldLeft(co przy okazji oznacza, że czasami możesz wyrazić to samo, używając jednego z nich).

Kiedy wywołasz reduceLeftsay na a List[Int], dosłownie zredukuje całą listę liczb całkowitych do pojedynczej wartości, która będzie typu Int(lub Intodtąd typu [B >: A]).

Kiedy wywołasz foldLeftsay na a List[Int], zwinie całą listę (wyobraź sobie zwinięcie kartki papieru) w jedną wartość, ale ta wartość nie musi być nawet związana Int(stąd [B]).

Oto przykład:

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

Ta metoda pobiera a List[Int]i zwraca a Tuple2[List[Int], Int]lub (List[Int], Int). Oblicza sumę i zwraca krotkę z listą liczb całkowitych i jej sumy. Nawiasem mówiąc, lista jest zwracana wstecz, ponieważ użyliśmy foldLeftzamiast foldRight.

Obejrzyj One Fold, aby rządzić nimi wszystkimi, by uzyskać bardziej szczegółowe wyjaśnienia.

agilesteel
źródło
Czy możesz wyjaśnić, dlaczego Bjest to nadtyp A? Wydaje się, że Bpowinien to być podtyp A, a nie nadtyp. Na przykład, zakładając Banana <: Fruit <: Food, że gdybyśmy mieli listę Fruits, wydaje się, że może ona zawierać niektóre Bananas, ale jeśli zawiera jakieś Foods, to typ byłby Food, prawda? Więc w tym przypadku, jeśli Bjest nadtyp Ai istnieje lista zawierająca zarówno Bs, jak i As, lista powinna być typu B, a nie A. Czy możesz wyjaśnić tę rozbieżność?
socom1880
Nie jestem pewien, czy dobrze rozumiem twoje pytanie. Moja 5-letnia odpowiedź na temat funkcji redukcji polega na tym, że List[Banana]można zredukować liczbę do jednego, Bananajednego Fruitlub jednego Food. Ponieważ Fruit :> Bananai „Jedzenie:> Banan”.
agilesteel 18.04.16
Tak ... to ma sens, dziękuję. Pierwotnie interpretowałem to jako „lista typów Bananamoże zawierać Fruit”, co nie ma sensu. Twoje wyjaśnienie ma sens - przekazana ffunkcja reduce()może dać a Fruitlub a Food, co oznacza, Bże podpis powinien być nadklasą, a nie podklasą.
socom1880 18.04.16
193

reduceLeftto tylko wygodna metoda. Jest to równoważne z

list.tail.foldLeft(list.head)(_)
Kim Stebel
źródło
11
Dobry, zwięzła odpowiedź :) chcieć poprawić pisownię reducelftchoć
Hanxue
10
Dobra odpowiedź. To również podkreśla, dlaczego folddziała na pustej liście, a reducenie działa.
Mansoor Siddiqui
44

foldLeftjest bardziej ogólny, możesz go użyć do stworzenia czegoś zupełnie innego niż to, co pierwotnie umieściłeś. Podczas gdy reduceLeftmoże dać tylko wynik końcowy tego samego typu lub supertypu typu kolekcji. Na przykład:

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

foldLeftZastosuje zamknięcia z ostatniego złożonego wyniku (pierwszy raz przy użyciu wartości początkowej) oraz kolejną wartość.

reduceLeftz drugiej strony najpierw połączą dwie wartości z listy i zastosują je do zamknięcia. Następnie połączy pozostałe wartości z łącznym wynikiem. Widzieć:

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

Jeśli lista jest pusta, foldLeftmoże przedstawić wartość początkową jako wynik prawny. reduceLeftz drugiej strony nie ma wartości prawnej, jeśli nie może znaleźć co najmniej jednej wartości na liście.

thoredge
źródło
5

Podstawowym powodem, dla którego oba znajdują się w standardowej bibliotece Scala, jest prawdopodobnie dlatego, że oba znajdują się w standardowej bibliotece Haskell (nazywanej foldli foldl1). Jeśli reduceLeftnie, to często jest definiowany jako metoda wygodna w różnych projektach.

Aleksiej Romanow
źródło
5

W celach informacyjnych reduceLeftwystąpi błąd, jeśli zostanie zastosowany do pustego pojemnika z następującym błędem.

java.lang.UnsupportedOperationException: empty.reduceLeft

Przerób kod do użycia

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

jest jedną z potencjalnych opcji. Innym jest użycie reduceLeftOptionwariantu, który zwraca wynik zawarty w Opcji.

myList reduceLeftOption {(a,b) => a+b} match {
  case None    => // handle no result as necessary
  case Some(v) => println(v)
}
Martin Smith
źródło
2

Z zasad programowania funkcjonalnego w Scali (Martin Odersky):

Funkcja reduceLeftjest zdefiniowane w kategoriach bardziej ogólnych funkcji foldLeft.

foldLeftjest jak, reduceLeftale bierze akumulator z , jako dodatkowy parametr, który jest zwracany, gdy foldLeftjest wywoływany na pustej liście:

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

[w przeciwieństwie do reduceLeft, który zgłasza wyjątek, gdy jest wywoływany na pustej liście.]

Kurs (patrz wykład 5.5) zawiera abstrakcyjne definicje tych funkcji, które ilustrują ich różnice, chociaż są one bardzo podobne w stosowaniu dopasowywania wzorców i rekurencji.

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

Zauważ, że foldLeftzwraca wartość typu U, która niekoniecznie jest tego samego typu List[T], ale redukcja zwraca wartość tego samego typu co lista).

C8H10N4O2
źródło
0

Aby naprawdę zrozumieć, co robisz ze składaniem / zmniejszaniem, sprawdź to: http://wiki.tcl.tk/17983 bardzo dobre wyjaśnienie. gdy pojawi się koncepcja składania, redukcja pojawi się wraz z odpowiedzią powyżej: list.tail.foldLeft (list.head) (_)

Henry H.
źródło