Intermediate values during folding?

Viewed 154

I have a collection of objects

case class Record(value: Whatever)
val list: List[Record]

and want to select the best ranked

list.foldLeft(list.head) { (best, current) =>
    if (rank(current.value) > rank(best.value)) {
        current
    } else {
        best
    }
}

Let's suppose that rank is expensive and better not be called twice on the same object. What are my options?

I can fold to tuple (rank, record) but this probably means creating auxiliary objects during iteration. Should I worry about the overhead? Or rather

  1. How can this be implemented efficiently in Scala?

  2. What's the proper 'functional' view of the problem?

2 Answers

If you're in a situation where repeating expensive calculations on the same objects appears unavoidable, you might try memoization.

// memoize this function (arity 1)
def memo[A,R](f :A => R): A => R =
  new collection.mutable.WeakHashMap[A,R] {
    override def apply(a: A) = getOrElseUpdate(a,f(a))
  }

// rankM is a memoized Record => Rank function
val rankM = memo{ r:Record => rank(r.value) }

A WeakHashMap is used sometimes when you want the Map to "forget" seldom accessed keys in a memory-challenged environment.

The initial value in foldLeft can be of a different type than collection, so you could initialize it with tuple, that would contain current element and calculated value:

val (result, _) = list.foldLeft((list.head, rank(list.head))) { case ((best, bestRank), current) =>
    val currentRank = rank(current.value)
    if (rank(current.value) > bestRank) {
      (current, currentRank)
    } else {
      (best, bestRank)
    }
}

But as was mentioned in comments maybe in your case, it would be better to just use: list.maxBy(r => rank(r.value)) ?

Related