38

1 回の呼び出しでcollect2 つの新しいリストを作成することはできますか? そうでない場合、どうすればこれを行うことができpartitionますか?

4

8 に答える 8

81

collect( TraversableLikeで定義され、すべてのサブクラスで使用可能) は、コレクションとPartialFunction. また、中括弧内で定義された一連の case 節が部分関数であることも偶然です ( Scala 言語仕様 [警告 - 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
}

メソッドへの引数collectPartialFunction[Any,String]. タイプ(のタイプ)PartialFunctionのすべての可能な入力に対して定義されているわけではなく、それがすべての句が返すものであるためです。AnyListString

mapの代わりに使用しようとするとcollect、末尾の double 値mixedListによって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
}

これは単なる例ですが、このように変更可能な変数を使用することは、戦争犯罪であると多くの人に考えられています。

はるかに優れた解決策は、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 }

ここでの問題は、 と の両方が type であるという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 つの方法がどのように役立つかを要約していただければ幸いです。

于 2011-01-24T16:44:49.133 に答える
7

collect変更可能なリストを使用せずにそれを行う方法はわかりませんが、partitionパターン マッチングも使用できます (もう少し冗長です)。

List("a", 1, 2, "b", 19).partition { 
  case s:String => true
  case _ => false 
}
于 2011-01-24T16:12:06.890 に答える
6

collectたとえば、通常使用される on の署名はSeq

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

これは実際には特定のケースです

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

したがって、デフォルト モードで使用する場合、答えはノーです。を実行すると、実際には 2 つのシーケンスにすることが可能であることがわかりますがCanBuildFrom、部分関数は「はい、私は属しています」または「いいえ、私は属していません」。BuilderThat

では、リストが多数の異なる部分に分割される結果となる複数の条件が必要な場合はどうすればよいでしょうか? 1 つの方法は、指標関数 を作成A => IntA、番号付きクラスにマップしてから を使用すること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)。

于 2011-01-24T17:03:20.667 に答える
5

私はこれを使います。これの良い点の 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)
于 2011-01-25T01:24:26.880 に答える
5

からScala 2.13、ほとんどのコレクションに、またはpartitionMapを返す関数に基づいて要素を分割するメソッドが提供されるようになりました。RightLeft

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)
于 2018-10-07T09:37:42.043 に答える
1

ここでは、この基本的な問題に対する満足のいく解決策を見つけることができませんでした。講義は必要あり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])
于 2016-05-26T15:31:51.863 に答える
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 のものと似ています。

不利益; コレクションが 2 回実行され、次のサポートがありませんCanBuildFrom

于 2019-08-01T10:01:08.003 に答える