2009-11-23 43 views
30

Trong Python, lớp defaultdict cung cấp một cách thuận tiện để tạo ra một ánh xạ từ key -> [list of values], trong ví dụ sau,có Java tương đương với defaultdict của Python không?

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

Có một tương đương với điều này trong Java?

Trả lời

20

Không có gì cung cấp cho hành vi của dict mặc định ra khỏi hộp. Tuy nhiên việc tạo ra dict mặc định của riêng bạn trong Java sẽ không khó.

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

Lớp này có thể được sử dụng như dưới đây:

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

Mã này sẽ in: {1=[2, 3]}

+3

Thay vì sử dụng 'Lớp', bạn cũng có thể thử chuyển Sử dụng 'Nhà cung cấp' - xem http://docs.guava-libraries.googlecode.com/git-history/v10.0/javadoc/com/google/common/base/Supplier.html –

+0

Hoặc nếu bạn không muốn sự phụ thuộc Ổi, chỉ cần xác định giao diện 'Nhà cung cấp ' của riêng bạn trong 'DefaultDict'. – Soulman

+0

tôi muốn xây dựng 'DefaultDict' với giá trị của riêng mình:' public DefaultDict (giá trị V) {this.value = value; } ' –

5

Bạn có thể sử dụng MultiMap từ Apache Commons.

+1

Liên kết: http://commons.apache.org/collections/api/org/apache/commons/collections/MultiMap.html –

+0

liên kết bị hỏng – HuStmpHrrr

7

ngoài bộ sưu tập apache, kiểm tra cũng google collections:

Một bộ sưu tập tương tự như một bản đồ, nhưng mà có thể kết hợp nhiều giá trị với một chìa khóa duy nhất. Nếu bạn gọi put (K, V) hai lần, với cùng một khóa nhưng giá trị khác nhau, multimap chứa ánh xạ từ khóa cho cả hai giá trị.

2

Sử dụng chỉ là thư viện thời gian chạy Java bạn có thể sử dụng một HashMap và thêm một ArrayList để giữ giá trị của bạn khi phím chưa hề tồn tại hoặc thêm giá trị vào danh sách khi chìa khóa không tồn tại.

1

Các giải pháp từ @ tendayi-mawushe không làm việc cho tôi với các loại nguyên thủy (ví dụ InstantiationException Integer), đây là một triển khai thực hiện với Integer, Double, Float. Tôi thường sử dụng Maps với những nhà xây dựng và tĩnh thêm cho 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); 
    } 
} 

Và một kiểm tra, cho cách cư xử tốt:

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

Trong những trường hợp phổ biến nhất mà bạn muốn có một defaultdict, bạn sẽ có hạnh phúc hơn với một Multimap hoặc Multiset được thiết kế phù hợp, đó là những gì bạn đang thực sự tìm kiếm. Multimap là một key -> mapping mapping (mặc định là một collection rỗng) và Multiset là một key -> int mapping (mặc định là zero).

Guava cung cấp triển khai rất tốt đẹp của cả hai Multimaps and Multisets sẽ bao gồm hầu như tất cả các trường hợp sử dụng.

Nhưng (và đây là lý do tại sao tôi đăng câu trả lời mới) với Java 8, bây giờ bạn có thể sao chép các trường hợp sử dụng còn lại của defaultdict với bất kỳ Map hiện có nào.

  • getOrDefault(), như tên cho thấy, trả về giá trị nếu có hoặc trả về giá trị mặc định. Điều này không lưu trữ giá trị mặc định trong bản đồ.
  • computeIfAbsent() tính giá trị từ hàm được cung cấp (có thể luôn trả về cùng giá trị mặc định) và làm lưu trữ giá trị được tính trong bản đồ trước khi quay lại.

Nếu bạn muốn để đóng gói các cuộc gọi, bạn có thể sử dụng ổi của 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()); 
    } 
} 

Sau đó xây dựng bản đồ mặc định của bạn như vậy:

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

Bất kỳ thông tin phản hồi, downvoter? – dimo414

0

tôi đã viết thư viện Guavaberry chứa cấu trúc dữ liệu như vậy : DefaultHashMap.

Nó được đánh giá cao và được ghi nhận. Bạn có thể tìm thấy nó và tích hợp nó khá dễ dàng thông qua Maven Central.

Ưu điểm chính là sử dụng lambda để xác định phương pháp nhà máy. Vì vậy, bạn có thể thêm một thể hiện được định nghĩa tùy ý của một lớp (thay vì dựa vào sự tồn tại của hàm dựng mặc định):

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

Tôi hy vọng có thể giúp được.

+0

Sẽ dễ sử dụng hơn nếu thay vì mở rộng từ 'HashMap' bạn đã sử dụng' ForwardingMap' và cho phép người gọi chỉ định bản đồ sao lưu. Ưu tiên thành phần cho thừa kế. – dimo414

Các vấn đề liên quan