Cómo contar el número de ocurrencias de un elemento en una Lista


173

Tengo una ArrayList, una clase de colección de Java, como sigue:

ArrayList<String> animals = new ArrayList<String>();
animals.add("bat");
animals.add("owl");
animals.add("bat");
animals.add("bat");

Como puede ver, animals ArrayListconsta de 3 batelementos y un owlelemento. Me preguntaba si hay alguna API en el marco de la Colección que devuelve el número de batocurrencias o si hay otra forma de determinar el número de ocurrencias.

Descubrí que la Colección de Google Multisettiene una API que devuelve el número total de ocurrencias de un elemento. Pero eso es compatible solo con JDK 1.5. Nuestro producto se encuentra actualmente en JDK 1.6, por lo que no puedo usarlo.


Esa es una de las razones por las que debe programar en una interfaz en lugar de una implementación. Si encuentra la colección correcta, deberá cambiar el tipo para usar esa colección. Publicaré una respuesta sobre esto.
OscarRyz

Respuestas:


333

Estoy bastante seguro de que el método de frecuencia estática en Colecciones sería útil aquí:

int occurrences = Collections.frequency(animals, "bat");

Así es como lo haría de todos modos. Estoy bastante seguro de que esto es jdk 1.6 directamente.


Siempre prefiera Api de JRE, que agrega otra dependencia al proyecto. ¡Y no reinventes la rueda!
Fernando.

Se introdujo en JDK 5 (aunque nadie usa una versión anterior, así que no importa) docs.oracle.com/javase/8/docs/technotes/guides/collections/…
Minion Jim

105

En Java 8:

Map<String, Long> counts =
    list.stream().collect(Collectors.groupingBy(e -> e, Collectors.counting()));

66
Usar Function.identity () (con importación estática) en lugar de e -> e hace que sea un poco más agradable de leer.
Kuchi

8
¿Por qué es esto mejor que Collections.frequency()? Parece menos legible.
rozina

Esto no es lo que se pidió. Hace más trabajo del necesario.
Alex Worden

8
Esto puede hacer más de lo que se le pidió, pero hace exactamente lo que quería (obtener un mapa de elementos distintos en una lista para sus recuentos). Además, esta pregunta fue el principal resultado en Google cuando busqué.
KJP

@rozina Obtienes todos los recuentos en una sola pasada.
atoMerz

22

Esto muestra por qué es importante " Referirse a los objetos por sus interfaces " como se describe en el libro Effective Java .

Si codifica la implementación y usa ArrayList en, digamos, 50 lugares en su código, cuando encuentre una buena implementación de "Lista" que cuente los elementos, tendrá que cambiar todos esos 50 lugares, y probablemente tendrá que rompa su código (si solo lo usa usted, no es gran cosa, pero si alguien más lo usa, también romperá su código)

Al programar en la interfaz, puede dejar esos 50 lugares sin cambios y reemplazar la implementación de ArrayList a "CountItemsList" (por ejemplo) o alguna otra clase.

A continuación se muestra una muestra muy básica sobre cómo se podría escribir esto. Esto es solo una muestra, una lista lista para producción sería mucho más complicada.

import java.util.*;

public class CountItemsList<E> extends ArrayList<E> { 

    // This is private. It is not visible from outside.
    private Map<E,Integer> count = new HashMap<E,Integer>();

    // There are several entry points to this class
    // this is just to show one of them.
    public boolean add( E element  ) { 
        if( !count.containsKey( element ) ){
            count.put( element, 1 );
        } else { 
            count.put( element, count.get( element ) + 1 );
        }
        return super.add( element );
    }

    // This method belongs to CountItemList interface ( or class ) 
    // to used you have to cast.
    public int getCount( E element ) { 
        if( ! count.containsKey( element ) ) {
            return 0;
        }
        return count.get( element );
    }

    public static void main( String [] args ) { 
        List<String> animals = new CountItemsList<String>();
        animals.add("bat");
        animals.add("owl");
        animals.add("bat");
        animals.add("bat");

        System.out.println( (( CountItemsList<String> )animals).getCount( "bat" ));
    }
}

Principios de OO aplicados aquí: herencia, polimorfismo, abstracción, encapsulación.


12
Bueno, uno siempre debe intentar la composición en lugar de la herencia. Su implementación ahora está pegada a ArrayList cuando puede haber ocasiones en que desee una LinkedList u otra. Su ejemplo debería haber tomado otra LISTA en su constructor / fábrica y devuelto un contenedor.
mP.

