是否可以使用一次collect
来拨打2个新列表?如果没有,我该如何使用partition
?
答案 0 :(得分:78)
collect
(在TraversableLike上定义,并在所有子类中提供)适用于集合和PartialFunction
。它也恰好在大括号内定义的一组case子句是一个部分函数(参见Scala Language Specification [warning - PDF] 的第8.5节)
与异常处理一样:
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
末尾的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
}
虽然这只是一个例子,但是许多人认为使用像这样的可变变量是一种战争罪 - 所以请不要这样做!
很多更好的解决方案是使用收集两次:
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
希望总结两种方法如何帮助你在这里!
答案 1 :(得分:7)
不确定如何使用collect
而不使用可变列表,但partition
也可以使用模式匹配(只是更冗长一点)
List("a", 1, 2, "b", 19).partition {
case s:String => true
case _ => false
}
答案 2 :(得分:6)
常用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
的多个副本)。
答案 3 :(得分: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)
答案 4 :(得分:2)
我在这里找不到令人满意的解决方案。
我不需要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])
示例用法:
http://localhost:8090/member/getAllMember
答案 5 :(得分:1)
从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)
答案 6 :(得分:0)
类似的事情可能会帮助
def partitionMap[IN, A, B](seq: Seq[IN])(function: IN => Either[A, B]): (Seq[A], Seq[B]) = {
val (eitherLeft, eitherRight) = seq.map(function).partition(_.isLeft)
eitherLeft.map(_.left.get) -> eitherRight.map(_.right.get)
}
称呼它
val seq: Seq[Any] = Seq(1, "A", 2, "B")
val (ints, strings) = CollectionUtils.partitionMap(seq) {
case int: Int => Left(int)
case str: String => Right(str)
}
ints shouldBe Seq(1, 2)
strings shouldBe Seq("A", "B")
Advantage是一个简单的API,与Scala 2.12中的API相似
缺点;集合运行了两次,并且缺少对CanBuildFrom
答案 7 :(得分:0)
为此,我个人将使用foldLeft或foldRight。与此处的其他一些答案相比,它具有几个优点。不使用var,所以这是一个纯函数(如果您关心这种类型的事情)。仅遍历该列表。不会创建任何无关紧要的对象。
折叠的想法是将列表转换为单一类型。但是,没有什么能阻止我们使这种单一类型成为任意数量的列表的元组。
此示例将一个列表转换为三个不同的列表:
val list: List[Any] = List(1,"two", 3, "four", 5.5)
// Start with 3 empty lists and prepend to them each time we find a new value
list.foldRight( (List.empty[Int]), List.empty[String], List.empty[Double]) {
(nextItem, newCollection) => {
nextItem match {
case i: Int => newCollection.copy(_1 = i :: newCollection._1)
case s: String => newCollection.copy(_2 = s :: newCollection._2)
case f: Double => newCollection.copy(_3 = f :: newCollection._3)
case _ => newCollection
}
}
}