如何简化期权处理
How to simplify Option processing
我有一个包含数据的源列表,我想将其分类为 3 个列表,这些列表包含经过清理的数据、更新的数据和记录的数据。来自源的所有元素都必须在记录列表中。所有具有非空值的元素都必须在清理列表中,如果更新了标志,该元素也必须出现在更新的文件夹中。
根据例子:
- 已更新 = 2,4 el
- 已记录 = 1,2,3,4 el
- 清洁 = 3, 4 el
你能帮我简化一下写的代码吗:
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))
}
)
我有一个包含数据的源列表,我想将其分类为 3 个列表,这些列表包含经过清理的数据、更新的数据和记录的数据。来自源的所有元素都必须在记录列表中。所有具有非空值的元素都必须在清理列表中,如果更新了标志,该元素也必须出现在更新的文件夹中。
根据例子:
- 已更新 = 2,4 el
- 已记录 = 1,2,3,4 el
- 清洁 = 3, 4 el
你能帮我简化一下写的代码吗:
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))
}
)