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.

MM.
fuente
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.

Lars Andren
fuente
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()));
Vitalii Fedorenko
fuente
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.

OscarRyz
fuente
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); }
}
Ray Hidayat
fuente
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.

Kevin
fuente
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"));
Khafaga
fuente
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();
Cristina
fuente
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?

Adeel Ansari
fuente
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();
}
Peter Lawrey
fuente
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));
atr
fuente
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}

Eswaran Venkatesan
fuente
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

mP.
fuente
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);
sabm
fuente
¡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.

Donald Raab
fuente
1

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

Shamik
fuente
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();
RUMANIA_ingeniero
fuente
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++);
}
Mark Renouf
fuente
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();
akuhn
fuente
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();

    }

}
Alexander Shapkin
fuente
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}
Ramling Muley
fuente
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");
}
fcm45
fuente
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

MD EMRUL EMRAN
fuente
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