How to avoid duplicate calculation in partial functions?

Viewed 85

Here is a simplified code snippet:

val pf = new PartialFunction[Int, Int]
{
    private def calc(x: Int): Int = x - 3

    override def isDefinedAt(x: Int): Boolean =
        {
            val result = calc(x)
            println(s"calc is called by isDefinedAt, result = $result")
            result > 0
        }

    override def apply(x: Int): Int =
        {
            val result = calc(x)
            println(s"calc is called by apply, result = $result")
            result
        }
}

(0 to 6).collect(pf)

The output is:

calc is called by isDefinedAt, result = -3
calc is called by isDefinedAt, result = -2
calc is called by isDefinedAt, result = -1
calc is called by isDefinedAt, result = 0
calc is called by isDefinedAt, result = 1
calc is called by apply, result = 1
calc is called by isDefinedAt, result = 2
calc is called by apply, result = 2
calc is called by isDefinedAt, result = 3
calc is called by apply, result = 3

Here, the calc method is called twice for result 1, 2, 3. Suppose that the the calc method is costly, then how to avoid duplicate calls to it for each call to the partial function pf?

3 Answers

There is not much you can do to minimise the amount of calls to internal calc method. isDefinedAt and apply are independent methods and you cannot make any assumptions of the way these methods are used by the caller.

One approach is to cache the input argument x and the result of calc method. This is not easy to do, may introduce nasty bugs, overcomplicate your code and of course increase memory footprint.

I'd focus on reducing time/memory complexity of the calc method before considering applying any cache solutions.

Maybe a better way is avoid using partition function and just use flatMap with Option for filtering not defined inputs in calc:

private def calc(x: Int): Int = x - 3

(0 to 6).flatMap(x => Option(calc(x)).filter(_ > 0))

Perhaps try some form of memoization like so

val pf = new PartialFunction[Int, Int] {
  private val expensiveCalc = (x: Int) => x - 3

  private val calc = new collection.mutable.WeakHashMap[Int, Int] {
    override def apply(a: Int) = getOrElseUpdate(a, expensiveCalc(a))
  }

  override def isDefinedAt(x: Int): Boolean = calc(x) > 0
  override def apply(x: Int): Int = calc(x)
}

(0 to 6).collect(pf)
Related