我目前正在尝试将一些Java代码转换为Scala代码。挑战在于确保转换后的Scala代码与原始Java代码相比,最终不会做出非常低效的事情。对于例如在尝试转换以下代码时:
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);
}
}
}
}
由于Java依赖于变异,因此该代码看起来很合理。 但是,现在我想将它转换为Scala等效的,而不是多次循环遍历集合(在这种情况下为3次)。
我当然可以使用Scala库中的mutable List
并实现与Java相同的功能,但是想知道是否有可能以函数/ Scala的方式从给定的序列/集合生成多个集合没有迭代集合n
次n
是条件计数。提前谢谢!
答案 0 :(得分:5)
一种纯函数和不可变的方法是使用谓词来将集合分成桶的泛型函数:
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))
答案 1 :(得分:3)
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]元组使这看起来很难看,你可能想为你的结果定义一个类型别名。)
答案 2 :(得分:3)
这是相当务实的。模式匹配人员列表,检查每个递归调用中的条件,并根据需要添加到结果,直到列表用尽。结果是List
Person
,其填充为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())
}
...测试
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)
答案 3 :(得分:2)
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)))
这个解决方案不仅仅是功能性的,而且更直接。在许多情况下,可变集合(如ListBuffer)工作得更好,只需确保不泄漏函数或类之外的可变状态。可读性的好处将使纯度的损失值得。
答案 4 :(得分:1)
使用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]