ใน Scala ฉันจะลบรายการที่ซ้ำกันออกจากรายการได้อย่างไร?


96

สมมติว่าฉันมี

val dirty = List("a", "b", "a", "c")

มีการดำเนินการรายการที่ส่งคืน "a", "b", "c" หรือไม่

คำตอบ:


177

มีลักษณะที่ ScalaDoc สำหรับSeq ,

scala> dirty.distinct
res0: List[java.lang.String] = List(a, b, c)

อัปเดต . คนอื่น ๆ ได้แนะนำให้ใช้มากกว่าSet Listไม่เป็นไร แต่โปรดทราบว่าโดยค่าเริ่มต้นSetอินเทอร์เฟซจะไม่รักษาลำดับองค์ประกอบ คุณอาจต้องการที่จะใช้ดำเนินการตั้งค่าอย่างชัดเจนว่าไม่รักษาคำสั่งเช่นcollection.mutable.LinkedHashSet


2
จะเป็นอย่างไรหากคุณมีรายชื่อไฟล์และต้องการเปรียบเทียบบางอย่างเช่นส่วนหนึ่งของชื่อไฟล์
โอโซน

4
@ozone คำถามที่น่าสนใจ บางทีวิธีที่ง่ายที่สุดคือการสร้างใหม่แผนที่ ชนิดMap[String, File]ที่ปุ่มเป็นส่วนหนึ่งของชื่อไฟล์ที่น่าสนใจ เมื่อสร้างแผนที่แล้วคุณสามารถเรียกใช้valuesเมธอดเพื่อรับIterableค่า - คีย์ทั้งหมดจะแตกต่างกันตามโครงสร้าง
Kipton Barros

@KiptonBarros และฉันคิดว่าคุณสามารถทำได้โดยใช้groupByสมาชิกของscala.collection.Iterable[A].
Louis-Jacob Lebel

20

scala.collection.immutable.Listตอนนี้มี.distinctวิธีการ

ดังนั้นการเรียกร้องdirty.distinctอยู่ในขณะนี้เป็นไปได้โดยไม่ต้องแปลงไปหรือSetSeq


1
.distinctไม่ได้กำหนดไว้สำหรับscala.collection.Iterable[A]. ดังนั้นในกรณีที่คุณจะต้องใช้การอัพเกรดdirtyไปSeqหรือSetไง (เช่นโดยการใช้อย่างใดอย่างหนึ่ง.toList, .toSeqหรือ.toSetสมาชิก) สำหรับการทำงาน
Louis-Jacob Lebel

16

ก่อนที่จะใช้โซลูชันของ Kitpon ให้คิดถึงการใช้Setมากกว่า a Listเพื่อให้แน่ใจว่าแต่ละองค์ประกอบไม่ซ้ำกัน

เป็นส่วนใหญ่การดำเนินงานรายการ ( foreach, map, filter, ... ) จะเหมือนกันสำหรับชุดและรายการเปลี่ยนคอลเลกชันที่อาจจะง่ายมากในรหัส


7

การใช้ Set ตั้งแต่แรกเป็นวิธีที่ถูกต้องแน่นอน แต่:

scala> List("a", "b", "a", "c").toSet.toList
res1: List[java.lang.String] = List(a, b, c)

ผลงาน หรือtoSetเช่นเดียวกับที่รองรับไฟล์Seq Traversable อินเตอร์เฟซ.


1
ฉันแก้ไขคำตอบของคุณเพราะSetการดำเนินการไม่ได้Traversable Seqความแตกต่างคือSeqการรับประกันการสั่งซื้อให้กับองค์ประกอบในขณะที่Traversableไม่มี
Kipton Barros

0

สำหรับรายการที่จัดเรียงแล้ว

หากคุณต้องการให้รายการที่แตกต่างจากรายการที่คุณรู้ว่าถูกจัดเรียงไว้แล้วตามที่ฉันต้องการบ่อยครั้งสิ่งต่อไปนี้จะมีความเร็วประมาณสองเท่า.distinct:

  def distinctOnSorted[V](seq: List[V]): List[V] =
    seq.foldLeft(List[V]())((result, v) =>
      if (result.isEmpty || v != result.head) v :: result else result)
    .reverse

ผลการดำเนินงานในรายการสุ่ม 100,000,000 Ints จาก 0-99:

distinct        : 0.6655373s
distinctOnSorted: 0.2848134s

ประสิทธิภาพกับ MutableList หรือ ListBuffer

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

List size 1e7, random 0 to 1e6
------------------------------
distinct            : 4562.2277ms
distinctOnSorted    : 201.9462ms
distinctOnSortedMut1: 4399.7055ms
distinctOnSortedMut2: 246.099ms
distinctOnSortedMut3: 344.0758ms
distinctOnSortedMut4: 247.0685ms

