避免在If Else中重复调用Scala中的同一函数



我想要优化以下代码片段:

import org.joda.time.{DateTime, Days, Months, Weeks, Years}
@scala.annotation.tailrec
def optimize(start: DateTime, end: DateTime, acc: List[(DateTime, DateTime)]): List[(DateTime, DateTime)] = {
def doDaysExist(startDate: DateTime, endDate: DateTime): Boolean = {
Days.daysBetween(startDate, endDate).getDays != 0
}
if (Years.yearsBetween(start, end.plusDays(1)).getYears != 0) {
val years = Years.yearsBetween(start, end.plusDays(1)).getYears
val newStart = start.plusYears(years).minusDays(1)
if (doDaysExist(newStart, end)) {
optimize(newStart, end, (start, newStart) +: acc)
} else (start, end) +: acc
} else if (Months.monthsBetween(start, end.plusDays(1)).getMonths != 0) {
val months = Months.monthsBetween(start, end.plusDays(1)).getMonths
val newStart = start.plusMonths(months).minusDays(1)
if (doDaysExist(newStart, end)) {
optimize(newStart, end, (start, newStart) +: acc)
} else (start, end) +: acc
} else if (Weeks.weeksBetween(start, end.plusDays(1)).getWeeks != 0) {
val weeks = Weeks.weeksBetween(start, end.plusDays(1)).getWeeks
val newStart = start.plusWeeks(weeks).minusDays(1)
if (doDaysExist(newStart, end)) {
optimize(newStart, end, (start, newStart) +: acc)
} else (start, end) +: acc
} else {
// For sure days
(start, end) +: acc
}
}

可以看出,if条件和后面的下一个语句是一种重复的代码。有没有更好的方法让它变得更好、更神秘?我必须通过这个层次结构,因为我必须首先检查年份,然后是月份,然后是星期,然后是日期。

我可以考虑将重复的代码块提取到lambdas中,但这足够好吗?我想不出将其重写为Map,因为该代码块在尾部递归函数中运行。

这样的东西怎么样:

def optimize(start: DateTime, end: DateTime, acc: List[(DateTime, DateTime)]): List[(DateTime, DateTime)] = {
val endPlusDay = end.plusDays(1)

val checks = List(
{
val years = Years.yearsBetween(start, endPlusDay).getYears
val newStart = start.plusYears(years).minusDays(1)

(years -> newStart)
},
{
val months = Months.monthsBetween(start, endPlusDay).getMonths
val newStart = start.plusMonths(months).minusDays(1)

(months -> newStart)
},
{
val weeks = Weeks.weeksBetween(start, endPlusDay).getWeeks
val newStart = start.plusWeeks(weeks).minusDays(1)

(weeks -> newStart)
}
)

checks.collectFirst {
case (difference, newStart) if (
(difference != 0) && (Days.daysBetween(newStart, end) != 0)
) =>
optimize(newStart, end, acc :+ (start, newStart))
}.getOrElse(acc :+ (start, end))
}

PS:两个建议

  1. 使用java.time而不是Joda
  2. Prepend,最后reverse到累加器,而不是append

只需预先计算值并将第二个测试移到if/else:之外

val years = Years.yearsBetween(start, end.plusDays(1)).getYears
lazy val months = Months.monthsBetween(start, end.plusDays(1)).getMonths
lazy val weeks = Weeks.weeksBetween(start, end.plusDays(1)).getWeeks
val newStart =
if (years != 0) {
start.plusYears(years).minusDays(1)
} else if (months != 0) {
start.plusMonths(months).minusDays(1)
} else if (weeks != 0) {
start.plusWeeks(weeks).minusDays(1)
} else {
start
}
if (doDaysExist(newStart, end)) {
optimize(newStart, end, (start, newStart) +: acc)
} else {
(start, end) +: acc
}

使用lazy可以提高性能,也可以不提高性能,所以要衡量哪一个是最好的。

最新更新