2009-08-11 41 views
28

Tôi có một Danh sách Bản đồ [Chuỗi, Đôi] và tôi muốn hợp nhất nội dung của chúng thành một Bản đồ [Chuỗi, Đôi]. Làm thế nào tôi nên làm điều này một cách thành ngữ? Tôi tưởng tượng rằng tôi sẽ có thể làm điều này với một lần. Một cái gì đó như:Scala: cách hợp nhất một bộ sưu tập Bản đồ

val newMap = Map[String, Double]() /: listOfMaps { (accumulator, m) => ... } 

Hơn nữa, tôi muốn xử lý các va chạm chính theo cách tổng quát. Tức là, nếu tôi thêm một khóa vào bản đồ đã tồn tại, tôi sẽ có thể chỉ định một hàm trả về một Double (trong trường hợp này) và lấy giá trị hiện tại cho khóa đó, cộng với giá trị mà tôi đang cố thêm vào . Nếu khóa chưa tồn tại trong bản đồ, thì chỉ cần thêm nó và giá trị của nó không bị thay đổi.

Trong trường hợp cụ thể, tôi muốn xây dựng một Map [String, Double] duy nhất sao cho nếu bản đồ đã chứa khóa, thì Double sẽ được thêm vào giá trị bản đồ hiện có.

Tôi đang làm việc với các bản đồ có thể thay đổi trong mã cụ thể của mình, nhưng tôi quan tâm đến các giải pháp chung chung hơn, nếu có thể.

Trả lời

23

Làm thế nào về vấn đề này một:

def mergeMap[A, B](ms: List[Map[A, B]])(f: (B, B) => B): Map[A, B] = 
    (Map[A, B]() /: (for (m <- ms; kv <- m) yield kv)) { (a, kv) => 
    a + (if (a.contains(kv._1)) kv._1 -> f(a(kv._1), kv._2) else kv) 
    } 

val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4)) 
val mm = mergeMap(ms)((v1, v2) => v1 + v2) 

println(mm) // prints Map(hello -> 5.5, world -> 2.2, goodbye -> 3.3) 

Và nó hoạt động trong cả hai 2.7.5 và 2.8.0.

+0

Đây chính là cách tôi đã cố gắng thực hiện nó ban đầu. Tôi đã không nghĩ rằng để đặt cho hiểu-có - Tôi vẫn nhận được sử dụng để sử dụng chúng như thế này, nhưng nó có ý nghĩa. Trong trường hợp này, tôi có thể thấy nó giống như sự hiểu biết danh sách của Python, cái mà tôi thấy thoải mái hơn nhiều. Cũng giống như việc sử dụng kết quả mang nếu biểu thức bên trong cuộc gọi đến a. +(). – Jeff

+0

câu trả lời rõ ràng. kudo –

37

Vâng, bạn có thể làm:

mapList reduce (_ ++ _) 

trừ các yêu cầu đặc biệt đối với va chạm.

Vì bạn làm có mà yêu cầu đặc biệt, có lẽ tốt nhất là nên làm một cái gì đó như thế này (2.8):

def combine(m1: Map, m2: Map): Map = { 
    val k1 = Set(m1.keysIterator.toList: _*) 
    val k2 = Set(m2.keysIterator.toList: _*) 
    val intersection = k1 & k2 

    val r1 = for(key <- intersection) yield (key -> (m1(key) + m2(key))) 
    val r2 = m1.filterKeys(!intersection.contains(_)) ++ m2.filterKeys(!intersection.contains(_)) 
    r2 ++ r1 
} 

Sau đó, bạn có thể thêm phương pháp này để các lớp bản đồ thông qua mô hình Pimp My Library, và sử dụng nó trong ví dụ ban đầu thay vì "++":

