2013-08-17 67 views
43

Nói rằng tôi có một Map<? extends Object, List<String>>cầu dẹt một bộ sưu tập

tôi có thể nhận các giá trị của bản đồ một cách dễ dàng đủ, và lặp trên nó để sản xuất một đơn List<String>.

for (List<String> list : someMap.values()) { 
     someList.addAll(list); 
    } 

Có cách nào để làm phẳng nó trong một cảnh không?

List<String> someList = SomeMap.values().flatten(); 
+0

Có gì sai khi sử dụng vòng lặp? –

+1

@JoshM Không có gì cả. Nhưng nếu tôi có thể sử dụng một cái gì đó được xây dựng trong, tôi nên. Tôi thường biết câu trả lời cho những loại câu hỏi này nhưng lần này thì không, vì vậy tôi nghĩ tôi sẽ hỏi. –

Trả lời

45

Nếu bạn đang sử dụng Java 8, bạn có thể làm một cái gì đó như thế này:

someMap.values().forEach(someList::addAll); 
+3

Nếu tôi không thực sự sai, điều này thực sự không được khuyến nghị - https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html Vui lòng xem phần phụ-hiệu ứng. > Các tác dụng phụ trong các thông số hành vi đối với các hoạt động dòng là, nói chung, không khuyến khích, vì chúng thường có thể dẫn đến các vi phạm vô tình về yêu cầu không quốc tịch, cũng như các mối nguy hiểm an toàn chủ đề khác. Vì vậy, trong trường hợp này, tốt hơn nên sử dụng 'Collector.toList()' –

5

Không, không có phương pháp ngắn hơn. Bạn phải sử dụng một vòng lặp.

Cập nhật tháng 4 năm 2014: Java 8 cuối cùng cũng đã xuất hiện. Trong phiên bản mới, bạn có thể sử dụng phương thức Iterable.forEach để đi bộ qua bộ sưu tập mà không sử dụng vòng lặp rõ ràng.

Cập nhật tháng 11 năm 2017: Tìm thấy câu hỏi này một cách tình cờ khi tìm kiếm giải pháp hiện đại. Kết thúc đi với reduce:

someMap.values().stream().reduce(new ArrayList(), (accum, list) -> { 
    accum.addAll(list); 
    return accum; 
}): 

Điều này tránh phụ thuộc vào trạng thái bên ngoài có thể thay đổi các forEach(someList::addAll) overhead của flatMap(List::stream).

0

Nếu bạn chỉ muốn lặp qua các giá trị, bạn có thể tránh tất cả những phương pháp addAll.

Tất cả bạn phải làm là viết một lớp mà đóng gói đồ của mình, và điều đó thực hiện các Iterator:

public class ListMap<K,V> implements Iterator<V> 
{ 
    private final Map<K,List<V>> _map; 
    private Iterator<Map.Entry<K,List<V>>> _it1 = null; 
    private Iterator<V> _it2 = null; 

    public ListMap(Map<K,List<V>> map) 
    { 
    _map = map; 
    _it1 = map.entrySet().iterator(); 
    nextList(); 
    } 

    public boolean hasNext() 
    { 
    return _it2!=null && _it2.hasNext(); 
    } 

    public V next() 
    { 
    if(_it2!=null && _it2.hasNext()) 
    { 
     return _it2.next(); 
    } 
    else 
    { 
     throw new NoSuchElementException(); 
    } 
    nextList(); 
    } 

    public void remove() 
    { 
    throw new NotImplementedException(); 
    } 

    private void nextList() 
    { 
    while(_it1.hasNext() && !_it2.hasNext()) 
    { 
     _it2 = _it1.next().value(); 
    } 
    } 
} 
6

Nếu bạn đang sử dụng Eclipse Collections, bạn có thể sử dụng Iterate.flatten().

MutableMap<String, MutableList<String>> map = Maps.mutable.empty(); 
map.put("Even", Lists.mutable.with("0", "2", "4")); 
map.put("Odd", Lists.mutable.with("1", "3", "5")); 
MutableList<String> flattened = Iterate.flatten(map, Lists.mutable.empty()); 
Assert.assertEquals(
    Lists.immutable.with("0", "1", "2", "3", "4", "5"), 
    flattened.toSortedList()); 

flatten() là trường hợp đặc biệt của tổng quát hơn RichIterable.flatCollect().

MutableList<String> flattened = 
    map.flatCollect(x -> x, Lists.mutable.empty()); 

Lưu ý: Tôi là người cam kết cho Bộ sưu tập Eclipse.

51

Sử dụng Java 8 và nếu bạn không muốn nhanh chóng một trường hợp List một mình, giống như trong các gợi ý (và được chấp nhận) giải pháp

someMap.values().forEach(someList::addAll); 

Bạn có thể làm tất cả bằng cách truyền với tuyên bố này:

List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList()); 

Bằng cách này, điều thú vị cần biết, trên Java 8 phiên bản được chấp nhận có vẻ thực sự là nhanh nhất. Nó có khoảng thời gian giống như

for (List<String> item : someMap.values()) ... 

và nhanh hơn so với giải pháp truyền trực tuyến thuần túy. Đây là mã kiểm tra nhỏ của tôi. Tôi rõ ràng không đặt tên cho nó chuẩn để tránh các cuộc thảo luận kết quả của sai sót điểm chuẩn. ;) Tôi làm tất cả các bài kiểm tra hai lần để hy vọng có được một phiên bản biên dịch đầy đủ.

