0

I'm new to Scala and I have been struggling with Option and Lists. I have the following object:

object Person {

    case class Person(fName: String,
                      lName: String,
                      jeans: Option[Jeans],
                      jacket: Option[Jacket],
                      location: List[Locations],
                      age: Int)

    case class Jeans(brand: String, price: Int, color: String)
...
}

And I'm trying to write the function that takes as input list of type person and return the average price of their jeans:

def avgPriceJeans(input: List[Person]): Int

Seth Tisue
  • 29,985
  • 11
  • 82
  • 149
James
  • 9
  • 2

3 Answers3

1

When you have a list of values and want to reduce all of them to a single value, applying some kind of operation. You need a fold, the most common one would be a foldLeft.

As you can see in the scaladoc. This method receives an initial value and a combination function.
It should be obvious that the initial value should be a zero. And that the combination function should take the current accumulate and add to it the price of the current jeans.

Nevertheless, now we have another problem, the jeans may or may not exists, thus we use option. In this case we need a way to say if they exists give me their price, if not give a default value (which in this case makes sense to be another zero).
And that is precisely what Option.fold give us.

Thus we end with something like:

val sum = input.foldLeft(0) {
  (acc, person) => 
    acc + person.jeans.fold(ifEmpty = 0)(_.price)
}

Now that you need the average, you only need to divide that sum with the count.

However, we can do the count in the same foldLeft, just to avoid an extra iteration.
(I changed the return type, as well as the price property, to Double to ensure accurate results).

def avgPriceJeans(input: List[Person]): Double = {
  val (sum, count) = input.foldLeft((0.0d, 0)) {
    case ((accSum, accCount), person) =>
      (
        accSum   + person.jeans.fold(ifEmpty = 0.0d)(_.price),
        accCount + 1
      )
  }
  sum / count
}
  • What is the d after the 0.0 for? Sorry, I don't know the syntax for Scala as well as I should. – Allen Han Sep 30 '19 at 18:15
  • Oh I see, it means double. – Allen Han Sep 30 '19 at 18:55
  • @AllenHan Yes, it is only to ensure that the compiler treats that `zero` as a **Double**. In general it should not be necessary, but I kind of like it. _(PS: That is standard in all the languages that i know)_. – Luis Miguel Mejía Suárez Sep 30 '19 at 19:00
  • You only need double for the division, it's not necessary throughout the summation. – pedrofurla Sep 30 '19 at 20:02
  • 1
    @pedrofurla as I said, I am assuming prices are also **Doubles**. If not, then yes absolutely. – Luis Miguel Mejía Suárez Sep 30 '19 at 20:04
  • @LuisMiguelMejíaSuárez Why `List.length` has to iterate through to count? Is it just to avoid mutable state on a principle? – Mario Galic Sep 30 '19 at 20:13
  • 1
    @MarioGalic **Lists** are simple ADTs, either it is empty or it is a cons. It does not know its size, it has to go all the way down until it founds the **Nil**. One can argue that it could store its size as a property, but that would increase the memory footprint a lot, since a **List** is built of other **Lists**. And since on a FP fashion you usually do not need its size _(and when you do, you can compute it pretty easily)_ then it is just not worth the cost. – Luis Miguel Mejía Suárez Sep 30 '19 at 20:19
  • This solution assumes that `jeans = None` represents price of the jeans is $0.0 and the subsequent unconditional `accCount + 1` includes the free jeans, thus lowering the overall average. Not sure that's what the OP intends to use `Option[Jeans]` for. – Leo C Oct 01 '19 at 01:03
  • @LeoC yes that is correct. OP was not clear how the average should be computes in terms of the **Option**. If the Nones should not be included, then the code can be easily adapted for that. – Luis Miguel Mejía Suárez Oct 01 '19 at 01:40
1

As @SethTissue points out, this is relatively straightforward:

val prices = persons.flatMap(_.jeans.map(_.price))
prices.sum.toDouble / prices.length

The first line needs some unpicking:

Taking this from the inside out, the expression jeans.map(_.price) takes the value of jeans, which is Option[Jeans], and extracts the price field to give Option[Int]. The flatMap call is equivalent to map followed by flatten. The map call applies this inner expression to the jeans field of each element of persons. This turns the List[Person] into a List[Option[Int]]. The flatten call extracts all the Int values from Some[Int] and discards all the None values. This gives List[Int] with one element for each Person that had a non-empty jeans field.

The second line simply sums the values in the List, converts it to Double and then divides by the length of the list. Adding error checking is left as an exercise!

Tim
  • 26,753
  • 2
  • 16
  • 29
0

One approach consist in calculate the value of the jeans price for each element of the list. After that you can sum all the values (with sum method) and divide by the list size. I managed the case when jeans is None with 0 as price value (so I consider it for the sum).

Here the code:

def avgPriceJeans(input: List[Person]): Int =
    input.map(_.jeans.map(_.price).getOrElse(0)).sum / input.size
alberto adami
  • 729
  • 1
  • 6
  • 25