9

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

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

我当然可以使用ListScala 库中的 mutable 并实现与 Java 所做的相同的事情,但想知道是否可以从给定的序列/集合以函数/Scala 方式生成多个集合,而无需n在集合中迭代n是标准计数。提前致谢!

4

6 回答 6

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))
于 2013-08-15T19:28:01.627 回答
3

这是相当务实的。对人员列表进行模式匹配,检查每个递归调用中的条件并根据需要添加到结果中,直到列表用尽。结果是 a被塞进 aList中。PersonTuple

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)
于 2013-08-15T21:54:19.237 回答
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] 元组让这个看起来很丑,你可能想为你的结果定义一个类型别名。)

于 2013-08-15T20:16:47.367 回答
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)工作得更好,只要确保不将可变状态泄漏到函数或类之外。可读性的好处将使纯度的损失变得值得。

于 2013-08-16T01:33:53.937 回答
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]
于 2013-08-16T15:55:07.047 回答
0

对我来说,最惯用的方法是使用FactoryandBuilder模式,尽管它需要一些样板代码。像catshapeless之类的一些库可能会对您有所帮助,但我并不精通它们。

你可以写:

val Seq(males, aNames, seniors) = persons.to(
  Factories.tee(
    Factories.filter(List, (_:Person).gender=='m'),
    Factories.filter(Set,  (_:Person).name.toLowerCase.startsWith("a")),
    Factories.filter(Seq,  (_:Person).age > 50)
  )
)

如果您定义了所有这些样板:

object Factories {
  class TeeFactory[-E, +C](factories: Seq[Factory[E, C]]) extends Factory[E, Seq[C]] {
    override def fromSpecific(it: IterableOnce[E]): Seq[C] = (newBuilder ++= it).result()
    override def newBuilder: Builder[E, Seq[C]] = new TeeBuilder(factories.map(_.newBuilder))
  }

  class TeeBuilder[-E, +C](builders: Seq[Builder[E, C]])
      extends Builder[E, Seq[C]] {
    override def addOne(e: E): this.type = {
      builders.foreach(_.addOne(e))
      this
    }
    override def clear(): Unit    = builders.foreach(_.clear())
    override def result(): Seq[C] = builders.map(_.result())
  }

  class FilterFactory[-E, +C](factory: Factory[E, C], predicate: E => Boolean)
      extends Factory[E, C] {
    override def fromSpecific(it: IterableOnce[E]): C = (newBuilder ++= it).result()
    override def newBuilder = new FilterBuilder(factory.newBuilder, predicate)
  }

  class FilterBuilder[-E, +C](builder: Builder[E, C], predicate: E => Boolean)
      extends Builder[E, C] {
    override def addOne(e: E): this.type = {
      if (predicate(e))
        builder += e
      this
    }
    override def clear(): Unit = builder.clear()
    override def result(): C   = builder.result()
  }

  def tee[E, C](fs: Factory[E, C]*) = new TeeFactory(fs)
  def filter[E, C](f: Factory[E, C], predicate: E => Boolean) = new FilterFactory(f, predicate)
  def filter[E, CC[_]](f: IterableFactory[CC], predicate: E => Boolean) = new FilterFactory(f, predicate)
}
于 2021-12-01T14:08:21.670 回答