javaintermediate

MultiMap and BiMap Implementations

Implement MultiMap and BiMap data structures for one-to-many and bidirectional key-value mappings.

java
import java.util.*;
import java.util.stream.Collectors;

// MultiMap: one key → many values
public class MultiMap<K, V> {
    private final Map<K, List<V>> map = new LinkedHashMap<>();

    public void put(K key, V value) {
        map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
    }

    public List<V> get(K key) {
        return map.getOrDefault(key, List.of());
    }

    public boolean remove(K key, V value) {
        List<V> values = map.get(key);
        if (values == null) return false;
        boolean removed = values.remove(value);
        if (values.isEmpty()) map.remove(key);
        return removed;
    }

    public Set<K> keys() { return map.keySet(); }
    public int size() { return map.values().stream().mapToInt(List::size).sum(); }

    @Override
    public String toString() { return map.toString(); }
}

// BiMap: bidirectional map (key ↔ value both unique)
class BiMap<K, V> {
    private final Map<K, V> forward = new LinkedHashMap<>();
    private final Map<V, K> inverse = new LinkedHashMap<>();

    public V put(K key, V value) {
        // Remove existing mappings
        if (forward.containsKey(key)) inverse.remove(forward.get(key));
        if (inverse.containsKey(value)) forward.remove(inverse.get(value));

        forward.put(key, value);
        inverse.put(value, key);
        return value;
    }

    public V get(K key) { return forward.get(key); }
    public K getKey(V value) { return inverse.get(value); }

    public V removeByKey(K key) {
        V value = forward.remove(key);
        if (value != null) inverse.remove(value);
        return value;
    }

    public K removeByValue(V value) {
        K key = inverse.remove(value);
        if (key != null) forward.remove(key);
        return key;
    }

    public boolean containsKey(K key) { return forward.containsKey(key); }
    public boolean containsValue(V value) { return inverse.containsKey(value); }
    public int size() { return forward.size(); }

    public BiMap<V, K> inverse() {
        BiMap<V, K> inv = new BiMap<>();
        inv.forward.putAll(this.inverse);
        inv.inverse.putAll(this.forward);
        return inv;
    }

    @Override
    public String toString() { return forward.toString(); }
}

class Demo {
    public static void main(String[] args) {
        // MultiMap
        MultiMap<String, String> tags = new MultiMap<>();
        tags.put("java", "spring-boot");
        tags.put("java", "concurrency");
        tags.put("java", "streams");
        tags.put("python", "django");
        System.out.println("java tags: " + tags.get("java"));  // [spring-boot, concurrency, streams]
        System.out.println("Total: " + tags.size());             // 4

        // BiMap
        BiMap<String, Integer> codes = new BiMap<>();
        codes.put("US", 1);
        codes.put("UK", 44);
        codes.put("IN", 91);
        System.out.println("US code: " + codes.get("US"));    // 1
        System.out.println("Code 44: " + codes.getKey(44));   // UK

        BiMap<Integer, String> reversed = codes.inverse();
        System.out.println("Reversed: " + reversed.get(91)); // IN
    }
}

Use Cases

  • Tag/category systems with multiple values per key
  • Bidirectional lookups (e.g., country codes)
  • Index inversion and reverse mapping

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.