Как в 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интерфейс не сохраняет порядок элементов. Вы можете использовать Set реализацию , которая явно имеет сохранение порядка, такие как collection.mutable.LinkedHashSet .


2
Что делать, если у вас есть список файлов и вам нужно сравнить что-то вроде части имени файла?
озон

4
@ozone Интересный вопрос. Возможно, самый простой способ - создать новую карту типа Map[String, File], где ключи являются частью интересующего имени файла. После построения карты вы можете вызвать valuesметод для получения Iterableзначений - все ключи будут отличаться по построению.
Киптон Баррос

@KiptonBarros, и я думаю, вы можете сделать это, используя groupByчлен scala.collection.Iterable[A].
Луи-Жакоб Лебель

19

scala.collection.immutable.Listтеперь есть .distinctметод.

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


1
.distinctне определено для scala.collection.Iterable[A]. Так что в этом случае вам придется использовать обновление dirtyдо a Seqили a в Setлюбом случае (то есть, используя либо .toList, .toSeqлибо .toSetучастников), чтобы это работало.
Луи-Джейкоб Лебель

15

Прежде чем использовать решение Kitpon, подумайте об использовании a, 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не нет.
Киптон Баррос,

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

Результаты производительности в списке из 100000000 случайных Ints от 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 уникальных значений, но у вас возникнут проблемы, если их будет намного больше, поскольку вы используете неизменяемую структуру. Чтобы работать еще быстрее, вы можете реализовать это с помощью изменяемой структуры.
Ник

@Nick Я изначально думал, что так и будет, но см. Правки выше.
voxoid

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

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

-3

inArr.distinct 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.