class CombiningMap(m1: Map[Symbol, Double]) { 
    def combine(m2: Map[Symbol, Double]) = { 
    val k1 = Set(m1.keysIterator.toList: _*) 
    val k2 = Set(m2.keysIterator.toList: _*) 
    val intersection = k1 & k2 
    val r1 = for(key <- intersection) yield (key -> (m1(key) + m2(key))) 
    val r2 = m1.filterKeys(!intersection.contains(_)) ++ m2.filterKeys(!intersection.contains(_)) 
    r2 ++ r1 
    } 
} 

// Then use this: 
implicit def toCombining(m: Map[Symbol, Double]) = new CombiningMap(m) 

// And finish with: 
mapList reduce (_ combine _) 

trong khi điều này được viết bằng 2,8, vì vậy keysIterator trở thành keys cho 2.7, filterKeys có thể cần phải được viết về 01.239.và map, & trở thành **, v.v., không được quá khác nhau.

+1

Kinda đánh bại điểm phớt lờ yêu cầu đó. – Jeff

+0

Đó là lý do tại sao tôi mở rộng nó. –

+0

Với Scala hiện đại: val k1 = m1.keysIterator.toSet – qerub

2

Thú vị, noodling xung quanh với điều này một chút, tôi đã nhận như sau (trên 2.7.5):

Maps

chung:

def mergeMaps[A,B](collisionFunc: (B,B) => B)(listOfMaps: Seq[scala.collection.Map[A,B]]): Map[A, B] = { 
    listOfMaps.foldLeft(Map[A, B]()) { (m, s) => 
     Map(
     s.projection.map { pair => 
     if (m contains pair._1) 
      (pair._1, collisionFunc(m(pair._1), pair._2)) 
     else 
      pair 
     }.force.toList:_*) 
    } 
    } 

Nhưng người đàn ông, đó là gớm ghiếc với dự báo và buộc và toList và không có gì. Câu hỏi riêng: cách tốt nhất để giải quyết vấn đề đó trong màn hình đầu tiên là gì?

Đối Maps có thể thay đổi, đó là những gì tôi đã đối phó với trong mã của tôi, và với một giải pháp ít Nói chung, tôi nhận điều này:

def mergeMaps[A,B](collisionFunc: (B,B) => B)(listOfMaps: List[mutable.Map[A,B]]): mutable.Map[A, B] = { 
    listOfMaps.foldLeft(mutable.Map[A,B]()) { 
     (m, s) => 
     for (k <- s.keys) { 
     if (m contains k) 
      m(k) = collisionFunc(m(k), s(k)) 
     else 
      m(k) = s(k) 
     } 
     m 
    } 
    } 

Đó có vẻ hơi bụi chút, nhưng sẽ chỉ làm việc với mutable Bản đồ như được viết. Thật thú vị, lần đầu tiên tôi đã thử ở trên (trước khi tôi hỏi câu hỏi) bằng cách sử dụng /: thay vì foldLeft, nhưng tôi đã nhận được lỗi loại. Tôi nghĩ /: và foldLeft về cơ bản là tương đương, nhưng trình biên dịch vẫn phàn nàn rằng tôi cần các loại rõ ràng cho (m, s). Có chuyện gì thế?

+0

Bạn không cần sử dụng 'force' ở đây, bởi vì' toList' là nghiêm ngặt. –

+0

Đối với 'foldLeft' so với' /: ', bạn nhận ra đối tượng và đối số đầu tiên được hoán đổi giữa chúng? Biểu thức 'x foldLeft y' tương đương với' y /: x'. Ngoài ra, có một loạt các vấn đề cú pháp. Về cơ bản, bạn * có * để viết '(y /: x) (biểu thức gấp)', trong khi 'foldLeft' có thể được sử dụng như' x.foldLeft (y) (biểu thức gấp) '. –

+0

Vâng, tôi biết về các phương thức kết thúc bằng việc: trao đổi đối tượng với đối số. Đó là cách tôi đã viết ví dụ trong câu hỏi. Tôi đã quên đặt y /: x trong parens, tuy nhiên, và tôi đặt cược đó là một vấn đề. Cảm ơn! – Jeff

3

