แปลงรายการ Scala เป็นทูเปิลหรือไม่


89

ฉันจะแปลงรายการที่มี (พูด) 3 องค์ประกอบเป็นทูเพิลขนาด 3 ได้อย่างไร

ตัวอย่างเช่นสมมติว่าฉันมีval x = List(1, 2, 3)และฉันต้องการแปลงเป็น(1, 2, 3)ไฟล์. ฉันจะทำเช่นนี้ได้อย่างไร?


1
เป็นไปไม่ได้ (ยกเว้น "ด้วยมือ") AFAIK ระบุdef toTuple(x: List[Int]): Rประเภทของ R ควรเป็นอย่างไร?

7
หากไม่จำเป็นต้องทำสำหรับทูเปิลที่มีขนาดตามอำเภอใจ (เช่นวิธีการสมมุติฐานที่นำเสนอด้านบน) ให้พิจารณาx match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) }และสังเกตว่าประเภทผลลัพธ์ได้รับการแก้ไขแล้วที่Tuple3[Int,Int,Int]

2
แม้ว่าสิ่งที่คุณต้องการจะทำจะเป็นไปไม่ได้Listแต่คุณสามารถมองเป็นHListประเภทShapeless ' ซึ่งช่วยให้สามารถแปลงไปและกลับจากสิ่งทอได้ ( github.com/milessabin/… ) อาจใช้ได้กับกรณีการใช้งานของคุณ

คำตอบ:


59

คุณไม่สามารถทำได้ด้วยวิธีที่ปลอดภัย ทำไม? เนื่องจากโดยทั่วไปเราไม่สามารถทราบความยาวของรายการได้จนกว่าจะถึงรันไทม์ แต่ "ความยาว" ของทูเปิลจะต้องถูกเข้ารหัสในประเภทของมันและด้วยเหตุนี้จึงเป็นที่รู้จักในเวลาคอมไพล์ ตัวอย่างเช่น(1,'a',true)มีชนิดซึ่งเป็นน้ำตาล(Int, Char, Boolean) Tuple3[Int, Char, Boolean]เหตุผลที่ทูเปิลมีข้อ จำกัด นี้ก็คือต้องสามารถจัดการกับประเภทที่ไม่เป็นเนื้อเดียวกันได้


มีรายการขนาดคงที่ขององค์ประกอบประเภทเดียวกันหรือไม่ ไม่นำเข้าไลบรารีที่ไม่ได้มาตรฐานเช่นไม่มีรูปร่างแน่นอน

1
@davips ไม่ใช่ว่าฉันรู้ แต่มันง่ายพอที่จะทำด้วยตัวคุณเองเช่นcase class Vec3[A](_1: A, _2: A, _3: A)
Tom Crockett

นี่จะเป็น "ทูเพิล" ขององค์ประกอบประเภทเดียวกันเช่นฉันใช้แผนที่กับมันไม่ได้

1
@davips เช่นเดียวกับประเภทข้อมูลใหม่ที่คุณต้องกำหนดว่าmapetc ทำงานอย่างไร
Tom Crockett

1
ข้อ จำกัด แบบนี้ดูเหมือนจะเป็นตัวบ่งชี้ความไร้ประโยชน์ของประเภทความปลอดภัยมากกว่าสิ่งอื่นใด มันทำให้ฉันนึกถึงคำพูด "เซตว่างมีคุณสมบัติที่น่าสนใจมากมาย"
ely

58

คุณสามารถทำได้โดยใช้ตัวแยกสกาลาและการจับคู่รูปแบบ ( ลิงค์ ):

val x = List(1, 2, 3)

val t = x match {
  case List(a, b, c) => (a, b, c)
}

ซึ่งส่งคืนทูเพิล

t: (Int, Int, Int) = (1,2,3)

นอกจากนี้คุณสามารถใช้ตัวดำเนินการตัวแทนหากไม่แน่ใจเกี่ยวกับขนาดของรายการ

val t = x match {
  case List(a, b, c, _*) => (a, b, c)
}

