List pattern matching add filtering based on case object

Viewed 68

I have a list of Male and Female inhabitants to be iterated over.

How to add filtering based on gender to the list pattern matching? (such that countOldManFloor would return 1 only if inhabitants gender is Male and as a result countOldManFloor(inhabitantsFemale) would return 0)

import scala.annotation.tailrec

trait Gender

case object Female extends Gender

case object Male extends Gender

case class Inhabitant(age: Int= 50, gender: Gender)

val olderThen = 30

val inhabitantsBoth: List[Inhabitant] = List(Inhabitant(gender=Male), Inhabitant(gender=Female))
val inhabitantsFemale: List[Inhabitant] = List(Inhabitant(gender=Female), Inhabitant(gender=Female))
val inhabitantsMale: List[Inhabitant] = List(Inhabitant(gender=Male), Inhabitant(gender=Male))


@tailrec
def countOldManFloor(inhabitants: List[Inhabitant]): Int = inhabitants match {
  case inhabitant :: inhabitants  if inhabitant.age > olderThen => 1
  case inhabitant :: inhabitants => countOldManFloor(inhabitants)
  case Nil => 0
}


println(countOldManFloor(inhabitantsBoth))
println(countOldManFloor(inhabitantsMale))
println(countOldManFloor(inhabitantsFemale))

Online code

I tried case inhabitant: Male :: inhabitants if inhabitant.age > olderThen => 1 and = inhabitants.filter() match {} but it did not work

3 Answers

You can match patterns within patterns. In this case the Male pattern, within the Inhabitant() pattern, within the :: pattern of a List.

@tailrec
def countOldManFloor(inhabitants : List[Inhabitant]
                    ,ageLimit    : Int
                    ,acc         : Int = 0): Int = inhabitants match {
  case Inhabitant(age,Male) :: tl if age > ageLimit => 
                  countOldManFloor(tl, ageLimit, acc + 1)
  case _ :: tl => countOldManFloor(tl, ageLimit, acc)
  case Nil     => acc
}

countOldManFloor(inhabitantsBoth, olderThan)    // 1
countOldManFloor(inhabitantsMale, olderThan)    // 2
countOldManFloor(inhabitantsFemale, olderThan)  // 0

Notice that I made olderThan a passed parameter. Methods that reference variables outside of their definition space is a code smell.

I understand that what you need is a counter of Males older than 30, so I added a check for Gender condition.

  def countOldManFloor(inhabitants: List[Inhabitant]): Int = {
    def checkGender(inhabitant: Gender): Boolean = inhabitant match {
      case Male => true
      case _ => false
    }

    @tailrec
    def loop(lst: List[Inhabitant], cont: Int): Int = {
      lst match {
        case Nil => cont
        case (h :: tail) if h.age > olderThen && checkGender(h.gender) => loop(tail, cont + 1)
        case _ => loop(lst.tail, cont)
      }
    }
    loop(inhabitants, 0)
  }

Your method isn't working because you're not adding anything, only 1 and 0 can be returned. If you don't care about tail recursion, this might work:

def countOldManFloor(inhabitants: List[Inhabitant]): Int = inhabitants match {
  case Inhabitant(age, Male) :: inhabitants if age > olderThan => countOldManFloor(inhabitants) + 1
  case inhabitant :: inhabitants => countOldManFloor(inhabitants)
  case Nil => 0
}

Scastie

I think you can do this with just count instead of any recursion, though:

def countOldManFloor(inhabitants: List[Inhabitant]): Int = 
  inhabitants.count(inhabitant => inhabitant.age > olderThan && inhabitant.gender == Male)

Scastie

Related