2009-11-23 16 views
30

En Python, la clase defaultdict proporciona una manera conveniente de crear una asignación de key -> [list of values], en el siguiente ejemplo,¿hay un equivalente Java del incumplimiento de Python?

from collections import defaultdict 
d = defaultdict(list) 
d[1].append(2) 
d[1].append(3) 
# d is now {1: [2, 3]} 

¿Hay un equivalente a esto en Java?

Respuesta

20

No hay nada que proporcione el comportamiento del dict predeterminado de fábrica. Sin embargo, crear tu propio dict predeterminado en Java no sería tan difícil.

import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.List; 

public class DefaultDict<K, V> extends HashMap<K, V> { 

    Class<V> klass; 
    public DefaultDict(Class klass) { 
     this.klass = klass;  
    } 

    @Override 
    public V get(Object key) { 
     V returnValue = super.get(key); 
     if (returnValue == null) { 
      try { 
       returnValue = klass.newInstance(); 
      } catch (Exception e) { 
       throw new RuntimeException(e); 
      } 
      this.put((K) key, returnValue); 
     } 
     return returnValue; 
    }  
} 

Esta clase se podría utilizar, como a continuación:

public static void main(String[] args) { 
    DefaultDict<Integer, List<Integer>> dict = 
     new DefaultDict<Integer, List<Integer>>(ArrayList.class); 
    dict.get(1).add(2); 
    dict.get(1).add(3); 
    System.out.println(dict); 
} 

Este código imprimiría: {1=[2, 3]}

+3

En lugar de utilizar una 'Clase ', también puede intentar pasar un Guava 'Supplier' - ver http://docs.guava-libraries.googlecode.com/git-history/v10.0/javadoc/com/google/common/base/Supplier.html –

+0

O, si no lo hace quiere la dependencia de Guava, simplemente defina su propia interfaz 'Supplier ' en 'DefaultDict'. – Soulman

+0

preferiría construir el 'DefaultDict' con mi propio valor:' public DefaultDict (valor V) {this.value = value; } ' –

5

Puede usar MultiMap desde Apache Commons.

+1

Enlace: http://commons.apache.org/collections/api/org/apache/commons/collections/MultiMap.html –

+0

el enlace está roto – HuStmpHrrr

7

, además de colecciones de apache, comprobar también google collections:

Una colección similar a un mapa, pero que pueden asociar varios valores con una sola llave. Si llama put (K, V) dos veces, con la misma clave pero diferentes valores, el multimap contiene asignaciones de la clave para ambos valores.

2

Utilizando sólo la biblioteca de tiempo de ejecución de Java se puede utilizar un HashMap y añadir un ArrayList para mantener sus valores cuando la clave no existe todavía o añadir el valor a la lista cuando no existe la clave.

1

La solución de @ Tendayi-mawushe no funcionó para mí con tipos primitivos (por ejemplo InstantiationException Integer), aquí hay una implementación que funciona con Integer, Double, Float. A menudo uso con estos mapas y constructores estáticos añadido para conveninence

import java.util.HashMap; 
import java.util.Map; 

/** Simulate the behaviour of Python's defaultdict */ 
public class DefaultHashMap<K, V> extends HashMap<K, V> { 
    private static final long serialVersionUID = 1L; 

    private final Class<V> cls; 
    private final Number defaultValue; 

    @SuppressWarnings({ "rawtypes", "unchecked" }) 
    public DefaultHashMap(Class factory) { 
     this.cls = factory; 
     this.defaultValue = null; 
    } 

    public DefaultHashMap(Number defaultValue) { 
     this.cls = null; 
     this.defaultValue = defaultValue; 
    } 

    @SuppressWarnings("unchecked") 
    @Override 
    public V get(Object key) { 
     V value = super.get(key); 
     if (value == null) { 
      if (defaultValue == null) { 
       try { 
        value = cls.newInstance(); 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 
      } else { 
       value = (V) defaultValue; 
      } 
      this.put((K) key, value); 
     } 
     return value; 
    } 

    public static <T> Map<T, Integer> intDefaultMap() { 
     return new DefaultHashMap<T, Integer>(0); 
    } 

    public static <T> Map<T, Double> doubleDefaultMap() { 
     return new DefaultHashMap<T, Double>(0d); 
    } 

    public static <T> Map<T, Float> floatDefaultMap() { 
     return new DefaultHashMap<T, Float>(0f); 
    } 

    public static <T> Map<T, String> stringDefaultMap() { 
     return new DefaultHashMap<T, String>(String.class); 
    } 
} 

Y una prueba, los buenos modales:

import static org.junit.Assert.assertEquals; 

import java.util.ArrayList; 
import java.util.List; 
import java.util.Map; 

import org.junit.Test; 

public class DefaultHashMapTest { 

