วิธีที่ดีที่สุดในการเปลี่ยนคอลเล็กชันให้เป็นแบบแผนที่โดยคีย์


165

หากฉันมีคอลเลกชันcประเภทTและมีสถานที่ให้บริการpบนT(จากประเภทPพูด) เป็นวิธีที่ดีที่สุดในการทำแผนที่โดยแยกแตกคีย์คืออะไร?

val c: Collection[T]
val m: Map[P, T]

วิธีหนึ่งคือสิ่งต่อไปนี้:

m = new HashMap[P, T]
c foreach { t => m add (t.getP, t) }

แต่ตอนนี้ฉันต้องการแผนที่ที่ไม่แน่นอน มีวิธีที่ดีกว่าในการทำเช่นนี้เพื่อให้เป็นหนึ่งในสายและฉันจบลงด้วยแผนที่ที่ไม่เปลี่ยนรูป ? (เห็นได้ชัดว่าฉันสามารถเปลี่ยนสิ่งต่าง ๆ ข้างต้นให้กลายเป็นยูทิลิตี้ห้องสมุดอย่างที่ฉันต้องการใน Java แต่ฉันสงสัยว่าใน Scala ไม่มีความจำเป็น)

คำตอบ:


232

คุณสามารถใช้ได้

c map (t => t.getP -> t) toMap

แต่ระวังว่าต้องใช้ 2 traversals


8
ฉันยังคงต้องการคำแนะนำของฉันใน trac ของ a Traversable[K].mapTo( K => V)และTraversable[V].mapBy( V => K)ดีกว่า!
oxbow_lakes

7
โปรดทราบว่านี่เป็นการดำเนินการกำลังสอง แต่จะเหมือนกันสำหรับตัวแปรอื่น ๆ ส่วนใหญ่ที่ระบุที่นี่ การดูซอร์สโค้ดของ scala.collection.mutable.MapBuilder ฯลฯ ดูเหมือนว่าสำหรับ tuple แต่ละอันจะมีการสร้างแผนที่ที่ไม่เปลี่ยนรูปแบบใหม่ซึ่งเพิ่ม tuple
jcsahnwaldt Reinstate Monica

30
ในเครื่องของฉันสำหรับรายการที่มีองค์ประกอบ 500,000 รหัส Scala นี้ช้ากว่าวิธี Java แบบตรงไปข้างหน้าประมาณ 20 เท่า (สร้าง HashMap ด้วยขนาดที่เหมาะสมวนรอบรายการใส่องค์ประกอบลงในแผนที่) สำหรับ 5,000 องค์ประกอบ Scala นั้นช้ากว่าประมาณ 8 เท่า วิธีวนลูปที่เขียนใน Scala นั้นเร็วกว่ารุ่น toMap ประมาณ 3 เท่า แต่ยังคงช้ากว่า Java อยู่ระหว่าง 2 ถึง 7 เท่า
jcsahnwaldt Reinstate Monica

8
คุณช่วยจัดหาแหล่งทดสอบให้กับชุมชน SO ได้หรือไม่ ขอบคุณ.
user573215

8
แทนที่cด้วยc.iteratorเพื่อหลีกเลี่ยงการสร้างคอลเลกชันระดับกลาง
ghik

21

คุณสามารถสร้างแผนที่ที่มีจำนวนตัวแปรอันดับ ดังนั้นใช้เมธอด map บนชุดสะสมเพื่อแปลงเป็นชุดของ tuples จากนั้นใช้เคล็ดลับ: _ * เพื่อแปลงผลลัพธ์ให้เป็นอาร์กิวเมนต์แบบแปรผัน

scala> val list = List("this", "maps", "string", "to", "length") map {s => (s, s.length)}
list: List[(java.lang.String, Int)] = List((this,4), (maps,4), (string,6), (to,2), (length,6))

