将 Seq[Either[String, Int]] 转换为 (Seq[String], Seq[Int]) 的高效 and/or 惯用方法
Efficient and/or idiomatic way to turn Seq[Either[String, Int]] to (Seq[String], Seq[Int])
稍微简化一下,我的问题来自一个字符串列表 input
,我想用返回 Either[String,Int]
的函数 parse
解析它。
然后是 list.map(parse)
returns Either
的列表。程序的下一步是格式化一条错误消息,总结所有错误 或 通过已解析整数列表。
让我们调用我正在寻找的解决方案 partitionEithers
。
通话中
partitionEithers(List(Left("foo"), Right(1), Left("bar")))
会给
(List("foo", "bar"),List(1))
最好在标准库中找到类似的东西。如果没有某种干净、惯用和高效的解决方案,那将是最好的。我也可以将某种有效的实用程序函数粘贴到我的项目中。
我在these 3 earlier questions之间很困惑。据我所知,这些问题都不符合我的情况,但那里的一些答案似乎包含对这个问题的有效答案。
这是模仿 Scala 集合内部结构样式的命令式实现。
我想知道是否应该有这样的东西,因为至少我 运行 不时地进入这个。
import collection._
import generic._
def partitionEithers[L, R, E, I, CL, CR]
(lrs: I)
(implicit evI: I <:< GenTraversableOnce[E],
evE: E <:< Either[L, R],
cbfl: CanBuildFrom[I, L, CL],
cbfr: CanBuildFrom[I, R, CR])
: (CL, CR) = {
val ls = cbfl()
val rs = cbfr()
ls.sizeHint(lrs.size)
rs.sizeHint(lrs.size)
lrs.foreach { e => evE(e) match {
case Left(l) => ls += l
case Right(r) => rs += r
} }
(ls.result(), rs.result())
}
partitionEithers(List(Left("foo"), Right(1), Left("bar"))) == (List("foo", "bar"), List(1))
partitionEithers(Set(Left("foo"), Right(1), Left("bar"), Right(1))) == (Set("foo", "bar"), Set(1))
Scala 集合提供 partition
函数:
val eithers: List[Either[String, Int]] = List(Left("foo"), Right(1), Left("bar"))
eithers.partition(_.isLeft) match {
case (leftList, rightList) =>
(leftList.map(_.left.get), rightList.map(_.right.get))
}
=> res0: (List[String], List[Int]) = (List(foo, bar),List(1))
更新
如果您想将它包装在一个(甚至可能在某种程度上更安全的)泛型函数中:
def partitionEither[Left : ClassTag, Right : ClassTag](in: List[Either[Left, Right]]): (List[Left], List[Right]) =
in.partition(_.isLeft) match {
case (leftList, rightList) =>
(leftList.collect { case Left(l: Left) => l }, rightList.collect { case Right(r: Right) => r })
}
你可以使用 foldLeft。
def f(s: Seq[Either[String, Int]]): (Seq[String], Seq[Int]) = {
s.foldRight((Seq[String](), Seq[Int]())) { case (c, r) =>
c match {
case Left(le) => (le +: r._1, r._2)
case Right(ri) => (r._1 , ri +: r._2)
}
}
}
val eithers: List[Either[String, Int]] = List(Left("foo"), Right(1), Left("bar"))
scala> f(eithers)
res0: (Seq[String], Seq[Int]) = (List(foo, bar),List(1))
我真的不明白其他答案的扭曲程度。所以这是一个班轮:
scala> val es:List[Either[Int,String]] =
List(Left(1),Left(2),Right("A"),Right("B"),Left(3),Right("C"))
es: List[Either[Int,String]] = List(Left(1), Left(2), Right(A), Right(B), Left(3), Right(C))
scala> es.foldRight( (List[Int](), List[String]()) ) {
case ( e, (ls, rs) ) => e.fold( l => ( l :: ls, rs), r => ( ls, r :: rs ) )
}
res5: (List[Int], List[String]) = (List(1, 2, 3),List(A, B, C))
您可以使用来自 MonadPlus
(scalaz) 或 MonadCombine
(cats) 的 separate
:
import scala.util.{Either, Left, Right}
import scalaz.std.list._
import scalaz.std.either._
import scalaz.syntax.monadPlus._
val l: List[Either[String, Int]] = List(Right(1), Left("error"), Right(2))
l.separate
// (List[String], List[Int]) = (List(error),List(1, 2))
稍微简化一下,我的问题来自一个字符串列表 input
,我想用返回 Either[String,Int]
的函数 parse
解析它。
然后是 list.map(parse)
returns Either
的列表。程序的下一步是格式化一条错误消息,总结所有错误 或 通过已解析整数列表。
让我们调用我正在寻找的解决方案 partitionEithers
。
通话中
partitionEithers(List(Left("foo"), Right(1), Left("bar")))
会给
(List("foo", "bar"),List(1))
最好在标准库中找到类似的东西。如果没有某种干净、惯用和高效的解决方案,那将是最好的。我也可以将某种有效的实用程序函数粘贴到我的项目中。
我在these 3 earlier questions之间很困惑。据我所知,这些问题都不符合我的情况,但那里的一些答案似乎包含对这个问题的有效答案。
这是模仿 Scala 集合内部结构样式的命令式实现。
我想知道是否应该有这样的东西,因为至少我 运行 不时地进入这个。
import collection._
import generic._
def partitionEithers[L, R, E, I, CL, CR]
(lrs: I)
(implicit evI: I <:< GenTraversableOnce[E],
evE: E <:< Either[L, R],
cbfl: CanBuildFrom[I, L, CL],
cbfr: CanBuildFrom[I, R, CR])
: (CL, CR) = {
val ls = cbfl()
val rs = cbfr()
ls.sizeHint(lrs.size)
rs.sizeHint(lrs.size)
lrs.foreach { e => evE(e) match {
case Left(l) => ls += l
case Right(r) => rs += r
} }
(ls.result(), rs.result())
}
partitionEithers(List(Left("foo"), Right(1), Left("bar"))) == (List("foo", "bar"), List(1))
partitionEithers(Set(Left("foo"), Right(1), Left("bar"), Right(1))) == (Set("foo", "bar"), Set(1))
Scala 集合提供 partition
函数:
val eithers: List[Either[String, Int]] = List(Left("foo"), Right(1), Left("bar"))
eithers.partition(_.isLeft) match {
case (leftList, rightList) =>
(leftList.map(_.left.get), rightList.map(_.right.get))
}
=> res0: (List[String], List[Int]) = (List(foo, bar),List(1))
更新
如果您想将它包装在一个(甚至可能在某种程度上更安全的)泛型函数中:
def partitionEither[Left : ClassTag, Right : ClassTag](in: List[Either[Left, Right]]): (List[Left], List[Right]) =
in.partition(_.isLeft) match {
case (leftList, rightList) =>
(leftList.collect { case Left(l: Left) => l }, rightList.collect { case Right(r: Right) => r })
}
你可以使用 foldLeft。
def f(s: Seq[Either[String, Int]]): (Seq[String], Seq[Int]) = {
s.foldRight((Seq[String](), Seq[Int]())) { case (c, r) =>
c match {
case Left(le) => (le +: r._1, r._2)
case Right(ri) => (r._1 , ri +: r._2)
}
}
}
val eithers: List[Either[String, Int]] = List(Left("foo"), Right(1), Left("bar"))
scala> f(eithers)
res0: (Seq[String], Seq[Int]) = (List(foo, bar),List(1))
我真的不明白其他答案的扭曲程度。所以这是一个班轮:
scala> val es:List[Either[Int,String]] =
List(Left(1),Left(2),Right("A"),Right("B"),Left(3),Right("C"))
es: List[Either[Int,String]] = List(Left(1), Left(2), Right(A), Right(B), Left(3), Right(C))
scala> es.foldRight( (List[Int](), List[String]()) ) {
case ( e, (ls, rs) ) => e.fold( l => ( l :: ls, rs), r => ( ls, r :: rs ) )
}
res5: (List[Int], List[String]) = (List(1, 2, 3),List(A, B, C))
您可以使用来自 MonadPlus
(scalaz) 或 MonadCombine
(cats) 的 separate
:
import scala.util.{Either, Left, Right}
import scalaz.std.list._
import scalaz.std.either._
import scalaz.syntax.monadPlus._
val l: List[Either[String, Int]] = List(Right(1), Left("error"), Right(2))
l.separate
// (List[String], List[Int]) = (List(error),List(1, 2))