Scala分区/收集用法

是否可以使用一次拨打
collect
来制作2个新列表?如果没有,我怎么能用
partition
做到这一点?     
已邀请:
collect
(在TraversableLike上定义并在所有子类中可用)与集合和
PartialFunction
一起使用。它也恰好在大括号内定义的一组case子句是一个部分函数(参见Scala语言规范的8.5节[警告 - PDF]) 与异常处理一样:
try {
  ... do something risky ...
} catch {
  //The contents of this catch block are a partial function
  case e: IOException => ...
  case e: OtherException => ...
}
定义一个只接受给定类型的某些值的函数是一种方便的方法。 考虑在混合值列表中使用它:
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
因为它没有为
Any
类型的所有可能输入定义(即
List
的类型)和
String
,因为这是所有条款返回的内容。 如果您尝试使用
map
而不是
collect
,则
mixedList
末尾的双精度值将导致
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
}
虽然这只是一个例子,但是使用这样的可变变量被许多人视为战争罪 - 所以请不要这样做! 更好的解决方案是使用两次收集:
val strings = mixedList collect { case s: String => s }
val ints = mixedList collect { case i: Int => i }
或者,如果您确定该列表仅包含两种类型的值,则可以使用
partition
,它将集合拆分为值,具体取决于它们是否与某些谓词匹配:
//if the list only contains Strings and Ints:
val (strings, ints) = mixedList partition { case s: String => true; case _ => false }
这里的问题是
strings
ints
都是
List[Any]
型,虽然你可以很容易地将它们强制回到更安全的东西(也许是通过使用
collect
......) 如果您已经拥有一个类型安全的集合并想要拆分元素的其他一些属性,那么事情对您来说更容易:
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
因此,如果你在默认模式下使用它,答案是否定的,肯定不是:你从中获得了一个序列。如果你按照
CanBuildFrom
Builder
,你会发现有可能使
That
实际上是两个序列,但它无法被告知一个项目应该进入哪个序列,因为部分函数只能说“是的,我属于“或”不,我不属于“。 那么,如果你想要有多个条件导致你的列表被拆分成一堆不同的部分,你会怎么做?一种方法是创建一个指示器功能
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)。不幸的是,如果你想忽略一些输入,你仍然必须将它们放在一个类中(例如,在这种情况下你可能不关心
None
的多个副本)。     
我用这个。关于它的一个好处是它在一次迭代中结合了分区和映射。一个缺点是它确实分配了一堆临时对象(
Either.Left
Either.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)
    
我在这里找不到令人满意的解决方案。 我不需要关于
collect
的讲座,也不在乎这是否是某人的家庭作业。另外,我不想要只适用于
List
的东西。 所以这是我的努力。高效且兼容任何
TraversableOnce
,甚至字符串:
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])
    
Scala 2.13
开始,大多数集合现在都提供了一个
partitionWith
方法,该方法根据返回
Right
Left
的函数对元素进行分区。 这允许我们根据类型(作为
collect
在分区列表中具有特定类型)或任何其他模式进行模式匹配:
 val (strings, ints) =
   List("a", 1, 2, "b", 19).partitionWith {
     case s: String => Left(s)
     case x: Int    => Right(x)
   }
 // strings: List[String] = List(a, b)
 // ints: List[Int] = List(1, 2, 19)
    

要回复问题请先登录注册