Cuando trabajamos con sets en Scala, es común notar que los elementos no mantienen un orden específico. Por ejemplo, si creamos un set con los números del 1 al 5, puede que el elemento 5 aparezca al principio o que al insertar números del 1 al 6, el 6 se ubique en medio del conjunto. Esto sucede porque, por naturaleza, los sets no garantizan ningún orden en la inserción o almacenamiento de sus elementos.
Si necesitamos que los elementos estén ordenados, podemos recurrir a SortedSet o TreeSet. Estas estructuras permiten mantener un orden definido, ya sea el orden natural que Scala proporciona por defecto o un orden personalizado que definamos nosotros mismos. Es importante destacar que SortedSet no forma parte de los paquetes importados automáticamente en Scala, por lo que debemos importarlo explícitamente, por ejemplo desde scala.collection.immutable.
Al crear un SortedSet con elementos como 1, 2, 3, 4, 5, 6, veremos que los elementos se mantienen ordenados, incluso si los insertamos en un orden diferente, como 1, 3, 2, 4, 5, 6. Esto se debe a que el SortedSet utiliza un criterio de ordenación para organizar los elementos internamente.
Pero, ¿qué pasa si queremos un orden distinto al predeterminado? Por ejemplo, un orden inverso, donde los números más grandes aparezcan primero. Para lograr esto, Scala nos ofrece la clase Ordering, que nos permite definir cómo comparar dos elementos para establecer su orden relativo. Podemos crear un Ordering personalizado usando el método fromLessThan, que recibe una función lambda que compara dos elementos y devuelve un valor booleano indicando si el primero debe ir antes que el segundo.
Para ilustrar esto, podemos definir un orden inverso para enteros de la siguiente manera:
import scala.collection.immutable.SortedSet
import scala.math.Ordering
val reverseOrdering: Ordering[Int] = Ordering.fromLessThan(_ > _)
val reversedSet = SortedSet.empty(reverseOrdering) ++ Seq(1, 3, 2, 4, 5, 6)
println(reversedSet) // Output: TreeSet(6, 5, 4, 3, 2, 1)
Aquí, reverseOrdering es un comparador que indica que un elemento es menor que otro si es mayor en valor, invirtiendo así el orden natural. Luego, creamos un SortedSet vacío con este orden y le añadimos los elementos deseados. El resultado es un conjunto ordenado de forma descendente.
Si quisiéramos mantener el orden natural, podríamos usar simplemente el orden predeterminado o definirlo explícitamente con Ordering.fromLessThan(_ < _), aunque esto no es necesario porque Scala ya lo proporciona.
Este mecanismo no se limita a números. Por ejemplo, si queremos ordenar cadenas de texto según su longitud, podemos definir un Ordering que compare las longitudes de las cadenas:
val lengthOrdering: Ordering[String] = Ordering.fromLessThan(_.length < _.length)
val stringsSet = SortedSet.empty(lengthOrdering) ++ Seq("Scala", "Java", "Python", "C")
println(stringsSet) // Output: TreeSet(C, Java, Scala, Python)
En este caso, el conjunto ordena las cadenas de menor a mayor longitud.
Para crear un SortedSet con un orden personalizado, debemos pasar el Ordering al método empty y luego añadir los elementos. Esto es necesario porque el constructor principal de SortedSet no permite especificar directamente el criterio de orden, y como los sets son inmutables, esta es la forma adecuada de construirlos con un orden definido.
Así, utilizando SortedSet junto con Ordering, podemos controlar cómo se ordenan los elementos en nuestros conjuntos, ya sea siguiendo el orden natural, un orden inverso o cualquier criterio personalizado que necesitemos.