2012-12-14 15 views
10

Jaki jest najlepszy sposób na znalezienie najczęstszego/wspólnego elementu w kolekcji? Na przykład:Odnajdywanie najczęściej występującego/wspólnego elementu w kolekcji?

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

Hmm .. Co można zrobić, to zrobić groupBy a potem map je length, a następnie wybierz jedną z największych. Więc wtedy dostanie:

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 

A potem w końcu wybrać klawisz (4), który mapuje do najwyższego numeru (2). (zagnieżdżone pytanie: jaki jest najlepszy sposób na zrobienie tego?). Ale wydaje się, że za tak prostą operację jest dużo pracy ...?

Czy istnieje lepszy/bardziej idiomatyczny sposób na zrobienie tego?

+3

zagnieżdżona odpowiedź: użyć 'maxBy'. – senia

+0

Pamiętaj, że może istnieć więcej niż jedna wartość, która jest maksymalna. W takim przypadku możesz filtrować mapę według maksymalnej znalezionej wartości. –

Odpowiedz

20

muszę powiedzieć, że:

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

lub po prostu:

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

naprawdę nie wydaje się to dużo pracy dla mnie.

Jeśli martwisz się o koszty budowania list dla każdej wartości, gdy trzeba tylko liczby, można wykonać następujące czynności:

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

lub nawet śledzić maksymalnie as you go, w celu uniknięcia dodatkowego przechodzenie na koniec:

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 

To jest oczywiście znacznie mniej czytelny niż jednej wkładki powyżej, chociaż, więc polecam trzymać się z nimi, chyba że można wykazać (tj z benchmarkingu, nie spekulacje), że są wąskim gardłem w twojej aplikacji.

+0

Nie musisz 'toSeq' przed' maxBy'. – senia

+0

@senia. Ach, jasne. Edytowane. –

1

Nie, myślę, że to najlepszy sposób. Ale to nie jest dużo pracy ...

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

daje

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

następnie, na przykład, można wziąć jej .maxBy(_._2) (edycja:! Dzięki @Travis Brown) i dostać krotki (4,2) (numer, który występuje najwięcej razy i ile razy występuje)

Jeśli jesteś fanem jednej wkładki:

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

Twój jedyny liniowiec działa tylko tutaj, ponieważ "4" również jest największą wartością. Potrzebujesz 'maxBy' (zobacz moją odpowiedź). –

+0

@TravisBrown: masz rację, dzięki! –

+0

Podany przez ciebie jeden liner pokazuje wadę tego podejścia. 1 i 4 są najczęściej wartościami, ale podano tylko 4. –

2

I nie sądzę, że to jest naprawdę każdy ładniejszy, ale można to zrobić:

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

Nie najpiękniejszy rozwiązanie.Co chcesz jest jakiś sposób na wykorzystanie maxBy na liście, a następnie odwołać się do listy tak:

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

Jestem zwolennikiem wyboru czytelności w porównaniu z małymi przyrostami wydajności w większości przypadków, ale nie jestem pewien, czy rozwiązanie kwadratowe dla problemu podrzędnego jest zawsze dobrym pomysłem. –

0

inny wariant:

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 
Powiązane problemy