2013-08-15 11 views
9

Attualmente sto provando a convertire codice Java in codice Scala. La sfida è assicurarsi che il codice Scala convertito non finisca per fare qualcosa di molto inefficiente se confrontato con quello originale di Java. Ad es. quando si cerca di convertire il codice seguente:Singolo iterazione => Raccolte di output multiple da Java a 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); 
      }    
     } 
    } 
} 

Poiché si basa su Java mutazione, questo codice sembra logico. Ma, ora voglio convertire questo al suo equivalente Scala senza looping sulla raccolta più volte (3 volte in questo caso).

posso ovviamente usare mutabile List dalla libreria Scala e ottenere la stessa cosa come fatta da Java, ma si stava chiedendo se fosse possibile generare più raccolte da una sequenza data/raccolta in uno Scala modo funzionale/senza iterando sulla collezione per n volte dove n è il conteggio dei criteri. Grazie in anticipo!

risposta

5

Un modo puramente funzionale e immutabile sarebbe avere una funzione generica che separa raccolte in segmenti, da predicato:

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

Poi un esempio d'uso potrebbe essere:

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

+1, ma wow, sembra complesso. Questo genere di cose è abbastanza comune in Scala-terra? – sasuke

+6

Un approccio più pragmatico è quello di utilizzare collezioni mutabili all'interno del metodo, come mutable.ListBuffer, quindi chiamare result() e restituire le raccolte immutabili. Non è puramente funzionale, ma più leggibile e intuitivo. –

+0

Sì, sto accettando questa risposta perché questo è quello che ho chiesto ma andrà con l'approccio mutevole. Questo perché sembra che non ci dovrebbero essere problemi finché la mutazione è contenuta in un metodo. – sasuke

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

(La tupla Lista [Persona] rende questo aspetto abbastanza brutto, potresti voler definire un alias di tipo per i risultati.)

3

Questo è abbastanza pragmatico. Pattern match sull'elenco di persone, verifica le condizioni in ogni call ricorsiva e aggiungi i risultati secondo necessità fino a quando l'elenco non è esaurito. I risultati sono uno List di Person che viene inserito in uno 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()) 
} 

Testing ...

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

questa soluzione non è puramente funzionale, ma più semplice. ci sono molte situazioni in cui le collezioni mutabili (come ListBuffer) funzionano meglio, ma assicurati di non perdere lo stato mutabile al di fuori della funzione o della classe. i vantaggi della leggibilità faranno valere la perdita di purezza.

+0

+1, grazie. Nel tuo caso non è 'predicates (i)' linear time? – sasuke

+0

sì, i predicati (i) richiedono un tempo lineare, ma non penso che la dimensione dei predicati sia sufficientemente ampia da influire sulle prestazioni. se ti interessa davvero, considera l'uso di Array invece – Septem

+0

Ah, capisco.Un'ultima domanda: in che modo la gente decide tra l'uso di 'ListBuffer' o' ArrayBuffer' o 'MutableList' quando si tratta di utilizzare strutture mutabili contenute in una data funzione? – sasuke

1

Un altro esempio di foldLeft, ma forse un po 'più facile da leggere.

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

+1 per nuova prospettiva. – sasuke

Problemi correlati