有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

单次迭代=>从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<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是标准计数。提前谢谢


共 (6) 个答案

  1. # 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]
    
  2. # 2 楼答案

    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]元组让这看起来很难看,您可能需要为结果定义一个类型别名。)

  3. # 3 楼答案

    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. # 4 楼答案

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

    你可以写:

    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)
    }
    
  5. # 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))
    
  6. # 6 楼答案

    这是相当务实的。对人员列表进行模式匹配,检查每个递归调用中的条件,并根据需要添加到结果中,直到列表用尽。结果是ListPerson被填充到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)