Search code examples
scalascala-collectionsfor-comprehension

Scala initialize a collection type variable to null outside a for loop and assign some method's return value to the variable inside the loop


I am a novice in working with Scala.

I have a Scala Vector containing Person objects on which I need to loop on and pass each element of the Vector to a method personCollector whose return value too is a Vector. As can be seen in the Scala REPL output below of for loop it prints the Vector returned by personCollector three times which is because the people Vector contains three entries.However I would like to print the Vector returned by personCollector only once i.e. after the for loop iteration is over.

In Java I could do it following way:

var peopleWithFirstName = null;
for (Person p :  people)
  peopleWithFirstName = firstNameCollector(p);

System.out.println(peopleWithFirstName);

However I cannot figure out to do the above in Scala.Below is my Scala code.

    Welcome to Scala version 2.10.4 (Java HotSpot(TM) Server VM, Java 1.7.0_04).
    Type in expressions to have them evaluated.
    Type :help for more information.

    scala>   case class Person(
         |       firstName: Option[String],
         |       middleName: Option[String],
         |       lastName: Option[String] )
    defined class Person

    scala> def isFirstNameValid(person: Person) = person.firstName.isDefined
    isFirstNameValid: (person: Person)Boolean

    scala>   def personCollector(isValid: (Person) => Boolean) = {
         |     var validPeople = Vector[Person]()
         |     (person: Person) => {
         |       if(isValid(person)) validPeople = validPeople :+ person
         |       validPeople
         |     }
         |   }
    personCollector: (isValid: Person => Boolean)Person => scala.collection.immutable.Vector[Person]

    scala> val p1 = Person(Some("First Name"), Some("Middle Name"), Some("Last Name"))
    p1: Person = Person(Some(First Name),Some(Middle Name),Some(Last Name))

    scala> val p2 = Person(None, Some("Middle Name"), None)
    p2: Person = Person(None,Some(Middle Name),None)

    scala> val p3 = Person(Some("First Name"), None, None)
    p3: Person = Person(Some(First Name),None,None)

    scala> val people = Vector(p1, p2, p3)
    people: scala.collection.immutable.Vector[Person] = Vector(Person(Some(First Name),Some(Middle Name),Some(Last Name)), Person(None,Some(Middle Name),None), Person(Some(First Name),None,None))

    scala> val firstNameCollector = personCollector(isFirstNameValid)
    firstNameCollector: Person => scala.collection.immutable.Vector[Person] = <function1>

    scala> for (p <- people)
         | println(firstNameCollector(p))
    Vector(Person(Some(First Name),Some(Middle Name),Some(Last Name)))
    Vector(Person(Some(First Name),Some(Middle Name),Some(Last Name)))
    Vector(Person(Some(First Name),Some(Middle Name),Some(Last Name)), Person(Some(First Name),None,None))

Thanks.


Solution

  • I'm not entirely sure what your actual goal is. I guess what you are trying to achieve seem to be better solved by filter and map:

    • filter serves as validator in this case, i.e., people.filter(isFirstNameValid). This returns a collection of all people with a defined first name -- is this what you want?
    • map serves as extractor of the desired field, in this case the first name. So overall people.filter(isFirstNameValid).map(_.firstName) in case you want your collection to represent first names instead of a complete person. If you furthermore want to convert from Option (of first name) to a concrete value you may want to use flatMap instead (this also makes the explicit validation unnecessary).

    In case you really what to stick to a solution based on modifying external state, you have to place your mutable variable in an external scope for instance...