Supongamos que tengo
val dirty = List("a", "b", "a", "c")
¿Existe una operación de lista que devuelva "a", "b", "c"
Supongamos que tengo
val dirty = List("a", "b", "a", "c")
¿Existe una operación de lista que devuelva "a", "b", "c"
Respuestas:
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 Set
lugar de List
. Está bien, pero tenga en cuenta que, de forma predeterminada, la Set
interfaz 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 .
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 values
método para obtener un Iterable
valor de: todas las claves serán distintas por construcción.
groupBy
miembro de scala.collection.Iterable[A]
.
scala.collection.immutable.List
ahora tiene un .distinct
método.
Entonces, llamar dirty.distinct
ahora es posible sin convertir a Set
o Seq
.
.distinct
no está definido para scala.collection.Iterable[A]
. Así que en ese caso, habría que utilizar la actualización dirty
a un Seq
o una Set
de todos modos (es decir, mediante el uso de cualquiera de los dos .toList
, .toSeq
o .toSet
los miembros) para que esto funcione.
Antes de usar la solución de Kitpon, piense en usar a en Set
lugar 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.
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 toSet
como es compatible conSeq Traversable
interfaz.
Set
implementa Traversable
, no Seq
. La diferencia es que Seq
garantiza un orden a los elementos, mientras Traversable
que no.
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
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")
}
}
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 " "
}