【问题标题】:Scala: Generalised method to find match and return match dependant values in collectionScala:在集合中查找匹配并返回匹配相关值的通用方法
【发布时间】:2012-08-29 22:22:28
【问题描述】:

我希望在 List 中找到匹配项并根据匹配项返回值。 CollectFirst 适用于匹配集合的元素,但在这种情况下,我想匹配元素的成员 swEl 而不是元素本身。

abstract class CanvNode (var swElI: Either[CSplit, VistaT])
{         
  private[this] var _swEl: Either[CSplit, VistaT] = swElI
  def member = _swEl
  def member_= (value: Either[CSplit, VistaT] ){ _swEl = value; attach}
  def attach: Unit
  attach

  def findVista(origV: VistaIn): Option[Tuple2[CanvNode,VistaT]] = member match
  {
    case Right(v) if (v == origV) => Option(this, v) 
    case _ => None
  }
}

def nodes(): List[CanvNode] = topNode :: splits.map(i => List(i.n1, i.n2)).flatten

//Is there a better way of implementing this? 
val temp: Option[Tuple2[CanvNode, VistaT]] = 
  nodes.map(i => i.findVista(origV)).collectFirst{case Some (r) => r}

我需要一个视图,还是 collectFirst 方法确保仅根据需要创建集合?

令我震惊的是,这一定是一个相当普遍的模式。另一个例子是,如果一个人有一个主 List 元素的 List 成员,并且想要返回第四个元素(如果有的话)。 有我可以调用的标准方法吗?如果我不能创建以下内容:

implicit class TraversableOnceRichClass[A](n: TraversableOnce[A])
{
  def findSome[T](f: (A) => Option[T]) = n.map(f(_)).collectFirst{case Some (r) => r}
}    

然后我可以将上面的替换为:

val temp: Option[Tuple2[CanvNode, VistaT]] = 
  nodes.findSome(i => i.findVista(origV))

这使用 2.10 中的隐式类,用于 2.10 之前的使用:

class TraversableOnceRichClass[A](n: TraversableOnce[A])
{
  def findSome[T](f: (A) => Option[T]) = n.map(f(_)).collectFirst{case Some (r) => r}
}

implicit final def TraversableOnceRichClass[A](n: List[A]):
  TraversableOnceRichClass[A] = new TraversableOnceRichClass(n)

【问题讨论】:

    标签: scala scala-collections


    【解决方案1】:

    作为介绍性侧节点:您描述的操作(如果存在则返回第一个Some,否则返回None)是“第一个”幺半群实例下Options 集合的总和为Option。例如,Scalaz 6:

    scala> Stream(None, None, Some("a"), None, Some("b")).map(_.fst).asMA.sum
    res0: scalaz.FirstOption[java.lang.String] = Some(a)
    

    或者你可以把这样的东西放在范围内:

    implicit def optionFirstMonoid[A] = new Monoid[Option[A]] {
      val zero = None
      def append(a: Option[A], b: => Option[A]) = a orElse b
    }
    

    并跳过.map(_.fst) 部分。不幸的是,这些方法在 Scalaz 中都没有适当的惰性,因此将评估整个流(与 Haskell 不同,例如 mconcat . map (First . Just) $ [1..] 就可以了)。


    编辑:作为此旁注的旁注:显然 Scalaz 确实提供了一个适当惰性的sumr(对于流-这些方法都不适用于视图)。所以例如你可以这样写:

    Stream.from(1).map(Some(_).fst).sumr
    

    不要像在 Haskell 版本中那样永远等待您的回答。


    但假设我们坚持使用标准库,而不是这样:

    n.map(f(_)).collectFirst{ case Some(r) => r }
    

    我会写以下内容,它或多或少是等价的,并且可以说更惯用:

    n.flatMap(f(_)).headOption
    

    例如,假设我们有一个整数列表。

    val xs = List(1, 2, 3, 4, 5)
    

    我们可以让这个惰性函数和map 成为一个带有副作用的函数,以便在访问它的元素时向我们显示:

    val ys = xs.view.map { i => println(i); i }
    

    现在我们可以flatMap 一个Option-returning 函数覆盖结果集合,并使用headOption (安全地)返回第一个元素(如果存在):

    scala> ys.flatMap(i => if (i > 2) Some(i.toString) else None).headOption
    1
    2
    3
    res0: Option[java.lang.String] = Some(3)
    

    很明显,当我们按需要达到一个非空值时,它就会停止。是的,如果你的原始集合是严格的,你肯定需要一个视图,否则headOption(或collectFirst)无法返回并阻止它之前的flatMap(或map)。

    在您的情况下,您可以跳过 findVista 并使用以下内容更加简洁:

    val temp = nodes.view.flatMap(
      node => node.right.toOption.filter(_ == origV).map(node -> _)
    ).headOption
    

    当然,您是否觉得这更清晰或只是一团糟,这是一个品味问题。

    【讨论】:

    • headOption 来自 TraversableLike。拉皮条 TraversableLike 比拉皮条 TraversableOnce 更棘手,所以在检查您的答案之前,我必须掌握它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-22
    • 1970-01-01
    相关资源
    最近更新 更多