2011-12-31 12 views
5

He luchado para encontrar una forma de mantenerme fiel al estilo funcional en expresiones cuando necesito recopilar múltiples parámetros de un objeto en una lista.Cómo mantenerme fiel al estilo funcional en Scala para expresiones

Como ejemplo, digamos que tengo un objeto de notificación, que tiene un fromId (el ID de usuario de la notificación es) y un objectOwnerId (el id del usuario que creó el objeto original). Estos pueden diferir en las notificaciones de estilo de Facebook ("X también comentó en la publicación de Y").

puedo recoger los userIds con una expresión como tal

val userIds = for { notification <- notifications } yield notification.fromId 

sin embargo digo que quiero para recoger tanto los fromIds y los objectOwnerIds en una sola lista, ¿hay alguna manera de hacer esto en una sola de expresión sin el usuario de vars?

que he hecho algo como esto en el pasado:

var ids = List() 
for { 
    notification <- notifications 
    ids = ids ++ List(notification.fromId, notification.objectOwnerId) 
} 
ids = ids.distinct 

pero se siente como que debe haber una mejor manera. El uso de una var y la necesidad de llamar distinct después de completar la colección son desagradables. Podría evitar lo distinto con algunos condicionales, pero estoy tratando de aprender los métodos funcionales adecuados para hacer las cosas.

¡Gracias de antemano por cualquier ayuda!

+0

Creo que la pregunta real es, "cómo asignar de [Y (x1, x2), Y (x3, x4)] a [x1, x2, x3, x4]? " que a veces se llama "flatMap". –

+0

@pst suponiendo que no hay forma de obtener una sola lista sin el uso de var, entonces sí, esa es la pregunta. – Kareem

+0

La 'var'/mutación es ortogonal en este caso. –

Respuesta

9

Para tales casos, hay foldLeft:

(notifications foldLeft Set.empty[Id]) { (set, notification) => 
    set ++ Seq(notification.fromId, notification.ownerId) 
} 

o en forma corta:

(Set.empty[Id] /: notifications) { (set, notification) => 
    set ++ Seq(notification.fromId, notification.ownerId) 
} 

Un conjunto no contiene duplicados. Después del doblez, puede convertir el conjunto a otra colección si lo desea.

+0

Esto parece prometedor. Disculpe mi ignorancia, pero ¿cuál es el /:? – Kareem

+0

Esa es la forma abreviada de foldLeft. Actualicé mi respuesta para mencionar esto. – sschaef

+0

Gracias a todos por su consejo. Esta solución parece ser la forma más directa de obtener una lista no pareada de múltiples campos de una Lista de objetos. – Kareem

4

Claro, en lugar de sólo produciendo el fromId, dió una tupla

val idPairs:List[(String, String)] = for(notification <- notifications) yield(notification.fromId, notification.objectOwnerId) 
+0

Lo que pasa es que no quiero emparejarlos. Quiero usar la lista de identificadores distintos para hacer una consulta de db y crear un Mapa desde el id hasta el objeto Usuario. ¿No hay forma de crear una lista sin usar una tupla? – Kareem

+1

@Kareem Puede simplemente llamar a idPairs.unzip() para convertir idPairs en dos listas separadas. – Destin

+0

Pero luego tengo que fusionarlos después. Definitivamente funcionaría, pero parece una rotonda. Obviamente estoy chiflado, pero solo curiosidad de la mejor manera. – Kareem

0

Estoy de acuerdo con la solución de Dave, pero otro enfoque es doblar la lista, produciendo su mapa de identificación al objeto del usuario sobre la marcha. La función para aplicar En la tapa consultará el archivo db para ambos usuarios y los agregará al mapa que se está acumulando.

+0

Eso también funcionaría, pero luego tienes una consulta db para cada usuario distinto. Parece que siempre hay una compensación. Mediante la recopilación de la lista que puedo consultar la base de datos una vez y asignarla: usuarios Val: Mapa [id, usuario] = User.findAllById (IDS) .map (u => u.id -> u) .toMap [Id, usuario] – Kareem

+0

La función de plegado puede probar el mapa que se ha construido hasta ahora y solo obtener el usuario de la base de datos si aún no están en el mapa. –

+0

Agregué mi comentario rápidamente. Sí, si quieres una única consulta masiva, debes hacer lo que dices. –

1

Bueno, aquí está mi respuesta a la siguiente:

cómo asignar de [Y (x1, x2), Y (x3, x4)] a [x1, x2, x3, x4]?

Uso flatMap (ver Collection.Traversable, pero tenga en cuenta que en realidad es la primera definido más arriba).

case class Y(a: Int, b: Int) 
var in = List(Y(1,2), Y(3,4)) 
var out = in.flatMap(x => List(x.a, x.b)) 

> defined class Y 
> in: List[Y] = List(Y(1,2), Y(3,4)) 
> out: List[Int] = List(1, 2, 3, 4) 

Además, dado que for..yield is filter, map and flatMap in one (pero también ver "sugar for flatMap?" que señala que esto no es tan eficiente como podría ser: no es un extra map):

var out = for { i <- in; x <- Seq(i.a, i.b) } yield x 

probable selección lo haría una de las otras respuestas, sin embargo, ya que esto no aborda directamente el problema final que se está resolviendo.

Happy coding.

5
val userIds = for { 
    notification <- notifications 
    id <- List(notification.fromId, notification.objectOwnerId) 
} yield id 

Aplicar distinct después si es necesario. Si la identificación solo se puede duplicar en una sola notificación, puede aplicar distinct en el segundo generador.

1

También puede utilizar la corriente para transformar los pares en una corriente de artículos individuales:

def toStream(xs: Iterable[Y]): Stream[Int] = { 
    xs match { 
    case Y(a, b) :: t => a #:: b #:: toStream(t) 
    case _ => Stream.empty 
    } 
} 

Pero como pst dijo, esto no soluciona el problema final de conseguir los valores distintos, pero una vez que haya la corriente es trivial:

val result = toStream(ys).toList.removeDuplicates 

O una ligera modificación a las sugerencias anteriores para utilizar aplanar - añadir una función que convierte una Y en una lista:

def yToList(y: Y) = List(y.a, y.b) 

entonces usted puede hacer:

val ys = List(Y(1, 2), Y(3, 4)) 
(ys map yToList flatten).removeDuplicates 
-1

¿Qué pasa sencilla map? AFAIK for yield se convierte a series de flatMap y map de todos modos. Su problema podría resolverse simplemente como sigue:

notifications.map(n => (n.fromId, n.objectOwnerId)).distinct 
Cuestiones relacionadas