scala> val list = List("this", "is", "a", "bunch", "of", "strings")
list: List[java.lang.String] = List(this, is, a, bunch, of, strings)

scala> val string2Length = Map(list map {s => (s, s.length)} : _*)
string2Length: scala.collection.immutable.Map[java.lang.String,Int] = Map(strings -> 7, of -> 2, bunch -> 5, a -> 1, is -> 2, this -> 4)

5
ฉันได้อ่านเกี่ยวกับสกาล่ามาแล้ว> 2 สัปดาห์และทำงานผ่านตัวอย่างและไม่เคยเห็นสัญลักษณ์ ": _ *" ครั้งนี้! ขอบคุณมากสำหรับความช่วยเหลือของคุณ
oxbow_lakes

เพียงสำหรับบันทึกที่ฉันสงสัยว่าทำไมเราต้องแม่นยำที่ว่านี้เป็นลำดับที่มี _ แผนที่ยังคงแปลงกลับรายการ tuple ที่นี่ ดังนั้นทำไม _ ? ฉันหมายถึงมันใช้งานได้ แต่ฉันต้องการที่จะเข้าใจประเภทของคำอธิบายที่นี่
MaatDeamon

1
นี่มีประสิทธิภาพมากกว่าวิธีอื่นหรือไม่?
Jus12

16

นอกเหนือจากวิธีแก้ปัญหาของ @James Iry แล้วยังสามารถทำสิ่งนี้ได้โดยใช้การพับ ฉันสงสัยว่าโซลูชันนี้เร็วกว่าวิธี tuple เล็กน้อย (มีการสร้างวัตถุขยะน้อยลง):

val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }

ฉันจะลอง (ฉันแน่ใจว่ามันใช้งานได้ :-) เกิดอะไรขึ้นกับฟังก์ชัน "(m, s) => m (s) = s.length" ฉันได้เห็นตัวอย่าง foldLeft ทั่วไปด้วยผลรวมและฟังก์ชั่น "_ + _"; นี่คือความสับสนมากขึ้น! ฟังก์ชั่นดูเหมือนจะคิดว่าฉันมี tuple (m, s) อยู่แล้วซึ่งฉันไม่ได้รับจริงๆ
oxbow_lakes

2
ชายสกาล่าแปลกไปแล้ว!
missingfaktor

8
@Daniel ฉันลองใช้รหัสของคุณ แต่ปรากฏข้อผิดพลาดต่อไปนี้: "การปรับปรุงค่าไม่ใช่สมาชิกของ scala.collection.immutable.Map [String, Int]" โปรดอธิบายรหัสวิธีการใช้งานรหัสนี้?
mr.boyfox

1
ดูเหมือนจะไม่ทำงานสำหรับฉันอย่างใดอย่างหนึ่ง "แอปพลิเคชันไม่รับพารามิเตอร์"
jayunit100

7
list.foldLeft(Map[String,Int]()) { (m,s) => m + (s -> s.length) }รุ่นไม่เปลี่ยนรูป: โปรดทราบว่าหากคุณต้องการใช้เครื่องหมายจุลภาคเพื่อสร้าง tuple ((s, s.length))ที่คุณจะต้องคู่พิเศษของวงเล็บ:
เคลวิน

11

สิ่งนี้สามารถดำเนินการได้อย่างไร้ที่ติและมีการสำรวจเส้นทางเดียวโดยการพับผ่านคอลเล็กชันดังต่อไปนี้

val map = c.foldLeft(Map[P, T]()) { (m, t) => m + (t.getP -> t) }

วิธีแก้ปัญหาทำงานได้เนื่องจากการเพิ่มลงในแผนที่ที่ไม่เปลี่ยนรูปแบบจะส่งคืนแผนที่ที่ไม่เปลี่ยนรูปแบบใหม่ด้วยรายการเพิ่มเติมและค่านี้ทำหน้าที่เป็นตัวสะสมผ่านการดำเนินการพับ

