Cosa fare con le operazioni per un tipo specifico di raccolta?
Domanda
In diversi luoghi della mia applicazione, devo prendere un Seq[SalesRow]
e restituire a Map[String,SalesRow]
, dove la stringa è il nome di un paese.
Devo usarlo in diversi luoghi. Ad esempio, prendo un elenco di tutti i venditori e ottengo una ripartizione globale delle vendite per paese. Ma in altri luoghi, voglio abbattere le mie vendite entro il mese e poi per paese (quindi Map[Month,Seq[SalesRow]]
diventa Map[Month,Map[String,Seq[SalesRow]]]
) - In altri luoghi, voglio crollare di giorno e poi per paese.
La mia domanda è: dove devo mettere la (piccola) quantità di logica che prende a Seq[SalesRow]
e restituisce una mappa dei paesi alle righe? In questo momento, lo sto mettendo in un metodo oggetto di accompagnamento, SalesRow.byCountry(rows : Seq[SalesReport]
. È ottimale?
Mi è venuta in mente un'idea leggermente più folle, che è quella di creare una conversione implicita da Seq[SalesRow]
a EnhancedSalesRowSeq
, che ha a byCountry
Metodo di istanza. Questo mi piace, perché l'operazione è applicabile a qualsiasi sequenza di venditori.
E 'questa una buona idea?
L'aggiunta della logica all'oggetto compagno è la mia scelta migliore o ci sono opzioni migliori?
Grazie.
Soluzione
Nel caso in cui non siate consapevole della biblioteca viene fornita con un groupBy
funzione. Fondamentalmente dato a Seq[SalesRow]
Ti darà un Map[T, Seq[SalesRow]]
basato su una funzione da SalesRow
a T
.
Quindi, se la tua funzione è facile, puoi facilmente ottenere una mappa. Mi piace la tua idea della seq migliorata in combinazione con il mettere l'implicito nel SalesRow
compagno:
case class SalesRow(val month:Int, val country:String,
val person:String, val amount:Float)
class EnhancedRow(rows: Seq[SalesRow]) {
def byCountry: Map[String, Seq[SalesRow]] =
rows.groupBy(_.country)
def byMonth: Map[Int, Seq[SalesRow]] =
rows.groupBy(_.month)
def byCountryByMonth: Map[String, Map[Int, Seq[SalesRow]]] = byCountry.mapValues(r => new EnhancedRow(rows).byMonth)
}
object SalesRow {
implicit def toEnhanced(rows: Seq[SalesRow]) = new EnhancedRow(rows)
}
object Test {
def main(args:Array[String] = null) {
val seq: Seq[SalesRow] = // ... fill this
println(seq.byCountry)
println(seq.byCountryByMonth)
// same as:
println(seq.byCountry.mapValues(_.byMonth))
}
}
Altri suggerimenti
Se le prestazioni non sono la tua principale preoccupazione, puoi mettere la logica a:
class RichTraversable[A](t: Traversable[A]) {
def toMapBy[B](f: A => B): Map[B,A] = t.map{ e => (f(e),e) }.toMap
}
Quindi, se una conversione implicita puoi girare ogni Seq
in un Map
con un membro che è la chiave.
Ti suggerisco di fare classi incapsulanti:
case class AllSalesTable(rows: Seq[SalesRow]) {
def toSalesByCountry: SalesByCountry
}
case class ContrySalesTable(rows: Seq[SalesRow])
case class SalesByCountry(map: Map[String, CountrySalesTable])
Avere un posto per mettere il metodo è un vantaggio, ma un altro vantaggio è che avrai una maggiore sicurezza di tipo a costo di un semplice ".rows
" qui e li.