2011-10-24 16 views
6

Si tengo un Guava Multimap, ¿cómo ordenaría las entradas según el número de valores para la clave dada?Ordenar Guava Multimap por número de valores

Por ejemplo:

Multimap<String, String> multiMap = ArrayListMultimap.create(); 
multiMap.put("foo", "1"); 
multiMap.put("bar", "2"); 
multiMap.put("bar", "3"); 
multiMap.put("bar", "99"); 

Teniendo en cuenta esto, cuando se itera sobre MultiMap, ¿cómo iba a conseguir las entradas "bar" que venir primero (desde el "bar" tiene 3 valores frente a sólo el 1 por "foo")?

Respuesta

13

Extracto de las entradas en una lista, a continuación, ordenar la lista:

List<Map.Entry<String, String>> entries = new ArrayList<Map.Entry<String, String>>(map.entries()); 
Collections.sort(entries, new Comparator<Map.Entry<String, String>>() { 
    @Override 
    public int compare(Map.Entry<String, String> e1, Map.Entry<String, String> e2) { 
     return Ints.compare(map.get(e2.getKey()).size(), map.get(e1.getKey()).size()); 
    } 
}); 

A continuación, iterar sobre las entradas.

Editar:

Si lo que se desea es, de hecho, iterar sobre las entradas de la hoja interior (Entry<String, Collection<String>>), a continuación, haga lo siguiente:

List<Map.Entry<String, Collection<String>>> entries = 
    new ArrayList<Map.Entry<String, Collection<String>>>(map.asMap().entrySet()); 
Collections.sort(entries, new Comparator<Map.Entry<String, Collection<String>>>() { 
    @Override 
    public int compare(Map.Entry<String, Collection<String>> e1, 
         Map.Entry<String, Collection<String>> e2) { 
     return Ints.compare(e2.getValue().size(), e1.getValue().size()); 
    } 
}); 

// and now iterate 
for (Map.Entry<String, Collection<String>> entry : entries) { 
    System.out.println("Key = " + entry.getKey()); 
    for (String value : entry.getValue()) { 
     System.out.println(" Value = " + value); 
    } 
} 
+0

Gracias. Eso ordenará las entradas individualmente, pero ahora ya no tengo un Multimap, solo una lista de objetos Map.Entry. Así que he perdido la agrupación de valores en una sola clave. Lo que quiero es mantener el Multimap, pero simplemente reordenar los elementos para que estén en orden descendente según el número de valores. –

+0

Dijiste que querías iterar sobre las entradas. Usted tiene una lista de entradas. Iteramos sobre estas entradas. El MultiMap sigue ahí, intacto. Un MultiMap no está ordenado, y ciertamente no por el número de valores para una clave dada. –

+0

Disculpa, mi pregunta original no estaba del todo clara. Quiero poder iterar sobre el Multimap original, pero obtengo las entradas de conteo más altas primero. Además, TreeMultimap ha clasificado las claves (y los valores), por lo que no es necesariamente exacto decir que no se ha ordenado un Multimap. –

8

que haría uso de keys Multiset entradas del Multimap, ordenarlos por frecuencia descendente (que será más fácil una vez que la funcionalidad descrita en issue 356 se agrega a Guava), y construir un nuevo Multimap iterando las claves ordenadas, obteniendo valores del Multimap original:

/** 
* @return a {@link Multimap} whose entries are sorted by descending frequency 
*/ 
public Multimap<String, String> sortedByDescendingFrequency(Multimap<String, String> multimap) { 
    // ImmutableMultimap.Builder preserves key/value order 
    ImmutableMultimap.Builder<String, String> result = ImmutableMultimap.builder(); 
    for (Multiset.Entry<String> entry : DESCENDING_COUNT_ORDERING.sortedCopy(multimap.keys().entrySet())) { 
     result.putAll(entry.getElement(), multimap.get(entry.getElement())); 
    } 
    return result.build(); 
} 

/** 
* An {@link Ordering} that orders {@link Multiset.Entry Multiset entries} by ascending count. 
*/ 
private static final Ordering<Multiset.Entry<?>> ASCENDING_COUNT_ORDERING = new Ordering<Multiset.Entry<?>>() { 
    @Override 
    public int compare(Multiset.Entry<?> left, Multiset.Entry<?> right) { 
     return Ints.compare(left.getCount(), right.getCount()); 
    } 
}; 

/** 
* An {@link Ordering} that orders {@link Multiset.Entry Multiset entries} by descending count. 
*/ 
private static final Ordering<Multiset.Entry<?>> DESCENDING_COUNT_ORDERING = ASCENDING_COUNT_ORDERING.reverse(); 

EDITAR: Esto no funciona si algunas entradas tienen la misma frecuencia (ver mi comentario)

Otro enfoque, utilizando una Orden sobre la base de las claves Multimapas Multiset y ImmutableMultimap.Builder.orderKeysBy():

/** 
* @return a {@link Multimap} whose entries are sorted by descending frequency 
*/ 
public Multimap<String, String> sortedByDescendingFrequency(Multimap<String, String> multimap) { 
    return ImmutableMultimap.<String, String>builder() 
      .orderKeysBy(descendingCountOrdering(multimap.keys())) 
      .putAll(multimap) 
      .build(); 
} 

private static Ordering<String> descendingCountOrdering(final Multiset<String> multiset) { 
    return new Ordering<String>() { 
     @Override 
     public int compare(String left, String right) { 
      return Ints.compare(multiset.count(left), multiset.count(right)); 
     } 
    }; 
} 

El segundo enfoque es más corto, pero no me gusta el hecho de que el orden tenga estado (depende de la clave de Multimap Multiset para comparar las claves).

+3

¡Gracias! El primer enfoque funcionó muy bien, exactamente lo que quería. Sin embargo, algo está mal con el segundo enfoque (no lo he depurado para averiguar qué todavía ... pero con mis datos de prueba, empiezo con una entrada Multimap cuya asMap() tiene el tamaño 432, pero el tamaño de salida de Multimap es AsMap() es solo 21. –

+0

Eso es realmente extraño. Voy a probarlo cuando pueda encontrar algo de tiempo. Podría haber cometido un error. Estás seguro de que no estás comparando inputMultimap.size() con outputMultimap.asMap().()? –

+0

Correcto, estaba comparando asMap(). Size() en ambos. –

Cuestiones relacionadas