2009-02-03 13 views
123

Tengo una ArrayList, una clase de colección de Java, de la siguiente manera:Cómo contar el número de apariciones de un elemento en una lista

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

Como se puede ver, el animalsArrayList consta de 3 bat elementos y un elemento owl. Me preguntaba si hay alguna API en el marco de recopilación que devuelve el número de ocurrencias bat o si hay otra forma de determinar el número de instancias.

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

+0

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

Respuesta

7

No existe 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.

+0

Consulte mi respuesta a continuación: la respuesta correcta es utilizar una estructura que admita la idea de recuento desde el principio en lugar de contar entradas de principio a fin cada vez que se realiza una consulta. –

+0

@mP Entonces, ¿acabas de rechazar a todos los que tienen una opinión diferente a la tuya? ¿Qué pasa si él no puede usar una bolsa por alguna razón o está atrapado con el uso de una de las colecciones nativas? – Kevin

+0

No merece un voto a favor, en mi humilde opinión. –

10

Lo sentimos, no existe una llamada a método simple que pueda hacerlo. Todo lo que necesitas 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); } 
} 
+0

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. –

+0

Sí, podría no ser una buena solución, no significa que esté mal. –

+0

Él solo quiere el número de ocurrencias 'murciélagos'. Simplemente itere una vez sobre ArrayList original e incremente un contador cada vez que vea 'bat'. – Frank

5

Lo que quiere 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 impl. Para que uno debe usar la colección Común Apache link text

+1

La mejor solución escalable y, si no puedes usar material de terceros, solo tienes que escribir el tuyo. Las bolsas no son ciencia espacial para crear. +1. – paxdiablo

+0

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

0

Así que hacerlo de la manera antigua y rodar su propia:

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++); 
} 
+0

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

+0

Tiene un error tipográfico. Necesita HashMap en su lugar, ya que lo está tomando en Map. Pero el error de poner 0 en vez de 1 es un poco más serio. –

8

me pregunto, ¿por qué no se puede utilizar esa API Colección de Google con JDK 1.6. ¿Eso dice? Creo que puede, no debería haber problemas de compatibilidad, ya que está diseñado para una versión más baja. El caso habría sido diferente si eso fue construido para 1.6 y está ejecutando 1.5.

¿Me equivoco?

+0

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

+1

Eso no hace que el anterior sea incompatible. ¿Lo hace? –

+0

No debería. Pero la forma en que lanzaban avisos, me incomoda usarlo en su versión 0.9 –

1

Coloque los elementos del arraylist en el hashMap para contar la frecuencia.

+0

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

6

Un enfoque ligeramente 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(); 
} 
20

Esto demuestra, por eso es importante "Refer to objects by their interfaces" como se describe en Effective Java libro.

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 esos 50 lugares, y probablemente usted Tendrás que romper tu código (si solo lo usas no hay gran problema, pero si lo usa alguien más, también romperás su código)

Programando en la interfaz que 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 de cómo podría escribirse esto. Esta es solo una muestra, una lista de producción lista sería mucho más 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")); 
    } 
} 

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

+11

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

+0

Estoy completamente de acuerdo con usted. La razón por la que utilicé la herencia en la muestra es porque es mucho más fácil mostrar un ejemplo en ejecución usando herencia que composición (teniendo que implementar la interfaz de la 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 sería necesario implementar la interfaz de la Lista. – flob

0

Si usted es un usuario de mi ForEach DSL, se puede hacer con una consulta Count.

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

Estoy bastante seguro de la frecuencia-método estático en las colecciones sería muy útil aquí:

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

Así es como yo lo haría de todos modos. Estoy bastante seguro de que esto es jdk 1.6 en línea recta.

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} 
47

En Java 8:

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

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

+7

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

+0

Esto no es lo que se pidió. Hace más trabajo de lo necesario. –

2

Si utiliza Eclipse Collections, se puede utilizar un Bag. Se puede devolver un MutableBag desde cualquier implementación de RichIterable llamando al 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 aplicación HashBag en CE está respaldado por una MutableObjectIntMap.

Nota: Soy un committer para colecciones de Eclipse.

10

En realidad, la clase Colecciones tiene un método estático denominado: frecuencia (Collection c, Object o) que devuelve el número de veces que aparece el elemento que está buscando, por cierto, esto va a 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")); 
+13

Lars Andren publicó la misma respuesta 5 años antes que la tuya. –

2

Java 8 - otro método

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

alternativa Java 8 solución usando Streams:

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

Para obtener las ocurrencias del objeto de la lista directamente:

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

Para obtener la ocurrencia de la colección de objetos de lista en el interior, reemplazar 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 al Collections.frequency como:

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

No quería que este caso fuera más difícil y lo hice con dos iteradores Tengo un HashMap con Apellido -> Nombre. Y mi método debe eliminar elementos con el nombre de pila.

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(); 

    } 

} 
1
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); 
+0

¡Bienvenido a Stack Overflow! Considere explicar su código para que otros entiendan mejor su solución. – Antimony

0

manera sencilla de encontrar la ocurrencia de valor de cadena en una matriz usando Java 8 funciones.

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, Cabra = 1, vaca = 1, vaca = 1, Perro = 1}

Se puede notar "vaca" y la vaca no se consideran como la misma cadena, en caso de que lo requiera bajo el mismo conteo, use .toLowerCase(). Encuentre el fragmento a continuación para el mismo.

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

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

Cuestiones relacionadas