2012-12-14 11 views
10

Qual è il modo migliore per trovare l'elemento più frequente/comune in una raccolta? Per esempio:Trovare l'elemento più frequente/comune in una raccolta?

list = List(1, 3, 4, 4, 2) 
list.mostCommon // => 4  !! This is what I want !! 

Hmm .. Che cosa si potrebbe fare è di fare un primo groupBy e poi li map da length, e quindi selezionare il più grande. Allora si otterrebbe:

Map(1 -> List(1), 4 -> List(4, 4), 3 -> List(3), 2 -> List(2)) 
(...) 
Map(1 -> 1, 4 -> 2, 3 -> 1, 2 -> 1) // mapped by length. 4 -> 2 since there's two 4s 

E poi, alla fine, scegliere il tasto (4) che mappa il numero più alto (2). (domanda nidificata: qual è il modo migliore per farlo?). Ma sembra un sacco di lavoro per un'operazione così semplice ..?

Esiste un modo migliore/più idiomatico per farlo?

+3

risposta nidificato: usare 'maxBy'. – senia

+0

Ricorda che potrebbe esserci più di un valore che è il massimo, nel qual caso puoi filtrare la mappa in base al valore massimo trovato. –

risposta

20

devo dire che:

list.groupBy(identity).mapValues(_.size).maxBy(_._2)._1 

O semplicemente:

list.groupBy(identity).maxBy(_._2.size)._1 

realtà non sembrare che molto lavoro per me.

Se siete preoccupati per il sovraccarico di costruire le liste per ogni valore quando hai solo bisogno conta, si potrebbe procedere come segue:

list.foldLeft(Map.empty[Int, Int].withDefaultValue(0)) { 
    case (m, v) => m.updated(v, m(v) + 1) 
}.maxBy(_._2)._1 

O anche tenere traccia del massimo, come si va, al fine di evitare l'attraversamento extra alla fine:

list.foldLeft(
    Map.empty[Int, Int].withDefaultValue(0), -1 -> Double.NegativeInfinity 
) { 
    case ((m, (maxV, maxCount)), v) => 
    val count = m(v) + 1 
    if (count > maxCount) (m.updated(v, count), v -> count) 
    else (m.updated(v, count), maxV -> maxCount) 
}._2._1 

Questo è ovviamente molto meno leggibile rispetto alle battute di cui sopra, però, così mi consiglia di attaccare con loro a meno che non si può mostrare (vale a dire, con l'analisi comparativa, non speculazione) che sono un collo di bottiglia nella vostra applicazione.

+0

Non è necessario 'toSeq' prima di' maxBy'. – senia

+0

@senia. Ah, certo. Modificato. –

1

No, penso che sia il modo migliore. Ma non è un sacco di lavoro ...

list.groupBy(identity).mapValues(_.size) 

ti dà

Map(2 -> 1, 4 -> 2, 1 -> 2, 3 -> 1) 

poi, per esempio, si può prendere il suo .maxBy(_._2) (A CURA: grazie @Travis Brown) e ottenere una tupla (4,2) (il numero che si verifica il maggior numero di volte e quante volte si verifica)

Se sei un fan di battute:

scala> List(1, 3, 4, 1, 4, 2).groupBy(identity).mapValues(_.size).maxBy(_._2) 
res0: (Int, Int) = (4,2) 
+0

Il tuo one-liner funziona solo qui perché anche il valore "4" è il più grande. Hai bisogno di 'maxBy' (vedi la mia risposta). –

+0

@TravisBrown: hai ragione, grazie! –

+0

L'one-liner che fornisci mostra il difetto di questo approccio. 1 e 4 sono entrambi i valori più frequenti, ma solo 4 sono dati. –

2

non credo che questo è davvero più gentile, ma si potrebbe fare questo:

List(1, 3, 4, 4, 2).groupBy(identity).maxBy(_._2.size)._1 

non la soluzione più bella.Quello che vogliamo è un modo per utilizzare maxBy sulla lista e quindi fare riferimento alla lista in questo modo:

val someList = List(1, 3, 4, 4, 2) 
someList.maxBy(x => list.count(_ == x)) 
+2

Sono un sostenitore della scelta della leggibilità rispetto ai piccoli guadagni in termini di prestazioni nella maggior parte dei casi, ma non sono sicuro che una soluzione quadratica per un problema subquadratico sia sempre una buona idea. –

0

un'altra variante:

val x = List(1, 3, 4, 1, 4, 2, 5, 5, 5) 
x.distinct.foldLeft((0,0))((a, b) => { 
    val cnt = x.count(_ == b); 
    if (cnt > a._1) (cnt, b) else a 
})._2 
Problemi correlati