Scala分区/收集用法

Adr*_*ski 37 scala list collect

是否可以使用一个电话collect来制作2个新列表?如果没有,我该如何使用partition?

Kev*_*ght 78

collect(在TraversableLike上定义并在所有子类中可用)与集合和a一起使用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 => ...
}
Run Code Online (Sandbox Code Playgroud)

定义一个只接受给定类型的某些值的函数是一种方便的方法.

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

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
}
Run Code Online (Sandbox Code Playgroud)

collect方法的参数是a PartialFunction[Any,String]. PartialFunction因为没有为类型的所有可能输入定义Any(这是类型List),String因为这是所有子句返回的内容.

如果您尝试使用map而不是collect,则结尾处的double值mixedList会导致a 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
}
Run Code Online (Sandbox Code Playgroud)

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

一个多更好的解决方案是两次收集使用:

val strings = mixedList collect { case s: String => s }
val ints = mixedList collect { case i: Int => i }
Run Code Online (Sandbox Code Playgroud)

或者,如果您确定该列表仅包含两种类型的值,则可以使用partition,这会将集合拆分为值,具体取决于它们是否与某些谓词匹配:

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

这里美中不足的是,这两个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
Run Code Online (Sandbox Code Playgroud)

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

  • 非常好的解释,但我认为OP想要的是`collect`和`partition`的组合,它返回一个收集值列表的元组和所有其余值的列表.`def collectAndPartition [A,B](pf:PartialFunction [A,B]):( List [B],List [A])`.这可能是使用本机库函数最优雅地实现的,即在TraversableLike中的`collect`源中我们有`for(x < - this)if(pf.isDefinedAt(x))b + = pf(x)` ,可以简单地在结尾处添加一个'else a + = x`,其中`a`将是所有其余列表的构建器. (4认同)
  • 我知道OP需要什么,而且我也知道这是一个功课问题(最近有很多关于堆栈溢出的问题),所以我很乐意在没有实际解决的情况下提供大量理论.至于collectAndPartition,我已经写过了,虽然我将方法命名为"collat​​e".如果有人正在将scala教授到学生与CanBuildFrom合作的水平,那么我会非常惊讶,这超出了目前在生产中使用scala的大多数人. (3认同)
  • `collect`,就像`map`一样,收集一个集合并将其转换为另一个集合.它肯定有助于处理数据并使其更容易在后续操作中分离,但除非您依赖副作用(即可变变量),否则无法使用`collect`生成两个集合.这种类型的黑客行为导致代码更难以阅读,并且最好留在没有其他替代方案的时候,或者它提供重要的性能提升,这两者都不适用于此. (3认同)

Ada*_*ung 7

不知道如何在collect不使用可变列表的情况下完成它,但partition也可以使用模式匹配(只是更冗长一点)

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


Rex*_*err 6

的常使用的签名collect上说,Seq是

collect[B](pf: PartialFunction[A,B]): Seq[B]
Run Code Online (Sandbox Code Playgroud)

这真的是一个特例

collect[B, That](pf: PartialFunction[A,B])(
  implicit bf: CanBuildFrom[Seq[A], B, That]
): That
Run Code Online (Sandbox Code Playgroud)

因此,如果你在默认模式下使用它,答案是否定的,肯定不是:你从中获得了一个序列.如果你遵循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)))
Run Code Online (Sandbox Code Playgroud)

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


Ale*_*ise 5

我用这个.关于它的一个好处是它在一次迭代中结合了分区和映射.一个缺点是它确实分配了一堆临时对象(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)
Run Code Online (Sandbox Code Playgroud)


Xav*_*hot 5

从 开始Scala 2.13,大多数集合现在都提供了一种partitionMap方法,该方法根据返回 或 的函数对元素进行Right分区Left。

这允许我们基于类型(这使得collect分区列表中具有特定类型)或任何其他模式进行模式匹配:

val (strings, ints) =
  List("a", 1, 2, "b", 19).partitionMap {
    case s: String => Left(s)
    case x: Int    => Right(x)
  }
// strings: List[String] = List("a", "b")
// ints: List[Int] = List(1, 2, 19)
Run Code Online (Sandbox Code Playgroud)