Подтвердить что ты не робот

Есть ли 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?

4b9b3361

Ответ 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) с одним и тем же ключом, но разными значениями, мультимап содержит сопоставления из ключа для обоих значений.

Ответ 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");

Надеюсь, это поможет.