如何在丢弃集合的某些元素时进行groupBy

时间:2011-05-15 17:27:31

标签: scala

我想基于Option类型的鉴别器将序列分组到序列图中,类似于groupBy方法的结果,但其中的值导致{{1}被丢弃了。或者,也许,通过None鉴别器进行分组并丢弃未定义部分功能的那些。

这是一个具体的例子:

我有一组名称和一组命名空间。一些(但不是全部)名称属于有效的名称空间,我想将那些名称分组到一个Map中,丢弃那些没有名称的名称。

目前我的解决方案相当于:

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]谓词。

5 个答案:

答案 0 :(得分:6)

避免收集丢弃名称的一种方法是使用flatMap

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

你可以用for-comprehension来实现同样的目标:

(for (n <- names; ns <- findNamespace(n)) yield (ns, n))
   .groupBy(_._1)
   .map { case (ns, pairs) => (ns, pairs map (_._2)) }

答案 1 :(得分:4)

我不确定toMap的效率如何,但将该选项置于for-comprehension中至少可以避免收集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))

答案 2 :(得分:4)

您可以使用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)反转值。

答案 3 :(得分:2)

我想出了一个关于huynhjl答案的变体,将match替换为map

val gn = (Map[String, List[String]]() /: names) { (acc, name) =>
  acc ++ findNamespace(name).map(ns => ns -> (name :: acc.getOrElse(ns, Nil)))
}

答案 4 :(得分:0)

我建议“首先过滤,然后使用groupBy”方法,如下所示:

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))