Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Single iteration => Multiple output collections from Java to Scala

Tags:

java

scala

I'm currently trying to convert some Java code to Scala code. The challenge is to ensure that the converted Scala code doesn't end up doing something very inefficient when compared to the original Java one. For e.g. when trying to convert the following code:

class Person {
    String name;
    Integer age;
    Character gender;
}

public class TestJava {
    public static void main(String[] args) {
        final List<Person> persons = new ArrayList<>();
        final List<Person> males = new ArrayList<>();
        final List<Person> aNames = new ArrayList<>();
        final List<Person> seniors = new ArrayList<>();
        for (final Person p: persons) {
            if (p.gender == 'm') {
                males.add(p);
            }
            if (p.age >= 60) {
                seniors.add(p);                
            }
            if (p.name.startsWith("a")) {
                aNames.add(p);
            }            
        }
    }
}

Since Java relies on mutation, this code looks logical. But, now I want to convert this to its Scala equivalent without looping over the collection multiple times (3 times in this case).

I can of course use mutable List from the Scala library and achieve the same thing as done by Java but was wondering if it was possible to generate multiple collections from a given sequence/collection in a functional/Scala way without iterating over the collection for n times where n is the criteria count. Thanks in advance!

like image 558
sasuke Avatar asked Aug 15 '13 19:08

sasuke


5 Answers

One purely functional and immutable way would be to have a generic function that separates collections into buckets, by predicate:

case class Person(name: String, age: Int, gender: String)

def bucketsByPredicate(people: Seq[Person], predicates: Seq[Person => Boolean]) = {
  people.foldLeft(predicates.map(predicate =>
    (predicate, List.empty[Person])
  )) { case (predicates, person) =>
      predicates.map { case (predicate, members) =>
        (predicate, if(predicate(person)) person :: members else members)
      }
  }.map(_._2)
}

Then an example usage could be:

val olderThan60 = (p: Person) => p.age >= 60
val male = (p: Person) => p.gender == "m"
val Seq(olderThan60People, malePeople) = bucketsByPredicate(people, Seq(olderThan60, male))
like image 82
Alex Yarmula Avatar answered Nov 08 '22 16:11

Alex Yarmula


This is fairly pragmatic. Pattern match over the list of persons, check the conditions in each recursive call and add to the results as necessary until the list is exhausted. The results are a List of Person that gets stuffed into a Tuple.

class Person(val name: String, val age: Integer, val gender: Character){
  override def toString = name + " " + age + " " + gender
}

val alice = new Person("Alice", 18, 'f')
val bob = new Person("Bob", 18, 'm')
val charlie = new Person("Charlie", 60, 'm')
val diane = new Person("Diane", 65, 'f')
val fred = new Person("Fred", 65, 'm')

def filterPersons(persons: List[Person]) = {
  import scala.collection.mutable.{ListBuffer => LB}
  def filterPersonsR(persons: List[Person], males: LB[Person], anames: LB[Person], seniors: LB[Person]): Tuple3[LB[Person], LB[Person], LB[Person]] = persons match {
    case Nil => (males, anames, seniors)
    case h :: t => {
      filterPersonsR(t, if(h.gender == 'm') males += h else males, if(h.name.startsWith("A"))  anames += h else anames, if(h.age >= 60) seniors += h else seniors)
    }
  }
  filterPersonsR(persons, LB(), LB(), LB())
}

Testing...

scala> filterPersons(List(alice, bob, charlie, diane, fred))
res25: (scala.collection.mutable.ListBuffer[Person], scala.collection.mutable.ListBuffer[Person], scala.collection.mutable.ListBuffer[Person]) = (ListBuffer(Bob 18 m, Charlie 60 m, Fred 65 m),ListBuffer(Alice 18 f),ListBuffer(Charlie 60 m, Diane 65 f, Fred 65 m))

scala> res25._1
res26: scala.collection.mutable.ListBuffer[Person] = ListBuffer(Bob 18 m, Charlie 60 m, Fred 65 m)

scala> res25._2
res27: scala.collection.mutable.ListBuffer[Person] = ListBuffer(Alice 18 f)

