2013-01-21 13 views
18

Ten kod jest od Scala Arkusz:Dlaczego groupBy w Scali zmienia kolejność elementów listy?

case class E(a: Int, b: String) 

val l = List(
    E(1, "One"), 
    E(1, "Another One"), 
    E(2, "Two"), 
    E(2, "Another Two"), 
    E(3, "Three") 
) 

l.groupBy(x => x.a)        
// res11: scala.collection.immutable.Map[Int,List[com.dci.ScratchPatch.E]] = 
// Map(
//  2 -> List(E(2,Two), E(2,Another Two)), 
//  1 -> List(E(1,One), E(1,Another One)), 
//  3 -> List(E(3,Three)) 
// ) 

Można zauważyć, że GroupBy zwraca mapę, ale kolejność elementów jest teraz inna, jakie były wcześniej. Masz pomysł, dlaczego tak się dzieje i jak najlepiej tego uniknąć?

+1

Hi @JacobusR. Twoje pytanie jest dobrze napisane w porównaniu z wieloma innymi pytaniami na temat SO, ale mam nadzieję, że następnym razem będziesz zastanawiał się, jak włożyć więcej wysiłku w formatowanie kodu. Jeśli pytasz mnie, to było prawie całkowicie nieczytelne i generalnie jestem bardziej skłonny odpowiedzieć na pytanie, jeśli nie muszę spędzić minuty "parsując" kod pierwszy :) – fresskoma

+0

Hi @ x3ro, milion przeprosin! To całkowicie wymknęło mi się z głowy. Byłem zajęty pisaniem tego pytania i ktoś wszedł do mojego biura, więc właśnie to opublikowałem. – Jack

+0

Aha, i dziękuję za edycję @ x3ro. Wygląda znacznie lepiej :-) – Jack

Odpowiedz

19

Jeśli konkretnie nie używasz podtypu SortedMap, mapa (podobnie jak zestaw) jest zawsze w nieokreślonej kolejności. Ponieważ "groupBy" nie zwraca SortedMap, ale tylko ogólną niezmienną.Map, a także nie korzysta z mechanizmu CanBuildFrom, myślę, że nie ma tu nic, co można tutaj zrobić.

Możesz znaleźć więcej na ten temat w odpowiedziach na podobne pytania, np. here.

Edit:

Jeśli chcesz przekonwertować afterwarts mapy do SortedMap (na zlecenie swoich kluczy), można zrobić SortedMap(l.groupBy(_.a).toSeq:_*) (z import scala.collection.immutable.SortedMap). Nie rób tego ...toSeq.sortWith(...).toMap, ponieważ to nie gwarantuje porządku na wynikowej mapie.

+0

Dzięki. Aby zamówić mapę użyłem l.groupBy (\ _. A) .toSeq.sortWith (_._ 1 <_._ 1) .toMap. Niezbyt elegancko, ale hej, jest poniedziałek. – Jack

+0

Lub 'l.groupBy (_._ 1) .mapValues ​​(_. Sorted) .view.force' –

+1

@Peter Schmitz: To nie to samo. Twój fragment sortuje wartości, ale problem z zamówieniem dotyczy kluczy. JacobusR ma rację, z wyjątkiem faktu ponownego powrotu do mapy poprzez 'toMap'. –

8

Wpadam na to cały czas, gdy mamy do czynienia z rekordami bazy danych. Baza danych sortuje je za pomocą jakiegoś klucza, ale następnie groupBy to cofa! Więc zacząłem stręczycielstwo klasę Sequence z funkcją że grupy wg kolejnych równych klawiszy:

class PimpedSeq[A](s: Seq[A]) { 

    /** 
    * Group elements of the sequence that have consecutive keys that are equal. 
    * 
    * Use case: 
    *  val lst = SQL("SELECT * FROM a LEFT JOIN b ORDER BY a.key") 
    *  val grp = lst.groupConsecutiveKeys(a.getKey) 
    */ 
    def groupConsecutiveKeys[K](f: (A) => K): Seq[(K, List[A])] = { 
    this.s.foldRight(List[(K, List[A])]())((item: A, res: List[(K, List[A])]) => 
     res match { 
     case Nil => List((f(item), List(item))) 
     case (k, kLst) :: tail if k == f(item) => (k, item :: kLst) :: tail 
     case _ => (f(item), List(item)) :: res 
     }) 
    } 
} 

object PimpedSeq { 
    implicit def seq2PimpedSeq[A](s: Seq[A]) = new PimpedSeq(s) 
} 

go używać:

import util.PimpedSeq._ // implicit conversion  
val dbRecords = db.getTheRecordsOrderedBy 
val groups = dbRecords.groupConsecutiveKeys(r => r.getKey) 
Powiązane problemy