Есть ли Java-эквивалент Python defaultdict?
В Python класс defaultdict
обеспечивает удобный способ создания отображения из key -> [list of values]
, в следующем примере
from collections import defaultdict
d = defaultdict(list)
d[1].append(2)
d[1].append(3)
# d is now {1: [2, 3]}
Есть ли эквивалент этого в Java?
Ответы
Ответ 1
Ничто не дает поведения по умолчанию 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]}
Ответ 2
В большинстве распространенных случаев, когда вам нужен defaultdict
, вы будете еще более счастливы с правильно разработанным Multimap или Multiset, что именно то, что вы действительно ищете. Multimap - это отображение ключа → коллекции (по умолчанию это пустая коллекция), а Multiset - это отображение ключа → int (по умолчанию - ноль).
Guava предоставляет очень хорошие реализации как Multimaps, так и Multisets, которые охватывают почти все варианты использования.
Но (и именно поэтому я опубликовал новый ответ) с Java 8 теперь вы можете реплицировать оставшиеся варианты использования defaultdict
на любой существующий Map
.
getOrDefault()
, как следует из названия, возвращает значение, если оно присутствует, или возвращает значение по умолчанию. Это не сохраняет значение по умолчанию на карте.
computeIfAbsent()
вычисляет значение из предоставленной функции (которая всегда может возвращать одно и то же значение по умолчанию) и сохраняет вычисленное значение на карте перед возвратом.
Если вы хотите инкапсулировать эти вызовы, вы можете использовать Guava 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());
}
}
Затем создайте карту по умолчанию следующим образом:
Map<String, List<String>> defaultMap = DefaultMap.create(ArrayList::new);
Ответ 3
в дополнение к коллекциям apache, также проверьте сбор google:
Коллекция, похожая на карту, но которая может связывать несколько значений с одним ключом. Если вы дважды вызываете put (K, V) с одним и тем же ключом, но разными значениями, мультимап содержит сопоставления из ключа для обоих значений.
Ответ 4
Вы можете использовать MultiMap
из Apache Commons.
Ответ 5
Используя только библиотеку времени выполнения Java, вы можете использовать HashMap
и добавить ArrayList
, чтобы удерживать ваши значения, когда ключ еще не существует или добавить значение в список, когда ключ существует.
Ответ 6
Решение от @tendayi-mawushe не работает для меня с примитивными типами (например, InstantiationException Integer
), вот одна реализация, которая работает с Integer, Double, Float. Я часто использую Карты с ними и добавляю статические конструкторы для удобства
import java.util.HashMap;
import java.util.Map;
/** Simulate the behaviour of Python 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"));
}
}
Ответ 7
В Java 8+ вы можете использовать:
map.computeIfAbsent(1, k -> new ArrayList<Integer>()).add(1);
Ответ 8
Я написал библиотеку Guavaberry, содержащую такую структуру данных: DefaultHashMap.
Он протестирован и документирован. Вы можете найти его и легко интегрировать с помощью Maven Central.
Основной аспект заключается в том, что он использует lambda для определения метода factory. Таким образом, вы можете добавить произвольно определенный экземпляр класса (вместо того, чтобы полагаться на существование конструктора по умолчанию):
DefaultHashMap<Integer, List<String>> map = new DefaultHashMap(() -> new ArrayList<>());
map.get(11).add("first");
Надеюсь, это поможет.