Каков наилучший способ получить суб-HashMap на основе списка ключей?

У меня есть HashMap, и я хотел бы получить новый HashMap, который содержит только элементы из первого HashMap, где K принадлежит к определенному списку.

Я мог бы просмотреть все ключи и заполнить новый HashMap, но мне было интересно, есть ли более эффективный способ сделать это?

спасибо

Ответ 1

С потоками Java8 существует функциональное (элегантное) решение. Если keys - список ключей для хранения, а map - источник map.

keys.stream()
    .filter(map::containsKey)
    .collect(Collectors.toMap(Function.identity(), map::get));

Полный пример:

    List<Integer> keys = new ArrayList<>();
    keys.add(2);
    keys.add(3);
    keys.add(42); // this key is not in the map

    Map<Integer, String> map = new HashMap<>();
    map.put(1, "foo");
    map.put(2, "bar");
    map.put(3, "fizz");
    map.put(4, "buz");

    Map<Integer, String> res = keys.stream()
        .filter(map::containsKey)
        .collect(Collectors.toMap(Function.identity(), map::get));

    System.out.println(res.toString());

Отпечатки: {2=bar, 3=fizz}

EDIT добавить filter для ключей, отсутствующих на карте

Ответ 2

В зависимости от вашего использования это может быть более эффективная реализация

public class MapView {
  List ak;
  Map map;
  public MapView(Map map, List allowableKeys) {
     ak = allowableKeys;
     map = map;
  }
  public Object get(Object key) {
    if (!ak.contains(key)) return null;
    return map.get(key);
  }
}

Ответ 3

Да, есть решение:

Map<K,V> myMap = ...;
List<K> keysToRetain = ...;
myMap.keySet().retainAll(keysToRetain);

Операция retainAll на Set обновляет базовую карту. См. java doc.

Edit Помните, что это решение изменит Map.

Ответ 4

Если у вас есть карты m1 и клавиши списка, попробуйте выполнить

Map m2 = new HashMap(m1);
m2.keySet().retainAll(keys);

Ответ 5

С помощью Guava.

Предположим, что у вас есть карта Map<String, String> и вы хотите подменять значения из списка List<String>.

Map<String, String> map = new HashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "4");

final List<String> list = Arrays.asList("2", "4");

Map<String, String> subMap = Maps.filterValues(
                map, Predicates.in(list));

Обновление/Примечание. Как упоминалось в комментарии @assylias, при использовании contains() у вас будет O (n). Поэтому, если у вас большой список, это может иметь огромное влияние на производительность.

С другой стороны HashSet.contains() - постоянное время O (1), поэтому, если есть возможность иметь Set вместо List, это может быть хороший подход (note), который преобразует List в Set будет стоить O (n) в любом случае, поэтому лучше не конвертировать:))

Ответ 6

Вместо того, чтобы просматривать все ключи, вы можете перебирать список и проверять, содержит ли HashMap отображение. Затем создайте новую HashMap с отфильтрованными записями:

List<String> keys = Arrays.asList('a', 'c', 'e');

Map<String, String> old = new HashMap<>();
old.put('a', 'aa');
old.put('b', 'bb');
old.put('c', 'cc');
old.put('d', 'dd');
old.put('e', 'ee');

// only use an inital capacity of keys.size() if you won't add
// additional entries to the map; anyways it more of a micro optimization
Map<String, String> newMap = new HashMap<>(keys.size(), 1f);

for (String key: keys) {
    String value = old.get(key);
    if (value != null) newMap.put(key, value);
}

Ответ 7

Если ваши ключи имеют порядок, вы можете использовать TreeMap.

Посмотрите TreeMap.subMap()

Это не позволяет вам делать это, используя список.

Ответ 8

Скопируйте карту, удалите все ключи, не входящие в список, со стандартными методами:

Map map2 = new Hashmap(map);
map2.keySet().retainAll(keysToKeep);

Ответ 9

вы можете использовать метод clone() для возвращаемого K HashMap.

что-то вроде этого:

import java.util.HashMap;

public class MyClone {    
     public static void main(String a[]) {    
        Map<String, HashMap<String, String>> hashMap = new HashMap<String, HashMap<String, String>>();    
        Map hashMapCloned = new HashMap<String, String>();    

        Map<String, String> insert = new HashMap<String, String>();

        insert.put("foo", "bar");
        hashMap.put("first", insert);

        hashMapCloned.put((HashMap<String, String>) hashMap.get("first").clone());

    }    
}

У него могут быть некоторые синтаксические ошибки, потому что я не тестировал их, но попробую что-то вроде этого.

Ответ 10

