Как сгруппировать, отбрасывая некоторые элементы коллекции - PullRequest
8 голосов
/ 15 мая 2011

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

Вот конкретный пример:

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

В настоящее время мое решение эквивалентно:

val names = List("ns1.foo", "ns2.bar", "ns2.baz", "froznit")
val namespaces = List("ns1", "ns2")

def findNamespace(n: String): Option[String] = namespaces.find(n.startsWith)

val groupedNames = names.groupBy(findNamespace).collect {
  case (Some(ns), name) => (ns, name)
}
// Map((ns1,List(ns1.foo)), (ns2,List(ns2.bar, ns2.baz)))

Мое беспокойство по поводу этого решения состоит в том, что, используя names.groupBy(findNamespace), я создаю промежуточную Карту, которая содержит все имена, которые меня не интересуют, под ключом None.Если количество имен, от которых я отказываюсь, становится большим, это решение становится менее привлекательным.

Моя попытка избежать этого - нечто вроде крушения поезда, хотя:

val groupedNames =
  names.
    map(n => (findNamespace(n), n)).
    collect({ case (Some(ns), n) => (ns, n) }).
    groupBy(_._1).
    map({ case (ns, names) => (ns, names.map(_._2)) })

Если бы вырешить это более умным способом, что бы это было?


Редактировать: в идеале решение должно вызывать findNamespace(name) только один раз для каждого имени и строить Карту, используя только значения Option[String],без вызова отдельного предиката hasNamespace(name).

Ответы [ 5 ]

6 голосов
/ 16 мая 2011

Один из способов избежать сбора сброшенных имен - использовать flatMap:

names.flatMap(n => findNamespace(n) map (ns => (ns, n)))
   .groupBy(_._1) 
   .map { case (ns, pairs) => (ns, pairs map (_._2)) }

Вы можете достичь того же с помощью понимания:

(for (n <- names; ns <- findNamespace(n)) yield (ns, n))
   .groupBy(_._1)
   .map { case (ns, pairs) => (ns, pairs map (_._2)) }
4 голосов
/ 15 мая 2011

Я не уверен, насколько эффективен toMap, но использование опции для полного понимания по крайней мере позволяет избежать сбора None результатов:

scala> val m = (for { n <- names; ns <- findNamespace(n) } yield n -> ns).toMap
m: scala.collection.immutable.Map[java.lang.String,String] = Map(ns1.foo -> ns1, ns2.bar -> ns2, ns2.baz -> ns2)

scala> val groupedNames = m.keys.groupBy(m)
groupedNames: scala.collection.immutable.Map[String,Iterable[java.lang.String]] = Map(ns1 -> Set(ns1.foo), ns2 -> Set(ns2.bar, ns2.baz))
4 голосов
/ 16 мая 2011

Вы можете использовать foldLeft:

val gn = names.foldLeft(Map[String, List[String]]()){ case (acc, name) =>
  findNamespace(name) match { 
    case Some(ns) => acc + (ns -> (name :: acc.get(ns).getOrElse(Nil)))
    case _ => acc
  }
}

При условии, что порядок не имеет значения, в противном случае вы можете изменить значения с помощью gn.mapValues(_.reverse).

2 голосов
/ 16 мая 2011

Я предложил вариант ответа huynhjl, заменив match на map:

val gn = (Map[String, List[String]]() /: names) { (acc, name) =>
  acc ++ findNamespace(name).map(ns => ns -> (name :: acc.getOrElse(ns, Nil)))
}
0 голосов
/ 15 мая 2011

Я бы предложил метод «сначала фильтруй, потом группуй», например:

scala> val names = List("ns1.foo", "ns2.bar", "ns2.baz", "froznit")
names: List[java.lang.String] = List(ns1.foo, ns2.bar, ns2.baz, froznit)

scala> val namespaces = List("ns1", "ns2")
namespaces: List[java.lang.String] = List(ns1, ns2)

scala> names filter { n => namespaces exists { n startsWith _ } } groupBy { _ take 3 }
res1: scala.collection.immutable.Map[String,List[java.lang.String]] = Map(ns1 -> List(ns1.foo), ns2 -> List(ns2.bar, ns2.baz))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...