    @Test 
    public void test() { 
     Map<String, List<String>> dm = new DefaultHashMap<String, List<String>>(
       ArrayList.class); 
     dm.get("nokey").add("one"); 
     dm.get("nokey").add("two"); 
     assertEquals(2, dm.get("nokey").size()); 
     assertEquals(0, dm.get("nokey2").size()); 
    } 

    @Test 
    public void testInt() { 
     Map<String, Integer> dm = DefaultHashMap.intDefaultMap(); 
     assertEquals(new Integer(0), dm.get("nokey")); 
     assertEquals(new Integer(0), dm.get("nokey2")); 
     dm.put("nokey", 3); 
     assertEquals(new Integer(0), dm.get("nokey2")); 
     dm.put("nokey3", 3); 
     assertEquals(new Integer(3), dm.get("nokey3")); 
    } 

    @Test 
    public void testString() { 
     Map<String, String> dm = DefaultHashMap.stringDefaultMap(); 
     assertEquals("", dm.get("nokey")); 
     dm.put("nokey1", "mykey"); 
     assertEquals("mykey", dm.get("nokey1")); 
    } 
} 
2

En los casos más comunes donde se desea una defaultdict, podrás aún más feliz con un Multimap o Multiset diseñado correctamente, que es lo que realmente estás buscando. Un Multimap es una asignación de colección de clave -> (por defecto es una colección vacía) y un Multiset es una asignación de clave -> int (por defecto es cero).

Guava proporciona implementaciones muy agradables de ambos Multimaps and Multisets que cubrirán casi todos los casos de uso.

Pero (y esta es la razón por la que publiqué una nueva respuesta) con Java 8 ahora puede replicar los casos de uso restantes de defaultdict con cualquier Map existente.

  • getOrDefault(), como su nombre indica, devuelve el valor si está presente, o devuelve un valor predeterminado. Este no almacena el valor predeterminado en el mapa.
  • computeIfAbsent() calcula un valor a partir de la función proporcionada (que siempre podría devolver el mismo valor predeterminado) y almacena el valor calculado en el mapa antes de volver.

Si desea encapsular estas llamadas se pueden utilizar de guayaba ForwardingMap:

public class DefaultMap<K, V> extends ForwardingMap<K, V> { 
    private final Map<K, V> delegate; 
    private final Supplier<V> default; 

    public static DefaultMap<K, V> create(V default) { 
    return create(() -> default); 
    } 

    public static DefaultMap<K, V> create(Supplier<V> default) { 
    return new DefaultMap<>(new HashMap<>(), default); 

    public DefaultMap<K, V>(Map<K, V> delegate, Supplier<V> default) { 
    this.delegate = delegate; 
    } 

    @Override 
    public V get(K key) { 
    return delegate().computeIfAbsent(key, k -> supplier.get()); 
    } 
} 

luego construir su mapa predeterminado de este modo:

Map<String, List<String>> defaultMap = DefaultMap.create(ArrayList::new); 
+0

¿Algún comentario, downvoter? – dimo414

0

escribí la biblioteca Guavaberry que contiene tal estructura de datos : DefaultHashMap.

Está altamente probado y documentado. Puede encontrarlo e integrarlo fácilmente a través de Maven Central.

La principal ventaja es que utiliza lambda para definir el método de fábrica. Por lo tanto, se puede añadir una instancia arbitrarly definido de una clase (en lugar de depender de la existencia del constructor por defecto):

DefaultHashMap<Integer, List<String>> map = new DefaultHashMap(() -> new ArrayList<>()); 
map.get(11).add("first"); 

espero que pueden ser de ayuda.

+0

Sería más útil si, en lugar de extender desde 'HashMap', usara' ForwardingMap' y permitiera al llamante especificar el mapa de respaldo. Prefiere la composición a la herencia. – dimo414

Cuestiones relacionadas