4
ในบริบทของโซลูชันนี้ข้อร้องเรียนเกี่ยวกับความปลอดภัยของประเภทหมายความว่าคุณอาจได้รับ MatchError เมื่อรันไทม์ หากคุณต้องการคุณสามารถลองจับข้อผิดพลาดนั้นและดำเนินการบางอย่างหากรายการมีความยาวไม่ถูกต้อง คุณสมบัติที่ดีอีกอย่างของโซลูชันนี้คือผลลัพธ์ไม่จำเป็นต้องเป็นทูเปิลอาจเป็นหนึ่งในคลาสที่คุณกำหนดเองหรือการเปลี่ยนแปลงของตัวเลข ฉันไม่คิดว่าคุณจะทำสิ่งนี้กับ non-Lists ได้ (ดังนั้นคุณอาจต้องดำเนินการ. toList กับอินพุต)
Jim Pivarski

คุณสามารถทำได้กับลำดับ Scala ประเภทใดก็ได้โดยใช้ไวยากรณ์ +: นอกจากนี้อย่าลืมเพิ่ม catch-all
ig-dev

48

ตัวอย่างการใช้ไม่มีรูปร่าง :

import shapeless._
import syntax.std.traversable._
val x = List(1, 2, 3)
val xHList = x.toHList[Int::Int::Int::HNil]
val t = xHList.get.tupled

หมายเหตุ: คอมไพลเลอร์ต้องการข้อมูลบางประเภทเพื่อแปลงรายการใน HList ว่าเหตุผลที่คุณต้องส่งข้อมูลประเภทไปยังtoHListเมธอด


19

Shapeless 2.0เปลี่ยนไวยากรณ์บางส่วน นี่คือโซลูชันที่อัปเดตโดยใช้ไม่มีรูปร่าง

import shapeless._
import HList._
import syntax.std.traversable._

val x = List(1, 2, 3)
val y = x.toHList[Int::Int::Int::HNil]
val z = y.get.tupled

ปัญหาหลักคือประเภทสำหรับ. toHListล่วงหน้า โดยทั่วไปแล้วเนื่องจาก tuples มีข้อ จำกัด ในการใช้งานการออกแบบซอฟต์แวร์ของคุณอาจให้บริการได้ดีกว่าด้วยโซลูชันอื่น

อย่างไรก็ตามหากคุณกำลังสร้างรายการแบบคงที่ให้พิจารณาวิธีแก้ปัญหาเช่นนี้และใช้แบบไม่มีรูปร่าง ที่นี่เราสร้าง HList โดยตรงและประเภทพร้อมใช้งานในเวลาคอมไพล์ โปรดจำไว้ว่า HList มีคุณสมบัติทั้งจากรายการและประเภท Tuple กล่าวคือสามารถมีองค์ประกอบที่มีประเภทต่างๆเช่น Tuple และสามารถแมปกับการดำเนินการอื่น ๆ เช่นคอลเล็กชันมาตรฐาน HLists ใช้เวลาสักครู่ในการทำความคุ้นเคยแม้ว่าคุณจะยังคงเหยียบอย่างช้าๆ

scala> import shapeless._
import shapeless._

scala> import HList._
import HList._

scala>   val hlist = "z" :: 6 :: "b" :: true :: HNil
hlist: shapeless.::[String,shapeless.::[Int,shapeless.::[String,shapeless.::[Boolean,shapeless.HNil]]]] = z :: 6 :: b :: true :: HNil

scala>   val tup = hlist.tupled
tup: (String, Int, String, Boolean) = (z,6,b,true)

scala> tup
res0: (String, Int, String, Boolean) = (z,6,b,true)

13

แม้จะมีความเรียบง่ายและไม่เหมาะสำหรับรายการที่มีความยาว แต่ก็ปลอดภัยสำหรับประเภทและคำตอบในกรณีส่วนใหญ่:

val list = List('a','b')
val tuple = list(0) -> list(1)

val list = List('a','b','c')
val tuple = (list(0), list(1), list(2))

ความเป็นไปได้อีกประการหนึ่งเมื่อคุณไม่ต้องการตั้งชื่อรายการหรือทำซ้ำ (ฉันหวังว่าใครบางคนสามารถแสดงวิธีหลีกเลี่ยงส่วน Seq / ส่วนหัว):

val tuple = Seq(List('a','b')).map(tup => tup(0) -> tup(1)).head
val tuple = Seq(List('a','b','c')).map(tup => (tup(0), tup(1), tup(2))).head

