Java: encontrar elementos duplicados en una secuencia

En este tutorial, veremos cómo encontrar elementos duplicados en un flujo de Java a partir de una lista, un mapa o un conjunto. Usaremos Stream API, Collectors y Collections.

Introducción

Introducida en Java 8, la Stream API se usa comúnmente para filtrar, mapear e iterar elementos. Cuando se trabaja con flujos, una de las tareas comunes es encontrar elementos duplicados.

En este tutorial, cubriremos varias formas de encontrar elementos duplicados en un flujo de Java.

Coleccionistas.toSet()

La forma más fácil de encontrar elementos duplicados es agregando los elementos a un ‘Conjunto’. Sets no puede contener valores duplicados, y el método Set.add() devuelve un valor booleano que es el resultado de la operación. Si no se agrega un elemento, se devuelve falso y viceversa.

Hagamos un Stream de Strings con algunos valores duplicados. Estos valores se verifican a través del método equals(), así que asegúrese de tener uno implementado adecuadamente para las clases personalizadas:

1
Stream<String> stream = Stream.of("john", "doe", "doe", "tom", "john");

Ahora, hagamos un Set para almacenar los elementos filtrados. Usaremos el método filter() para filtrar los valores duplicados y devolverlos:

1
2
3
4
5
Set<String> items = new HashSet<>();

stream.filter(n -> !items.add(n))
        .collect(Collectors.toSet())
        .forEach(System.out::println);

Aquí, tratamos de ‘agregar ()’ cada elemento al ‘Conjunto’. Si no se agrega, debido a que está duplicado, recopilamos ese valor y lo imprimimos:

1
2
john
doe

Collectors.toMap()

Alternativamente, también puede contar las ocurrencias de elementos duplicados y mantener esa información en un mapa que contiene los elementos duplicados como claves y su frecuencia como valores.

Vamos a crear una Lista de tipo Entero:

1
List<Integer> list = Arrays.asList(9, 2, 2, 7, 6, 6, 5, 7);

Luego, recopilemos los elementos en un Mapa y contemos sus ocurrencias:

1
2
3
4
Map<Integer, Integer> map = list.stream()
        .collect(Collectors.toMap(Function.identity(), value -> 1, Integer::sum));
        
System.out.println(map);

No hemos eliminado ningún elemento, solo contamos sus ocurrencias y las almacenamos en un Mapa:

1
{2=2, 5=1, 6=2, 7=2, 9=1}

Collectors.groupingBy(Function.identity(), Collectors.counting()) with Collectors.toList()

El método Collectors.groupingBy() se usa para agrupar elementos, en función de alguna propiedad, y devolverlos como una instancia de Map.

En nuestro caso, el método recibe dos parámetros: Function.identity(), que siempre devuelve sus argumentos de entrada y Collectors.counting(), que cuenta los elementos pasados ​​en el flujo.

Luego, usaremos el método groupingBy() para crear un mapa de la frecuencia de estos elementos. Después de eso, podemos simplemente filtrar() el flujo de elementos que tienen una frecuencia superior a 1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
list.stream()
        // Creates a map {4:1, 5:2, 7:2, 8:2, 9:1}
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
        .entrySet()
        // Convert back to stream to filter
        .stream()
        .filter(element -> element.getValue() > 1)
        // Collect elements to List and print out the values
        .collect(Collectors.toList())
        .forEach(System.out::println);

Esto resulta en:

1
2
3
5=2
7=2
8=2

Si desea extraer solo los elementos duplicados, sin su frecuencia, puede incluir un map() adicional en el proceso. Después de filtrar, y antes de recopilar en una lista, solo obtendremos las claves:

1
.map(Map.Entry::getKey)

Colecciones.frecuencia()

Collections.frequency() es otro método que proviene de la clase Java Collections que cuenta las ocurrencias de un elemento específico en el flujo de entrada al atravesar cada elemento. Toma dos parámetros, la colección y el elemento cuya frecuencia se quiere determinar.

Ahora, filtraremos() el flujo para cada elemento que tenga una frecuencia() mayor que 1:

1
2
3
4
5
list.stream()
        .filter(i -> Collections.frequency(list, i) > 1)
        //Collect elements to a Set and print out the values 
        .collect(Collectors.toSet())
        .forEach(System.out::println);

Aquí, podemos recopilar en un ‘Conjunto’ o en una ‘Lista’. Si recopilamos en una lista, tendrá todos los elementos duplicados, por lo que algunos pueden repetirse. Si recopilamos en un conjunto, tendrá elementos duplicados únicos.

Esto resulta en:

1
2
3
5
7
8

Flujo.distinto()

El método distinct() es un método con estado (tiene en cuenta el estado de los elementos anteriores) y compara elementos utilizando el método equals(). Si son distintos/únicos, se devuelven, que podemos completar en otra lista.

Hagamos una lista con algunos valores duplicados y extraigamos los distintos valores:

1
2
3
4
5
List<String> list = new ArrayList(Arrays.asList("A", "B", "C", "D", "A", "B", "C", "A", "F", "C"));

List<String> distinctElementList = list.stream()
        .distinct()
        .collect(Collectors.toList());

Ahora, todos los valores no distintos tienen más de una aparición. Si eliminamos los valores distintos, nos quedaremos con elementos duplicados:

1
2
3
for (String distinctElement : distinctElementList) {
    list.remove(distinctElement);
}

Ahora, imprimamos los resultados:

1
list.forEach(System.out::print)

Estos son los elementos duplicados, con sus respectivas ocurrencias:

1
ABCAC

Si desea tamizarlos también, y solo mostrar una ocurrencia de cada elemento duplicado (en lugar de todos ellos por separado), puede ejecutarlos a través del método distinct() nuevamente:

1
2
3
4
list.stream()
        .distinct()
        .collect(Collectors.toList())
        .forEach(System.out::print);

Esto resulta en:

1
ABC

Conclusión

En este artículo, hemos repasado algunos enfoques para encontrar elementos duplicados en un flujo de Java.

Hemos cubierto el método Stream.distinct() de Stream API, los métodos Collectors.toSet(), Collectors.toMap() y Collectors.groupingBy() de Java Collectors, también como método Collections.frequency() del framework Collections.