2016-03-10 18 views
5

Giả sử tôi có một danh sách các khoảng thời gian (sắp xếp theo bắt đầu) và tôi muốn chia nhỏ chúng để tôi có danh sách các nhóm chồng chéo nhau. Vì vậy, ví dụ, với Interval như:Danh sách phân vùng Java 8 thành các nhóm theo điều kiện liên quan đến các phần tử trước

public class Interval { 
    private final int start; 
    private final int end; 

    public Interval(int start,int end){ 
     this.start = start; 
     this.end = end; 
    } 

    public int getStart(){return start;} 
    public int getEnd(){return end;} 

    public String toString(){ return "("+start+","+end+")"; } 
} 

Và một List<Interval> như:

[(0,4),(1,7),(6,10),(13,17),(20,100),(22,31),(60,65)] 

Tôi muốn có một sản lượng List<List<Interval>>:

[[(0,4),(1,7),(6,10)],[(13,17)],[(20,100),(22,31),(60,65)]] 

tôi có thể mã này lên, nhưng tôi thực sự thích cách tiếp cận chức năng hơn của Java 8, và muốn biết nếu có bất cứ điều gì giống như một cách thành ngữ để làm điều này bằng cách sử dụng các luồng Java 8.

Tôi đã xem kiểu "nhóm theo" được cung cấp Collectors, nhưng dường như chúng không áp dụng vì tôi không thực sự nhóm theo trình phân loại - bạn không thể tính toán các nhóm chỉ dựa trên một thuộc tính của từng phần tử riêng lẻ, bạn phải xem xét các thuộc tính của từng phần tử liên quan đến các nhóm đã được tính toán cho đến nay.

Chắc chắn có cách không điên rồ để làm điều này bằng các ngôn ngữ chức năng (mặc dù tôi nói như một người không thực sự là một lập trình viên chức năng :-)). Làm thế nào tôi có thể làm điều đó với các luồng trong Java 8?

+2

Tôi đoán, 'this.start = end; 'không phải là thứ bạn muốn. Nhưng đó là một điều tốt khi bạn đang sử dụng các biến 'final' để lỗi ngay lập tức được trình biên dịch phát hiện. – Holger

+1

Nhân tiện, đầu ra là gì khi đầu vào như sau: '[(60, 65), (22, 31), (20, 100)]'? Tất cả ba khoảng thời gian có nên được hợp nhất với nhau không? Nói cách khác, có thể thứ tự các khoảng đầu vào thay đổi kết quả không? –

+1

@Tagir Valeev: điều kiện tiên quyết của câu hỏi (trong câu đầu tiên) là các phần tử được sắp xếp theo điểm bắt đầu của chúng. Thật dễ dàng để thư giãn yêu cầu đó bằng cách thêm một bước phân loại vào bất kỳ giải pháp nào. – Holger

Trả lời

4

Bạn không thể. Các luồng không phù hợp với loại sự cố này; các luồng không có khái niệm về "các phần tử trước" và được phép hoạt động trên các phần tử theo thứ tự tùy ý. Bạn có thể làm điều đó trong Java, chắc chắn, và bạn có thể làm điều đó bằng các ngôn ngữ chức năng, nhưng điều đó không có nghĩa là các luồng hoạt động giống như các cấu trúc dữ liệu ngôn ngữ chức năng mà bạn đã từng sử dụng.

+0

** sẽ upvote nhưng thiếu rep – codeCogs

+2

Không phải tất cả các hoạt động được phép xử lý các phần tử theo thứ tự tùy ý. Việc cho phép thứ tự tùy ý sẽ thực hiện, ví dụ: 'Collectors.toList()', rất khó ... – Holger

+0

Nhưng toList cho phép các phần tử theo thứ tự tùy ý, nó chỉ kết hợp các bộ tích lũy lại với nhau theo một trật tự được sắp xếp. –

4

Bạn đang tìm đúng nơi khi nghiên cứu các nhà sưu tầm groupingBy, nhưng bạn cũng đúng rằng họ sẽ không cung cấp logic cần thiết cho các khoảng thời gian hợp nhất. Nhưng chúng là các yếu tố kết hợp khái niệm vào trạng thái được tạo ra bởi các phần tử trước đó. Bạn phải tự mình thực hiện một bộ sưu tập tương tự.

Dựa vào đặc điểm kỹ thuật của bạn rằng các yếu tố đã presorted bởi chỉ số bắt đầu của họ, bạn có thể làm điều đó thích:

Comparator<Interval> byStart = Comparator.comparingInt(Interval::getStart); 
Comparator<Interval> byEnd = Comparator.comparingInt(Interval::getEnd); 
Collection<List<Interval>> merged = intervalList.stream().collect(
     () -> new TreeMap<Interval,List<Interval>>(byStart), 
     (map,i) -> { 
      Map.Entry<Interval,List<Interval>> e=map.floorEntry(i); 
      if(e!=null && Collections.max(e.getValue(), byEnd).getEnd()>=i.getStart()) 
       e.getValue().add(i); 
      else map.computeIfAbsent(i, x->new ArrayList<>()).add(i); 
     }, 
     (m1,m2) -> m2.forEach((i,list) -> { 
      Map.Entry<Interval,List<Interval>> e=m1.floorEntry(i); 
      if(e!=null && Collections.max(e.getValue(), byEnd).getEnd()>=i.getStart()) 
       e.getValue().addAll(list); 
      else m1.put(i, list); 
     }) 
    ).values(); 

này tạo ra một Collection chứ không phải là một List, nhưng bạn chỉ có thể tạo ra một List ra khỏi nó:

List<List<Interval>> list = new ArrayList<>(merged); 

bạn nên làm điều đó chắc chắn nếu bạn có ý định giữ kết quả trong một thời gian dài chứ không phải chế biến nó ngay lập tức, như Collection trở bởi người sưu tập là chế độ xem thành một tài khoản TreeMap có nhiều tài nguyên hơn mức cần thiết.

Tôi đoán, trong hầu hết các trường hợp, bạn nên sử dụng giải pháp dựa trên vòng lặp.

+0

Giải pháp tuyệt vời! (1) Tôi nghĩ rằng combiner là vô ích nếu accumulator chạy qua tất cả các yếu tố dòng serially (mà phải là trường hợp nếu giải pháp này sẽ làm việc). (2) Đồng ý: một vòng lặp có thể minh bạch và hiệu quả hơn trong trường hợp này. – codeCogs

+1

@codeCogs: bộ kết hợp sẽ không được sử dụng cho một luồng tuần tự nhưng đó là một kiểu mã hóa tốt để luôn cung cấp chức năng kết hợp làm việc để tránh những điều bất ngờ trong tương lai. Chính thức, không làm như vậy thậm chí là vi phạm hợp đồng API. Bộ kết hợp của giải pháp này hoạt động chính xác, nhưng đi đến độ dài lớn để đạt được điều này, có thể ăn bất kỳ lợi ích của việc xử lý song song, nếu có bất kỳ để bắt đầu với… – Holger

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