ข้อเสียคือความเรียบง่ายของรหัสเมื่อเทียบกับประสิทธิภาพ ดังนั้นสำหรับคอลเลกชันขนาดใหญ่วิธีนี้อาจจะเหมาะกว่าการใช้ 2 การใช้งานข้ามเช่นการประยุกต์ใช้และmaptoMap


8

วิธีแก้ไขปัญหาอื่น (อาจใช้ไม่ได้กับทุกประเภท)

import scala.collection.breakOut
val m:Map[P, T] = c.map(t => (t.getP, t))(breakOut)

หลีกเลี่ยงการสร้างรายการตัวกลางข้อมูลเพิ่มเติมที่นี่: Scala 2.8 breakOut


7

สิ่งที่คุณพยายามจะทำคือการไม่ได้กำหนดไว้เล็กน้อย
จะเกิดอะไรขึ้นหากสินค้าสองรายการขึ้นไปมีcส่วนร่วมเหมือนกันp? ไอเท็มใดจะถูกแม็พกับสิ่งนั้นpในแผนที่

วิธีที่แม่นยำยิ่งขึ้นในการดูสิ่งนี้คือการให้แผนที่ระหว่างpและcรายการทั้งหมดที่มี:

val m: Map[P, Collection[T]]

สามารถทำได้อย่างง่ายดายด้วยgroupBy :

val m: Map[P, Collection[T]] = c.groupBy(t => t.p)

หากคุณยังต้องการแผนที่ดั้งเดิมอยู่คุณสามารถแผนที่pไปยังแผนที่แรกtที่มี:

val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) =>  p -> ts.head }

1
หนึ่งบิดที่มีประโยชน์เกี่ยวกับเรื่องนี้คือการใช้แทนcollect เช่น:map c.group(t => t.p) collect { case (Some(p), ts) => p -> ts.head }วิธีนี้คุณสามารถทำสิ่งต่าง ๆ เช่นแผนที่แบนเมื่อคุณคีย์เป็นตัวเลือก [_]
healsjnr

@healsjnr แน่นอนว่านี่สามารถพูดได้สำหรับแผนที่ใด ๆ มันไม่ใช่ประเด็นหลักที่นี่
Eyal Roth

1
คุณสามารถใช้.mapValues(_.head)แทนแผนที่
lex82

2

นี่อาจไม่ใช่วิธีที่มีประสิทธิภาพที่สุดในการเปลี่ยนรายการเป็นแผนที่ แต่ทำให้รหัสการโทรอ่านได้ง่ายขึ้น ฉันใช้การแปลงโดยนัยเพื่อเพิ่มวิธีmapByไปยังรายการ:

implicit def list2ListWithMapBy[T](list: List[T]): ListWithMapBy[T] = {
  new ListWithMapBy(list)
}

class ListWithMapBy[V](list: List[V]){
  def mapBy[K](keyFunc: V => K) = {
    list.map(a => keyFunc(a) -> a).toMap
  }
}

ตัวอย่างรหัสการโทร:

val list = List("A", "AA", "AAA")
list.mapBy(_.length)                  //Map(1 -> A, 2 -> AA, 3 -> AAA)

โปรดทราบว่าเนื่องจากการแปลงโดยนัยรหัสผู้โทรต้องนำเข้า implicitConversions ของ scala


2
c map (_.getP) zip c

ทำงานได้ดีและเป็นสัญชาตญาณมาก


8
กรุณาเพิ่มรายละเอียด
Syeda Zunaira

2
ฉันขอโทษ. แต่นี่เป็นคำตอบสำหรับคำถามที่ว่า "วิธีที่ดีที่สุดในการเปลี่ยนคอลเล็กชันให้เป็นแบบแผนที่โดยใช้คีย์" อย่างที่ Ben Lings เป็น
JörgBächtiger

1
และเบ็นไม่ได้อธิบายอะไรเลยเหรอ?
shinzou

