Як видалити дублікати зі списку в Scala?


94

Припустимо, що маю

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

Чи існує операція зі списком, яка повертає "a", "b", "c"

Відповіді:


175

Погляньте на ScalaDoc для Seq ,

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

Оновлення . Інші пропонують Setскоріше використовувати , ніж List. Це добре, але майте на увазі, що за замовчуванням Setінтерфейс не зберігає порядок елементів. Ви можете використовувати Set реалізацію , яка явно має збереження порядку, такі як collection.mutable.LinkedHashSet .


2
Що робити, якщо у вас є список файлів і вам потрібно порівняти щось на зразок частини імені файлу?
озон

4
@ozone Цікаве питання. Можливо, найпростіший спосіб - створити нову карту типу Map[String, File], де ключі є частиною цікавого імені файлу. Після того, як карта побудована, ви можете викликати valuesметод, щоб отримати Iterableзначення значень - усі ключі будуть відрізнятися за конструкцією.
Kipton Barros

@KiptonBarros, і я думаю, ви можете зробити це, використовуючи groupByчлен scala.collection.Iterable[A].
Louis-Jacob Lebel

18

scala.collection.immutable.Listтепер має .distinctметод.

Отже, виклик dirty.distinctтепер можливий без перетворення на Setабо Seq.


1
.distinctне визначено для scala.collection.Iterable[A]. Тож у такому випадку вам доведеться використовувати оновлення dirtyдо a Seqабо a Set(тобто, використовуючи або .toList, .toSeqабо .toSetучасників), щоб це працювало.
Louis-Jacob Lebel

15

Перш ніж використовувати рішення Kitpon, подумайте про використання, Setа не а List, воно гарантує, що кожен елемент унікальний.

У більшості списків операцій ( foreach, map, filter...) є однаковими для множин і списків, змінюючи колекцію може бути дуже легко в коді.


7

Використання Set в першу чергу - це правильний спосіб це зробити, звичайно, але:

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

Працює. Або так само, toSetяк він підтримуєПослідовне 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 випадкових входів від 0 до 99:

distinct        : 0.6655373s
distinctOnSorted: 0.2848134s

Продуктивність за допомогою MutableList або ListBuffer

Хоча здається, що більш мінливий / нефункціональний підхід до програмування може бути швидшим, ніж підготовка до незмінного списку, практика показує інше. Незмінна реалізація стабільно працює краще. Я думаю, що причина полягає в тому, що Scala фокусує свої оптимізатори компілятора на незмінних колекціях і робить це добре. (Я вітаю інших подавати кращі варіанти реалізації.)

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 унікальних значень, але ви б потрапили в проблеми, якби їх було набагато більше, оскільки ви використовуєте незмінну структуру. Щоб рухатися ще швидше, ви можете реалізувати це із змінною структурою.
Нік

@ Нік Я спочатку думав, що це теж буде так, проте дивіться редагування вище.
voxoid

Я спробував вищезазначене сам, оскільки не можу зрозуміти, чому незмінний варіант був би кращим для цього, але він продовжує залишатися таким, навіть якщо ви значно збільшуєте кількість різних значень. Я також випробував кілька змінних структур, де prepend є більш ефективним, але навіть без зворотного результату в кінці це відбувається повільніше.
Нік

-3

inArr.disinct foreach println _


це друкує бажаний результат, хіба OP не просив повернути його (як список, мабуть)?
RobP

-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
У нього є список, а не рядок. Це не відповідає на запитання.
Тім Готьє
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.