10

FWIW ฉันต้องการให้ทูเพิลสร้างฟิลด์จำนวนหนึ่งขึ้นมาและต้องการใช้น้ำตาลวากยสัมพันธ์ของการกำหนดทูเปิล เช่น:

val (c1, c2, c3) = listToTuple(myList)

ปรากฎว่ามีน้ำตาลวากยสัมพันธ์สำหรับกำหนดเนื้อหาของรายการด้วย ...

val c1 :: c2 :: c3 :: Nil = myList

ดังนั้นไม่จำเป็นต้องมีสิ่งทอหากคุณมีปัญหาเดียวกัน


@javadba ฉันกำลังมองหาวิธีใช้ listToTuple () แต่สุดท้ายก็ไม่จำเป็นต้องทำ รายการ syntactic sugar ช่วยแก้ปัญหาของฉันได้
Peter L

นอกจากนี้ยังมีไวยากรณ์อื่นซึ่งในความคิดของฉันดูดีกว่าเล็กน้อย:val List(c1, c2, c3) = myList
Kolmar

7

คุณไม่สามารถทำได้ด้วยวิธีที่ปลอดภัย ใน Scala รายการเป็นลำดับความยาวตามอำเภอใจขององค์ประกอบบางประเภท เท่าที่ระบบประเภทรู้xอาจเป็นรายการของความยาวโดยพลการ

ในทางตรงกันข้ามต้องทราบความเป็นเอกภาพของทูเปิลในเวลาคอมไพล์ มันจะละเมิดการรับประกันความปลอดภัยของระบบประเภทที่อนุญาตให้มอบหมายได้xประเภททูเพิลได้

ในความเป็นจริงด้วยเหตุผลทางเทคนิคสิ่งทอของ Scala ถูก จำกัด ไว้ที่ 22 องค์ประกอบแต่ขีด จำกัด ไม่มีอยู่ใน 2.11 อีกต่อไปขีด จำกัด คลาสเคสถูกยกขึ้นใน 2.11 https://github.com/scala/scala/pull/2305

เป็นไปได้ที่จะเขียนโค้ดฟังก์ชันด้วยตนเองที่แปลงรายการขององค์ประกอบได้ถึง 22 รายการและแสดงข้อยกเว้นสำหรับรายการขนาดใหญ่ การสนับสนุนเทมเพลตของ Scala ซึ่งเป็นคุณลักษณะที่กำลังจะมาถึงจะทำให้สิ่งนี้กระชับมากขึ้น แต่นี่จะเป็นการแฮ็กที่น่าเกลียด


ประเภทผลลัพธ์ของฟังก์ชันสมมุติฐานนี้จะเป็นอย่างไร

ขีด จำกัด คลาสเคสถูกยกขึ้นใน 2.11 github.com/scala/scala/pull/2305
gdoubleod

5

หากคุณแน่ใจมากว่า list.size ของคุณ <23 ใช้มัน:

def listToTuple[A <: Object](list:List[A]):Product = {
  val class = Class.forName("scala.Tuple" + list.size)
  class.getConstructors.apply(0).newInstance(list:_*).asInstanceOf[Product]
}
listToTuple: [A <: java.lang.Object](list: List[A])Product

scala> listToTuple(List("Scala", "Smart"))
res15: Product = (Scala,Smart)

5

นอกจากนี้ยังสามารถทำได้shapelessโดยใช้แผ่นสำเร็จรูปน้อยลงโดยใช้Sized:

scala> import shapeless._
scala> import shapeless.syntax.sized._

scala> val x = List(1, 2, 3)
x: List[Int] = List(1, 2, 3)

scala> x.sized(3).map(_.tupled)
res1: Option[(Int, Int, Int)] = Some((1,2,3))

เป็นประเภทที่ปลอดภัย: คุณจะได้รับNoneหากขนาดทูเปิลไม่ถูกต้อง แต่ขนาดทูเพิลต้องเป็นตัวอักษรหรือfinal val(สามารถแปลงเป็นได้shapeless.Nat)


ดูเหมือนว่าจะใช้ไม่ได้กับขนาดที่ใหญ่กว่า 22 อย่างน้อยสำหรับขนาดที่ไม่มีรูป
ทรง_2.11