tôi đọc câu hỏi này một cách nhanh chóng vì vậy tôi không chắc chắn nếu tôi là thiếu một cái gì đó (như nó đã làm việc cho 2.7.x hoặc không scalaz):

import scalaz._ 
import Scalaz._ 
val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4)) 
ms.reduceLeft(_ |+| _) 
// returns Map(goodbye -> 3.3, hello -> 5.5, world -> 2.2) 

Bạn có thể thay đổi định nghĩa monoid cho đôi và nhận được một cách khác để tích lũy các giá trị, ở đây nhận được tối đa:

implicit val dbsg: Semigroup[Double] = semigroup((a,b) => math.max(a,b)) 
ms.reduceLeft(_ |+| _) 
// returns Map(goodbye -> 3.3, hello -> 4.4, world -> 2.2) 
+0

+1, mặc dù tôi muốn viết 'ms.suml', ngắn gọn hơn và có thêm lợi thế là không ném ngoại lệ thời gian chạy vào danh sách trống. –

+0

@TravisBrown, vâng, rất nhiều chức năng tiện lợi trong scalaz; mặc dù 'suml' có thể là chỉ số 7? Tôi chỉ thấy 'sumr' trong 6.x. – huynhjl

0

một oneliner helper-func, mà sử dụng đọc gần như sạch như sử dụng scalaz:

def mergeMaps[K,V](m1: Map[K,V], m2: Map[K,V])(f: (V,V) => V): Map[K,V] = 
    (m1 -- m2.keySet) ++ (m2 -- m1.keySet) ++ (for (k <- m1.keySet & m2.keySet) yield { k -> f(m1(k), m2(k)) }) 

val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4)) 
ms.reduceLeft(mergeMaps(_,_)(_ + _)) 
// returns Map(goodbye -> 3.3, hello -> 5.5, world -> 2.2) 

để có thể đọc cuối cùng bọc nó trong một kiểu tùy chỉnh ngầm:

class MyMap[K,V](m1: Map[K,V]) { 
    def merge(m2: Map[K,V])(f: (V,V) => V) = 
    (m1 -- m2.keySet) ++ (m2 -- m1.keySet) ++ (for (k <- m1.keySet & m2.keySet) yield { k -> f(m1(k), m2(k)) }) 
} 
implicit def toMyMap[K,V](m: Map[K,V]) = new MyMap(m) 

val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4)) 
ms reduceLeft { _.merge(_)(_ + _) } 
2

tôi đã viết một bài đăng blog về vấn đề này, check it out:

http://www.nimrodstech.com/scala-map-merge/

cơ bản sử dụng scalaz nhóm bán bạn có thể đạt được điều này khá dễ dàng

trông giống như sau:

import scalaz.Scalaz._ 
    listOfMaps reduce(_ |+| _) 
+0

Bạn thực sự có thể sử dụng 'listOfMaps.suml'; nó nên làm điều tương tự. từ những gì tôi hiểu nó có nghĩa là sumLeft, nơi nó về cơ bản chạy 'reduceLeft (_ | + | _)' – JBarber

17

Tôi ngạc nhiên không ai đưa ra giải pháp này được nêu ra:

myListOfMaps.flatten.toMap 

Liệu chính xác những gì bạn cần:

  1. Kết hợp danh sách để một bản đồ đơn
  2. Cỏ dại ra bất kỳ trùng lặp các phím

Ví dụ:

scala> List(Map('a -> 1), Map('b -> 2), Map('c -> 3), Map('a -> 4, 'b -> 5)).flatten.toMap 
res7: scala.collection.immutable.Map[Symbol,Int] = Map('a -> 4, 'b -> 5, 'c -> 3) 

flatten biến danh sách các bản đồ vào một danh sách phẳng của các bộ, toMap biến danh sách các hàng vào một bản đồ với tất cả các phím trùng lặp loại bỏ

+2

Đây chính xác là những gì tôi cần, nhưng không tính giá trị cho các khóa trùng lặp như OP yêu cầu. –

+0

Hoặc bạn có thể sử dụng flatMap – wbmrcb

Các vấn đề liên quan