Estoy completamente de acuerdo contigo. La razón por la que usé la herencia en la muestra es porque es mucho más fácil mostrar un ejemplo en ejecución usando la herencia que la composición (tener que implementar la interfaz de Lista). La herencia crea el acoplamiento más alto.
OscarRyz

2
Pero al nombrarlo CountItemsList implica que hace dos cosas, cuenta elementos y es una lista. Creo que una sola responsabilidad para esa clase, contar las ocurrencias, sería tan simple y no necesitaría implementar la interfaz de Lista.
flob

11

Lo sentimos, no hay una llamada a un método simple que pueda hacerlo. Sin embargo, todo lo que debe hacer es crear un mapa y contar la frecuencia con él.

HashMap<String,int> frequencymap = new HashMap<String,int>();
foreach(String a in animals) {
  if(frequencymap.containsKey(a)) {
    frequencymap.put(a, frequencymap.get(a)+1);
  }
  else{ frequencymap.put(a, 1); }
}

Esto realmente no es una solución escalable: imagine que el conjunto de datos de MM tenía cientos y miles de entradas y MM quería saber las frecuencias para cada entrada. Esto podría ser una tarea muy costosa, especialmente cuando hay formas mucho mejores de hacerlo.
mP.

Sí, puede que no sea una buena solución, no significa que esté mal.
Adeel Ansari

1
@dehmann, no creo que literalmente quiera el número de ocurrencias de murciélagos en una colección de 4 elementos, creo que solo se trata de datos de muestra para que podamos entender mejor :-).
paxdiablo 03 de

2
@Vinegar 2/2. La programación se trata de hacer las cosas correctamente ahora, por lo que no causamos dolores de cabeza o una mala experiencia para otra persona, ya sea un usuario u otro programador en el futuro. PD: Cuanto más código escribas, más posibilidades hay de que algo salga mal.
mP.

2
@mP: explique por qué esta no es una solución escalable. Ray Hidayat está construyendo un conteo de frecuencia para cada ficha para que cada ficha se pueda buscar. ¿Cuál es una mejor solución?
stackoverflowuser2010

10

No hay un método nativo en Java para hacer eso por usted. Sin embargo, puede usar IterableUtils # countMatches () de Apache Commons-Collections para hacerlo por usted.


Consulte mi respuesta a continuación: la respuesta correcta es usar una estructura que respalde la idea de contar desde el principio en lugar de contar las entradas de principio a fin cada vez que se realiza una consulta.
mP.

@mP Entonces, ¿usted simplemente rechaza a todos los que tienen una opinión diferente a la suya? ¿Qué pasa si no puede usar una bolsa por alguna razón o se queda atrapado con el uso de una de las colecciones nativas?
Kevin

-1 por ser un mal perdedor :-) Creo que mP lo rechazó porque su solución cuesta tiempo cada vez que quiere un resultado. Una bolsa cuesta un poco de tiempo solo en la inserción. Al igual que las bases de datos, este tipo de estructuras tienden a ser "más leídas que escritas", por lo que tiene sentido utilizar la opción de bajo costo.
paxdiablo 03 de

Y parece que su respuesta también requiere cosas no nativas, por lo que su comentario parece un poco extraño.
paxdiablo 03 de

Gracias a ambos, chicos. Creo que uno de los dos enfoques o ambos podrían funcionar. Lo intentaré mañana.
MM.

9

En realidad, la clase Colecciones tiene un método estático llamado: frecuencia (Colección c, Objeto o) que devuelve el número de ocurrencias del elemento que está buscando, por cierto, esto funcionará perfectamente para usted:

ArrayList<String> animals = new ArrayList<String>();
animals.add("bat");
animals.add("owl");
animals.add("bat");
animals.add("bat");
System.out.println("Freq of bat: "+Collections.frequency(animals, "bat"));

27
Lars Andren publicó la misma respuesta 5 años antes que la tuya.
Fabian Barney

9

Solución alternativa de Java 8 usando Streams :

long count = animals.stream().filter(animal -> "bat".equals(animal)).count();

8

Me pregunto por qué no puedes usar esa API de Google Collection con JDK 1.6. ¿Eso lo dice? Creo que puede, no debería haber problemas de compatibilidad, ya que está diseñado para una versión inferior. El caso habría sido diferente si se hubiera creado para 1.6 y está ejecutando 1.5.

¿Me equivoco en alguna parte?


Han mencionado claramente que están en el proceso de actualizar su api a jdk 1.6.
MM.

