2009-07-21 18 views
30

Supongamos que tengozipWith (mapeo a través de múltiples Seq) en Scala

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

y deseo para producir una SEC donde el baz (i) = foo (i) + bar (i). Una forma que se me ocurre de hacerlo es

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

Sin embargo, esto se siente tanto fea e ineficiente - Tengo que convertir ambos SEQs a listas (que estalla con las listas perezoso), crear esta lista temporal de tuplas, solo para mapearlo y dejar que sea 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?

Respuesta

15

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 this Trac ticket.

+2

Lo sentimos, no zipWith en Scala 2.8. –

+3

Para ser claros (y estoy seguro de que Daniel estaría de acuerdo), Scala no tiene nada por lo que disculparse, lo que obtienes con Scala es aún mejor. Vea la respuesta de Martin a continuación, y la de Daniel. Sería bueno que alguien pudiera hacer de Martin la respuesta aprobada a esta pregunta ... – AmigoNico

3

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 utilizarlas. 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 que quede claro, debe utilizar 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.

ideal sería que el algoritmo debe ser capaz de trabajar en dos infinitas corrientes sin volar (suponiendo que no hace ningún folding, por supuesto, pero sólo lee y genera corrientes).

+0

Sé lo que es una lista perezosa, pero no estoy muy familiarizado con Scala. foo.toList no es flojo, ¿verdad? En cualquier caso, viniendo de un trasfondo de CL, se siente muy raro que no haya una función mapMultiple, por lo que la razón para hacer esta pregunta es solo para averiguar cuál es la forma correcta de hacerlo de Scala. El rendimiento es realmente bastante importante; esto está en mi ciclo interno, y aunque puedo intentar optimizarlo más adelante, me gustaría codificarlo de una manera razonable primero. – bsdfish

+0

Te digo que estabas en lo cierto cuando dijiste "quizás las transmisiones resuelvan el problema": usa la versión de flujo de zip. Si cree que las asignaciones pequeñas ejercen presión sobre el GC, escriba un equivalente imperativo en el lenguaje JVM de su elección y crúcelas para ver si es cierto (con frecuencia me sorprendieron los brillantes de las VM que manejan lotes). de pequeñas asignaciones de corta vida). –

9

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 esto, 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 en realidad va a estar disponible en Scala 2.8 es la siguiente:

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

ACTUALIZACIÓN: Se ha señalado (en los comentarios) que esta "respuesta" doesn De hecho, abordan la pregunta que se hace. Esta respuesta mapeará sobre cada combinación de foo y bar, produciendo N x M elementos, en lugar de la min (M, N) a lo solicitado. Por lo tanto, esto es mal, pero se fue para la posteridad ya que es una buena información.


La mejor manera de hacerlo es con flatMap combinado con map. Código habla más que mil palabras:

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

Esto producirá una sola Seq[Double], tal y como era de esperar. Este patrón es tan común que Scala en realidad incluye un poco de magia sintáctica que prevé su aplicación:

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

O, alternativamente:

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

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

+4

No voy a votar esta pregunta por ahora, pero está completamente equivocado. Eso dará como resultado elementos NxN, y lo que la pregunta solicitada dará como resultado solo N elementos. Está agregando cada combinación de elementos de foo y bar, pero lo que se solicita es foo (i) + barra (i). –

+1

Buen punto. Era un poco temprano en la mañana, así que aparentemente mi cerebro no funcionaba correctamente. Voy a eliminar esta respuesta, ya que realmente no proporciona lo que el autor me estaba pidiendo. –

+1

En realidad, solo actualizaré la respuesta. Es buena información, simplemente no aplicable a esta pregunta. –

74

En Scala 2.8:

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

Y funciona desde hace más de dos operandos de la misma manera. Es decir. A continuación, puede seguir esto con:

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

No parece funcionar con más de tres operandos, sin embargo. ¿Es eso correcto? – Debilski

+14

Correcto, 'zip 'se define solo en' Tuple2' y 'Tuple3'. El resumen sobre arity es una de las fronteras finales en Scala (y en la mayoría de los otros lenguajes tipados estáticamente). HLists ofrece una posibilidad ... – retronym

+7

@retronym también existe el enfoque '<*>'/'<$>' que tomamos con ZipLists en Haskell, donde en realidad no necesita abstraerse debido a la homogeneidad de las funciones curried. Por lo tanto, si quisiera comprimirla con una 'f' de 5 parámetros, podría hacer más o menos' f <$> xs <*> ys <*> zs <*> ps <*> qs'. Desafortunadamente las funciones al curry son mucho más difíciles de tratar en Scala :(quizás algunas ideas podrían ser transferidas, ya que este enfoque parece mucho más elegante que el de 'HList'. –

1

Cuando se enfrenta una tarea similar, he añadido la 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 
    } 
    } 
} 

Tener esto, se 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 pasada como anidada Iterable, ya que tail y head serán llamados recurrentemente en i t. Por lo tanto, idealmente debe pasar la colección Iterable[List] o other con tail y head rápidos.

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.

Cuestiones relacionadas