如何简化期权处理

How to simplify Option processing

我有一个包含数据的源列表,我想将其分类为 3 个列表,这些列表包含经过清理的数据、更新的数据和记录的数据。来自源的所有元素都必须在记录列表中。所有具有非空值的元素都必须在清理列表中,如果更新了标志,该元素也必须出现在更新的文件夹中。

根据例子:

你能帮我简化一下写的代码吗:

case class Source(value: Option[String], date: String, isUpdate: Boolean)

case class Cleaned(value: String, date: String)
case class Logged(value: String, date: String)
case class Updated(value: String, date: String)

val sources: List[Source] = List(
    Source(Option.empty[String], "2020-01-09", false), // 1
    Source(Option.empty[String], "2020-01-09", true), // 2
    Source(Option("Some Data"), "2020-01-09", false), // 3
    Source(Option("Some Data 2"), "2020-01-09", true) // 4
)


val target = sources.foldLeft(List.empty[Updated], List.empty[Logged], List.empty[Cleaned]){
    case ((updated, logged, cleaned), el) if el.isUpdate =>
        (updated  :+ Updated(el.value.getOrElse(""), el.date),
            logged :+ Logged(el.value.getOrElse(""), el.date),
            el.value.fold(cleaned)(d => cleaned :+ Cleaned(d, el.date))
        )

    case ((updated, logged, cleaned), el) =>
        (updated,
            logged :+ Logged(el.value.getOrElse(""), el.date),
            el.value.fold(cleaned)(d => cleaned :+ Cleaned(d, el.date))
        )
}

我个人认为这种问题最好用递归来解决。
代码更大,但恕我直言,更容易理解和更改。

type Result = (List[Logged], List[Cleaned], List[Updated])

def process(data: List[Source]): Result = {
  @annotation.tailrec
  def loop(remaining: List[Source], logged: List[Logged], cleaned: List[Cleaned], updated: List[Updated]): Result =
    remaining match {
      case Source(Some(value), date, true) :: xs =>
        loop(
          remaining = xs,
          Logged(value = value, date) :: logged,
          Cleaned(value, date) :: cleaned,
          Updated(value, date) :: updated
        )
      
      case Source(Some(value), date, false) :: xs =>
        loop(
          remaining = xs,
          Logged(value = value, date) :: logged,
          Cleaned(value, date) :: cleaned,
          updated
        )
      
      case Source(None, date, _) :: xs =>
        loop(
          remaining = xs,
          Logged(value = "", date) :: logged,
          cleaned,
          updated
        )
      
      case Nil=>
        // If the order is not important, remove all the reverse.
        (
          logged.reverse,
          cleaned.reverse,
          updated.reverse
        )
    }
  
  loop(remaining = data, logged = List.empty, cleaned = List.empty, updated = List.empty)
}

可以看到代码运行 here.

您可以构建自己的折叠运算符并使用 foldRight 方法传递它:

type Targets = (List[Updated], List[Logged], List[Cleaned])
val target = sources.foldRight(List.empty[Updated], List.empty[Logged], List.empty[Cleaned])(process)

def process(el: Source, acc: Targets): Targets = acc match {
  case (updated, logged, cleaned) => el match {
    case Source(None, date, false) =>
      (updated, Logged("", date) :: logged, cleaned)

    case Source(Some(value), date, false) =>
      (updated, Logged(value, date) :: logged, Cleaned(value, date) :: cleaned)

    case Source(None, date, true) =>
      (Updated("", date) :: updated, Logged("", date) :: logged, cleaned)

    case Source(Some(value), date, true) =>
      (Updated(value, date) :: updated, Logged(value, date) :: logged, Cleaned(value, date) :: cleaned)
  }
}

除非存在大量性能问题,否则只需处理列表 3 次:

val target =
  (
    sources.flatMap { el =>
      if (el.isUpdate) Some(Updated(el.value.getOrElse(""), el.date)) else None
    },
    sources.map(el => Logged(el.value.getOrElse(""), el.date)),
    sources.flatMap { el =>
      el.value.map(d => Cleaned(d, el.date))
    }
  )