Когда следует использовать reduceLeft
, reduceRight
, foldLeft
, foldRight
, scanLeft
или scanRight
?
Я хочу интуицию/обзор их различий - возможно, с некоторыми простыми примерами.
Когда следует использовать reduceLeft
, reduceRight
, foldLeft
, foldRight
, scanLeft
или scanRight
?
Я хочу интуицию/обзор их различий - возможно, с некоторыми простыми примерами.
В общем, все 6-кратные функции применяют двоичный оператор к каждому элементу коллекции. Результат каждого шага передается на следующий шаг (в качестве ввода одному из двоичного оператора два аргумента). Таким образом, мы можем накопить результат.
reduceLeft
и reduceRight
кумуляция одного результата.
foldLeft
и foldRight
кумуляция одного результата с использованием начального значения.
scanLeft
и scanRight
накапливают совокупность промежуточных кумулятивных результатов с использованием начального значения.
От LEFT и вперед...
С помощью набора элементов abc
и двоичного оператора add
мы можем исследовать, что делают различные функции фолда при переходе от элемента LEFT коллекции (от A до C):
val abc = List("A", "B", "C")
def add(res: String, x: String) = {
println(s"op: $res + $x = ${res + x}")
res + x
}
abc.reduceLeft(add)
// op: A + B = AB
// op: AB + C = ABC // accumulates value AB in *first* operator arg `res`
// res: String = ABC
abc.foldLeft("z")(add) // with start value "z"
// op: z + A = zA // initial extra operation
// op: zA + B = zAB
// op: zAB + C = zABC
// res: String = zABC
abc.scanLeft("z")(add)
// op: z + A = zA // same operations as foldLeft above...
// op: zA + B = zAB
// op: zAB + C = zABC
// res: List[String] = List(z, zA, zAB, zABC) // maps intermediate results
С ПРАВО и назад...
Если мы начнем с элемента RIGHT и вернемся назад (от C до A), мы заметим, что теперь второй аргумент нашему двоичному оператору накапливает результат (оператор тот же, мы просто переключили имена аргументов, чтобы сделать их роли понятны):
def add(x: String, res: String) = {
println(s"op: $x + $res = ${x + res}")
x + res
}
abc.reduceRight(add)
// op: B + C = BC
// op: A + BC = ABC // accumulates value BC in *second* operator arg `res`
// res: String = ABC
abc.foldRight("z")(add)
// op: C + z = Cz
// op: B + Cz = BCz
// op: A + BCz = ABCz
// res: String = ABCz
abc.scanRight("z")(add)
// op: C + z = Cz
// op: B + Cz = BCz
// op: A + BCz = ABCz
// res: List[String] = List(ABCz, BCz, Cz, z)
.
От LEFT и вперед...
Если вместо этого мы должны были декомпрессировать некоторый результат путем вычитания, начиная с элемента LEFT из коллекции, мы бы кумулировали результат через первый аргумент res
нашего двоичного оператора minus
:
val xs = List(1, 2, 3, 4)
def minus(res: Int, x: Int) = {
println(s"op: $res - $x = ${res - x}")
res - x
}
xs.reduceLeft(minus)
// op: 1 - 2 = -1
// op: -1 - 3 = -4 // de-cumulates value -1 in *first* operator arg `res`
// op: -4 - 4 = -8
// res: Int = -8
xs.foldLeft(0)(minus)
// op: 0 - 1 = -1
// op: -1 - 2 = -3
// op: -3 - 3 = -6
// op: -6 - 4 = -10
// res: Int = -10
xs.scanLeft(0)(minus)
// op: 0 - 1 = -1
// op: -1 - 2 = -3
// op: -3 - 3 = -6
// op: -6 - 4 = -10
// res: List[Int] = List(0, -1, -3, -6, -10)
С ПРАВО и назад...
Но обратите внимание на изменения xRight сейчас! Помните, что (де-кумулированное значение в вариациях xRight передается второму параметру res
нашего двоичного оператора minus
:
def minus(x: Int, res: Int) = {
println(s"op: $x - $res = ${x - res}")
x - res
}
xs.reduceRight(minus)
// op: 3 - 4 = -1
// op: 2 - -1 = 3 // de-cumulates value -1 in *second* operator arg `res`
// op: 1 - 3 = -2
// res: Int = -2
xs.foldRight(0)(minus)
// op: 4 - 0 = 4
// op: 3 - 4 = -1
// op: 2 - -1 = 3
// op: 1 - 3 = -2
// res: Int = -2
xs.scanRight(0)(minus)
// op: 4 - 0 = 4
// op: 3 - 4 = -1
// op: 2 - -1 = 3
// op: 1 - 3 = -2
// res: List[Int] = List(-2, 3, -1, 4, 0)
Последний список (-2, 3, -1, 4, 0), возможно, не то, что вы бы интуитивно ожидали!
Как вы видите, вы можете проверить, что делает ваш foldX, просто запустив scanX и отлаживая накопленный результат на каждом шаге.
reduceLeft
или reduceRight
.foldLeft
или foldRight
, если у вас есть начальное значение.Кумуляция коллекции промежуточных результатов с помощью scanLeft
или scanRight
.
Используйте вариацию xLeft, если вы хотите перейти вперед через коллекцию.
Обычно метод REDUCE, FOLD, SCAN работает, накапливая данные по LEFT и продолжая изменять переменную RIGHT. Основное различие между ними: REDUCE, FOLD: -
Fold всегда начинается с значения seed
, то есть определяемого пользователем начального значения.
Сокращение будет вызывать исключение, если коллекция пуста, где в качестве сбрасывает значение семени. Всегда будет иметь одно значение.
Сканирование используется для некоторого порядка обработки элементов с левой или с правой стороны, тогда мы можем использовать предыдущий результат при последующем вычислении. Это означает, что мы можем сканировать элементы. Всегда будет выводить коллекцию.
RIGHT_REDUCE противоположно reduceLeft, т.е. он накапливает значения в RIGHT и продолжает изменять левую переменную.
reduceLeftOption и reduceRightOption похожи на left_reduce и right_reduce, только разница заключается в том, что они возвращают результаты в объект OPTION.
Часть выхода для упомянутого ниже кода будет: -
используя операцию scan
над списком чисел (используя seed
значение 0
) List(-2,-1,0,1,2)
{0, -2} = > - 2 {-2, -1} = > - 3 {-3,0} = > - 3 {-3,1} = > - 2 {-2, 2} = > 0 Список сканирования (0, -2, -3, -3, -2, 0)
{0, -2} = > - 2 {-2, -1} = > - 3 {-3,0} = > - 3 {-3,1} = > - 2 {-2, 2} = > 0 scanLeft (a + b) Список (0, -2, -3, -3, -2, 0)
{0, -2} = > - 2 {-2, -1} = > - 3 {-3,0} = > - 3 {-3,1} = > - 2 {-2, 2} = > 0 scanLeft (b + a) Список (0, -2, -3, -3, -2, 0)
{2,0} = > 2 {1,2} = > 3 {0,3} = > 3 {-1,3} = > 2 {-2,2} = > 0 scanRight ( a + b) Список (0, 2, 3, 3, 2, 0)
{2,0} = > 2 {1,2} = > 3 {0,3} = > 3 {-1,3} = > 2 {-2,2} = > 0 scanRight ( b + a) Список (0, 2, 3, 3, 2, 0)
с помощью операций reduce
, fold
над списком строк List("A","B","C","D","E")
Код:
object ScanFoldReduce extends App {
val list = List("A","B","C","D","E")
println("reduce (a+b) "+list.reduce((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" ")
a+b
}))
println("reduceLeft (a+b) "+list.reduceLeft((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" ")
a+b
}))
println("reduceLeft (b+a) "+list.reduceLeft((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" " )
b+a
}))
println("reduceRight (a+b) "+list.reduceRight((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println("reduceRight (b+a) "+list.reduceRight((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" ")
b+a
}))
println("scan "+list.scan("[")((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println("scanLeft (a+b) "+list.scanLeft("[")((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println("scanLeft (b+a) "+list.scanLeft("[")((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" " )
b+a
}))
println("scanRight (a+b) "+list.scanRight("[")((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println("scanRight (b+a) "+list.scanRight("[")((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" " )
b+a
}))
//Using numbers
val list1 = List(-2,-1,0,1,2)
println("reduce (a+b) "+list1.reduce((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" ")
a+b
}))
println("reduceLeft (a+b) "+list1.reduceLeft((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" ")
a+b
}))
println("reduceLeft (b+a) "+list1.reduceLeft((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" " )
b+a
}))
println(" reduceRight (a+b) "+list1.reduceRight((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println(" reduceRight (b+a) "+list1.reduceRight((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" ")
b+a
}))
println("scan "+list1.scan(0)((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println("scanLeft (a+b) "+list1.scanLeft(0)((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b
}))
println("scanLeft (b+a) "+list1.scanLeft(0)((a,b)=>{
print("{"+a+","+b+"}=>"+ (b+a)+" " )
b+a
}))
println("scanRight (a+b) "+list1.scanRight(0)((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
a+b}))
println("scanRight (b+a) "+list1.scanRight(0)((a,b)=>{
print("{"+a+","+b+"}=>"+ (a+b)+" " )
b+a}))
}