2009-08-06 3 views
12

Ok, ecco il mio problema. Devo fare il HashSet, io uso il metodo removeAll per cancellare i valori che esistono in un set dall'altro.La raccolta rimuove Tutte le maiuscole e minuscole?

Prima di chiamare il metodo, ovviamente aggiungo i valori a Set s. Io chiamo .toUpperCase() su ogni String prima di aggiungere perché i valori sono di casi diversi in entrambi gli elenchi. Non c'è nessuna rima o ragione per il caso.

Una volta chiamo removeAll, ho bisogno di avere i casi originali indietro per i valori che sono rimasti nel Set. C'è un modo efficace per farlo senza scorrere l'elenco originale e utilizzare CompareToIgnoreCase?

Esempio:

List1:

"BOB" 
"Joe" 
"john" 
"MARK" 
"dave" 
"Bill" 

Lista2:

"JOE" 
"MARK" 
"DAVE" 

Dopo questo, creare un separato HashSet per ciascun List mediante toUpperCase() su String s. Quindi chiamare removeAll.

Set1.removeAll(set2); 

Set1: 
    "BOB" 
    "JOHN" 
    "BILL" 

ho bisogno di ottenere l'elenco per assomigliare a questo nuovo:

"BOB" 
"john" 
"Bill" 

Tutte le idee sarebbe molto apprezzato. So che è scadente, dovrebbe esserci uno standard per la lista originale, ma non spetta a me decidere.

risposta

13

nella mia risposta iniziale, io senza pensarci suggerito di utilizzare un Comparator, ma questo fa sì che il TreeSet di violare la equals contract ed è un bug in attesa di accadere:

// Don't do this: 
Set<String> setA = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); 
setA.add("hello"); 
setA.add("Hello"); 
System.out.println(setA); 

Set<String> setB = new HashSet<String>(); 
setB.add("HELLO"); 
// Bad code; violates symmetry requirement 
System.out.println(setB.equals(setA) == setA.equals(setB)); 

E ' meglio usare un tipo dedicato:

public final class CaselessString { 
    private final String string; 
    private final String normalized; 

    private CaselessString(String string, Locale locale) { 
    this.string = string; 
    normalized = string.toUpperCase(locale); 
    } 

    @Override public String toString() { return string; } 

    @Override public int hashCode() { return normalized.hashCode(); } 

    @Override public boolean equals(Object obj) { 
    if (obj instanceof CaselessString) { 
     return ((CaselessString) obj).normalized.equals(normalized); 
    } 
    return false; 
    } 

    public static CaselessString as(String s, Locale locale) { 
    return new CaselessString(s, locale); 
    } 

    public static CaselessString as(String s) { 
    return as(s, Locale.ENGLISH); 
    } 

    // TODO: probably best to implement CharSequence for convenience 
} 

Questo codice è meno probabilità di causare bug:

Set<CaselessString> set1 = new HashSet<CaselessString>(); 
set1.add(CaselessString.as("Hello")); 
set1.add(CaselessString.as("HELLO")); 

Set<CaselessString> set2 = new HashSet<CaselessString>(); 
set2.add(CaselessString.as("hello")); 

System.out.println("1: " + set1); 
System.out.println("2: " + set2); 
System.out.println("equals: " + set1.equals(set2)); 

Questo è, purtroppo, più prolisso.

+4

Non è necessario eseguire il rollover del proprio comparatore. La classe String ne fornisce uno per te: http://java.sun.com/javase/6/docs/api/java/lang/String.html#CASE_INSENSITIVE_ORDER – banjollity

+0

@bankollity. Grazie! - È passato da Java 1.2 e non l'ho mai notato. Codice modificato – McDowell

+1

Wow è stato estremamente semplice da implementare anche se la documentazione porta a credere che il comparatore sia utilizzato esclusivamente per l'ordinamento. TreeSet (Comparator c): crea un nuovo set vuoto, ordinato in base al comparatore specificato. http://java.sun.com/j2se/1.4.2/docs/api/java/util/TreeSet.html#TreeSet%28java.util.Comparator%29. Sono contento che abbia funzionato, grazie mille per la tua risposta! – user84786

1

È possibile utilizzare uno hashmap e utilizzare il set di maiuscole come chiavi associate al set di maiuscole e minuscole.

Le chiavi delle hashmap sono univoche e è possibile ottenerne una serie utilizzando HashMap.keyset();

per recuperare il caso originale, è semplice come HashMap.get ("UPPERCASENAME").

E secondo il documentation:

Restituisce una vista set di chiavi contenute in questa mappa. Il set è supportato dalla mappa, quindi le modifiche alla mappa si riflettono nel set e viceversa. Il set supporta la rimozione dell'elemento , che rimuove la corrispondente mappatura da questa mappa, tramite l'Iterator.remove, Set.remove, removeAll, retainAll e chiare operazioni. Non supporta le operazioni add o addAll.

Così HashMap.keyset() removeAll effettuerà il hashmap :)

