collect
を1回呼び出して2つの新しいリストを作成することはできますか?そうでない場合、partition
を使用してこれを行うにはどうすればよいですか?
collect
( TraversableLike で定義され、すべてのサブクラスで使用可能)は、コレクションとPartialFunction
で機能します。また、中かっこ内で定義された一連のcase句が部分関数であることもあります( Scala Language Specification[warning-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
の最後のdouble値によりMatchError
が発生します。 collect
を使用すると、これと、PartialFunctionが定義されていない他の値が破棄されます。
考えられる用途の1つは、リストの要素に異なるロジックを適用することです。
var strings = List.empty[String]
var ints = List.empty[Int]
mixedList collect {
case s: String => strings :+= s
case i: Int => ints :+= i
}
これは単なる例にすぎませんが、このような可変変数の使用は多くの人が戦争犯罪であると見なしているので、実行しないでください!
muchより良い解決策は、collectを2回使用することです。
val strings = mixedList collect { case s: String => s }
val ints = mixedList collect { case i: Int => i }
または、リストに2種類の値しか含まれていないことがわかっている場合は、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
2つの方法がここでどのように役立つかをまとめてください。
可変リストを使用せずに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
を実際に2つのシーケンスにすることが可能ですが、どのシーケンスがアイテムであるかを知る方法はありません。部分関数は「はい、私は属している」または「いいえ、私は属していない」としか言うことができないためです。
リストをさまざまな部分に分割する複数の条件が必要な場合はどうしますか? 1つの方法は、インジケーター関数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
の複数のコピーについてはおそらく気にしません)。
これを使用します。 1つの良い点は、1つの反復でパーティション化とマッピングを組み合わせていることです。 1つの欠点は、多数の一時オブジェクト(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)
開始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)
ここでは、この基本的な問題に対する満足のいく解決策を見つけることができませんでした。 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])
このような何かが役立ちます
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")
利点は、Scala 2.12
不利益;コレクションが2回実行され、CanBuildFrom
のサポートがありません