tags:

views:

135

answers:

2

I'm on my second evening of scala, and I'm resisting the urge to write things in scala how I used to do them in java and trying to learn all of the idioms. In this case I'm looking to just compute an average using such things as closures, mapping, and perhaps list comprehension. Irrespective of whether this is the best way to compute an average, I just want to know how to do these things in scala for learning purposes only

Here's an example: the average method below is left pretty much unimplemented. I've got a couple of other methods for looking up the rating an individual userid gave that uses the find method of TraversableLike (I think), but nothing more that is scala specific, really. How would I compute an average given a List[RatingEvent] where RatingEvent.rating is a double value that I'd to compute an average of across all values of that List in a scala-like manner?.

package com.brinksys.liftnex.model

class Movie(val id : Int, val ratingEvents : List[RatingEvent]) {

    def getRatingByUser(userId : Int) : Int =  {
        return getRatingEventByUserId(userId).rating
    }

    def getRatingEventByUserId(userId : Int) : RatingEvent = {
        var result = ratingEvents find {e => e.userId == userId }
        return result.get
    }

    def average() : Double = {
        /* 
         fill in the blanks where an average of all ratingEvent.rating values is expected
        */
       return 3.8
    }

}

How would a seasoned scala pro fill in that method and use the features of scala to make it as concise as possible? I know how I would do it in java, which is what I want to avoid.

If I were doing it in python, I assume the most pythonic way would be:

sum([re.rating. for re in ratingEvents]) / len(ratingEvents)

or if I were forcing myself to use a closure (which is something I at least want to learn in scala):

reduce(lambda x, y : x + y, [re.rating for re in ratingEvents]) / len(ratingEvents)

It's the usage of these types of things I want to learn in scala.

Your suggestions? Any pointers to good tutorials/reference material relevant to this are welcome :D

+7  A: 

If you're going to be doing math on things, using List is not always the fastest way to go because List has no idea how long it is--so ratingEvents.length takes time proportional to the length. (Not very much time, granted, but it does have to traverse the whole list to tell.) But if you're mostly manipulating data structures and only occasionally need to compute a sum or whatever, so it's not the time-critical core of your code, then using List is dandy.

Anyway, the canonical way to do it would be with a fold to compute the sum:

(0.0 /: ratingEvents){_ + _.rating} / ratingEvents.length

// Equivalently, though more verbosely:
// ratingEvents.foldLeft(0.0)(_ + _.rating) / ratingEvents.length

or by mapping and then summing (2.8 only):

ratingEvents.map(_.rating).sum / ratingEvents.length

For more information on maps and folds, see this question on that topic.

Rex Kerr
Good answer, thanks! I had to do some further looking up to grok what :/ is (operator for something like foldLeft on certain types) and I had to look at the documentation on foldLeft to figure out why you had it written as (_ + _.rating), which looked totally unintuitive until I peeked at the foldLeft docs text for what it returns. The 2.8 example was a bit easier to get at the first glance.Also, what would you suggest as an alternative to List for heavy math work? I only picked List out of habit, really as I'm still just learning.
whaley
@whaley: I'd use `Array` for genuinely math-heavy work--and, sadly, I'd mostly loop through it using `var sum,i=0; while (i<a.length) { sum += a(i); i+=1 }`. But I'd also stick these sorts of unpleasantness inside a class designed to deal with the mathematical aspect of the code and write methods to do such things at a high level. Unless I knew that my code was spending the overwhelming amount of its time doing math slowly, I'd just use `List` (or whatever other collection was well-suited for the problem at hand).
Rex Kerr
+2  A: 

You might calculate sum and length in one go, but I doubt that this helps except for very long lists. It would look like this:

val (s,l) = ratingEvents.foldLeft((0.0, 0))((t, r)=>(t._1 + r.rating, t._2 + 1)) 
val avg = s / l

I think for this example Rex' solution is much better, but in other use cases the "fold-over-tuple-trick" can be essential.

Landei
I'd probably resort to using a `var len=0` that was incremented by the closure before tupling this way. A second traversal of a list element is cheaper than the (transient) creation of a new tuple every element. But it is good to see this pattern; there are cases where it is a great strategy (at least for compact and powerful code; it never has superlative performance, but most of the time when you use it you wouldn't need that performance).
Rex Kerr