Как оставаться верным функциональному стилю в Scala для выражений - PullRequest
5 голосов
/ 31 декабря 2011

Я изо всех сил пытался найти способ придерживаться функционального стиля для выражений, когда мне нужно собрать несколько параметров объекта в Список.

В качестве примера, скажем, у меня есть объект Notification, который имеет и fromId (идентификатор пользователя, от которого получено уведомление), и objectOwnerId (идентификатор пользователя, создавшего исходный объект). Они могут отличаться в уведомлениях в стиле Facebook («X также прокомментировал сообщение Y»).

Я могу собрать userIds с выражением for, например, так:

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

однако говорят, что я хочу собрать и fromIds, и objectOwnerIds в один список, есть ли способ сделать это в одном выражении for без пользователя vars?

Я делал что-то подобное в прошлом:

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

но такое ощущение, что должен быть лучший способ. Использование var и необходимость вызывать Different после того, как я завершу сбор, уродливы. Я мог бы избежать отличия с помощью некоторых условных выражений, но я пытаюсь изучить правильные функциональные методы, чтобы что-то делать.

Заранее спасибо за любую помощь!

Ответы [ 7 ]

9 голосов
/ 31 декабря 2011

Для таких случаев есть foldLeft:

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

или в краткой форме:

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

Набор не содержит дубликатов. После сгиба вы можете преобразовать набор в другую коллекцию, если хотите.

5 голосов
/ 01 января 2012
val userIds = for { 
  notification <- notifications 
  id <- List(notification.fromId, notification.objectOwnerId)
} yield id

Применить distinct впоследствии, если требуется. Если идентификатор можно дублировать только в одном уведомлении, вместо него можно применить distinct ко второму генератору.

4 голосов
/ 31 декабря 2011

Конечно, вместо того, чтобы просто выдавать fromId, получим кортеж

val idPairs:List[(String, String)] = for(notification <- notifications) yield(notification.fromId, notification.objectOwnerId)
1 голос
/ 01 января 2012

Вы также можете использовать Stream для преобразования пар в поток отдельных элементов:

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

Но, как сказал pst, это не решит вашу последнюю проблему получения различных значений, но как только выесть поток, это тривиально:

val result = toStream(ys).toList.removeDuplicates

Или небольшое изменение к предыдущим предложениям использовать flatten - добавьте функцию, которая превращает Y в список:

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

Тогда вы можетеделать:

val ys = List(Y(1, 2), Y(3, 4))
(ys map yToList flatten).removeDuplicates
1 голос
/ 01 января 2012

Хорошо, вот мой ответ на следующий вопрос:

Как сопоставить [Y (x1, x2), Y (x3, x4)] с [x1, x2, x3, x4]?

Использовать flatMap (см. Collection.Traversable , но обратите внимание, что это на самом деле сначала определено выше).

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)

Кроме того, так как for..yield - это filter, map и flatMap в одном (но также можно увидеть «сахар для flatMap?» , который указывает, что это не так эффективно, как этоможет быть: есть дополнительный map):

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

Я бы, вероятно, выбрал один из других ответов , однако, поскольку это не решает проблему окончательно решаемой проблемы..

Счастливого кодирования.

0 голосов
/ 02 января 2012

А как насчет простого map?AFAIK for yield в любом случае конвертируется в серии flatMap и map.Ваша проблема может быть решена просто следующим образом:

notifications.map(n => (n.fromId, n.objectOwnerId)).distinct
0 голосов
/ 01 января 2012

Я согласен с решением Дейва, но другой подход заключается в том, чтобы свернуть список, создавая вашу карту id для объекта User по мере продвижения.Применяемая функция В сгибе запросит базу данных для обоих пользователей и добавит их к накапливаемой карте.

...