EDIT:. Usare la soluzione di McDowell. Ho trascurato il fatto che in realtà non avevi bisogno che le lettere fossero maiuscole: P

0

per quanto ne so, hashset usa il metodo hashCode dell'oggetto per distinguerli l'uno dall'altro. dovresti quindi sovrascrivere questo metodo nel tuo oggetto per casi distinti.

se si utilizza realmente la stringa, non è possibile sovrascrivere questo metodo poiché non è possibile estendere la classe String.

quindi è necessario creare la propria classe contenente una stringa come attributo che si riempie con il contenuto. potresti voler avere un metodo getValue() e setValue (String) per modificare la stringa.

quindi è possibile aggiungere la propria classe all'hashmap.

questo dovrebbe risolvere il tuo problema.

riguarda

1

Questa sarebbe una soluzione interessante da utilizzare utilizzando google-collections. Si potrebbe avere un predicato costante in questo modo:

private static final Function<String, String> TO_UPPER = new Function<String, String>() { 
    public String apply(String input) { 
     return input.toUpperCase(); 
} 

e poi quello che stai dopo potrebbe essere fatto someting in questo modo:

Collection<String> toRemove = Collections2.transform(list2, TO_UPPER); 

Set<String> kept = Sets.filter(list1, new Predicate<String>() { 
    public boolean apply(String input) { 
     return !toRemove.contains(input.toUpperCase()); 
    } 
} 

cioè:

  • Costruire un maiuscolo versione solo case dell'elenco "scartare"
  • Applicare un filtro all'elenco originale, mantenendo solo quegli elementi di cui si il valore di ppercased è non nell'elenco di maiuscole/minuscole.

Nota che l'uscita del Collections2.transform non è un efficiente Set implementazione, quindi se hai a che fare con un sacco di dati e il costo di sondare quella lista ti farà del male, è possibile utilizzare invece

Set<String> toRemove = Sets.newHashSet(Collections2.transform(list2, TO_UPPER)); 

che ripristinerà una ricerca efficiente, restituendo il filtro su O (n) anziché su O (n^2).

3

potrebbe essere fatto da:

  1. Spostamento del contenuto delle liste in case-insensitive TreeSet s,
  2. quindi rimuovendo tutti i comuni String s insensitively-caso grazie TreeSet#removeAll(Collection<?> c)
  3. e, infine, basandosi su il fatto che ArrayList#retainAll(Collection<?> c) itererà sugli elementi della lista e per ogni elemento che chiamerà contains(Object o) sulla raccolta fornita per sapere se il valore deve essere mantenuto o meno e in questo caso, poiché la raccolta non fa distinzione tra maiuscole e minuscole, manterremo solo il String s che corrisponde alla maiuscole/minuscole con quello che abbiamo nell'istanza TreeSet fornita.

Il corrispondente codice:

List<String> list1 = new ArrayList<>(
    Arrays.asList("BOB", "Joe", "john", "MARK", "dave", "Bill") 
); 

List<String> list2 = Arrays.asList("JOE", "MARK", "DAVE"); 

// Add all values of list1 in a case insensitive collection 
Set<String> set1 = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); 
set1.addAll(list1); 
// Add all values of list2 in a case insensitive collection 
Set<String> set2 = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); 
set2.addAll(list2); 
// Remove all common Strings ignoring case 
set1.removeAll(set2); 
// Keep in list1 only the remaining Strings ignoring case 
list1.retainAll(set1); 

for (String s : list1) { 
    System.out.println(s); 
} 

uscita:

BOB 
john 
Bill 

NB 1: È importante avere il contenuto del secondo elenco in un TreeSet soprattutto se si non si conosce la dimensione di esso perché il comportamento di TreeSet#removeAll(Collection<?> c) dipende dalla dimensione di entrambe le raccolte, se la dimensione di La raccolta attuale è strettamente più grande della dimensione della collezione fornita, quindi chiamerà direttamente remove(Object o) sulla raccolta corrente per rimuovere ogni elemento, in questo caso la raccolta fornita potrebbe essere un elenco. Ma se è il contrario, chiamerà lo contains(Object o) sulla raccolta fornita per sapere se un determinato elemento debba essere rimosso o meno, quindi se non è una raccolta senza distinzione tra maiuscole e minuscole, non otterremo il risultato previsto.

NB 2: Il comportamento del metodo ArrayList#retainAll(Collection<?> c) sopra descritto è lo stesso che il comportamento della implementazione predefinita del metodo retainAll(Collection<?> c) che possiamo trovare in AbstractCollection tale che questo approccio potrà mai funzionare con qualsiasi collezioni la cui attuazione retainAll(Collection<?> c) ha lo stesso comportamento.

+0

Molto bello. Mi chiedo come il metodo retainAll sappia mantenere i valori sebbene in set1 siano un po 'diversi – Muky

+0

@ Muky thx per il feedback, ho migliorato la mia risposta per chiarire, sperando che ora sia abbastanza buono –