2017-03-07 1 views
1

En scala j'ai une liste de fonctions qui renvoient une valeur. L'ordre dans lequel les fonctions sont exécutées est important puisque l'argument de la fonction n est la sortie de la fonction n-1.Scala carte avec variables dépendantes

Ceci laisse présager d'utiliser foldLeft, quelque chose comme:

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

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

(détail: le type A est en fait un dataframe Spark).

Cependant, les résultats de chaque fonction s'excluent mutuellement et à la fin je veux l'union de tous les résultats pour chaque fonction. Ceci laisse présager d'utiliser un map, quelque chose comme:

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

Mais ici chaque fonction est appliquée à base qui est pas ce que je veux.

courte: Une liste de longueur variable des fonctions commandées doit renvoyer une liste de longueur égale des valeurs de retour, où chaque valeur est la n-1 entrée pour fonction n (à partir de basen=0). De telle sorte que les valeurs de résultat peuvent être concaténées.

Comment puis-je y parvenir?

EDIT exemple:

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| 
+---+-----+ 

Cela devrait fonctionner pour un nombre arbitraire de fonctions (par exemple toA, toB ... toN où chaque calcul le reste du résultat précédent et est passé dans. la fonction suivante.A la fin une union est appliquée à tous les résultats

Répondre

1

Seq a déjà une méthode scanLeft qui fait cela hors-the-box:

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

Assurez-vous de laisser tomber le premier élément du résultat de scanLeft si vous ne voulez pas base à inclure .


En utilisant seulement foldLeft il est possible aussi:

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

Ou:

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

L'astuce consiste à accumuler dans un Seq à l'intérieur du fold.

Exemple:

scala> val base = 7 
base: Int = 7 

scala> val funcs: List[Int => Int] = List(_ * 2, _ + 3) 
funcs: List[Int => Int] = List($$Lambda$1772/[email protected], $$Lambda$1773/[email protected]) 

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 
+0

J'ai ajouté un exemple pour clarifier ma question. – Tim

+0

Votre premier exemple était exactement ce dont j'avais besoin. Bonne idée pour accumuler dans une liste, jamais pensé que vous pourriez utiliser foldLeft comme ça. Impressionnant :). – Tim

0

J'ai une solution simplifiée en utilisant les collections normales, mais le même principe s'applique

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) 

Cette solution ne tient pas si vous voulez une seule valeur réduite comme sortie finale, par ex. unique Int; donc cela fonctionne comme: F[B] -> F[B] -> F[B] et non comme F[B] -> F[B] -> B; mais je suppose que c'est ce dont vous avez besoin.

+0

J'ajouté un exemple pour ma question plus claire. – Tim