Нет, потому что HashMap не поддерживает порядок его записей. Вы можете использовать TreeMap, если вам нужен субкадр между некоторым диапазоном. Кроме того, посмотрите question; это похоже на аналогичные вам линии.

Ответ 11

Вы попросили новый HashMap. Поскольку HashMap не поддерживает совместное использование структуры, нет лучшего подхода, чем очевидный. (Я предположил, что null не может быть значением).

Map<K, V> newMap = new HashMap<>();
for (K k : keys) {
    V v  = map.get(k);
    if (v != null)
        newMap.put(k, v);
}

Если вы абсолютно не требуете создания нового объекта HashMap, вы можете создать новый класс (идеально расширяющийся AbstractMap<K, V>), представляющий ограниченный вид исходного Map. Класс будет иметь два закрытых конечных поля

Map<? extends K, ? extends V> originalMap;
Set<?> restrictedSetOfKeys;

Метод get для нового Map будет примерно таким

@Override
public V get(Object k) {
    if (!restrictedSetOfKeys.contains(k))
        return null;
    return originalMap.get(k);
}

Обратите внимание, что лучше, если restrictedSetOfKeys является Set, а не List, потому что если это HashSet, у вас обычно будет сложность времени O (1) для метода get.

Ответ 12

Вы даже можете вырасти самостоятельно:

public class FilteredMap<K, V> extends AbstractMap<K, V> implements Map<K, V> {

    // The map I wrap.
    private final Map<K, V> map;
    // The filter.
    private final Set<K> filter;

    public FilteredMap(Map<K, V> map, Set<K> filter) {
        this.map = map;
        this.filter = filter;
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        // Make a new one to break the bond with the underlying map.
        Set<Entry<K, V>> entries = new HashSet<>(map.entrySet());
        Set<Entry<K, V>> remove = new HashSet<>();
        for (Entry<K, V> entry : entries) {
            if (!filter.contains(entry.getKey())) {
                remove.add(entry);
            }
        }
        entries.removeAll(remove);
        return entries;
    }

}

public void test() {
    Map<String, String> map = new HashMap<>();
    map.put("1", "One");
    map.put("2", "Two");
    map.put("3", "Three");
    Set<String> filter = new HashSet<>();
    filter.add("1");
    filter.add("2");
    Map<String, String> filtered = new FilteredMap<>(map, filter);
    System.out.println(filtered);

}

Если вы обеспокоены всем копированием, вы также можете увеличить отфильтрованные Set и filterd Iterator.

public interface Filter<T> {

    public boolean accept(T t);
}

public class FilteredIterator<T> implements Iterator<T> {

    // The Iterator
    private final Iterator<T> i;
    // The filter.
    private final Filter<T> filter;
    // The next.
    private T next = null;

    public FilteredIterator(Iterator<T> i, Filter<T> filter) {
        this.i = i;
        this.filter = filter;
    }

    @Override
    public boolean hasNext() {
        while (next == null && i.hasNext()) {
            T n = i.next();
            if (filter.accept(n)) {
                next = n;
            }
        }
        return next != null;
    }

    @Override
    public T next() {
        T n = next;
        next = null;
        return n;
    }
}

public class FilteredSet<K> extends AbstractSet<K> implements Set<K> {

    // The Set
    private final Set<K> set;
    // The filter.
    private final Filter<K> filter;

    public FilteredSet(Set<K> set, Filter<K> filter) {
        this.set = set;
        this.filter = filter;
    }

    @Override
    public Iterator<K> iterator() {
        return new FilteredIterator(set.iterator(), filter);
    }

    @Override
    public int size() {
        int n = 0;
        Iterator<K> i = iterator();
        while (i.hasNext()) {
            i.next();
            n += 1;
        }
        return n;
    }

}

public class FilteredMap<K, V> extends AbstractMap<K, V> implements Map<K, V> {

    // The map I wrap.
    private final Map<K, V> map;
    // The filter.
    private final Filter<K> filter;

    public FilteredMap(Map<K, V> map, Filter<K> filter) {
        this.map = map;
        this.filter = filter;
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        return new FilteredSet<>(map.entrySet(), new Filter<Entry<K, V>>() {

            @Override
            public boolean accept(Entry<K, V> t) {
                return filter.accept(t.getKey());
            }

        });
    }

}

public void test() {
    Map<String, String> map = new HashMap<>();
    map.put("1", "One");
    map.put("2", "Two");
    map.put("3", "Three");
    Set<String> filter = new HashSet<>();
    filter.add("1");
    filter.add("2");
    Map<String, String> filtered = new FilteredMap<>(map, new Filter<String>() {

        @Override
        public boolean accept(String t) {
            return filter.contains(t);
        }
    });
    System.out.println(filtered);

}