簡體   English   中英

單次迭代 => 從 Java 到 Scala 的多個輸出集合

[英]Single iteration => Multiple output collections from Java to Scala

我目前正在嘗試將一些 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 次)。

我當然可以使用 Scala 庫中的可變List並實現與 Java 所做的相同的事情,但想知道是否有可能以函數/Scala 方式從給定的序列/集合生成多個集合,而無需迭代n的集合次數,其中n是標准計數。 提前致謝!

一種純函數和不可變的方法是通過謂詞具有將集合分成桶的泛型函數:

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))
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]元組使這看起來很難看,你可能想為你的結果定義一個類型別名。)

這是相當務實的。 模式匹配人員列表,檢查每個遞歸調用中的條件,並根據需要添加到結果,直到列表用盡。 結果是一個填入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)
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)工作得更好,只需確保不泄漏函數或類之外的可變狀態。 可讀性的好處將使純度的損失值得。

使用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]

對我來說,最慣用的方法是使用FactoryBuilder模式,盡管它需要一些樣板代碼。 一些庫,如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)
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM