2009-08-06 16 views
12

Ok, así que aquí está mi problema. Tengo que HashSet, utilizo el método removeAll para eliminar valores que existen en un conjunto del otro.Colección removeTodo caso ignorante?

Antes de llamar al método, obviamente agrego los valores al Set s. Llamo al .toUpperCase() en cada String antes de agregar porque los valores son de casos diferentes en ambas listas. No hay rima o razón para el caso.

Una vez que llamo al removeAll, necesito recuperar las fundas originales para los valores que quedan en el Set. ¿Hay una forma eficiente de hacerlo sin tener que pasar por la lista original y usar CompareToIgnoreCase?

Ejemplo:

Lista1:

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

Lista2:

"JOE" 
"MARK" 
"DAVE" 

Después de esto, crear una separada HashSet para cada lista usando toUpperCase() en String s. Luego llame al removeAll.

Set1.removeAll(set2); 

Set1: 
    "BOB" 
    "JOHN" 
    "BILL" 

necesito para obtener la lista para parecerse a esto de nuevo:

"BOB" 
"john" 
"Bill" 

Cualquier idea sería muy apreciada. Sé que es pobre, debería haber un estándar para la lista original, pero eso no me corresponde a mí decidir.

Respuesta

13

en mi respuesta original, sin pensarlo sugirieron utilizar un Comparator, pero esto hace que el TreeSet violar la equals contract y es un error a punto de ocurrir:

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

Es mejor utilizar un tipo dedicado:

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 
} 

Este código es menos probable que cause errores:

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

Ésta es, por desgracia, más prolija.

+4

No necesita hacer su propio comparador. La clase String proporciona una para usted: http://java.sun.com/javase/6/docs/api/java/lang/String.html#CASE_INSENSITIVE_ORDER – banjollity

+0

@bankollity. ¡Gracias! - Eso estuvo allí desde Java 1.2 y nunca lo había notado. Código enmendado – McDowell

+1

Wow fue extremadamente simple de implementar aunque la documentación lo lleva a creer que el comparador se usa únicamente para la clasificación. TreeSet (Comparador c): construye un nuevo conjunto vacío, ordenado de acuerdo con el comparador especificado. http://java.sun.com/j2se/1.4.2/docs/api/java/util/TreeSet.html#TreeSet%28java.util.Comparator%29. Me alegro de que haya funcionado, ¡muchas gracias por su respuesta! – user84786

1

Puede usar un hashmap y usar el conjunto de mayúsculas como las teclas que se asignan al conjunto mixto de mayúsculas y minúsculas.

Las claves de hashmaps son únicas y puede obtener un conjunto de ellas usando HashMap.keyset();

para recuperar la caja original, es tan simple como HashMap.get ("UPPERCASENAME").

Y de acuerdo con la documentation:

Devuelve una vista de conjunto de las teclas contenidas en este mapa. El conjunto es respaldado por el mapa, por lo que los cambios en el mapa se reflejan en el conjunto y viceversa. El conjunto soporta eliminación elemento , que elimina el correspondiente cartografía de este mapa, a través de la Iterator.remove, Set.remove, removeAll, retainAll, y claros operaciones. No es compatible con agregar o agregar todas las operaciones.

Así HashMap.keyset() removeAll afectará a la HashMap :)

EDIT:. Utilizar la solución de McDowell. Pasé por alto el hecho de que en realidad no necesitabas que las letras fueran mayúsculas: P

0

hasta donde yo sé, hashset usa el método hashCode del objeto para diferenciarlas entre sí. , por lo tanto, debe anular este método en su objeto para distintos casos.

si realmente está utilizando cadenas, no puede anular este método ya que no puede extender la clase String.

por lo tanto, necesita crear su propia clase que contenga una cadena como atributo que usted completa con su contenido. es posible que desee tener un método getValue() y setValue (String) para modificar la cadena.

luego puede agregar su propia clase al hashmap.

esto debería resolver su problema.

respecto

1

Esta sería una interesante solución usando google-collections. Usted podría tener un predicado constante, así:

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

y luego lo que está buscando que se podría hacer calle detrás de esta manera:

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

Es decir:

  • Construir un mayúsculas versión de solo-caso de la lista 'para descartar'
  • Aplicar un filtro a la lista original, conservando solo esos elementos cuya u el valor ponderado es no en la lista de solo mayúsculas.

Tenga en cuenta que la salida de Collections2.transform no es una Set implementación eficiente, por lo que si usted está tratando con una gran cantidad de datos y el costo de sondear esa lista hará daño a usted, usted puede utilizar en su lugar

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

que restaurará una búsqueda eficiente, devolviendo el filtrado a O (n) en lugar de O (n^2).

3

se podía hacer por:

  1. Mover el contenido de sus listas en mayúsculas y minúsculas TreeSet s,
  2. luego retirar todo String común s mayúsculas y minúsculas gracias TreeSet#removeAll(Collection<?> c)
  3. y, finalmente, que dependen de el hecho de que ArrayList#retainAll(Collection<?> c) iterará sobre los elementos de la lista y para cada elemento llamará al contains(Object o) en la colección proporcionada para saber si el valor debe mantenerse o no y aquí, como la colección no distingue entre mayúsculas y minúsculas, solo guardaremos el String s que coinciden con mayúsculas y minúsculas con lo que tenemos en la instancia de TreeSet proporcionada.

El código correspondiente:

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

Salida:

BOB 
john 
Bill 

NB 1: Es importante tener el contenido de la segunda lista en un TreeSet sobre todo si tenemos No sé el tamaño de la misma porque el comportamiento de TreeSet#removeAll(Collection<?> c) depende del tamaño de ambas colecciones, si el tamaño de th La colección actual es estrictamente más grande que el tamaño de la colección proporcionada, luego llamará directamente al remove(Object o) en la colección actual para eliminar cada elemento, en este caso la colección provista podría ser una lista. Pero si es lo contrario, llamará al contains(Object o) en la colección proporcionada para saber si un elemento dado debe eliminarse o no, de modo que si no es una colección que no distingue entre mayúsculas y minúsculas, no obtendremos el resultado esperado.

NB 2: El comportamiento del método ArrayList#retainAll(Collection<?> c) descrito anteriormente es el mismo que el comportamiento de la aplicación por defecto del método retainAll(Collection<?> c) que podemos encontrar en AbstractCollection de tal manera que este enfoque funcione de verdad con todas las colecciones cuya puesta en práctica de retainAll(Collection<?> c) tiene el mismo comportamiento.

+0

Muy agradable. Me pregunto cómo el método de keepAll sabe para mantener los valores, aunque en set1 son un poco diferentes – Muky

+0

@Muky thx para la retroalimentación, mejoré mi respuesta para dejar en claro, con la esperanza de que será lo suficientemente bueno ahora –