Scala分区/收集使用

是否可以使用一个电话来collect 2个新的名单? 如果没有,我怎样才能使用partition

collect (在TraversableLike上定义,并在所有子类中可用)与集合和PartialFunction 。 同样,大括号内的大量case子句也是一个部分函数(参见Scala语言规范第8.5节[warning – PDF]

和exception处理一样:

 try { ... do something risky ... } catch { //The contents of this catch block are a partial function case e: IOException => ... case e: OtherException => ... } 

定义一个只接受给定types的值的函数是一个方便的方法。

考虑在混合值列表中使用它:

 val mixedList = List("a", 1, 2, "b", 19, 42.0) //this is a List[Any] val results = mixedList collect { case s: String => "String:" + s case i: Int => "Int:" + i.toString } 

collect方法的参数是PartialFunction[Any,String]PartialFunction因为它没有被定义为Anytypes的所有可能的input(这是List的types)和String因为这是所有的子句返回的。

如果您尝试使用map而不是collect ,则mixedList结尾的double值会导致MatchError 。 使用collect只是丢弃这个以及任何其他未定义PartialFunction的值。

一种可能的用法是将不同的逻辑应用于列表的元素:

 var strings = List.empty[String] var ints = List.empty[Int] mixedList collect { case s: String => strings :+= s case i: Int => ints :+= i } 

虽然这只是一个例子,但使用这样的可变variables被许多人认为是战争罪 – 所以请不要这样做!

一个更好的解决scheme是使用两次收集:

 val strings = mixedList collect { case s: String => s } val ints = mixedList collect { case i: Int => i } 

或者,如果您确实知道列表只包含两种types的值,则可以使用partition ,根据partition是否与谓词相匹配将partition分为不同的值:

 //if the list only contains Strings and Ints: val (strings, ints) = mixedList partition { case s: String => true; case _ => false } 

这里的结果是, stringsints都是List[Any]types,尽pipe你可以很容易地将它们强制回到更安全的types(也许通过使用collect …)

如果你已经有了一个types安全的集合,并且想要在元素的某个其他属性上分割,那么事情对你来说会更容易些:

 val intList = List(2,7,9,1,6,5,8,2,4,6,2,9,8) val (big,small) = intList partition (_ > 5) //big and small are both now List[Int]s 

希望总结一下这两种方法如何帮助你在这里!

不知道如何使用collect而不使用可变列表,但partition也可以使用模式匹配(只是稍微冗长)

 List("a", 1, 2, "b", 19).partition { case s:String => true case _ => false } 

正常使用的collect ,例如Seq的签名是

 collect[B](pf: PartialFunction[A,B]): Seq[B] 

这真的是一个特例

 collect[B, That](pf: PartialFunction[A,B])( implicit bf: CanBuildFrom[Seq[A], B, That] ): That 

所以如果你在默认模式下使用它,答案是否定的,肯定不是:你从中得到一个正确的序列。 如果你通过Builder关注CanBuildFrom ,你会发现有可能让That实际上是两个序列,但是它没有办法告诉项目应该进入哪个序列,因为部分函数只能说“是的,我属于“或”不,我不属于“。

那么,如果你想要有多个条件导致你的列表被分成一堆不同的部分,你该怎么做? 一种方法是创build一个指标函数A => Int ,其中A映射到一个编号的类,然后使用groupBy 。 例如:

 def optionClass(a: Any) = a match { case None => 0 case Some(x) => 1 case _ => 2 } scala> List(None,3,Some(2),5,None).groupBy(optionClass) res11: scala.collection.immutable.Map[Int,List[Any]] = Map((2,List(3, 5)), (1,List(Some(2))), (0,List(None, None))) 

现在你可以按照类来查找你的子列表(在这个例子中是0,1和2)。 不幸的是,如果你想忽略一些input,你仍然必须把它们放在一个类中(例如,在这种情况下你可能不关心None的多个副本)。

我用这个 关于它的一个好处是它在一次迭代中结合了分区和映射。 一个缺点是它确实分配了一堆临时对象( Either.LeftEither.Right实例)

 /** * Splits the input list into a list of B's and a list of C's, depending on which type of value the mapper function returns. */ def mapSplit[A,B,C](in: List[A])(mapper: (A) => Either[B,C]): (List[B], List[C]) = { @tailrec def mapSplit0(in: List[A], bs: List[B], cs: List[C]): (List[B], List[C]) = { in match { case a :: as => mapper(a) match { case Left(b) => mapSplit0(as, b :: bs, cs ) case Right(c) => mapSplit0(as, bs, c :: cs) } case Nil => (bs.reverse, cs.reverse) } } mapSplit0(in, Nil, Nil) } val got = mapSplit(List(1,2,3,4,5)) { case x if x % 2 == 0 => Left(x) case y => Right(y.toString * y) } assertEquals((List(2,4),List("1","333","55555")), got) 

我找不到满意的解决scheme来解决这个基本问题。 我不需要collect讲座,也不必关心这是否是某人的功课。 另外,我不希望某些仅用于List东西。

所以这里是我的刺伤。 高效且兼容任何TraversableOnce ,甚至string:

 implicit class TraversableOnceHelper[A,Repr](private val repr: Repr)(implicit isTrav: Repr => TraversableOnce[A]) { def collectPartition[B,Left](pf: PartialFunction[A, B]) (implicit bfLeft: CanBuildFrom[Repr, B, Left], bfRight: CanBuildFrom[Repr, A, Repr]): (Left, Repr) = { val left = bfLeft(repr) val right = bfRight(repr) val it = repr.toIterator while (it.hasNext) { val next = it.next if (!pf.runWith(left += _)(next)) right += next } left.result -> right.result } def mapSplit[B,C,Left,Right](f: A => Either[B,C]) (implicit bfLeft: CanBuildFrom[Repr, B, Left], bfRight: CanBuildFrom[Repr, C, Right]): (Left, Right) = { val left = bfLeft(repr) val right = bfRight(repr) val it = repr.toIterator while (it.hasNext) { f(it.next) match { case Left(next) => left += next case Right(next) => right += next } } left.result -> right.result } } 

示例用法:

 val (syms, ints) = Seq(Left('ok), Right(42), Right(666), Left('ko), Right(-1)) mapSplit identity val ctx = Map('a -> 1, 'b -> 2) map {case(n,v) => n->(n,v)} val (bound, unbound) = Vector('a, 'a, 'c, 'b) collectPartition ctx println(bound: Vector[(Symbol, Int)], unbound: Vector[Symbol])