@kfkhalili ใช่และไม่ใช่ข้อ จำกัด ที่ไม่มีรูปร่าง Scala 2 เองไม่อนุญาตสิ่งที่มีมากกว่า 22 องค์ประกอบดังนั้นจึงไม่สามารถแปลงรายการที่มีขนาดใหญ่กว่าเป็น Tuple โดยใช้วิธีการใด ๆ
Kolmar

4

ใช้การจับคู่รูปแบบ:

val intTuple = List(1,2,3) match {case List(a, b, c) => (a, b, c)}

เมื่อตอบคำถามที่เก่ากว่านี้ (มากกว่า 6 ปี) มักเป็นความคิดที่ดีที่จะชี้ให้เห็นว่าคำตอบของคุณแตกต่างจากคำตอบที่เก่ากว่าทั้งหมด (12) คำตอบที่ส่งไปแล้ว โดยเฉพาะอย่างยิ่งคำตอบของคุณจะใช้ได้ก็ต่อเมื่อความยาวของList/ Tupleเป็นค่าคงที่ที่ทราบ
jwvh

ขอบคุณ @ jwvh จะพิจารณาความคิดเห็นของคุณต่อไป
Michael Olafisoye

2

โพสต์ 2015 เพื่อให้คำตอบของ Tom Crockett มีความชัดเจนมากขึ้นนี่คือตัวอย่างจริง

ตอนแรกก็งง ๆ tuple(list(1,2,3))เพราะผมมาจากงูหลามที่คุณก็สามารถทำได้
เป็นภาษาสกาลาหรือไม่? (คำตอบคือ - ไม่เกี่ยวกับ Scala หรือ Python แต่เกี่ยวกับประเภทคงที่และประเภทไดนามิก)

นั่นเป็นสาเหตุให้ฉันพยายามหาปมว่าทำไม Scala ถึงทำสิ่งนี้ไม่ได้


ดำเนินตัวอย่างรหัสต่อไปtoTupleวิธีการซึ่งมีชนิดปลอดภัยและชนิดที่ไม่ปลอดภัยtoTupleNtoTuple

toTupleวิธีการได้รับข้อมูลประเภทความยาวที่ใช้เวลาเช่นไม่มีข้อมูลประเภทที่มีความยาวที่รวบรวมเวลาดังนั้นชนิดกลับเป็นProductซึ่งเป็นมากเช่นงูหลามเป็นtupleแน่นอน (ชนิดที่ไม่แต่ละตำแหน่งและความยาวของประเภทไม่ได้)
ด้วยวิธีนี้จะแสดงเป็นข้อผิดพลาดรันไทม์เช่นประเภทไม่ตรงกันหรือIndexOutOfBoundException . (ดังนั้นรายการต่อทูเพิลที่สะดวกของ Python จึงไม่ใช่อาหารกลางวันฟรี)

ในทางกลับกันมันเป็นข้อมูลที่ผู้ใช้ให้มาซึ่งทำให้toTupleNเวลาคอมไพล์ปลอดภัย

implicit class EnrichedWithToTuple[A](elements: Seq[A]) {
  def toTuple: Product = elements.length match {
    case 2 => toTuple2
    case 3 => toTuple3
  }
  def toTuple2 = elements match {case Seq(a, b) => (a, b) }
  def toTuple3 = elements match {case Seq(a, b, c) => (a, b, c) }
}

val product = List(1, 2, 3).toTuple
product.productElement(5) //runtime IndexOutOfBoundException, Bad ! 

val tuple = List(1, 2, 3).toTuple3
tuple._5 //compiler error, Good!

ดูดี - ทดลองใช้ตอนนี้
StephenBoesch

2

คุณสามารถทำได้เช่นกัน

  1. ผ่านการจับคู่รูปแบบ (สิ่งที่คุณไม่ต้องการ) หรือ
  2. โดยการวนซ้ำรายการและใช้แต่ละองค์ประกอบทีละรายการ

    val xs: Seq[Any] = List(1:Int, 2.0:Double, "3":String)
    val t: (Int,Double,String) = xs.foldLeft((Tuple3[Int,Double,String] _).curried:Any)({
      case (f,x) => f.asInstanceOf[Any=>Any](x)
    }).asInstanceOf[(Int,Double,String)]
    

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