2011-10-24 1 views
6

Se si dispone di un multimetro Guava, come dovrei ordinare le voci in base al numero di valori per la chiave specificata?Sort Guava Multimap per numero di valori

Per esempio:

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

Detto questo, quando l'iterazione di Multimap, come vorrei ottenere le voci "bar" a venire prima (dal "bar" ha 3 valori contro solo l'1 per "foo")?

risposta

13

Estrarre le voci in un elenco, quindi ordinare l'elenco:

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

Poi iterare le voci.

Edit:

Se quello che vuoi è di fatto iterare sulle voci della mappa interna (Entry<String, Collection<String>>), quindi effettuare le seguenti operazioni:

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

Grazie. Questo ordinerà le voci individualmente, ma ora non ho più un Multimap, solo una lista di oggetti Map.Entry. Quindi ho perso il raggruppamento di valori in una singola chiave. Quello che voglio è mantenere il Multimap, ma riordinare semplicemente gli elementi in modo che siano in ordine decrescente in base al numero di valori. –

+0

Hai detto che volevi scorrere le voci. Hai una lista di voci. Scorri sopra queste voci. Il MultiMap è ancora lì, intatto. Una MultiMap non è ordinata, e certamente non dal numero di valori per una data chiave. –

+0

Scusa, la mia domanda iniziale non era abbastanza chiara. Voglio essere in grado di scorrere il Multimap originale, ma ottenere le voci di conteggio più alte prima. Inoltre, un TreeMultimap ha ordinato chiavi (e valori), quindi non è necessariamente esatto dire che un Multimap non è ordinato. –

8

userei keys Multiset voci del Multimap, ordinali in base alla frequenza decrescente (che sarà più facile quando la funzionalità descritta in issue 356 viene aggiunta a Guava) e costruisci un nuovo Multimap ripetendo le chiavi ordinate, ottenendo i valori dalla Multimap originale:

/** 
* @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(); 

EDIT: QUESTO NON FUNZIONA Se alcune voci hanno la stessa frequenza (vedi il mio commento)

Un altro approccio, utilizzando un ordinamento in base ai Multimaps' chiavi Multiset, e 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)); 
     } 
    }; 
} 

Il secondo approccio è più breve, ma non mi piace il fatto che l'ordine abbia uno stato (dipende dalla chiave Multiset della Multimap per confrontare le chiavi).

+3

Grazie! Il primo approccio ha funzionato alla grande, esattamente quello che volevo. C'è qualcosa di sbagliato nel secondo approccio, anche se (non ho provato il debug per capire cosa ancora ... ma con i miei dati di test comincio con un multimap di input di cui asMap() ha dimensione 432, ma la dimensione di asMap() di Multimap di output è solo 21. –

+0

Questo è davvero strano, lo testerò quando riuscirò a trovare un po 'di tempo, forse avrò commesso un errore .Sai sicuro che non stai confrontando inputMultimap.size() con outputMultimap.asMap(). size()? –

+0

Corretto, stavo confrontando asMap(). Size() su entrambi. –