2010-06-10 35 views
37

Tôi thường cần có danh sách đối tượng và nhóm chúng thành Bản đồ dựa trên giá trị chứa trong đối tượng. Ví dụ. lấy danh sách Người dùng và nhóm theo Quốc gia.Phím tắt để thêm vào Danh sách trong HashMap

Mã của tôi cho điều này thường trông giống như:

Map<String, List<User>> usersByCountry = new HashMap<String, List<User>>(); 
for(User user : listOfUsers) { 
    if(usersByCountry.containsKey(user.getCountry())) { 
     //Add to existing list 
     usersByCountry.get(user.getCountry()).add(user); 

    } else { 
     //Create new list 
     List<User> users = new ArrayList<User>(1); 
     users.add(user); 
     usersByCountry.put(user.getCountry(), users); 
    } 
} 

Tuy nhiên tôi không thể không nghĩ rằng đây là vụng về và một số guru có một cách tiếp cận tốt hơn. Gần nhất tôi có thể thấy cho đến nay là MultiMap from Google Collections.

Có cách tiếp cận chuẩn nào không?

Cảm ơn!

+1

nên đó thực sự là 'Bản đồ >'? Câu trả lời tạo sự khác biệt cho những gì bạn chọn để xây dựng hoặc sử dụng. Lưu ý rằng Bộ sưu tập của Google cung cấp các sàng lọc cho các bộ sưu tập lồng nhau có nhiều loại danh sách và tập hợp khác nhau. – seh

+0

Chỉ cần thả Java cho .Net và LINQ. –

+1

@Hamish: vâng, bởi vì những lo lắng của chúng ta về phụ thuộc hoàn toàn không liên quan rồi! – Carl

Trả lời

49

Trong Java 8, bạn có thể sử dụng Map#computeIfAbsent().

Map<String, List<User>> usersByCountry = new HashMap<>(); 

for (User user : listOfUsers) { 
    usersByCountry.computeIfAbsent(user.getCountry(), k -> new ArrayList<>()).add(user); 
} 

Hoặc tận dụng Suối API Collectors#groupingBy() để đi từ List để Map trực tiếp:

Map<String, List<User>> usersByCountry = listOfUsers.stream().collect(Collectors.groupingBy(User::getCountry)); 

Trong Java 7 hoặc thấp hơn, tốt nhất những gì bạn có thể nhận được là dưới đây:

Map<String, List<User>> usersByCountry = new HashMap<>(); 

for (User user : listOfUsers) { 
    List<User> users = usersByCountry.get(user.getCountry()); 
    if (users == null) { 
     users = new ArrayList<>(); 
     usersByCountry.put(user.getCountry(), users); 
    } 
    users.add(user); 
} 

Commons CollectionsLazyMap nhưng không được tham số hóa. Guava không có loại LazyMap hoặc LazyList, nhưng bạn có thể sử dụng Multimap cho điều này như được hiển thị trong answer of polygenelubricants below.

+0

Bạn có thể rút ngắn nó thêm một chút: 'usersByCountry.put (user.getCountry(), users = new ArrayList <>());' Mặc dù tôi chắc chắn một số sẽ cau mày về điều đó. – shmosel

+0

Tôi biết điều đó không quan trọng nhưng có lẽ người mới cần biết rằng chức năng ánh xạ sẽ lấy khóa làm đối số, vì vậy tốt hơn nên sử dụng 'k' thay vì 'v' 'người dùngByCountry.computeIfAbsent (user.getCountry (), k -> new ArrayList <>()) thêm (người dùng); ' –

2

Khi tôi phải đối phó với một bản đồ có giá trị thu thập, tôi chỉ cần luôn viết lên một phương thức tiện ích tĩnh putIntoListMap() nhỏ trong lớp. Nếu tôi thấy mình cần nó trong nhiều lớp, tôi ném phương thức đó vào một lớp tiện ích. Các cuộc gọi phương thức tĩnh như thế có một chút xấu xí, nhưng chúng sạch hơn việc gõ mã ra mỗi lần. Trừ khi nhiều bản đồ đóng một vai trò khá trung tâm trong ứng dụng của bạn, IMHO có lẽ không đáng để nó kéo theo sự phụ thuộc khác.

+0

Ngoài ra, tối ưu hóa của BalusC là một điều tốt để biết. –

1

Có vẻ như nhu cầu chính xác của bạn được đáp ứng bởi LinkedHashMultimap trong thư viện GC. Nếu bạn có thể sống với sự phụ thuộc, tất cả các mã của bạn trở thành:

SetMultimap<String,User> countryToUserMap = LinkedHashMultimap.create(); 
// .. other stuff, then whenever you need it: 
countryToUserMap.put(user.getCountry(), user); 

