2015-01-16 33 views
8

Tôi nhận được một dòng Số nguyên và tôi muốn nhóm các chỉ mục của các phần tử theo giá trị của từng phần tử.
Ví dụ, {1, 1, 1, 2, 3, 3, 4} được nhóm như Integer để liệt kê các chỉ số bản đồ:Java 8, Stream of Integer, Chỉ mục nhóm của một luồng theo các số nguyên?

1 -> 0, 1, 2 
2 -> 3 
3 -> 4, 5 
4 -> 6 

Tôi đã thử sử dụng dòng, nhưng với một lớp học thêm:

@Test 
public void testGrouping() throws Exception { 
    // actually it is being read from a disk file 
    Stream<Integer> nums = Stream.of(1, 1, 1, 2, 3, 3, 4); 
    // list to map by index 
    int[] ind = {0}; // capture array, effectively final 
    class Pair { 
     int left; 
     int right; 

     public Pair(int left, int right) { 
      this.left = left; 
      this.right = right; 
     } 
    } 

    Map<Integer, List<Integer>> map = nums.map(e -> new Pair(ind[0]++, e)) 
      .collect(Collectors.groupingBy(e -> e.right)) 
      .entrySet().parallelStream() 
      .collect(Collectors.toConcurrentMap(
        Map.Entry::getKey, 
        e -> e.getValue().parallelStream().map(ee -> ee.left).collect(Collectors.toList()) 
      )); 
} 

tôi phải đọc Suối kể từ Stream của Integer được đọc từ một tệp đĩa trong ứng dụng của tôi.
Tôi cảm thấy cách làm như trên là khá tối ưu. Có cách nào tốt hơn hay thanh lịch hơn để làm điều đó?
Cảm ơn sự giúp đỡ của bạn.

+0

Có thể sử dụng: http://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors .html # groupingBy-java.util.function.Function-java.util.stream.Collector-? (Xin lỗi, không có Java8 ngay bây giờ để xây dựng) – GPI

Trả lời

4

Với một phương pháp helper ít cho việc thu thập:

class MapAndIndex { 
    Map<Integer,List<Integer>> map=new HashMap<>(); 
    int index; 

    void add(int value) { 
     map.computeIfAbsent(value, x->new ArrayList<>()).add(index++); 
    } 
    void merge(MapAndIndex other) { 
     other.map.forEach((value,list) -> { 
      List<Integer> l=map.computeIfAbsent(value, x->new ArrayList<>()); 
      for(int i: list) l.add(i+index); 
     }); 
     index+=other.index; 
    } 
} 

toàn bộ hoạt động trở thành:

Map<Integer,List<Integer>> map = IntStream.of(1, 1, 1, 2, 3, 3, 4) 
    .parallel() 
    .collect(MapAndIndex::new, MapAndIndex::add, MapAndIndex::merge).map; 

Khi bạn cần theo dõi các chỉ mục chưa biết trước, bạn cần trạng thái có thể thay đổi và do đó hoạt động được gọi là “mutable reduction”.

Lưu ý rằng bạn không cần ConcurrentMap tại đây. Việc triển khai Stream sẽ đã xử lý đồng thời. Nó sẽ tạo một vùng chứa MapAndIndex cho mỗi chuỗi liên quan và gọi hoạt động merge trên hai vùng chứa khi cả hai chuỗi liên kết đều được thực hiện với công việc của chúng. Điều này cũng sẽ được thực hiện theo cách giữ lại thứ tự, nếu Stream có một đơn đặt hàng, như trong ví dụ này (nếu không nhiệm vụ ghi lại chỉ mục của bạn sẽ không có ý nghĩa ...).

+1

Thêm một cho "giảm bớt có thể thay đổi" –

2
  1. Bạn có thể sử dụng phương thức IntStream#range(int startInclusive, int endExclusive) để nhận chỉ mục của từng phần tử.
  2. Sau đó, sử dụng phương pháp IntStream.boxed() để chuyển đổi IntStream đến một Stream với đóng hộp Integer s
  3. Nhóm bằng cách ánh xạ mỗi chỉ số để các yếu tố tương ứng từ mảng i -> array[i] và thu thập các yếu tố lặp lại vào một danh sách.

Ví dụ:

int[] array = {1, 1, 1, 2, 3, 3, 4}; 
Map<Integer, List<Integer>> result = 
     IntStream.range(0, array.length) 
       .boxed() 
       .collect(Collectors.groupingBy(i -> array[i], Collectors.toList())); 

Cập nhật: Nếu bạn không có mảng (và do đó các yếu tố đếm), nhưng một Stream<Integer>, bạn có thể thu thập các yếu tố của ban đầu Stream vào một số List<Integer>. Bằng cách này bạn sẽ biết kích thước của Stream và sau đó bạn có thể làm:

Stream<Integer> = .... // The input stream goes here 
//Collecting the input stream to a list, so that we get it's size. 
List<Integer> list = stream.collect(Collectors.toList()); 
//Grouping process 
Map<Integer, List<Integer>> result = 
    IntStream.range(0, list.size()) 
      .boxed() 
      .collect(Collectors.groupingBy(i -> list.get(i), Collectors.toList())); 
+0

Điều gì sẽ xảy ra nếu tôi không biết chiều dài mảng trước bàn tay? – Daniel

+0

Và tôi không nhận được quyền truy cập ngẫu nhiên vào mảng gốc mà là luồng. – Daniel

+0

Vì vậy, bạn nhận được một 'Stream ' và không có gì khác, đúng không? –

0

Những gì bạn có thể làm là

Map<Integer, List<Integer>> map = nums.map(e -> new Pair(ind[0]++, e)) 
     .collect(groupingBy(p -> p.right, HashMap::new, 
          mapping(p -> p.left, toList()))); 

này cho phép bạn áp dụng một bản đồ các yếu tố trước khi chúng được bổ sung vào danh sách.

1

Tại sao không:

Stream<Integer> nums = Stream.of(1, 1, 1, 2, 3, 3, 4); 

OfInt indexes = IntStream.iterate(0, x -> x + 1).iterator(); 
Map<Integer, List<Integer>> result = new HashMap<>(); 

nums.iterator().forEachRemaining(i -> result.merge(i, 
                new ArrayList<>(Arrays.asList(indexes.next())), 
                (l1, l2) -> {l1.addAll(l2); return l1;}) 
           ); 

Kết quả:

{1=[0, 1, 2], 2=[3], 3=[4, 5], 4=[6]} 
Các vấn đề liên quan