2009-11-23 2 views
30

В Python, то defaultdict класс обеспечивает удобный способ для создания отображения из key -> [list of values], в следующем примере,Есть ли Java-эквивалент Python defaultdict?

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

Есть ли эквивалент этого в Java?

ответ

20

Нет ничего, что давало бы поведение по умолчанию dict из коробки. Однако создать свой собственный дефолт по умолчанию на Java не было бы так сложно.

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

Этот класс может быть использован, как показано ниже:

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

Этот код будет печатать: {1=[2, 3]}

+3

Вместо использования 'класса' вы также можете попробовать пройти мимо Guava 'Поставщик' - см. Http://docs.guava-libraries.googlecode.com/git-history/v10.0/javadoc/com/google/common/base/Supplier.html –

+0

Или, если вы этого не сделаете хотите, чтобы зависания Гуавы, просто определите свой собственный 'Поставщик ' интерфейс в 'DefaultDict'. – Soulman

+0

Я бы предпочел бы построить 'DefaultDict' с моим собственным значением:' public DefaultDict (значение V) {this.value = value; } ' –

5

Вы можете использовать MultiMap от Apache Commons.

+1

Ссылка: http://commons.apache.org/collections/api/org/apache/commons/collections/MultiMap.html –

+0

ссылка сломана – HuStmpHrrr

7

в дополнении к апачу коллекции, проверьте также google collections:

Коллекции, похожую на карту, но которая может связать несколько значений с одним ключом. Если вы дважды вызываете put (K, V) с одним и тем же ключом, но с разными значениями, мультимап содержит сопоставления из ключа для обоих значений.

2

Использование только библиотека времени выполнения Java можно использовать HashMap и добавить ArrayList держать свои значения, когда ключ еще не существует, или добавить значение в список, когда ключ существует.

1

Решение от @ tendayi-mawushe не работает для меня с примитивными типами (например, InstantiationException Integer), вот одна реализация, которая работает с Integer, Double, Float. Я часто использую карты с этим и добавлены статические конструкторы для 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); 
    } 
} 

И тест, хорошие манеры:

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

В наиболее распространенных случаях, когда вы хотите defaultdict, вы будете еще счастливее с правильно спроектированным Multimap или Multiset, который вы действительно ищете. Multimap - это сопоставление коллекции key -> (по умолчанию - пустая коллекция), а Multiset - это отображение ключа -> int (по умолчанию - ноль).

Guava обеспечивает очень хорошие реализации как Multimaps and Multisets, которые будут охватывать практически все варианты использования.

Но (и поэтому я написал новый ответ) с Java 8 теперь вы можете копировать оставшиеся варианты использования defaultdict с любыми существующими Map.

  • getOrDefault(), как следует из названия, возвращает значение, если оно присутствует, или возвращает значение по умолчанию. Этот не сохраняет значение по умолчанию на карте.
  • computeIfAbsent() вычисляет значение из предоставленной функции (которая всегда может вернуть одно и то же значение по умолчанию), а делает запоминает вычисленное значение на карте перед возвратом.

Если вы хотите, чтобы инкапсулировать эти вызовы можно использовать гуавы-х 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()); 
    } 
} 

Затем построить карту по умолчанию следующим образом:

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

Любая обратная связь, downvoter? – dimo414

0

Я написал библиотеку Guavaberry содержащий такую ​​структуру данных : DefaultHashMap.

Он протестирован и задокументирован. Вы можете найти его и легко интегрировать через Maven Central.

Главным преимуществом является то, что он использует лямбда для определения заводского метода. Таким образом, вы можете добавить arbitrarly определенный экземпляр класса (вместо того, чтобы полагаться на существование конструктора по умолчанию):

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

Я надеюсь, что может помочь.

+0

Это было бы более полезно, если вместо расширения из «HashMap» вы использовали «ForwardingMap» и разрешили вызывающей стороне указывать карту поддержки. Предпочитают композицию для наследования. – dimo414