What type to use to store an in-memory mutable data table in Scala?

前端 未结 5 1824
不知归路
不知归路 2020-11-28 08:55

Each time a function is called, if it\'s result for a given set of argument values is not yet memoized I\'d like to put the result into an in-memory table. One column is mea

5条回答
  •  刺人心
    刺人心 (楼主)
    2020-11-28 09:21

    You could use a mutable.Map[TupleN[A1, A2, ..., AN], R] , or if memory is a concern, a WeakHashMap[1]. The definitions below (built on the memoization code from michid's blog) allow you to easily memoize functions with multiple arguments. For example:

    import Memoize._
    
    def reallySlowFn(i: Int, s: String): Int = {
       Thread.sleep(3000)
       i + s.length
    }
    
    val memoizedSlowFn = memoize(reallySlowFn _)
    memoizedSlowFn(1, "abc") // returns 4 after about 3 seconds
    memoizedSlowFn(1, "abc") // returns 4 almost instantly
    

    Definitions:

    /**
     * A memoized unary function.
     *
     * @param f A unary function to memoize
     * @param [T] the argument type
     * @param [R] the return type
     */
    class Memoize1[-T, +R](f: T => R) extends (T => R) {
       import scala.collection.mutable
       // map that stores (argument, result) pairs
       private[this] val vals = mutable.Map.empty[T, R]
    
       // Given an argument x, 
       //   If vals contains x return vals(x).
       //   Otherwise, update vals so that vals(x) == f(x) and return f(x).
       def apply(x: T): R = vals getOrElseUpdate (x, f(x))
    }
    
    object Memoize {
       /**
        * Memoize a unary (single-argument) function.
        *
        * @param f the unary function to memoize
        */
       def memoize[T, R](f: T => R): (T => R) = new Memoize1(f)
    
       /**
        * Memoize a binary (two-argument) function.
        * 
        * @param f the binary function to memoize
        * 
        * This works by turning a function that takes two arguments of type
        * T1 and T2 into a function that takes a single argument of type 
        * (T1, T2), memoizing that "tupled" function, then "untupling" the
        * memoized function.
        */
       def memoize[T1, T2, R](f: (T1, T2) => R): ((T1, T2) => R) = 
          Function.untupled(memoize(f.tupled))
    
       /**
        * Memoize a ternary (three-argument) function.
        *
        * @param f the ternary function to memoize
        */
       def memoize[T1, T2, T3, R](f: (T1, T2, T3) => R): ((T1, T2, T3) => R) =
          Function.untupled(memoize(f.tupled))
    
       // ... more memoize methods for higher-arity functions ...
    
       /**
        * Fixed-point combinator (for memoizing recursive functions).
        */
       def Y[T, R](f: (T => R) => T => R): (T => R) = {
          lazy val yf: (T => R) = memoize(f(yf)(_))
          yf
       }
    }
    

    The fixed-point combinator (Memoize.Y) makes it possible to memoize recursive functions:

    val fib: BigInt => BigInt = {                         
       def fibRec(f: BigInt => BigInt)(n: BigInt): BigInt = {
          if (n == 0) 1 
          else if (n == 1) 1 
          else (f(n-1) + f(n-2))                           
       }                                                     
       Memoize.Y(fibRec)
    }
    

    [1] WeakHashMap does not work well as a cache. See http://www.codeinstructions.com/2008/09/weakhashmap-is-not-cache-understanding.html and this related question.

提交回复
热议问题