spark pattern for comprehension companion classes scala functional-programming higher-order-functions

pattern - scala for



zipWith(mapeo sobre mĂșltiples Seq) en Scala (6)

Bueno, eso, la falta de zip, es una deficiencia en Scala 2.7 Seq. Scala 2.8 tiene un diseño de colección bien pensado, para reemplazar la forma ad-hoc en que surgieron las colecciones presentes en 2.7 (tenga en cuenta que no todas fueron creadas a la vez, con un diseño unificado).

Ahora, cuando desee evitar crear una colección temporal, debe usar "proyección" en Scala 2.7, o "ver" en Scala 2.8. Esto le dará un tipo de colección para la cual ciertas instrucciones, particularmente map, flatMap y filter, no son estrictas. En Scala 2.7, la proyección de una lista es una secuencia. En Scala 2.8, hay un SequenceView de una Secuencia, pero hay un zipWith allí mismo en la Secuencia, ni siquiera lo necesitarías.

Habiendo dicho eso, como se mencionó, JVM está optimizado para manejar asignaciones temporales de objetos y, cuando se ejecuta en modo servidor, la optimización en tiempo de ejecución puede hacer maravillas. Por lo tanto, no optimice prematuramente. Pruebe el código en las condiciones en que se ejecutará, y si no ha planificado ejecutarlo en modo servidor, vuelva a pensar que si se espera que el código sea de larga ejecución, y optmize cuándo / dónde / si es necesario.

EDITAR

Lo que realmente estará disponible en Scala 2.8 es este:

(foo,bar).zipped.map(_+_)

Supongamos que tengo

val foo : Seq[Double] = ... val bar : Seq[Double] = ...

y deseo producir un seq donde el baz (i) = foo (i) + barra (i). Una forma en que puedo pensar para hacer esto es

val baz : Seq[Double] = (foo.toList zip bar.toList) map ((f: Double, b : Double) => f+b)

Sin embargo, esto se siente feo e ineficiente: tengo que convertir ambos seqs en listas (que explotan con listas perezosas), creo esta lista temporal de tuplas, solo para mapearla y dejarla en GCed. Tal vez las transmisiones resuelvan el problema de la pereza, pero en cualquier caso, esto parece innecesariamente feo. En lisp, la función del mapa se correlacionaría en múltiples secuencias. Yo escribiría

(mapcar (lambda (f b) (+ f b)) foo bar)

Y no se crearían listas temporales en ninguna parte. ¿Existe una función de mapa sobre múltiples listas en Scala, o se combina el código postal con la desestructuración como la forma "correcta" de hacer esto?


Cuando enfrenté una tarea similar, agregué el siguiente proxeneta a Iterable s:

implicit class IterableOfIterablePimps[T](collOfColls: Iterable[Iterable[T]]) { def mapZipped[V](f: Iterable[T] => V): Iterable[V] = new Iterable[V] { override def iterator: Iterator[V] = new Iterator[V] { override def next(): V = { val v = f(itemsLeft.map(_.head)) itemsLeft = itemsLeft.map(_.tail) v } override def hasNext: Boolean = itemsLeft.exists(_.nonEmpty) private var itemsLeft = collOfColls } } }

Teniendo esto, uno puede hacer algo como:

val collOfColls = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) collOfColls.mapZipped { group => group // List(1, 4, 7), then List(2, 5, 8), then List(3, 6, 9) }

Tenga en cuenta que debe considerar cuidadosamente el tipo de colección pasado como Iterable anidado, ya que se recurrirá repetidamente a la tail y la head . Entonces, lo ideal es que pases Iterable[List] u other colección con tail y head rápidas.

Además, este código espera colecciones anidadas del mismo tamaño. Ese era mi caso de uso, pero sospecho que se puede mejorar, si es necesario.


En Scala 2.8:

val baz = (foo, bar).zipped map (_ + _)

Y funciona para más de dos operandos de la misma manera. Es decir, podrías seguir esto con:

(foo, bar, baz).zipped map (_ * _ * _)


La función que desea se llama zipWith , pero no es parte de la biblioteca estándar. Será en 2.8 (ACTUALIZACIÓN: Aparentemente no, ver comentarios).

foo zipWith((f: Double, b : Double) => f+b) bar

Ver este ticket de Trac .


Una lista diferida no es una copia de una lista; se parece más a un solo objeto. En el caso de una implementación de zip perezoso, cada vez que se le pide el siguiente elemento, toma un elemento de cada una de las dos listas de entrada y crea una tupla de ellos, y luego divide la tupla con la coincidencia de patrones en tu lambda

Por lo tanto, nunca es necesario crear una copia completa de la (s) lista (s) de entrada completa antes de comenzar a operar en ellas. Todo se reduce a un patrón de asignación muy similar a cualquier aplicación que se ejecute en la JVM: muchas asignaciones de muy corta duración pero pequeñas, que la JVM está optimizada para manejar.

Actualización: para ser claros, debe usar Streams (listas diferidas) no Lists. Las transmisiones de Scala tienen un zip que funciona de manera perezosa, por lo que no conviene convertir las cosas en listas.

Idealmente, su algoritmo debería ser capaz de trabajar en dos flujos infinitos sin explotar (suponiendo que no se folding , por supuesto, sino que solo lee y genera flujos).


ACTUALIZACIÓN: Se ha señalado (en comentarios) que esta "respuesta" en realidad no aborda la pregunta que se hace. Esta respuesta se asignará a cada combinación de foo y bar , produciendo N x M elementos, en lugar del mínimo (M, N) según lo solicitado. Entonces, esto está mal , pero se dejó para la posteridad ya que es buena información.

La mejor forma de hacerlo es con flatMap combinado con el map . El código habla más fuerte que las palabras:

foo flatMap { f => bar map { b => f + b } }

Esto producirá un único Seq[Double] , exactamente como cabría esperar. Este patrón es tan común que Scala realmente incluye algo de magia sintáctica que lo implementa:

for { f <- foo b <- bar } yield f + b

O alternativamente:

for (f <- foo; b <- bar) yield f + b

La sintaxis for { ... } es realmente la forma más idiomática de hacer esto. Puede seguir agregando cláusulas de generador (por ejemplo, b <- bar ) según sea necesario. Por lo tanto, si de repente se convierte en tres Seq que debe mapear, puede escalar fácilmente su sintaxis junto con sus requisitos (para acuñar una frase).