Map<String, List<String>> map = new HashMap<>(); 
    long millis; 

    map.put("test", Arrays.asList("1", "2", "3", "4")); 
    map.put("test2", Arrays.asList("10", "20", "30", "40")); 
    map.put("test3", Arrays.asList("100", "200", "300", "400")); 

    int maxcounter = 1000000; 

    System.out.println("1 stream flatmap"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList()); 
    } 
    System.out.println(System.currentTimeMillis() - millis); 

    System.out.println("1 parallel stream flatmap"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> someList = map.values().parallelStream().flatMap(c -> c.stream()).collect(Collectors.toList()); 
    } 
    System.out.println(System.currentTimeMillis() - millis); 

    System.out.println("1 foreach"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> mylist = new ArrayList<String>(); 
     map.values().forEach(mylist::addAll); 
    } 
    System.out.println(System.currentTimeMillis() - millis);   

    System.out.println("1 for"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> mylist = new ArrayList<String>(); 
     for (List<String> item : map.values()) { 
      mylist.addAll(item); 
     } 
    } 
    System.out.println(System.currentTimeMillis() - millis); 


    System.out.println("2 stream flatmap"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList()); 
    } 
    System.out.println(System.currentTimeMillis() - millis); 

    System.out.println("2 parallel stream flatmap"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> someList = map.values().parallelStream().flatMap(c -> c.stream()).collect(Collectors.toList()); 
    } 
    System.out.println(System.currentTimeMillis() - millis); 

    System.out.println("2 foreach"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> mylist = new ArrayList<String>(); 
     map.values().forEach(mylist::addAll); 
    } 
    System.out.println(System.currentTimeMillis() - millis);   

    System.out.println("2 for"); 
    millis = System.currentTimeMillis(); 
    for (int i = 0; i < maxcounter; i++) { 
     List<String> mylist = new ArrayList<String>(); 
     for (List<String> item : map.values()) { 
      mylist.addAll(item); 
     } 
    } 
    System.out.println(System.currentTimeMillis() - millis); 

Và đây là kết quả:

1 stream flatmap 
468 
1 parallel stream flatmap 
1529 
1 foreach 
140 
1 for 
172 
2 stream flatmap 
296 
2 parallel stream flatmap 
1482 
2 foreach 
156 
2 for 
141 

Sửa 2016/05/24 (hai năm sau):

Chạy thử nghiệm tương tự sử dụng một phiên bản thực tế Java 8 (U92) trên cùng một máy:

1 stream flatmap 
313 
1 parallel stream flatmap 
3257 
1 foreach 
109 
1 for 
141 
2 stream flatmap 
219 
2 parallel stream flatmap 
3830 
2 foreach 
125 
2 for 
140 

Có vẻ như có một spee dup để xử lý tuần tự các luồng và chi phí lớn hơn cho các luồng song song.

+7

Trong khi thực sự dài hơn một vài ký tự, việc viết 'flatMap (Collections :: stream)' có thể thích hợp hơn với kiểu 'flatMap (c -> c.stream()) '. –

+4

Đó là 'Bộ sưu tập :: luồng', bằng cách sử dụng' Bộ sưu tập' sẽ không thực hiện trong thử nghiệm của tôi. – BAER

+0

Loại nào nhanh hơn cũng có thể phụ thuộc vào dữ liệu đầu vào của bạn. Tôi sẽ không ngạc nhiên nếu phiên bản luồng nhanh hơn khi đầu vào là nhiều danh sách nhỏ. Nếu nó đủ thông minh, nó có cơ hội phân bổ bộ nhớ cho toàn bộ kết quả cùng một lúc, trong khi phiên bản forEach sẽ phải phân bổ lại nó một vài lần. – danadam

25

Khi tìm kiếm "java 8 flatten", đây chỉ là đề cập đến. Và nó cũng không phải về luồng phẳng. Vì vậy, đối tốt tuyệt vời, tôi chỉ để lại nó ở đây

.flatMap(Collection::stream) 

Tôi cũng ngạc nhiên không ai đã cho java đồng thời 8 câu trả lời cho câu hỏi ban đầu là

.collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll); 
+2

Tôi tin rằng '.collect (ArrayList :: new, ArrayList :: addAll, ArrayList :: addAll);' là câu trả lời đúng. 'flatMap()' không hữu ích trong tình huống này. 'flatMap()' có thể hữu ích nếu bạn cần gọi một phương thức khác trên đối số trước khi lấy một luồng (tức là, gọi phương thức 'stream()'). Tuy nhiên, ở đây, chúng tôi đã có một tham chiếu đến một đối tượng mà chúng tôi có thể trực tiếp lấy một dòng. –

0

Một giải pháp tốt đẹp cho các subcase của một bản đồ của Maps là lưu trữ, nếu có thể, dữ liệu trong số Table của ổi.

https://github.com/google/guava/wiki/NewCollectionTypesExplained#table

Vì vậy, ví dụ một Map<String,Map<String,String>> được thay thế bằng Table<String,String,String> vốn đã flattend. Trong thực tế, các tài liệu nói rằng HashBasedTable, Table 's thực hiện Hash, được về cơ bản được hỗ trợ bởi một HashMap<R, HashMap<C, V>>

6

đề nghị bởi một đồng nghiệp:

listOfLists.stream().flatMap(e -> e.stream()).collect(Lists.toList()) 

Tôi muốn nó tốt hơn foreach().

+1

Bạn có thể thay thế e -> e.stream() bằng tham chiếu phương thức của List :: stream. Nên nhanh hơn một chút. –

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