scala> res25._3
res28: scala.collection.mutable.ListBuffer[Person] = ListBuffer(Charlie 60 m, Diane 65 f, Fred 65 m)
like image 23
Brian Avatar answered Nov 08 '22 17:11

Brian


case class Person(gender:Sex,name:String,age:Int)

sealed trait Sex
case object Male extends Sex  
case object Female extends Sex

def part3(l:List[Person]) = {
  def aux(l:List[Person],acc:(List[Person],List[Person],List[Person])) : (List[Person],List[Person],List[Person]) = l match {
    case Nil => acc
    case head::tail => {
      val newAcc = (if (head.gender == Male) head :: acc._1 else acc._1,
                    if (head.age >= 60) head :: acc._2 else acc._2,
                    if (head.name startsWith "a") head :: acc._3 else acc._3)
      aux(tail,newAcc)
    }
  }
  val emptyTuple = (List[Person](),List[Person](),List[Person]())
  // It is (much) faster to reverse the input list and then prepend during to recursive loop.
  // prepend is O(1), append is O(n)
  aux(l.reverse,emptyTuple)
}

val (males,seniors,aNames) = part3(List(Person(Male,"abc",20),Person(Female,"def",61),Person(Male,"Nope",99)))
println(s"males : $males \nseniors : $seniors \naNames : $aNames")

// outputs
// males : List(Person(Male,abc,20), Person(Male,Nope,99))
// seniors : List(Person(Female,def,61), Person(Male,Nope,99))
// aNames : List(Person(Male,abc,20))

(The List[Person] tuple makes this look quite ugly, you may want to define a type alias for your results.)

like image 35
Marth Avatar answered Nov 08 '22 16:11

Marth


import scala.collection.immutable.List
import scala.collection.mutable.ListBuffer

case class Person(name: String, age: Int, gender: String)

def partition(persons: List[Person], predicates: List[Person => Boolean]): List[List[Person]] = {

    val bufs = List.fill(predicates.size)(new ListBuffer[Person])

    persons.foreach { person =>
        (0 until predicates.size).foreach{ i =>
            if (predicates(i)(person)) bufs(i) += person
        }
    }

    bufs map (_.toList)
}

val alice = new Person("Alice", 18, "f")
val bob = new Person("Bob", 18, "m")
val charlie = new Person("Charlie", 60, "m")
val diane = new Person("Diane", 65, "f")
val fred = new Person("Fred", 65, "m")

val olderThan60 = (p: Person) => p.age >= 60
val male = (p: Person) => p.gender == "m"
val nameStartsWith = (p: Person) => p.name.startsWith("A")

println(partition(List(alice, bob, charlie, diane, fred), List(olderThan60, male, nameStartsWith)))

this solution is not purely functional, but more straightforward. there are many situations where mutable collections(like ListBuffer) work better, just make sure you do not leak the mutable state outside the function or class. the benifits of readability will make the loss of purity worth it.

like image 26
Septem Avatar answered Nov 08 '22 15:11

Septem


Another example using foldLeft, but maybe a bit easier to read.

//setup test data
case class Person(gender: Char, age: Int, name: String)
val persons = List(Person('m', 30, "Steve"), Person('m', 15, "John"), Person('f', 50, "Linda"))

//function that takes a list, a person and a predicate. 
//returning same list if predicate is false, else new list with person added
def inBucket(f: Person => Boolean, p: Person, list: List[Person]) = if (f(p)) p :: list else list

//what to do in each fold step. produces a new intermediate tuple of lists every time
def bucketize(lists: (List[Person], List[Person], List[Person]), next: Person): (List[Person], List[Person], List[Person]) = {
  val (males, females, adults) = lists;
  ( inBucket(_.gender == 'm', next, males),
    inBucket(_.gender == 'f', next, females),
    inBucket(_.age >= 18, next, adults) )
}
val (males, females, adults) = persons.foldLeft( (List[Person](), List[Person](), List[Person]()) )(bucketize)

//you can use males, females and adults now, they are of type List[Person]
like image 1
rompetroll Avatar answered Nov 08 '22 15:11

rompetroll