1
Eso no hace viejo incompatible. ¿Lo hace?
Adeel Ansari

No debería. Pero la forma en que arrojaban las renuncias me hace sentir incómodo de usarlo en su versión 0.9
MM.

Lo usamos con 1.6. ¿Dónde dice que solo es compatible con 1.5?
Patrick

2
Al "actualizar a 1.6" probablemente se refieren a "actualizar para aprovechar las nuevas cosas en 1.6", no "arreglar la compatibilidad con 1.6".
Adam Jaskiewicz 03 de

6

Un enfoque un poco más eficiente podría ser

Map<String, AtomicInteger> instances = new HashMap<String, AtomicInteger>();

void add(String name) {
     AtomicInteger value = instances.get(name);
     if (value == null) 
        instances.put(name, new AtomicInteger(1));
     else
        value.incrementAndGet();
}

6

Para obtener las ocurrencias del objeto de la lista directamente:

int noOfOccurs = Collections.frequency(animals, "bat");

Para obtener la aparición de la colección Object dentro de la lista, anule el método equals en la clase Object como:

@Override
public boolean equals(Object o){
    Animals e;
    if(!(o instanceof Animals)){
        return false;
    }else{
        e=(Animals)o;
        if(this.type==e.type()){
            return true;
        }
    }
    return false;
}

Animals(int type){
    this.type = type;
}

Llame a Colecciones.frecuencia como:

int noOfOccurs = Collections.frequency(animals, new Animals(1));

6

Manera simple de encontrar la aparición del valor de cadena en una matriz utilizando las características de Java 8.

public void checkDuplicateOccurance() {
        List<String> duplicateList = new ArrayList<String>();
        duplicateList.add("Cat");
        duplicateList.add("Dog");
        duplicateList.add("Cat");
        duplicateList.add("cow");
        duplicateList.add("Cow");
        duplicateList.add("Goat");          
        Map<String, Long> couterMap = duplicateList.stream().collect(Collectors.groupingBy(e -> e.toString(),Collectors.counting()));
        System.out.println(couterMap);
    }

Salida: {Cat = 2, Goat = 1, Cow = 1, cow = 1, Dog = 1}

Puede notar que "Vaca" y vaca no se consideran como la misma cadena, en caso de que lo requiera con el mismo recuento, use .toLowerCase (). Encuentra el fragmento a continuación para lo mismo.

Map<String, Long> couterMap = duplicateList.stream().collect(Collectors.groupingBy(e -> e.toString().toLowerCase(),Collectors.counting()));

Salida: {gato = 2, vaca = 2, cabra = 1, perro = 1}


nit: porque la lista es una lista de cadenas, toString()es innecesaria. Simplemente puede hacer:duplicateList.stream().collect(Collectors.groupingBy(e -> e,Collectors.counting()));
Tad

5

Lo que quieres es una bolsa, que es como un conjunto pero también cuenta el número de ocurrencias. Desafortunadamente, el marco de colecciones de Java es genial, ya que no tienen una bolsa impl. Para eso hay que usar el texto del enlace Apache Common Collection


1
La mejor solución escalable y, si no puede usar material de terceros, simplemente escriba el suyo. Las bolsas no son ciencia espacial para crear. +1.
paxdiablo 03 de

Votado negativamente por dar una respuesta vaga, mientras que otros han proporcionado implementaciones para estructuras de datos de conteo de frecuencias. La estructura de datos de 'bolsa' a la que se vinculó tampoco es una solución adecuada a la pregunta del OP; esa estructura de 'bolsa' está destinada a contener un número específico de copias de un token, no para contar el número de ocurrencias de tokens.
stackoverflowuser2010

2
List<String> list = Arrays.asList("as", "asda", "asd", "urff", "dfkjds", "hfad", "asd", "qadasd", "as", "asda",
        "asd", "urff", "dfkjds", "hfad", "asd", "qadasd" + "as", "asda", "asd", "urff", "dfkjds", "hfad", "asd",
        "qadasd", "as", "asda", "asd", "urff", "dfkjds", "hfad", "asd", "qadasd");

Método 1:

Set<String> set = new LinkedHashSet<>();
set.addAll(list);

for (String s : set) {

    System.out.println(s + " : " + Collections.frequency(list, s));
}

Método 2:

int count = 1;
Map<String, Integer> map = new HashMap<>();
Set<String> set1 = new LinkedHashSet<>();
for (String s : list) {
    if (!set1.add(s)) {
        count = map.get(s) + 1;
    }
    map.put(s, count);
    count = 1;

}
System.out.println(map);

