การต่อกันรายการสกาล่า, ::: vs ++


362

มีความแตกต่างระหว่าง:::และ++สำหรับรายการที่ต่อกันใน Scala หรือไม่?

scala> List(1,2,3) ++ List(4,5)
res0: List[Int] = List(1, 2, 3, 4, 5)

scala> List(1,2,3) ::: List(4,5)
res1: List[Int] = List(1, 2, 3, 4, 5)

scala> res0 == res1
res2: Boolean = true

จากเอกสารที่ดูเหมือนว่า++เป็นทั่วไปมากขึ้นในขณะที่:::เป็นList-specific มีให้หลังเพราะมันถูกใช้ในภาษาการทำงานอื่น ๆ ?


4
นอกจากนี้ยัง:::เป็นตัวดำเนินการคำนำหน้าเช่นวิธีการทั้งหมดที่เริ่มต้นด้วย:
Ben Jackson

3
คำตอบที่อธิบายอย่างชัดเจนเกี่ยวกับวิธีการที่สกาล่าได้รับการพัฒนารอบ ๆ รายการและความสม่ำเสมอของผู้ปฏิบัติงานในสกาล่า (หรือการขาดในภายหลัง) มันค่อนข้างโชคร้ายที่บางสิ่งที่เรียบง่ายเช่นนี้มีหางยาวของ minutiae ที่สับสนและเสียเวลาของผู้เรียนสกาล่า ฉันหวังว่ามันจะถูกเลเวลใน 2.12
matanster

คำตอบ:


321

มรดก รายการเดิมถูกกำหนดให้เป็นลักษณะการทำงานภาษา:

1 :: 2 :: Nil // a list
list1 ::: list2  // concatenation of two lists

list match {
  case head :: tail => "non-empty"
  case Nil          => "empty"
}

แน่นอนว่าสกาล่าพัฒนาคอลเลคชั่นอื่น ๆ ในลักษณะเฉพาะกิจ เมื่อ 2.8 ออกมาคอลเลกชันได้รับการออกแบบใหม่เพื่อนำโค้ดกลับมาใช้สูงสุดและ API ที่สอดคล้องกันเพื่อให้คุณสามารถใช้++เชื่อมคอลเลกชันสองชุดใด ๆ - และแม้แต่ตัววนซ้ำ รายการอย่างไรก็ตามต้องเก็บโอเปอเรเตอร์ดั้งเดิมไว้นอกเหนือจากหนึ่งหรือสองรายการซึ่งเลิกใช้แล้ว


19
ดังนั้นวิธีที่ดีที่สุดที่จะหลีกเลี่ยง:::ใน++ตอนนี้คืออะไร? ยังใช้+:แทน::หรือไม่
Luigi Plinge

37
::มีประโยชน์เนื่องจากการจับคู่รูปแบบ (ดูตัวอย่างที่สองของ Daniel) คุณไม่สามารถทำได้ด้วย+:
กระบวนทัศน์

1
@Luigi หากคุณกำลังใช้ListแทนคุณSeqอาจใช้Listวิธีการใช้สำนวน ในทางกลับกันมันจะทำให้ยากที่จะเปลี่ยนเป็นประเภทอื่นหากคุณต้องการทำเช่นนั้น
Daniel C. Sobral

2
ฉันคิดว่ามันดีที่มีการดำเนินการแบบ List List (like ::and :::) และการดำเนินงานทั่วไปอื่น ๆ ที่ใช้ร่วมกับคอลเลกชันอื่น ๆ ฉันจะไม่ลดการทำงานอย่างใดอย่างหนึ่งจากภาษา
Giorgio

21
@paradigmatic Scala 2.10 มี:+และตัว+:แยกวัตถุ
0__

97

:::การใช้งานเสมอ มีสองเหตุผลคือประสิทธิภาพและความปลอดภัยของประเภท

อย่างมีประสิทธิภาพ

x ::: y ::: zเร็วกว่าx ++ y ++ zเนื่องจาก:::มีการเชื่อมโยงที่ถูกต้อง x ::: y ::: zถูกแยกวิเคราะห์เป็นx ::: (y ::: z)ซึ่งเร็วกว่าอัลกอริทึม(x ::: y) ::: z(หลังต้องใช้ O (| x |) ขั้นตอนเพิ่มเติม)

ประเภทความปลอดภัย

ด้วย:::คุณสามารถต่อกันเพียงสองListวินาทีเท่านั้น ด้วย++คุณสามารถผนวกคอลเลกชันใด ๆListที่น่ากลัว:

scala> List(1, 2, 3) ++ "ab"
res0: List[AnyVal] = List(1, 2, 3, a, b)

++ยังง่ายต่อการผสมกับ+:

scala> List(1, 2, 3) + "ab"
res1: String = List(1, 2, 3)ab

