font - mostrar texto en jlabel java
¿Cómo implementar un mapa con múltiples claves? (27)
¿Por qué no simplemente descartar el requisito de que la clave tenga que ser de un tipo específico, es decir, simplemente utilizar Mapa <Objeto, V>.
A veces los genéricos simplemente no vale la pena el trabajo extra.
Esta pregunta ya tiene una respuesta aquí:
Necesito una estructura de datos que se comporte como un Mapa, pero usa varias claves (de tipo diferente) para acceder a sus valores.
(No seamos demasiado generales, digamos dos claves)
Las llaves tienen la garantía de ser únicas.
Algo como:
MyMap<K1,K2,V> ...
Con métodos como:
getByKey1(K1 key)...
getByKey2(K2 key)...
containsKey1(K1 key)...
containsKey2(K2 key)...
¿Tienes alguna sugerencia?
Lo único que puedo pensar es:
Escribe una clase que use dos mapas internamente.
EDITAR Algunas personas me sugieren que use una tupla , un par o similar como clave para el Mapa de Java, pero esto no funcionaría para mí:
Debo, como está escrito anteriormente, poder buscar valores solo con una de las dos claves especificadas.
Los mapas usan códigos hash de claves y verifican su igualdad.
¿Qué hay de usar una estructura de datos trie?
http://en.wikipedia.org/wiki/Trie
La raíz del trie will by blank. Los hermanos de primer nivel serán sus claves principales del mapa, los hermanos de segundo nivel serán sus claves secundarias y el tercer nivel serán los nodos terminales que tendrán el valor a lo largo de nulo para indicar la terminación de esa rama. También puede agregar más de dos claves usando el mismo esquema.
Buscar es simple DFS.
¿Qué tal si declaras la siguiente clase "Clave"?
public class Key {
public Object key1, key2, ..., keyN;
public Key(Object key1, Object key2, ..., Object keyN) {
this.key1 = key1;
this.key2 = key2;
...
this.keyN = keyN;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Key))
return false;
Key ref = (Key) obj;
return this.key1.equals(ref.key1) &&
this.key2.equals(ref.key2) &&
...
this.keyN.equals(ref.keyN)
}
@Override
public int hashCode() {
return key1.hashCode() ^ key2.hashCode() ^
... ^ keyN.hashCode();
}
}
Declarando el mapa
Map<Key, Double> map = new HashMap<Key,Double>();
Declarando el objeto clave
Key key = new Key(key1, key2, ..., keyN)
Llenar el mapa
map.put(key, new Double(0))
Obtener el objeto del mapa
Double result = map.get(key);
Commons-collections proporciona justo lo que estás buscando: http://commons.apache.org/proper/commons-collections/javadocs/api-release/index.html
Parece que ahora las colecciones comunes están escritas.
Puede encontrar una versión mecanografiada en: https://github.com/megamattron/collections-generic
Esto soportará exactamente su caso de uso:
MultiKeyMap<k1,k2,...,kn,v> multiMap = ??
Creé esto para resolver un problema similar.
Estructura de datos
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
public class HashBucket {
HashMap<Object, ArrayList<Object>> hmap;
public HashBucket() {
hmap = new HashMap<Object, ArrayList<Object>>();
}
public void add(Object key, Object value) {
if (hmap.containsKey(key)) {
ArrayList al = hmap.get(key);
al.add(value);
} else {
ArrayList al = new ArrayList<Object>();
al.add(value);
hmap.put(key, al);
}
}
public Iterator getIterator(Object key) {
ArrayList al = hmap.get(key);
return hmap.get(key).iterator();
}
}
Recuperar un valor:
(Nota * Vuelve a colocar el Objeto en el tipo insertado. En mi caso fue mi Objeto Evento)
public Iterator getIterator(Object key) {
ArrayList al = hmap.get(key);
if (al != null) {
return hmap.get(key).iterator();
} else {
List<Object> empty = Collections.emptyList();
return empty.iterator();
}
}
Insertar
Event e1 = new Event();
e1.setName("Bob");
e1.setTitle("Test");
map.add("key",e1);
Defina una clase que tenga una instancia de K1 y K2. Luego usa eso como clase como tu tipo de clave.
Dependiendo de cómo se usará, puede hacer esto con dos mapas: Map<K1, V>
y Map<K2, V>
o con dos mapas Map<K1, V>
y Map<K2, K1>
. Si una de las teclas es más permanente que la otra, la segunda opción puede tener más sentido.
Dos mapas Un Map<K1, V>
y un Map<K2, V>
. Si debe tener una sola interfaz, escriba una clase contenedora que implemente dichos métodos.
Me parece que los métodos que desea en su pregunta son compatibles directamente con Map. El que pareciera querer es
put(K1 key, K2 key, V value)
put(K1 key, V value)
put(K2 key, V value)
Tenga en cuenta que en el mapa, get()
y containsKey()
etc. todos toman argumentos de Object
. No hay nada que le impida utilizar el método get()
para delegar en todos los mapas compuestos que combine (como se indica en su pregunta y otras respuestas). Tal vez necesites un registro de tipo para que no tengas problemas de conversión de clase (si son especiales + implementados ingenuamente).
Un registro basado en mecanografía también te permitirá recuperar el mapa "correcto" que se utilizará:
Map<T,V> getMapForKey(Class<T> keyClass){
//Completely naive implementation - you actually need to
//iterate through the keys of the maps, and see if the keyClass argument
//is a sub-class of the defined map type. And then ordering matters with
//classes that implement multiple interfaces...
Map<T,V> specificTypeMap = (Map<T,V) maps.get(keyClass);
if (specificTypeMap == null){
throw new IllegalArgumentException("There is no map keyed by class" + keyClass);
}
return maps.get(keyClass);
}
V put(Object key, V value) {
//This bit requires generic suppression magic - but
//nothing leaves this class and you''re testing it right?
//(You can assert that it *is* type-safe)
Map map = getMapForKey(key.getClass());
map.put(object, key);
}
void put(Object[] keys, V value) { //Or put(V value, Object ... keys)
//Might want to catch exceptions for unsupported keys and log instead?
.....
}
Solo algunas ideas ...
Otra solución es usar la guayaba de Google
import com.google.common.collect.Table;
import com.google.common.collect.HashBasedTable;
Table<String, String, Integer> table = HashBasedTable.create();
El uso es realmente simple:
String row = "a";
String column = "b";
int value = 1;
if (!table.contains(row, column)) {
table.put(row, column, value);
}
System.out.println("value = " + table.get(row, column));
El método HashBasedTable.create()
básicamente está haciendo algo como esto:
Table<String, String, Integer> table = Tables.newCustomTable(
Maps.<String, Map<String, Integer>>newHashMap(),
new Supplier<Map<String, Integer>>() {
public Map<String, Integer> get() {
return Maps.newHashMap();
}
});
si intenta crear algunos mapas personalizados, debe elegir la segunda opción (como sugiere @Karatheodory), de lo contrario, debería estar bien con la primera opción.
Otra solución posible que proporciona la posibilidad de claves más complicadas se puede encontrar aquí: http://insidecoffe.blogspot.de/2013/04/indexable-hashmap-implementation.html
Parece que tu solución es bastante plausible para esta necesidad, sinceramente no veo ningún problema si tus dos tipos de teclas son realmente distintos. Solo hace que escribas tu propia implementación para esto y lidies con problemas de sincronización si es necesario.
Puedo ver los siguientes enfoques:
a) Usa 2 mapas diferentes. Puedes envolverlos en una clase como sugieres, pero incluso eso podría ser un exceso. Simplemente use los mapas directamente: key1Map.getValue (k1), key2Map.getValue (k2)
b) Puede crear una clase de clave sensible al tipo, y usar eso (no probado).
public class Key {
public static enum KeyType { KEY_1, KEY_2 }
public final Object k;
public final KeyType t;
public Key(Object k, KeyType t) {
this.k = k;
this.t= t;
}
public boolean equals(Object obj) {
KeyType kt = (KeyType)obj;
return k.equals(kt.k) && t == kt.t;
}
public int hashCode() {
return k.hashCode() ^ t.hashCode();
}
}
Por cierto, en muchos casos comunes, el espacio de key1
y el espacio de key2
no se cruzan. En ese caso, no es necesario que hagas nada especial. Simplemente defina un mapa que tenga entradas key1=>v
así como key2=>v
Qué tal algo como esto:
Su afirmación dice que las claves son únicas, por lo que es muy posible guardar los mismos objetos de valor en diferentes claves y cuando envíe cualquier clave que coincida con dicho valor, podremos volver al objeto de valor.
Vea el código a continuación:
Un valor de Clase de objeto,
public class Bond {
public Bond() {
System.out.println("The Name is Bond... James Bond...");
}
private String name;
public String getName() { return name;}
public void setName(String name) { this.name = name; }
}
public class HashMapValueTest {
public static void main(String[] args) {
String key1 = "A";
String key2 = "B";
String key3 = "C";
Bond bond = new Bond();
bond.setName("James Bond Mutual Fund");
Map<String, Bond> bondsById = new HashMap<>();
bondsById.put(key1, bond);
bondsById.put(key2, bond);
bondsById.put(key3, bond);
bond.setName("Alfred Hitchcock");
for (Map.Entry<String, Bond> entry : bondsById.entrySet()) {
System.out.println(entry.getValue().getName());
}
}
}
El resultado es:
The Name is Bond... James Bond...
Alfred HitchCock
Alfred HitchCock
Alfred HitchCock
Recomiendo algo como esto:
public class MyMap {
Map<Object, V> map = new HashMap<Object, V>();
public V put(K1 key,V value){
return map.put(key, value);
}
public V put(K2 key,V value){
return map.put(key, value);
}
public V get(K1 key){
return map.get(key);
}
public V get(K2 key){
return map.get(key);
}
//Same for conatains
}
Entonces puedes usarlo como:
myMap.put(k1,value)
o myMap.put(k2,value)
Ventajas : Es simple, aplica seguridad de tipo y no almacena datos repetidos (como las dos soluciones de mapas, aunque aún almacenan valores duplicados).
Inconvenientes : no genérico.
Si las claves son únicas, entonces no hay necesidad de 2 mapas, mapas de mapas, mapOfWhateverThereIs. Es necesario que haya solo 1 mapa único y solo un método de envoltura simple que coloque las claves y el valor en ese mapa. Ejemplo:
Map<String, String> map = new HashMap<>();
public void addKeysAndValue(String key1, String key2, String value){
map.put(key1, value);
map.put(key2, value);
}
public void testIt(){
addKeysAndValue("behemoth", "hipopotam", "hornless rhino");
}
Luego usa tu mapa como lo harías normalmente. Ni siquiera necesitas esos elegantes getByKeyN y containsKeyN.
Si tiene la intención de usar la combinación de varias teclas como una sola, entonces tal vez Commander MultiKey sea su amigo. No creo que funcione uno por uno, aunque ..
Suena como una tupla de Python. Siguiendo en ese espíritu, puedes crear una clase inmutable de tu propio diseño que implemente Comparable y lo tendrás.
Sugeriría la estructura
Map<K1, Map<K2, V>>
aunque buscar la segunda clave podría no ser eficiente
Tanto MultiMap como MultiKeyMap de Commons o Guava funcionarán.
Sin embargo, una solución rápida y simple podría ser extender la compra de la clase Map manejando una clave compuesta usted mismo, considerando que las claves son de tipo primitivo.
Todavía voy a sugerir la solución de 2 mapas, pero con un tweest
Map<K2, K1> m2;
Map<K1, V> m1;
Este esquema le permite tener un número arbitrario de "alias" clave.
También le permite actualizar el valor a través de cualquier tecla sin que los mapas se desincronicen.
Una solución sucia y simple, si usa los mapas solo para ordenar, digamos, es agregar un valor muy pequeño a una clave hasta que el valor no exista, pero no agregue el mínimo (por ejemplo Double.MIN_VALUE) porque causará un error. Como dije, esta es una solución muy sucia pero simplifica el código.
Usé dicha implementación para múltiples objetos clave. Me permite usar una cantidad innumerable de claves para el mapa. Es escalable y bastante simple. Pero tiene limitaciones: las claves se ordenan de acuerdo con el orden de los argumentos en el constructor y no funcionaría con las matrices en 2D, debido al uso de Arrays.equals (). Para solucionarlo, puede usar Arrays.deepEquals ();
Espero que te ayude. Si conoce algún motivo por el cual no se puede utilizar como una solución para tales problemas, ¡por favor hágamelo saber!
public class Test {
private static Map<InnumerableKey, Object> sampleMap = new HashMap<InnumerableKey, Object>();
private static class InnumerableKey {
private final Object[] keyParts;
private InnumerableKey(Object... keyParts) {
this.keyParts = keyParts;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof InnumerableKey)) return false;
InnumerableKey key = (InnumerableKey) o;
if (!Arrays.equals(keyParts, key.keyParts)) return false;
return true;
}
@Override
public int hashCode() {
return keyParts != null ? Arrays.hashCode(keyParts) : 0;
}
}
public static void main(String... args) {
boolean keyBoolean = true;
double keyDouble = 1d;
Object keyObject = new Object();
InnumerableKey doubleKey = new InnumerableKey(keyBoolean, keyDouble);
InnumerableKey tripleKey = new InnumerableKey(keyBoolean, keyDouble, keyObject);
sampleMap.put(doubleKey, "DOUBLE KEY");
sampleMap.put(tripleKey, "TRIPLE KEY");
// prints "DOUBLE KEY"
System.out.println(sampleMap.get(new InnumerableKey(true, 1d)));
// prints "TRIPLE KEY"
System.out.println(sampleMap.get(new InnumerableKey(true, 1d, keyObject)));
// prints null
System.out.println(sampleMap.get(new InnumerableKey(keyObject, 1d, true)));
}
}
Ver colecciones de Google . O, como sugieres, usa un mapa internamente y haz que ese mapa use un par. Tendrás que escribir o encontrar Pair <>; es bastante fácil pero no forma parte de las Colecciones estándar.
es probable que todas las teclas multilíneas fallen, porque el put ([key1, key2], val) y el get ([null, key2]) terminan usando las teclas [key1, key2] y [null, key2]. Si el mapa de respaldo no contiene cubos de hash por clave, las búsquedas son realmente lentas.
Creo que el camino a seguir es usar un decorador de índices (vea los ejemplos de key1, key2 arriba) y si las claves de índice extra son propiedades del valor almacenado, puede usar los nombres y reflexiones de la propiedad para construir los mapas secundarios cuando pones (key , val) y agrega un método extra get (propertyname, propertyvalue) para usar ese índice.
el tipo de retorno del get (propertyname, propertyvalue) podría ser una colección, por lo que incluso ninguna clave única está indexada ...
sol: cancanizar ambas teclas y hacer una clave final, usar esto como clave.
para valores clave,
concatena ket-1 y key-2 con come "," in beetween, usa esto como clave original.
clave = tecla-1 + "," + tecla-2;
myMap.put (clave, valor);
de manera similar al recuperar valores.
Propuesta, según lo sugerido por algunos contestadores:
public interface IDualMap<K1, K2, V> {
/**
* @return Unmodifiable version of underlying map1
*/
Map<K1, V> getMap1();
/**
* @return Unmodifiable version of underlying map2
*/
Map<K2, V> getMap2();
void put(K1 key1, K2 key2, V value);
}
public final class DualMap<K1, K2, V>
implements IDualMap<K1, K2, V> {
private final Map<K1, V> map1 = new HashMap<K1, V>();
private final Map<K2, V> map2 = new HashMap<K2, V>();
@Override
public Map<K1, V> getMap1() {
return Collections.unmodifiableMap(map1);
}
@Override
public Map<K2, V> getMap2() {
return Collections.unmodifiableMap(map2);
}
@Override
public void put(K1 key1, K2 key2, V value) {
map1.put(key1, value);
map2.put(key2, value);
}
}