trật tự chèn được duy trì (về tất cả có vẻ như bạn đã làm với danh sách của bạn) và bản sao được loại trừ; tất nhiên bạn có thể chuyển sang một tập hợp dựa trên băm đơn giản hoặc một bộ cây như nhu cầu đọc (hoặc một danh sách, mặc dù điều đó dường như không phải là những gì bạn cần). Bộ sưu tập trống sẽ được trả về nếu bạn yêu cầu một quốc gia không có người dùng, mọi người đều nhận được ngựa, v.v. - ý tôi là, hãy xem API. Nó sẽ làm rất nhiều cho bạn, vì vậy sự phụ thuộc có thể đáng giá.

+0

+1 Cảm ơn bạn đã biết nhưng việc chọn lọc của BalusC là những gì tôi đã làm sau đó. – Damo

0

Một cách sạch sẽ và dễ đọc để thêm một yếu tố như sau:

String country = user.getCountry(); 
Set<User> users 
if (users.containsKey(country)) 
{ 
    users = usersByCountry.get(user.getCountry()); 
} 
else 
{ 
    users = new HashSet<User>(); 
    usersByCountry.put(country, users); 
} 
users.add(user); 

Lưu ý rằng gọi containsKeyget không phải là chậm hơn so với chỉ gọi get và thử nghiệm kết quả cho null.

+0

Cuộc gọi của riêng nó thực sự không chậm hơn, nhưng tra cứu hiện nay xảy ra hai lần thay vì một lần. – BalusC

+0

Tôi đã làm rõ nó. – starblue

19

ổi của Multimap thực sự là cấu trúc dữ liệu thích hợp nhất cho điều này, và trong thực tế, có Multimaps.index(Iterable<V>, Function<? super V,K>) phương pháp hữu ích mà thực hiện chính xác những gì bạn muốn: lấy một Iterable<V> (mà một List<V> là), và áp dụng các Function<? super V, K> để có được các phím cho số Multimap<K,V>.

Dưới đây là một ví dụ từ các tài liệu:

Ví dụ,

List<String> badGuys 
     = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde"); 
    Function<String, Integer> stringLengthFunction = ...; 
    Multimap<Integer, String> index 
     = Multimaps.index(badGuys, stringLengthFunction); 
    System.out.println(index); 

in

{4=[Inky], 5=[Pinky, Pinky, Clyde], 6=[Blinky]} 

Trong trường hợp của bạn, bạn muốn viết một Function<User,String> userCountryFunction = ....

+2

+1 Nó làm tôi thất vọng rằng câu trả lời liên quan đến việc viết nhiều mã hơn số này được xếp hạng cao hơn, chỉ vì họ là người nhanh nhất đến. :( –

+2

@Kevin: Tôi đã hy vọng bạn sẽ dừng lại cuối cùng =) Nhân tiện , Tôi dự định cuối cùng sẽ viết các bài viết Q/A về stackoverflow trên các lớp ổi khác nhau để chứng minh khả năng của nó. – polygenelubricants

+2

Tôi dừng lại một lần hoặc hai lần một ngày, do đó đảm bảo rằng tôi không bao giờ có cơ hội nhận được câu trả lời của tôi được bình chọn. Tôi nghĩ ý tưởng của bạn là một ý tưởng tuyệt vời. Tôi cho rằng bạn có nghĩa là đăng một câu hỏi và tự trả lời câu hỏi đó. Bạn sẽ nhận được một vài người nói với bạn rằng có cái gì đó vô đạo đức về điều này, nhưng nó bị xử phạt một cách rõ ràng bởi cộng đồng SO rộng lớn hơn, vì mục tiêu của họ là để SO có nội dung tuyệt vời. –

2

Bằng cách sử dụng lambdaj bạn có thể có được kết quả đó chỉ với một dòng mã như nó sau:

Group<User> usersByCountry = group(listOfUsers, by(on(User.class).getCountry())); 

Lambdaj cũng cung cấp nhiều tính năng khác để thao tác các bộ sưu tập với một ngôn ngữ cụ thể miền rất dễ đọc.

+0

+1 thật tuyệt. Có vẻ rất hữu dụng. – Damo

2

Chúng tôi dường như để làm điều này rất nhiều lần vì vậy tôi tạo ra một lớp mẫu

public abstract class ListGroupBy<K, T> { 
public Map<K, List<T>> map(List<T> list) { 
    Map<K, List<T> > map = new HashMap<K, List<T> >(); 
    for (T t : list) { 
     K key = groupBy(t); 
     List<T> innerList = map.containsKey(key) ? map.get(key) : new ArrayList<T>(); 
     innerList.add(t); 
     map.put(key, innerList); 
    } 
    return map; 
} 

protected abstract K groupBy(T t); 
} 

Bạn chỉ cần cung cấp impl cho groupby

trong trường hợp của bạn

String groupBy(User u){return user.getCountry();} 
0
Map<String, List<User>> usersByCountry = new HashMap<String, List<User>>(); 
for(User user : listOfUsers) { 
    List<User> users = usersByCountry.get(user.getCountry()); 
    if (users == null) {   
     usersByCountry.put(user.getCountry(), users = new ArrayList<User>()); 
    } 
    users.add(user); 
} 
Các vấn đề liên quan