1
สิ่งนี้จะสร้างสองรายการและรวมเป็น "แผนที่" โดยใช้องค์ประกอบcเป็นคีย์ (เรียงลำดับ) หมายเหตุ "map" เนื่องจากการรวบรวมผลลัพธ์ไม่ใช่ scala Mapแต่สร้างรายการอื่น / ซ้ำได้ของ tuples ... แต่ผลเหมือนกันสำหรับวัตถุประสงค์ของ OP ฉันจะไม่ลดความเรียบง่าย แต่ไม่มีประสิทธิภาพเท่าfoldLeftโซลูชันหรือไม่ใช่ คำตอบที่แท้จริงสำหรับคำถาม "แปลงเป็นคอลเล็กชันให้เป็นแผนที่โดยใช้คีย์"
Dexter Legaspi


1

สำหรับสิ่งที่คุ้มค่าต่อไปนี้เป็นวิธีที่ไม่มีจุดหมายสองวิธี:

scala> case class Foo(bar: Int)
defined class Foo

scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._

scala> val c = Vector(Foo(9), Foo(11))
c: scala.collection.immutable.Vector[Foo] = Vector(Foo(9), Foo(11))

scala> c.map(((_: Foo).bar) &&& identity).toMap
res30: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))

scala> c.map(((_: Foo).bar) >>= (Pair.apply[Int, Foo] _).curried).toMap
res31: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))

ยิ่งไปกว่านั้นนี่คือลักษณะที่ทั้งสองจะมองใน Haskell: Map.fromList $ map (bar &&& id) c, Map.fromList $ map (bar >>= (,)) c.
missingfaktor

-1

สิ่งนี้ใช้ได้กับฉัน:

val personsMap = persons.foldLeft(scala.collection.mutable.Map[Int, PersonDTO]()) {
    (m, p) => m(p.id) = p; m
}

แผนที่จะต้องไม่แน่นอนและจะต้องส่งคืนแผนที่เนื่องจากการเพิ่มลงในแผนที่ที่ไม่แน่นอนไม่ได้ส่งคืนแผนที่


1
ที่จริงแล้วมันสามารถดำเนินการได้อย่างไม่เปลี่ยนแปลงดังนี้: val personsMap = persons.foldLeft(Map[Int, PersonDTO]()) { (m, p) => m + (p.id -> p) }แผนที่สามารถเปลี่ยนรูปไม่ได้ดังที่ได้กล่าวไว้ข้างต้นเนื่องจากการเพิ่มเข้าไปในแผนที่ที่ไม่เปลี่ยนรูปนั้นจะส่งคืนแผนที่ที่ไม่เปลี่ยนรูปแบบใหม่ด้วยรายการเพิ่มเติม ค่านี้ทำหน้าที่เป็นตัวสะสมผ่านการดำเนินการพับ
RamV13

-2

ใช้ map () กับคอลเล็กชันตามด้วย toMap

val map = list.map(e => (e, e.length)).toMap

3
สิ่งนี้แตกต่างจากคำตอบที่ส่งและยอมรับเมื่อ 7 ปีที่แล้ว?
jwvh

-4

หากแปลงจาก Json String (อ่านไฟล์ json) เป็น scala Map

import spray.json._
import DefaultJsonProtocol._

val jsonStr = Source.fromFile(jsonFilePath).mkString
val jsonDoc=jsonStr.parseJson
val map_doc=jsonDoc.convertTo[Map[String, JsValue]]

// Get a Map key value
val key_value=map_doc.get("key").get.convertTo[String]

// If nested json, re-map it.
val key_map=map_doc.get("nested_key").get.convertTo[Map[String, JsValue]]
println("Nested Value " + key_map.get("key").get)

คำถามอายุ 11 ปีนี้ไม่ถามอะไรเกี่ยวกับ JSON คำตอบของคุณไม่ถูกต้อง
jwvh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.