2012-03-21 15 views
9

Desidero implementare una mappa hash senza distinzione tra maiuscole e minuscole. Questa domanda di per sé non è nuova, ma volevo aggiungere funzionalità extra e non so quale direzione generale prendere. Voglio che il cliente sia in grado di fare qualcosa del genere:Appoggio appropriato di una distinzione tra maiuscole e minuscole

boolean preserve_case = true; 
Map<String, MyClass> maplet = new CaseInsensitiveHashMap<MyClass>(preserve_case); // If the client enters true at construction, then the put, get, and remove methods should still be case insensitive, but the entry and key sets should preserve the case that the client used when calling put. 

maplet.put("FoO", my_class); 

MyClass bar = maplet.get("foo"); // Should return a reference to my_class 

Set<Entry<String, MyClass>> case_sensitive_set = maplet.entrySet(); // Since the client input true to preserve order, this entry set should be ["FoO"=my_class.toString()] 

posso gestire la maggior parte di questo abbastanza bene; Mi limito a tenere un HashMap sul backend. Quando un cliente inserisce qualcosa, prima di aggiungerlo alla mappa, inserisco la chiave in maiuscolo.

Sto solo facendo fatica a scrivere i metodi keySet() e entrySet(). Voglio che il set di voci restituite e il set di chiavi siano supportati dalla mappa, come lo standard con le mappe Java.

Tuttavia, l'unico modo che posso pensare di gestire è quello di creare una seconda struttura di dati di supporto, come un qualcosa preserved_case_map, che contiene il input.toUpperCase() => ingresso come coppie di valori chiave. Quando il client chiama lo entrySet() (o keySet()), è possibile creare il set di voci restituite eseguendo il ciclo attraverso lo preserved_case_map. Il problema qui è che il set di voci restituite non verrà modificato se apporto modifiche allo HashMap, a meno che non fraintenda qualcosa ...

Fammi sapere se questo ha un senso, o se sto contorcendo un semplice situazione.

+1

La sua non è chiaro se si desidera keySet() per restituire tutte le chiavi che sono state inserite (foo, foo, foo) o solo l'ultima che rimane nella mappa. – Andrejs

+0

Il mio errore. Voglio che keySet() restituisca [FoO], poiché il client è entrato true nel costruttore e ha inserito la coppia di valori chiave come "FoO", my_class. – Sal

+1

Usa un'opzione TreeMap? – Andrejs

risposta

24

È possibile utilizzare una TreeMap con comparatore senza distinzione tra maiuscole e minuscole. Il TreeMap utilizzerà il comparatore per confrontare le chiavi in ​​un modo case-insensitive:

Map<String, Integer> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); 

    map.put("Foo", 1); 
    map.put("fOo", 2); 

    System.out.println(map.get("foo")); // prints 2 
    System.out.println(map.keySet()); // prints [Foo] 
+0

Questo è esattamente quello che sto cercando, Andrejs. Perché HashMap non può contenere un comparatore durante la costruzione? Potrebbe non avere senso nel senso che un comparatore non può conservare l'ordine in una HashMap, ma può usare il comparatore per i metodi get e put e gestirlo proprio come la TreeMap come hai menzionato. – Sal

+1

Ottimo! È perché HashMap si basa su key.hashCode() e i suoi elementi non devono essere comparabili. Le chiavi TreeMap devono essere comparabili perché eseguono confronti interni delle voci. Tuttavia, il comparatore è configurabile. – Andrejs

+0

Molto buono, il comparatore è configurabile; c'è un modo per legare quel comparatore al mio CaseInsensitiveHashMap? Voglio davvero preservare la velocità delle ricerche hash, quindi idealmente, mi piacerebbe creare una classe semplice, come menzionato nella domanda. Fatemi sapere se si pensa di un'altra soluzione, ma credo che la miglior risposta possibile è la creazione di una classe come indicato nella domanda iniziale, quindi la creazione di voci personalizzate, EntrySets, keyset, iteratori, ecc In questo modo, si può assicurare che le modifiche il set di voci è supportato dalla mappa originale e viceversa. – Sal

3

