Статьи

Как сопоставить шаблоны и отобразить смежные строки в Java

Недавно мы опубликовали нашу статью о потрясающей поддержке оконных функций в jOOλ 0.9.9 , которая, я считаю, является одним из лучших дополнений к библиотеке, которые мы когда-либо делали.

Сегодня мы рассмотрим удивительное применение оконных функций в сценарии использования, вдохновленном этим вопросом переполнения стека Шон Нгуен:

Как получить строки до и после сопоставления из потока Java 8, как grep?

У меня есть текстовые файлы, в которых много строк. Если я хочу найти строки до и после сопоставления в grep, я сделаю так:

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

Как я могу реализовать эквивалент в Java 8, используя потоки?

Итак, вопрос:

Как я могу реализовать эквивалент в Java 8, используя потоки?

jool-логотип Что ж, оболочка Unix и ее различные «pipable» команды — это единственное, что еще более удивительно (и таинственно), чем оконные функции. Возможность найти определенную строку в файле, а затем отобразить «окно» из нескольких строк, весьма полезна.

Однако с jOOλ 0.9.9 мы можем сделать это очень легко и в Java 8 . Посмотрите на этот маленький фрагмент:

01
02
03
04
05
06
07
08
09
10
11
Seq.seq(Files.readAllLines(Paths.get(
        new File("/path/to/Example.java").toURI())))
   .window()
   .filter(w -> w.value().contains("ABC"))
   .forEach(w -> {
       System.out.println();
       System.out.println("-1:" + w.lag().orElse(""));
       System.out.println(" 0:" + w.value());
       System.out.println("+1:" + w.lead().orElse(""));
       // ABC: Just checking
   });

Эта программа выведет:

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

Итак, я запустил программу сам и нашел все строки, которые соответствуют «ABC», плюс предыдущие строки («lagging» / lag() ) и следующие строки ( lead() / lead() ). Эти функции lead() и lag() работают так же, как их SQL-эквиваленты .

Но в отличие от SQL, составление функций в Java (или других языках общего назначения) немного проще, так как здесь меньше синтаксического беспорядка. Мы можем легко выполнить агрегацию по рамке окна, чтобы собрать общее количество строк, «отстающих» и «ведущих» совпадение. Рассмотрим следующую альтернативу:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
int lower = -5;
int upper =  5;
         
Seq.seq(Files.readAllLines(Paths.get(
        new File("/path/to/Example.java").toURI())))
   .window(lower, upper)
   .filter(w -> w.value().contains("ABC"))
   .map(w -> w.window()
              .zipWithIndex()
              .map(t -> tuple(t.v1, t.v2 + lower))
              .map(t -> (t.v2 > 0
                       ? "+"
                       : t.v2 == 0
                       ? " " : "")
                       + t.v2 + ":" + t.v1)
              .toString("\n"))

И результат, который мы получаем:

01
02
03
04
05
06
07
08
09
10
11
-5:int upper =  5;
-4:       
-3:Seq.seq(Files.readAllLines(Paths.get(
-2:        new File("/path/to/Example.java").toURI())))
-1:   .window(lower, upper)
 0:   .filter(w -> w.value().contains("ABC"))
+1:   .map(w -> w.window()
+2:              .zipWithIndex()
+3:              .map(t -> tuple(t.v1, t.v2 + lower))
+4:              .map(t -> (t.v2 > 0
+5:                       ? "+"

Может ли это быть более кратким? Я так не думаю. Большая часть логики выше просто генерировала индекс рядом со строкой.

Вывод

Оконные функции очень мощные. Недавнее обсуждение reddit о нашей предыдущей статье о поддержке оконных функций jOOλ показало, что другие языки также поддерживают примитивы для создания аналогичной функциональности. Но обычно эти строительные блоки не так лаконичны, как те, что представлены в jOOλ, вдохновленные SQL.

Благодаря jOOλ, имитирующему оконные функции SQL, при составлении мощных операций в потоках данных в памяти практически не возникает когнитивных трений.

Узнайте больше об оконных функциях в этих статьях здесь: