2015-10-04 17 views
7

Tôi có một tệp văn bản có nhiều dòng chuỗi trong đó. Nếu tôi muốn tìm các dòng trước và sau khi kết hợp trong grep, tôi sẽ làm như sau:Làm thế nào để có được dòng trước và sau khi kết hợp từ dòng java 8 như grep?

grep -A 10 -B 10 "ABC" myfile.txt 

Tôi có thể thực hiện tương đương trong luồng sử dụng Java 8 bằng cách nào?

+0

Đó là tiếc là không được hỗ trợ bởi các API Suối ra khỏi hộp, nhưng những gì bạn muốn được gọi là "trượt cửa sổ". –

Trả lời

2

Kịch bản như vậy không được API hỗ trợ tốt vì các phương pháp hiện tại không cung cấp quyền truy cập vào các phần tử lân cận trong luồng. Các giải pháp gần gũi nhất mà tôi có thể nghĩ ra mà không cần tạo lặp tùy chỉnh/spliterators và các cuộc gọi thư viện của bên thứ ba là để đọc các tập tin đầu vào vào List và sau đó sử dụng các chỉ số Dòng:

List<String> input = Files.readAllLines(Paths.get(fileName)); 
Predicate<String> pred = str -> str.contains("ABC"); 
int contextLength = 10; 

IntStream.range(0, input.size()) // line numbers 
    // filter them leaving only numbers of lines satisfying the predicate 
    .filter(idx -> pred.test(input.get(idx))) 
    // add nearby numbers 
    .flatMap(idx -> IntStream.rangeClosed(idx-contextLength, idx+contextLength)) 
    // remove numbers which are out of the input range 
    .filter(idx -> idx >= 0 && idx < input.size()) 
    // sort numbers and remove duplicates 
    .distinct().sorted() 
    // map to the lines themselves 
    .mapToObj(input::get) 
    // output 
    .forEachOrdered(System.out::println); 

Sản lượng grep cũng bao gồm delimiter đặc biệt như "--" để chỉ định các dòng bị bỏ qua. Nếu bạn muốn đi xa hơn và bắt chước hành vi như vậy là tốt, tôi có thể đề nghị bạn thử miễn phí StreamEx thư viện của tôi vì nó có intervalMap phương pháp đó là hữu ích trong trường hợp này:

// Same as IntStream.range(...).filter(...) steps above 
IntStreamEx.ofIndices(input, pred) 
    // same as above 
    .flatMap(idx -> IntStream.rangeClosed(idx-contextLength, idx+contextLength)) 
    // remove numbers which are out of the input range 
    .atLeast(0).less(input.size()) 
    // sort numbers and remove duplicates 
    .distinct().sorted() 
    .boxed() 
    // merge adjacent numbers into single interval and map them to subList 
    .intervalMap((i, j) -> (j - i) == 1, (i, j) -> input.subList(i, j + 1)) 
    // flatten all subLists prepending them with "--" 
    .flatMap(list -> StreamEx.of(list).prepend("--")) 
    // skipping first "--" 
    .skip(1) 
    .forEachOrdered(System.out::println); 
1

Như Tagir Valeev lưu ý, loại vấn đề không được hỗ trợ tốt bởi API luồng. Nếu bạn từng muốn đọc các dòng từ đầu vào và in ra các dòng phù hợp với ngữ cảnh, bạn sẽ phải giới thiệu một giai đoạn đường ống trạng thái (hoặc một bộ sưu tập tùy chỉnh hoặc bộ tách) cho biết thêm một chút phức tạp.

Nếu bạn sẵn sàng đọc tất cả các dòng vào bộ nhớ, nó chỉ ra rằng BitSet là một đại diện hữu ích cho thao tác nhóm các trận đấu. Điều này mang một số điểm tương đồng với giải pháp của Tagir, nhưng thay vì sử dụng dãy số nguyên để biểu diễn các dòng được in, nó sử dụng 1 bit trong một BitSet. Một số ưu điểm của BitSet là nó có một số hoạt động hàng loạt được tích hợp sẵn và nó có một biểu diễn bên trong nhỏ gọn. Nó cũng có thể tạo ra một luồng các chỉ mục của 1-bit, điều này khá hữu ích cho vấn đề này.

Đầu tiên, chúng ta hãy bắt đầu bằng cách tạo ra một BitSet mà có một 1-bit cho mỗi dòng phù hợp với vị ngữ:

void contextMatch(Predicate<String> pred, int before, int after, List<String> input) { 
    int len = input.size(); 
    BitSet matches = IntStream.range(0, len) 
           .filter(i -> pred.test(input.get(i))) 
           .collect(BitSet::new, BitSet::set, BitSet::or); 

Bây giờ chúng ta có tập bit của dòng phù hợp, chúng tôi dòng ra các chỉ số của mỗi 1 bit. Sau đó chúng ta thiết lập các bit trong bitet đại diện cho bối cảnh trước và sau. Điều này cho chúng ta một số BitSet có 1 bit đại diện cho tất cả các dòng được in, bao gồm các dòng ngữ cảnh.

BitSet context = matches.stream() 
     .collect(BitSet::new, 
       (bs,i) -> bs.set(Math.max(0, i - before), Math.min(i + after + 1, len)), 
       BitSet::or); 

Nếu chúng ta chỉ muốn in ra tất cả các dòng, bao gồm bối cảnh, chúng ta có thể làm điều này:

context.stream() 
      .forEachOrdered(i -> System.out.println(input.get(i))); 

Các thực tế grep -A a -B b lệnh in một dấu phân cách giữa từng nhóm dòng ngữ cảnh. Để tìm ra khi in dấu phân cách, chúng ta xem xét từng bit 1 bit trong tập bit bối cảnh. Nếu có 0 bit trước nó, hoặc nếu nó ở ngay từ đầu, chúng tôi đã đặt một chút vào kết quả. Điều này cho chúng ta 1 bit ở đầu mỗi nhóm ngữ cảnh:

Chúng tôi không muốn in dấu phân cách trước mỗi nhóm dòng ngữ cảnh; chúng tôi muốn in nó giữa mỗi nhóm.Điều đó có nghĩa là chúng tôi phải xóa 1 bit đầu tiên (nếu có):

// clear the first bit 
    int first = separators.nextSetBit(0); 
    if (first >= 0) { 
     separators.clear(first); 
    } 

Bây giờ, chúng tôi có thể in ra các dòng kết quả. Nhưng trước khi in mỗi dòng, chúng tôi kiểm tra để xem liệu chúng ta nên in một tách đầu tiên:

context.stream() 
      .forEachOrdered(i -> { 
       if (separators.get(i)) { 
        System.out.println("--"); 
       } 
       System.out.println(input.get(i)); 
      }); 
} 
+0

Cách tiếp cận thú vị, được thăng hạng. Một lựa chọn khác là kết hợp hai bước đầu tiên với nhau để lấy 'IntStream.range (..). Filter (..). FlatMap (..). Filter (..)' bước từ giải pháp của tôi, sau đó '.collect (BitSet :: mới, BitSet :: set, BitSet :: hoặc) 'thay vì' .distinct(). sort() '. Điều này sẽ bảo vệ hiệu quả bộ nhớ trong khi có thể trông có vẻ “nhiều hơn”. Btw 'i> 0 &&! Context.get (i-1) || i == 0' có thể được rút ngắn thành 'i == 0 || ! context.get (i-1) '. –

+2

Tôi đã đơn giản hóa bước trung gian của bạn. Tôi hy vọng bạn không nhớ rằng tôi đã chỉnh sửa trực tiếp; nó có vẻ quá phức tạp cho một bình luận với tôi trong khi dễ hiểu ngay trong ngữ cảnh của nó. – Holger

+0

@TagirValeev Đề xuất tốt trong "btw" của bạn. Tôi đã thêm trường hợp 'i == 0' sau khi chọn trường hợp cạnh đó và tôi không nhận thấy sự đơn giản hóa có thể được thực hiện. Đã chỉnh sửa. –

4

Nếu bạn sẵn sàng để sử dụng một thư viện của bên thứ ba và không cần xử lý song song, sau đó jOOλ cung cấp cửa sổ SQL-style chức năng như sau

Seq.seq(Files.readAllLines(Paths.get(new File("/path/to/Example.java").toURI()))) 
    .window(-1, 1) 
    .filter(w -> w.value().contains("ABC")) 
    .forEach(w -> { 
     System.out.println("-1:" + w.lag().orElse("")); 
     System.out.println(" 0:" + w.value()); 
     System.out.println("+1:" + w.lead().orElse("")); 
     // ABC: Just checking 
    }); 

yielding

-1:  .window(-1, 1) 
0:  .filter(w -> w.value().contains("ABC")) 
+1:  .forEach(w -> { 
-1:   System.out.println("+1:" + w.lead().orElse("")); 
0:   // ABC: Just checking 
+1:  }); 

chức năng lead() truy cập các giá trị tiếp theo để traversal từ cửa sổ, các 0 Chức năngtruy cập hàng trước đó.

Disclaimer: Tôi làm việc cho công ty đằng sau jOOλ

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