¡Bienvenido a Stack Overflow! Considere explicar su código para facilitar que otros entiendan su solución.
Antimonio

2

Si usa Eclipse Collections , puede usar a Bag. A MutableBagpuede ser devuelto desde cualquier implementación de RichIterablemediante una llamada toBag().

MutableList<String> animals = Lists.mutable.with("bat", "owl", "bat", "bat");
MutableBag<String> bag = animals.toBag();
Assert.assertEquals(3, bag.occurrencesOf("bat"));
Assert.assertEquals(1, bag.occurrencesOf("owl"));

La HashBagimplementación en Eclipse Collections está respaldada por a MutableObjectIntMap.

Nota: Soy un committer para Eclipse Collections.


1

Coloque los elementos de la lista de arrays en el hashMap para contar la frecuencia.


Esto es exactamente lo mismo que dice tweakt con una muestra de código.
mP.

1

Java 8 : otro método

String searched = "bat";
long n = IntStream.range(0, animals.size())
            .filter(i -> searched.equals(animals.get(i)))
            .count();

0

Así que hazlo a la antigua usanza y hazlo tú mismo:

Map<String, Integer> instances = new HashMap<String, Integer>();

void add(String name) {
     Integer value = instances.get(name);
     if (value == null) {
        value = new Integer(0);
        instances.put(name, value);
     }
     instances.put(name, value++);
}

Con el apropiado "sincronizado", si es necesario, para evitar condiciones de carrera. Pero aún así preferiría ver esto en su propia clase.
paxdiablo 03 de

Tienes un error tipográfico. Necesita HashMap en su lugar, ya que lo está tomando en el Mapa. Pero el error de poner 0 en lugar de 1 es un poco más grave.
Adeel Ansari

0

Si es usuario de mi DSL de ForEach , puede hacerlo con una Countconsulta.

Count<String> query = Count.from(list);
for (Count<Foo> each: query) each.yield = "bat".equals(each.element);
int number = query.result();

0

No quería hacer este caso más difícil y lo hice con dos iteradores. Tengo un HashMap con Apellido -> Nombre. Y mi método debería eliminar elementos con FirstName debidamente.

public static void removeTheFirstNameDuplicates(HashMap<String, String> map)
{

    Iterator<Map.Entry<String, String>> iter = map.entrySet().iterator();
    Iterator<Map.Entry<String, String>> iter2 = map.entrySet().iterator();
    while(iter.hasNext())
    {
        Map.Entry<String, String> pair = iter.next();
        String name = pair.getValue();
        int i = 0;

        while(iter2.hasNext())
        {

            Map.Entry<String, String> nextPair = iter2.next();
            if (nextPair.getValue().equals(name))
                i++;
        }

        if (i > 1)
            iter.remove();

    }

}

0
List<String> lst = new ArrayList<String>();

lst.add("Ram");
lst.add("Ram");
lst.add("Shiv");
lst.add("Boss");

Map<String, Integer> mp = new HashMap<String, Integer>();

for (String string : lst) {

    if(mp.keySet().contains(string))
    {
        mp.put(string, mp.get(string)+1);

    }else
    {
        mp.put(string, 1);
    }
}

System.out.println("=mp="+mp);

Salida:

=mp= {Ram=2, Boss=1, Shiv=1}

0
Map<String,Integer> hm = new HashMap<String, Integer>();
for(String i : animals) {
    Integer j = hm.get(i);
    hm.put(i,(j==null ? 1 : j+1));
}
for(Map.Entry<String, Integer> val : hm.entrySet()) {
    System.out.println(val.getKey()+" occurs : "+val.getValue()+" times");
}

0
package traversal;

import java.util.ArrayList;
import java.util.List;

public class Occurrance {
    static int count;

    public static void main(String[] args) {
        List<String> ls = new ArrayList<String>();
        ls.add("aa");
        ls.add("aa");
        ls.add("bb");
        ls.add("cc");
        ls.add("dd");
        ls.add("ee");
        ls.add("ee");
        ls.add("aa");
        ls.add("aa");

        for (int i = 0; i < ls.size(); i++) {
            if (ls.get(i) == "aa") {
                count = count + 1;
            }
        }
        System.out.println(count);
    }
}

Salida: 4


Es una buena práctica en Stack Overflow agregar una explicación de por qué su solución debería funcionar o es mejor que las soluciones existentes. Para obtener más información, lea Cómo responder .
Samuel Liew
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.