单次迭代=>从Java到Scala的多个输出集合

我目前正在尝试将一些Java代码转换为Scala代码。 面临的挑战是确保转换后的Scala代码与原始Java代码相比,最终不会产生非常低效的代码。 例如,当尝试转换以下代码时:

class Person { String name; Integer age; Character gender; } public class TestJava { public static void main(String[] args) { final List persons = new ArrayList(); final List males = new ArrayList(); final List aNames = new ArrayList(); final List 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); } } } } 

由于Java依赖于变异,因此该代码看起来很合理。 但是 ,现在我想将它转换为Scala等价物而不会多次循环遍历该集合(在这种情况下为3次)。

我当然可以使用Scala库中的可变List并实现与Java相同的操作但是想知道是否有可能以函数/ Scala方式从给定序列/集合生成多个集合而无需迭代n的集合时间,其中n是标准计数。 提前致谢!

一种纯粹的function和不可变的方式是通过谓词具有将集合分成桶的通用函数:

 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) } 

然后一个示例用法可能是:

 val olderThan60 = (p: Person) => p.age >= 60 val male = (p: Person) => p.gender == "m" val Seq(olderThan60People, malePeople) = bucketsByPredicate(people, Seq(olderThan60, male)) 

这是相当务实的。 模式匹配人员列表,检查每个递归调用中的条件,并根据需要添加到结果,直到列表用尽。 结果是一个填入TuplePerson List

 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()) } 

测试…

 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) 
 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)) 

(List [Person]元组使这看起来很难看,你可能想为你的结果定义一个类型别名。)

 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))) 

这个解决方案不仅仅是function性的,而是更直接的。 在许多情况下,可变集合(如ListBuffer)工作得更好,只要确保不泄漏函数或类之外的可变状态。 可读性的好处将使纯度的损失值得。

使用foldLeft的另一个例子,但可能更容易阅读。

 //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]