En Scala, ¿cómo elimino los duplicados de una lista?

94

Supongamos que tengo

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

¿Existe una operación de lista que devuelva "a", "b", "c"

deltanovember
fuente

Respuestas:

175

Eche un vistazo a ScalaDoc para Seq ,

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

Actualizar . Otros han sugerido usar en Setlugar de List. Está bien, pero tenga en cuenta que, de forma predeterminada, la Setinterfaz no conserva el orden de los elementos. Es posible que desee utilizar una aplicación que figura explícitamente que no preservar el orden, como collection.mutable.LinkedHashSet .

Kipton Barros
fuente
2
¿Qué sucede si tiene una lista de archivos y necesita comparar algo como parte del nombre del archivo?
ozono
4
@ozone Pregunta interesante. Quizás la forma más fácil es crear un nuevo mapa de tipo Map[String, File], donde las claves son la parte del nombre del archivo de interés. Una vez que se construye el mapa, puede llamar al valuesmétodo para obtener un Iterablevalor de: todas las claves serán distintas por construcción.
Kipton Barros
@KiptonBarros y creo que puedes hacer esto usando el groupBymiembro de scala.collection.Iterable[A].
Louis-Jacob Lebel
18

scala.collection.immutable.Listahora tiene un .distinctmétodo.

Entonces, llamar dirty.distinctahora es posible sin convertir a Seto Seq.

verduras
fuente
1
.distinctno está definido para scala.collection.Iterable[A]. Así que en ese caso, habría que utilizar la actualización dirtya un Seqo una Setde todos modos (es decir, mediante el uso de cualquiera de los dos .toList, .toSeqo .toSetlos miembros) para que esto funcione.
Louis-Jacob Lebel
15

Antes de usar la solución de Kitpon, piense en usar a en Setlugar de a List, asegura que cada elemento sea único.

Como la mayoría de las operaciones de lista ( foreach, map, filter, ...) son los mismos para los conjuntos y listas, el cambio de la colección podría ser muy fácil en el código.

paradigmático
fuente
7

Usar Set en primer lugar es la forma correcta de hacerlo, por supuesto, pero:

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

Trabajos. O simplemente toSetcomo es compatible conSeq Traversable interfaz.

zentrope
fuente
1
Edité tu respuesta porque Setimplementa Traversable, no Seq. La diferencia es que Seqgarantiza un orden a los elementos, mientras Traversableque no.
Kipton Barros
0

Para listas ya ordenadas

Si desea los distintos elementos de una lista que sabe que ya está ordenada , como a menudo he necesitado, lo siguiente funciona aproximadamente al doble de velocidad que .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

Resultados de rendimiento en una lista de 100.000.000 Ints aleatorios de 0 a 99:

distinct        : 0.6655373s
distinctOnSorted: 0.2848134s

Rendimiento con MutableList o ListBuffer

Si bien parecería que un enfoque de programación más mutable / no funcional podría ser más rápido que anteponer a una lista inmutable, la práctica muestra lo contrario. La implementación inmutable se desempeña mejor constantemente. Supongo que la razón es que scala enfoca sus optimizaciones del compilador en colecciones inmutables y lo hace bien. (Doy la bienvenida a otros para que presenten mejores implementaciones).

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

Implementaciones:

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
  }
}

Prueba:

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")
  }
}
voxoide
fuente
Esto es bastante eficiente ya que solo hay 100 valores únicos, pero te meterías en problemas si hubiera muchos más ya que estás usando una estructura inmutable. Para ir aún más rápido, puede implementar esto con una estructura mutable.
Nick
@Nick Originalmente pensé que también sería así, sin embargo, vea las ediciones anteriores.
voxoid
Probé lo anterior yo mismo, ya que no puedo entender por qué inmutable sería mejor para esto, pero sigue siéndolo incluso si aumenta considerablemente el número de valores distintos. También probé algunas estructuras mutables donde el prefijo es más eficiente, pero incluso sin revertir el resultado al final, es más lento.
Nick
0

También puede utilizar la recursividad y la coincidencia de patrones:

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)
}
codeepic
fuente
1
removeDuplicates(tail.filter(_ != head))
jwvh
-3

inArr.distinct para cada println _

Sumit Pal
fuente
esto imprime la salida deseada, ¿OP no estaba pidiendo devolverla (como una Lista, presumiblemente)?
RobP
-5

La forma algorítmica ...

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 " "
}
Farquad
fuente
1
Tiene una lista, no una cadena. Esto no responde a la pregunta.
Tim Gautier