List size 1e7, random 0 to 100
------------------------------
distinct            : 88.9158ms
distinctOnSorted    : 41.0373ms
distinctOnSortedMut1: 3283.8945ms
distinctOnSortedMut2: 54.4496ms
distinctOnSortedMut3: 58.6073ms
distinctOnSortedMut4: 51.4153ms

การใช้งาน:

object ListUtil {
  def distinctOnSorted[V](seq: List[V]): List[V] =
    seq.foldLeft(List[V]())((result, v) =>
      if (result.isEmpty || v != result.head) v :: result else result)
    .reverse

  def distinctOnSortedMut1[V](seq: List[V]): Seq[V] = {
    if (seq.isEmpty) Nil
    else {
      val result = mutable.MutableList[V](seq.head)
      seq.zip(seq.tail).foreach { case (prev, next) =>
        if (prev != next) result += next
      }
      result //.toList
    }
  }

  def distinctOnSortedMut2[V](seq: List[V]): Seq[V] = {
    val result = mutable.MutableList[V]()
    if (seq.isEmpty) return Nil
    result += seq.head
    var prev = seq.head
    for (v <- seq.tail) {
      if (v != prev) result += v
      prev = v
    }
    result //.toList
  }

  def distinctOnSortedMut3[V](seq: List[V]): List[V] = {
    val result = mutable.MutableList[V]()
    if (seq.isEmpty) return Nil
    result += seq.head
    var prev = seq.head
    for (v <- seq.tail) {
      if (v != prev) v +=: result
      prev = v
    }
    result.reverse.toList
  }

  def distinctOnSortedMut4[V](seq: List[V]): Seq[V] = {
    val result = ListBuffer[V]()
    if (seq.isEmpty) return Nil
    result += seq.head
    var prev = seq.head
    for (v <- seq.tail) {
      if (v != prev) result += v
      prev = v
    }
    result //.toList
  }
}

ทดสอบ:

import scala.util.Random

class ListUtilTest extends UnitSpec {
  "distinctOnSorted" should "return only the distinct elements in a sorted list" in {
    val bigList = List.fill(1e7.toInt)(Random.nextInt(100)).sorted

    val t1 = System.nanoTime()
    val expected = bigList.distinct
    val t2 = System.nanoTime()
    val actual = ListUtil.distinctOnSorted[Int](bigList)
    val t3 = System.nanoTime()
    val actual2 = ListUtil.distinctOnSortedMut1(bigList)
    val t4 = System.nanoTime()
    val actual3 = ListUtil.distinctOnSortedMut2(bigList)
    val t5 = System.nanoTime()
    val actual4 = ListUtil.distinctOnSortedMut3(bigList)
    val t6 = System.nanoTime()
    val actual5 = ListUtil.distinctOnSortedMut4(bigList)
    val t7 = System.nanoTime()

    actual should be (expected)
    actual2 should be (expected)
    actual3 should be (expected)
    actual4 should be (expected)
    actual5 should be (expected)

    val distinctDur = t2 - t1
    val ourDur = t3 - t2

    ourDur should be < (distinctDur)

    print(s"distinct            : ${distinctDur / 1e6}ms\n")
    print(s"distinctOnSorted    : ${ourDur / 1e6}ms\n")
    print(s"distinctOnSortedMut1: ${(t4 - t3) / 1e6}ms\n")
    print(s"distinctOnSortedMut2: ${(t5 - t4) / 1e6}ms\n")
    print(s"distinctOnSortedMut3: ${(t6 - t5) / 1e6}ms\n")
    print(s"distinctOnSortedMut4: ${(t7 - t6) / 1e6}ms\n")
  }
}

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

@ นิกเดิมทีฉันคิดว่ามันจะเป็นอย่างนั้นเช่นกันอย่างไรก็ตามดูการแก้ไขด้านบน
โมฆะ

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

0

คุณยังสามารถใช้การเรียกซ้ำและการจับคู่รูปแบบ:

def removeDuplicates[T](xs: List[T]): List[T] = xs match {
  case Nil => xs
  case head :: tail => head :: removeDuplicates(for (x <- tail if x != head) yield x)
}


1
removeDuplicates(tail.filter(_ != head))
jwvh


-5

วิธีอัลกอริทึม ...

def dedupe(str: String): String = {
  val words = { str split " " }.toList

  val unique = words.foldLeft[List[String]] (Nil) {
    (l, s) => {
      val test = l find { _.toLowerCase == s.toLowerCase } 
      if (test == None) s :: l else l
    }
  }.reverse

  unique mkString " "
}

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