Is there a Java equivalent of Python's defaultdict?

后端 未结 8 1535
失恋的感觉
失恋的感觉 2021-02-05 00:05

In Python, the defaultdict class provides a convenient way to create a mapping from key -> [list of values], in the following example,



        
相关标签:
8条回答
  • 2021-02-05 00:24

    In Java 8+ you can use:

    map.computeIfAbsent(1, k -> new ArrayList<Integer>()).add(2);
    
    0 讨论(0)
  • 2021-02-05 00:24

    Using just the Java runtime library you could use a HashMap and add an ArrayList to hold your values when the key does not exist yet or add the value to the list when the key does exist.

    0 讨论(0)
  • 2021-02-05 00:28

    There is nothing that gives the behaviour of default dict out of the box. However creating your own default dict in Java would not be that difficult.

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

    This class could be used like below:

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

    This code would print: {1=[2, 3]}

    0 讨论(0)
  • 2021-02-05 00:32

    The solution from @tendayi-mawushe did not work for me with Primitive types (e.g. InstantiationException Integer), here is one implementation that works with Integer, Double, Float. I often use Maps with these and added static constructors for 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);
        }
    }
    

    And a test, for good manners:

    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"));
        }
    }
    
    0 讨论(0)
  • 2021-02-05 00:35

    In most common cases where you want a defaultdict, you'll be even happier with a properly designed Multimap or Multiset, which is what you're really looking for. A Multimap is a key -> collection mapping (default is an empty collection) and a Multiset is a key -> int mapping (default is zero).

    Guava provides very nice implementations of both Multimaps and Multisets which will cover almost all use cases.

    But (and this is why I posted a new answer) with Java 8 you can now replicate the remaining use cases of defaultdict with any existing Map.

    • getOrDefault(), as the name suggests, returns the value if present, or returns a default value. This does not store the default value in the map.
    • computeIfAbsent() computes a value from the provided function (which could always return the same default value) and does store the computed value in the map before returning.

    If you want to encapsulate these calls you can use Guava's ForwardingMap:

    public class DefaultMap<K, V> extends ForwardingMap<K, V> {
      private final Map<K, V> delegate;
      private final Supplier<V> defaultSupplier;
    
      /**
       * Creates a map which uses the given value as the default for <i>all</i>
       * keys. You should only use immutable values as a shared default key.
       * Prefer {@link #create(Supplier)} to construct a new instance for each key.
       */
      public static DefaultMap<K, V> create(V defaultValue) {
        return create(() -> defaultValue);
      }
    
      public static DefaultMap<K, V> create(Supplier<V> defaultSupplier) {
        return new DefaultMap<>(new HashMap<>(), defaultSupplier);
      }
    
      public DefaultMap<K, V>(Map<K, V> delegate, Supplier<V> defaultSupplier) {
        this.delegate = Objects.requireNonNull(delegate);
        this.defaultSupplier = Objects.requireNonNull(defaultSupplier);
      }
    
      @Override
      public V get(K key) {
        return delegate().computeIfAbsent(key, k -> defaultSupplier.get());
      }
    }
    

    Then construct your default map like so:

    Map<String, List<String>> defaultMap = DefaultMap.create(ArrayList::new);
    
    0 讨论(0)
  • 2021-02-05 00:36

    in addition to apache collections, check also google collections:

    A collection similar to a Map, but which may associate multiple values with a single key. If you call put(K, V) twice, with the same key but different values, the multimap contains mappings from the key to both values.

    0 讨论(0)
提交回复
热议问题