views:

72

answers:

2

Hi, basically I'm not really a Java/Scala fan, but unfortunately I'm forced to use it for my studies. Anyways, I was given an assignment:

What the program gets is a list of objects like: Mark(val name String, val style_mark Int, val other_mark Int).

How can I use groupBy, to group the marks by name, and get an average for style_mark and other_mark?

Mark("John", 2, 5)
Mark("Peter", 3, 7)
Mark("John", 4, 3)

Should return:

Mark("John", 3, 4)
Mark("Peter", 3, 7)

Thats the code:

class Mark(val name: String, val style_mark: Int, val other_mark: Int) {}

object Test extends Application
  {
  val m1 = new Mark("Smith", 18, 16);
  val m2 = new Mark("Cole", 14, 7);
  val m3 = new Mark("James", 13, 15);
  val m4 = new Mark("Jones", 14, 16);
  val m5 = new Mark("Richardson", 20, 19);
  val m6 = new Mark("James", 4, 18);

  val marks = List(m1, m2, m3, m4, m5, m6);

  def avg(xs: List[Int]) = xs.sum / xs.length

  marks.groupBy(_.name).map { kv => Mark(kv._1, avg(kv._2.map(_.style_mark)), avg(kv._2.map(_.other_mark))) }

  println(marks);
  }

Any help would be greatly appreciated,

Paul

+2  A: 

As you already said, we can use groupBy to group the Marks by name. Now we have a Map where each key is the name and the value is a list of Marks with that name.

We can now iterate over that Map and replace each key-value pair with a Mark-object that has the key as its name, and the average of the style_marks in the list as its style_mark and the average of other_marks in the list as its other_mark. Like this:

def avg(xs: List[Int]) = xs.sum / xs.length
marks.groupBy(_.name).map { kv =>
  Mark(kv._1, avg(kv._2.map(_.style_mark)), avg(kv._2.map(_.other_mark)))
}
sepp2k
Thanks for the answer, I sort of get it. Updated my question with the code that I have right know, but its giving me an error `error: not found: value Mark` at `Mark(kv._1,...`
PawelMysior
@Pawel: If `Mark` is not a case class, you need to write `new Mark(bla,bla,bla)` instead of `Mark(bla,bla,bla)`.
sepp2k
@Pawel: Also note that the value of `marks` will remain unchanged, so you should print the return value of the expression, not the value of `marks`. (Though it's easier to test code in the REPL than by creating a file and running it, in which case you can just enter the expression and will see its result).
sepp2k
+4  A: 

Just a couple of points here:

  1. You can use pattern matching to avoid all that tedious _1, _2 stuff that comes with tuples.

  2. Underscores in variable/parameter names are a Bad Thing™, they're already used far too heavily elsewhere in the language

So having stated that:

UPDATE: replaced avg with avgOf, reducing duplication :)

//Needs two param lists so that inference will work properly
//when supplying the closure
def avgOf[T](xs:List[T])(f:(T)=>Int) = xs.map(f).sum / xs.length

marks.groupBy(_.name).map {
  case (k,v) => new Mark(k, avgOf(v)(_.styleMark), avgOf(v)(_.otherMark))
}

In the real world, I'd probably pimp Traversable to add the avgOf method, so you could write v.avgOf(_.styleMark), but that would just complicate this example.

Kevin Wright
There should be `case` before `(k, v)`.
missingfaktor
Wouldn't be the first time I've made that mistake either :)
Kevin Wright