Invece di memorizzare semplicemente il valore, memorizzare un KeyValuePair sia della chiave di input (che ha ancora la custodia fornita dall'utente) che del valore e utilizzare la chiave maiuscola come chiave.

Quando si estrae il valore, estrarlo dal KeyValuePair prima di restituirlo. È anche possibile recuperare il caso della chiave originale eseguendo la stessa ricerca con la chiave maiuscola e quindi estraendo la chiave originale.

1

Un modo è ereditare da HashMap<String, V>, quindi chiamare i metodi nella classe base dopo aver chiamato toUpperCase() sulle chiavi.

Tuttavia, è molto più facile da ignorare semplicemente il metodo hashCode() in MyClass (o qualsiasi altra cosa le classi che verrà utilizzato), e quindi utilizzare la classe standard HashMap<K, V>, senza implementare il proprio. Per esempio:

public int hashCode() { 
    return this.toString().toUperCase().hashCode(); 
} 

Se che mettere in una classe di base e quindi avere il vostro classi ereditano da essa, allora si avrà una soluzione DRY e molto semplice al tuo problema.

+0

Questa è una grande idea, anche se volevo consentire al client di inserire una classe arbitraria. Tuttavia, se creo una breve classe contenente, posso sovrascrivere il metodo put per eseguire un trattamento simile e memorizzare la chiave con un hashCode che potrebbe sembrare key.toUpperCase(). HashCode()? – Sal

+0

Per essere più esplicito, posso eseguire l'override della ricerca hash sulla struttura dei dati di backup, in modo che possa inviare la chiave in arrivo aUpperCase()? – Sal

+0

Tuttavia, poiché il client (programmatore che utilizza la mappa senza distinzione tra maiuscole e minuscole) può fornire qualsiasi classe per 'MyClass', è necessario richiedere * loro * per sovrascrivere hashCode e uguale. –

5

Per semplificare il wrapping di una mappa, è possibile utilizzare ForwardingMap dalle librerie Guava di Google ma questo è facoltativo.

Prima di mettere/ottenere qualcosa dalla mappa di supporto, avvolgere la chiave String in una classe che esegue l'override di hashCode()/equals() e utilizzare il wrapper come chiave nella mappa. Qualcosa di simile:

class KeyWrapper { 
    int hashCode() { 
     return actualStringKey.toUpperCase().hashCode() 
    } 
    boolean equals(Object o) {...} // compare case-insensitive 
} 

Se si ignorare keySet() è possibile creare un nuovo set e riempirlo con actualStringKeys.

+0

Sembra che potrebbe sicuramente essere utile, ma comporterebbe l'importazione di una grande libreria nella mia base di codice ... – Sal

2

Faccio qualcosa in questo modo per una cache mappata che ho scritto. Il nome è cambiato qui per riflettere la tua insensibilità al caso invece del mio caching, ma i concetti sono gli stessi.

Ciò fornirebbe una classe UncasedMap che potrebbe essere riutilizzata per contenere qualcosa, ma sarebbe limitata all'utilizzo di Stringhe come chiavi.

// Map that uses case insensitive strings as keys to any kind of element 
public class UncasedMap<V> extends HashMap<String, V> 
{ 
    private static final long serialVersionUID = 1L; 
    private Map<String, MappedObject> backingMap = new HashMap<String, MappedObject>(); 

    @Override 
    public V put(String key, V value) 
    { 
     MappedObject currentVal = backingMap.get(key.toUpperCase()); 
     backingMap.put(key.toUpperCase(), new MappedObject(key.toUpperCase(), value)); 
     return currentVal.getElement(); 

    } 
    @Override 
    public V get(Object key) 
    { 
     return backingMap.get(key.toString().toUpperCase()).getElement(); 
    } 

    // a private inner class of my case insensitive map, to wrap the callers element 
    private class MappedObject 
    { 
     private String key; 
     private String orig_key; 
     private V mapElement; 

     public MappedObject(String keyx, V elem) 
     { 
      this.orig_key = keyx; 
      this.key = keyx.toUpperCase(); 
      this.mapElement = elem; 
     } 

     public V getElement() 
     { 
      return this.mapElement; 
     } 
    } 
} 

Attuazione keySet() e emptySet() sarebbe più complicato, ma fatto lungo le stesse linee.

+1

Penso di vedere dove sta andando, ma hai ragione, implementando keySet() e emptySet() così che sostengono il supporto. La carta sarebbe un po 'complicata ... In effetti, non vedo come potrebbe essere fatto. – Sal

Problemi correlati