9
เมื่อทำการต่อกันเพียง 2 รายการไม่มีความแตกต่าง แต่ในกรณีที่ 3 หรือมากกว่าคุณมีจุดดีและฉันยืนยันด้วยมาตรฐานที่รวดเร็ว แต่ถ้าคุณกังวลเกี่ยวกับประสิทธิภาพควรจะถูกแทนที่ด้วยx ::: y ::: z pastebin.com/gkx7HpadList(x, y, z).flatten
Luigi Plinge

3
โปรดอธิบายว่าทำไมการเชื่อมโยงเชื่อมโยงที่ยังเหลืออยู่จึงต้องมีขั้นตอน O (x) มากขึ้น ฉันคิดว่าทั้งคู่ทำงานให้กับ O (1)
Pacman

6
@pacman รายการมีการเชื่อมโยงเดี่ยว ๆ เพื่อผนวกรายการหนึ่งไปยังอีกรายการหนึ่งคุณจะต้องทำสำเนาของรายการแรกที่แนบมากับรายการที่สองในตอนท้าย การต่อข้อมูลดังนั้น O (n) ที่เกี่ยวข้องกับจำนวนขององค์ประกอบในรายการแรก ความยาวของรายการที่สองไม่มีผลต่อรันไทม์ดังนั้นจึงเป็นการดีกว่าที่จะผนวกรายการแบบยาวเข้ากับตัวย่อแบบสั้นแทนที่จะเพิ่มรายการแบบสั้นต่อท้ายแบบยาว
puhlen

1
@pacman รายการ Scala มีไม่เปลี่ยนรูป นั่นเป็นเหตุผลที่เราไม่สามารถแทนที่ลิงก์สุดท้ายเมื่อทำการต่อข้อมูล เราต้องสร้างรายการใหม่ตั้งแต่ต้น
ZhekaKozlov

4
@pacman ความซับซ้อนเป็นเส้นตรงเสมอ wrt ยาวxและy( zไม่ซ้ำในกรณีใด ๆ ดังนั้นจึงไม่มีผลกระทบในเวลาทำงานนี่คือเหตุผลที่ดีกว่าที่จะผนวกรายการยาวกับสั้น ๆ มากกว่าวิธีอื่น ๆ ) แต่ ความซับซ้อนเชิงซีกไม่ได้บอกเรื่องราวทั้งหมด x ::: (y ::: z)iterates yและผนวกzแล้ว iterates และผนวกผลมาจากการ x และซ้ำทั้งสองครั้ง iterates และผนวกแล้ว iterates ผลมาจากและผนวก ยังคงวนซ้ำหนึ่งครั้ง แต่ซ้ำสองครั้งในกรณีนี้ y ::: zxy(x ::: y) ::: zxyx ::: yzyx
puhlen

84

:::ใช้งานได้กับรายการเท่านั้นในขณะที่++สามารถใช้ได้กับทุกเส้นทาง ในการดำเนินการปัจจุบัน (2.9.0) ++ตกกลับมาอยู่บนถ้าอาร์กิวเมนต์นี้ยังมี:::List


4
ดังนั้นจึงเป็นเรื่องง่ายมากที่จะใช้ทั้งสอง ::: และ ++ การทำงานกับรายการ ที่อาจทำให้สับสนกับรหัส / สไตล์
ses

24

จุดที่แตกต่างคือประโยคแรกที่แยกวิเคราะห์เป็น:

scala> List(1,2,3).++(List(4,5))
res0: List[Int] = List(1, 2, 3, 4, 5)

ในขณะที่ตัวอย่างที่สองจะถูกแยกวิเคราะห์เป็น:

scala> List(4,5).:::(List(1,2,3))
res1: List[Int] = List(1, 2, 3, 4, 5)

ดังนั้นหากคุณใช้มาโครคุณควรระวัง

นอกจากนี้++สำหรับสองรายการกำลังโทร:::แต่มีค่าใช้จ่ายมากขึ้นเพราะมันจะขอค่าโดยนัยที่จะมีการสร้างจากรายการไปยังรายการ แต่ microbenchmarks ไม่ได้พิสูจน์อะไรที่มีประโยชน์ในแง่นั้นฉันเดาว่าคอมไพเลอร์จะปรับการเรียกเช่นนั้นให้เหมาะสม

Micro-Benchmarks หลังจากอุ่นขึ้น

scala>def time(a: => Unit): Long = { val t = System.currentTimeMillis; a; System.currentTimeMillis - t}
scala>def average(a: () => Long) = (for(i<-1 to 100) yield a()).sum/100

scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ++ List(e) } })
res1: Long = 46
scala>average (() => time { (List[Int]() /: (1 to 1000)) { case (l, e) => l ::: List(e ) } })
res2: Long = 46

ดังที่ Daniel C. Sobrai กล่าวคุณสามารถเพิ่มเนื้อหาของคอลเลกชันใด ๆ ลงในรายการโดยใช้++ในขณะที่:::คุณสามารถเชื่อมโยงรายการเท่านั้น


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