Câu hỏi Tại sao groupBy trong Scala thay đổi thứ tự các mục của danh sách?


Mã này là từ một bảng tính Scala:

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

Bạn sẽ thấy rằng groupBy trả về một bản đồ, nhưng thứ tự của các phần tử bây giờ khác với cách mà chúng đã có trước đây. Bất kỳ ý tưởng nào tại sao điều này xảy ra và cách tốt nhất để tránh điều này là gì?


20
2018-01-21 07:31


gốc


Xin chào @JacobusR. Câu hỏi của bạn được viết tốt so với nhiều câu hỏi khác trên SO, nhưng tôi hy vọng bạn sẽ xem xét nỗ lực nhiều hơn vào định dạng mã trong lần tiếp theo. Nếu bạn hỏi tôi, nó gần như hoàn toàn không thể đọc được, và tôi thường có xu hướng trả lời một câu hỏi nếu tôi không phải mất một phút "phân tích cú pháp" mã đầu tiên :) - fresskoma
Xin chào @ x3ro, một triệu lời xin lỗi! Nó hoàn toàn trượt tâm trí tôi. Tôi đang bận viết câu hỏi và một người nào đó bước vào văn phòng của tôi, vì vậy tôi đã đăng nó lên. - Jack
Oh, và cảm ơn vì đã chỉnh sửa @ x3ro. Nó trông tốt hơn nhiều :-) - Jack
Mặc dù các nhóm kết quả sẽ có các phần tử theo cùng thứ tự như danh sách gốc. Điều đó có được đảm bảo không? Nói cách khác chúng ta có thể có 2 -> Danh sách (E (2, Một Hai), E (2, Hai)) - Ustaman Sangat


Các câu trả lời:


Trừ khi bạn đặc biệt sử dụng một loại phụ của SortedMap, một bản đồ (như một bộ) luôn luôn theo một thứ tự không xác định. Vì "groupBy" không trả về một SortedMap nhưng chỉ có một immutable.Map chung và cũng không sử dụng cơ chế CanBuildFrom, tôi nghĩ rằng không có gì mà bạn có thể làm ở đây.

Bạn có thể tìm thêm về chủ đề này trong câu trả lời cho các câu hỏi tương tự, ví dụ: đây.

Chỉnh sửa:

Nếu bạn muốn chuyển đổi bản đồ sau đó thành một SortedMap (được sắp xếp theo các khóa của nó), bạn có thể làm SortedMap(l.groupBy(_.a).toSeq:_*) (với import scala.collection.immutable.SortedMap). Đừng làm ...toSeq.sortWith(...).toMap bởi vì điều đó sẽ không đảm bảo thứ tự trong bản đồ kết quả.


19
2018-01-21 07:35



Cảm ơn. Để đặt hàng bản đồ tôi đã sử dụng l.groupBy (_. A) .toSeq.sortWith (_._ 1 <_._ 1) .toMap. Không phải là rất thanh lịch, nhưng hey, đó là thứ hai. - Jack
Hoặc là l.groupBy(_._1).mapValues(_.sorted).view.force - Peter Schmitz
@Peter Schmitz: Điều này là không giống nhau. Đoạn mã của bạn sắp xếp các giá trị nhưng vấn đề đặt hàng là với các phím. JacobusR đã làm đúng, ngoại trừ thực tế tái lập lại bản đồ qua toMap. - Régis Jean-Gilles
oh xin lỗi, đúng rồi! - Peter Schmitz
Rant: rất bất tiện sẽ gặp rắc rối khi sắp xếp truy vấn cơ sở dữ liệu chỉ để groupBy hoàn toàn làm sáng tỏ thứ tự, buộc một thứ phải SortedMap nó trở lại với nhau một lần nữa. Đặc biệt đau đớn khi bạn có bộ sưu tập các bộ sưu tập mà bạn cần để groupBy trên. Kết thúc rant, groupBy cũng mạnh mẽ và vô cùng hữu ích. - virtualeyes


Tôi chạy vào tất cả các thời gian khi giao dịch với hồ sơ cơ sở dữ liệu. Cơ sở dữ liệu sắp xếp chúng theo một số khóa nhưng sau đó groupBy sẽ hủy bỏ nó! Vì vậy, tôi đã bắt đầu pimping lớp Sequence với một hàm mà nhóm bằng các phím bằng nhau liên tiếp:

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

Để dùng nó:

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

10
2017-07-16 17:45