Карта Scala с зависимыми переменными

В Scala у меня есть список функций, которые возвращают значение. Порядок, в котором выполняются функции, важен, так как аргумент функции n это выход функции n-1,

Это подсказки для использования foldLeft, что-то вроде:

val base: A
val funcs: Seq[Function[A, A]]

funcs.foldLeft(base)(x, f) => f(x)

(деталь: тип A на самом деле Spark DataFrame).

Однако результаты каждой функции являются взаимоисключающими, и в конце я хочу объединить все результаты для каждой функции. Это намекает на использование map, что-то вроде:

funcs.map(f => f(base)).reduce(_.union(_)

Но здесь каждая функция применяется к base что не то, что я хочу.

Коротко: список переменной длины упорядоченных функций должен возвращать список равной длины возвращаемых значений, где каждое значение n-1 был вход для функции n (начиная с base где n=0). Так что значения результата могут быть объединены.

Как мне этого добиться?

РЕДАКТИРОВАТЬ пример:

case class X(id:Int, value:Int)
val base = spark.createDataset(Seq(X(1, 1), X(2, 2), X(3, 3), X(4, 4), X(5, 5))).toDF

def toA = (x: DataFrame) => x.filter('value.mod(2) === 1).withColumn("value", lit("a"))
def toB = (x: DataFrame) => x.withColumn("value", lit("b"))

val a = toA(base)
val remainder = base.join(a, Seq("id"), "leftanti")
val b = toB(remainder)

a.union(b)

+---+-----+
| id|value|
+---+-----+
|  1|    a|
|  3|    a|
|  5|    a|
|  2|    b|
|  4|    b|
+---+-----+

Это должно работать для произвольного числа функций (например, toA, toB... toN, Где каждый раз остаток предыдущего результата вычисляется и передается в следующую функцию. В конце концов, объединение применяется ко всем результатам.

2 ответа

Решение

Seq уже есть метод scanLeft это делает это из коробки:

funcs.scanLeft(base)((acc, f) => f(acc)).tail

Обязательно отбросьте первый элемент результата scanLeft если ты не хочешь base быть включенным.


Используя только foldLeft, это также возможно:

funcs.foldLeft((base, List.empty[A])){ case ((x, list), f) => 
  val res = f(x)
  (res, res :: list) 
}._2.reverse.reduce(_.union(_))

Или же:

funcs.foldLeft((base, Vector.empty[A])){ case ((x, list), f) => 
  val res = f(x)
  (res, list :+ res) 
}._2.reduce(_.union(_))

Хитрость заключается в накоплении в Seq внутри fold,

Пример:

scala> val base = 7
base: Int = 7

scala> val funcs: List[Int => Int] = List(_ * 2, _ + 3)
funcs: List[Int => Int] = List($$Lambda$1772/1298658703@7d46af18, $$Lambda$1773/107346281@5470fb9b)

scala> funcs.foldLeft((base, Vector.empty[Int])){ case ((x, list), f) => 
     |   val res = f(x)
     |   (res, list :+ res) 
     | }._2
res8: scala.collection.immutable.Vector[Int] = Vector(14, 17)

scala> .reduce(_ + _)
res9: Int = 31

У меня есть упрощенное решение с использованием обычных коллекций, но применяется тот же принцип.

val list: List[Int] = List(1, 2, 3, 4, 5)
val funcs: Seq[Function[List[Int], List[Int]]] = Seq(times2, by2)

funcs.foldLeft(list) { case(collection, func) => func(collection) } foreach println // prints 1 2 3 4 5

def times2(l: List[Int]): List[Int] = l.map(_ * 2)

def by2(l: List[Int]): List[Int] = l.map(_ / 2)

Это решение не имеет места, если вы хотите одно уменьшенное значение в качестве конечного результата, например, один Int; поэтому это работает как:F[B] -> F[B] -> F[B] а не как F[B] -> F[B] -> B; хотя, думаю, это то, что тебе нужно.

Другие вопросы по тегам