Scala函数结果缓存方法及混淆矩阵度量类方法调用疑问
在Scala里实现函数结果缓存(也就是记忆化)有不少实用方案,我给你整理几个常用的:
手动实现基础缓存:用
Map存储已计算的输入和对应结果,适合简单场景。比如给一个耗时的纯函数加缓存:def calculateHeavy(n: Int): Int = { println(s"正在计算n=$n") // 用于验证是否重复计算 Thread.sleep(1000) n * 3 } // 封装带缓存的版本 val memoizedCalculate = { val cache = scala.collection.mutable.Map.empty[Int, Int] (n: Int) => cache.getOrElseUpdate(n, calculateHeavy(n)) }第一次调用
memoizedCalculate(5)会执行计算,后续调用直接取缓存值。用
lazy val缓存特定输入结果:如果只需要缓存固定几个输入的结果,lazy val是个轻量选择,它只会在第一次访问时计算,之后直接返回缓存值:lazy val cachedResultFor10 = calculateHeavy(10)借助第三方缓存库实现复杂策略:如果需要过期时间、容量限制这类高级特性,推荐用Caffeine或Guava这类成熟库。以Caffeine为例:
import com.github.benmanes.caffeine.cache.Caffeine val cache = Caffeine.newBuilder() .maximumSize(150) // 最大缓存150个条目 .expireAfterWrite(10, java.util.concurrent.TimeUnit.MINUTES) // 写入后10分钟过期 .build[Int, Int]() def memoizedHeavy(n: Int): Int = cache.get(n, calculateHeavy)get方法会先查缓存,不存在则调用目标函数计算并缓存结果。封装通用记忆化工具函数:可以写一个通用方法,快速给任意纯函数加上缓存能力:
def memoize[A, B](f: A => B): A => B = { val cache = scala.collection.mutable.Map.empty[A, B] a => cache.getOrElseUpdate(a, f(a)) } val memoizedHeavyFunc = memoize(calculateHeavy)
首先提个小问题:你写的require(tp < 0); require(fp < 0); require(fn < 0)条件是反的,应该是tp >= 0; fp >= 0; fn >= 0,不然创建Measure实例时会直接抛出非法参数异常。
回到你的核心疑问:调用fMeasure时,precision和recall会各自被调用两次吗?
答案是:默认情况下会各自被调用两次。因为Scala默认是严格求值,在fMeasure的表达式2 * (precision * recall) / (precision + recall)里,precision和recall各被引用了两次,每一次引用都会触发一次方法调用。
你可以加打印语句验证:
case class Measure(tp: Int, fp: Int, fn: Int){ require(tp >= 0); require(fp >= 0); require(fn >= 0) def precision: Double = { println("调用precision方法") tp.toDouble / (tp + fp) // 注意转成Double,避免整数除法丢失精度 } def recall: Double = { println("调用recall方法") tp.toDouble / (tp + fn) } def fMeasure: Double = 2 * (precision * recall) / (precision + recall) } val m = Measure(10, 5, 3) m.fMeasure
运行后会输出:
调用precision方法 调用recall方法 调用precision方法 调用recall方法
这就证明了两个方法各被调用了两次。
如果想优化成仅调用一次,只需要在fMeasure里先把结果存为局部变量,之后复用即可:
def fMeasure: Double = { val p = precision val r = recall 2 * (p * r) / (p + r) }
修改后再调用fMeasure,就只会打印一次调用precision方法和调用recall方法了。
补充说明:Scala不会自动对这类方法调用做缓存优化,必须显式将结果赋值给变量来复用。
内容的提问来源于stack exchange,提问作者THIS USER NEEDS HELP

