Andre Queen Andre Queen - 25 days ago 5
Scala Question

Finding the average from a mapped list of int

I am trying to get the average from a mapped list of ints, then return that value to the user when requested.
Here is my current code with problems, what am I doing wrong? I have included my functionality to find the last element of a tail, that works.

// *******************************************************************************************************************
// application logic
// read data from file
val mapdata = readFile("data.txt")




// *******************************************************************************************************************
// UTILITY FUNCTIONS
//GETS THE DATA FROM THE DATA.TXT
def readFile(filename: String): Map[String, List[Int]] = {
processInput(Source.fromFile(filename).getLines)
}
def processInput(lines: Iterator[String]): Map[String, List[Int]] = {
Try {
lines.foldLeft(Map[String, List[Int]]()) { (acc, line) =>

val splitline = line.split(",").map(_.trim).toList
acc.updated(splitline.head, splitline.tail.map(_.toInt))
}
}.getOrElse {
println("Sorry, an exception happened.")
Map()
}
}



//functionality to find the last tail element
def findLast(list:List[Int]):Int = {
if(list.tail == Nil)
list.head
else
findLast(list.tail)
}

//Function to find the average
def average(list:List[Int]):Double =
list.foldLeft(0.0)(_+_) / list.foldLeft(0)((r,c)=>r+1)



//Show last element in the list, most current WORKS
def currentStockLevel (stock: String): (String, Int) = {
(stock, mapdata.get (stock).map(findLast(_)).getOrElse(0))
}

//Show last element in the list, most current DOES NOT WORK
def averageStockLevel (stock: String): (String, Int) = {
(stock, mapdata.get (stock).map(average(_)).getOrElse(0))
}


my txt file

SK1, 9, 7, 2, 0, 7, 3, 7, 9, 1, 2, 8, 1, 9, 6, 5, 3, 2, 2, 7, 2, 8, 5, 4, 5, 1, 6, 5, 2, 4, 1
SK2, 0, 7, 6, 3, 3, 3, 1, 6, 9, 2, 9, 7, 8, 7, 3, 6, 3, 5, 5, 2, 9, 7, 3, 4, 6, 3, 4, 3, 4, 1
SK4, 2, 9, 5, 7, 0, 8, 6, 6, 7, 9, 0, 1, 3, 1, 6, 0, 0, 1, 3, 8, 5, 4, 0, 9, 7, 1, 4, 5, 2, 8
SK5, 2, 6, 8, 0, 3, 5, 5, 2, 5, 9, 4, 5, 3, 5, 7, 8, 8, 2, 5, 9, 3, 8, 6, 7, 8, 7, 4, 1, 2, 3


The error that I am getting is that expression of type AnyVal does not conform to type Int

Answer

Your averageStockLevel function returns the average value as an Int (the return type is (String, Int)) whereas the calculation that is done in average returns a Double.

So you either need to convert the calculated Double to an Int within averageStockLevel (e.g. by doing average(_).toInt), or you can change the return type of averageStockLevel to (String, Double). The latter variant is obviously the better one since you don't loose the precision of your average value.

def averageStockLevel (stock: String): (String, Double) = {
  (stock, mapdata.get(stock).map(average).getOrElse(0.0))
}

This works, but whether or not it's a good idea to return 0.0 in case of a missing key is for you to decide. Another possibility is to omit the getOrElse part and return an Option[(String,Double)].

Apart from that, your code is quite complex. findLast and average can be defined much easier (it's not really worth it to create an own function for finding the last element, but for the sake of completeness...):

// will throw an exception for empty lists, but so does your current code
def findLast(list:List[Int]) = list.last 

def average(list:List[Int]): Double = list.sum.toDouble / list.size

Another idea is to replace List by Vector. For operations such as .size and .last, List needs linear time whereas Vector basically takes constant time.