Java поток: как группировать по ключевому слову? - PullRequest
0 голосов
/ 25 апреля 2020

У меня есть текстовый файл, который выглядит как

Group: A 
12
27 
14 
Group: B 
68
10 
42
79
Group: D
...

Я хочу вычислить статистику для списка номеров каждой группы.

Вопрос: Есть ли хороший способ сделать это с Java потоками?

Примечание: я знаю, что при IntStream статистику можно получить, применив метод summaryStatistics. Моя проблема в том, что я не знаю, как сгруппировать элементы под ключевым словом «Группа:».

Добавлено: По поводу несколько странных комментариев: без потоков проблему можно решить с помощью

   String group = null;  
   boolean first = true; 
   List<String> lines = Files.readAllLines(path); 
   for (String s: lines) { 
     if (s.startsWith("Group:") { 
       if (!first) { 
         System.out.println(group + " sum: " + sum); 
       }  
       first = false; 
       group = s; 
       sum = 0; 
     } else { 
       sum += Integer.parseInt(s.strip()); 
     }
   }
   System.out.println(group + " sum: " + sum); 

Ответы [ 2 ]

1 голос
/ 25 апреля 2020

Попробуйте это. Я поместил данные в массив для демонстрации.


        String[] vals = {
                "Group: A ",
                "12       ",
                "27       ",
                "14       ",
                "Group: B ",
                "68       ",
                "10       ",
                "42       ",
                "79       ",
                "Group: D ",
                "10       ",
                "20       ",
                "30       ", 
                "18       ",
                "12       "
                };

Группировать данные на карте было простым захватом тега Group, а затем добавить значения к значению List для этого тега. , Данные обрезаются из пробелов перед обработкой, а соответствующие строки преобразуются в целые числа. Из-за асинхронного характера данных я не мог придумать более краткий способ сделать это. У других могут быть лучшие идеи.

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


        List<Integer> numbs = null;
        for (String v : vals) {
            v = v.trim();
            if (v.startsWith("Group")) {
                // add a new List and save its reference
                map.put(v, numbs = new ArrayList<>());
            } else {
                // add using current object
                numbs.add(Integer.valueOf(v));
            }
        }

На втором шаге я только что создал карту сводной статистики, набранную с идентификатором группы.

        Map<String, IntSummaryStatistics> results = 

                map.entrySet()

                // stream of entryset
                .stream()
                // put them in a map
                .collect(Collectors
                      .toMap(
                        e -> e.getKey(),
                        // generate summary stats for each list by 
                        // streaming the list and collecting the values
                        e -> e.getValue()
                           .stream()
                            .collect(Collectors.summarizingInt(r->r))));

results.entrySet().forEach(System.out::println);

Печать

Group: A=IntSummaryStatistics{count=3, sum=53, min=12, average=17.666667, max=27}
Group: B=IntSummaryStatistics{count=4, sum=199, min=10, average=49.750000, max=79}
Group: D=IntSummaryStatistics{count=5, sum=90, min=10, average=18.000000, max=30}

0 голосов
/ 26 апреля 2020

Как упоминалось Fureei sh, потоки не подходят для операций с состоянием. Однако обработка serial может превратить несколько строк каждой группы в строку для каждой группы, поэтому (src/temp.txt):

Group: A 
12
27 
14 
Group: B 
68
10 
42
79
Group: D
98
187
894
67
Group: G
3
3
Group: G
5
5

становится следующим:

Group:A 12 27 14
Group:B 68 10 42 79
Group:D 98 187 894 67
Group:G 3 3
Group:G 5 5

в List из String, добавляя символ новой строки перед каждым Group: X, обрезая все пробелы, а затем соединяя все строки. Затем вы можете выполнить потоковую передачу списка без сохранения состояния и сопоставить каждую группу с IntSummaryStatistics:

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.IntSummaryStatistics;

import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;

public class StackOverflowTest {

  public static void main(String[] args){
    Path path = Path.of("src/temp.txt");

    try {
      // transform the Strings by adding a newline to Strings matching the group characteristics
      List<String> transformed =
        Files.readAllLines(path)
             .stream()
             .map(s -> s.matches("^[a-zA-Z]*:.*") ? 
                                 "\n" + s.replace(" ","") : s.trim())
             .collect(Collectors.joining(" "))  // one long string
             .lines()                           // split according to the newline
             .skip(1)                           // don't want the first empty line
             .collect(Collectors.toList());
      System.out.println("Transformed list:\n" + transformed);

      System.out.println();

      // map the transformed list to the individual groups with their own IntSummaryStatistics
      Map<String,IntSummaryStatistics> mapSummary =
        transformed
             .stream()
             .map(s -> s.split("\\s"))
             .collect(Collectors.toMap(s -> s[0],         // first index is the group
                                       s -> IntStream.range(1, s.length)
                                                     .map(i -> Integer.parseInt(s[i]))
                                                     .summaryStatistics(),
                                       (a, b) -> {a.combine(b); return a;}
                                       )
                     );
      System.out.println("Mapping statistics:\n" + mapSummary);

    } catch (IOException ex) {
      System.out.println("Oops: " + ex);
    }
  }
}

Результатом (обманутым добавлением новых строк):

Transformed list:
[Group:A 12 27 14 , Group:B 68 10 42 79 , Group:D 98 187 894 67 , Group:G 3 3 , Group:G 5 5]

Mapping statistics:
{
Group:B=IntSummaryStatistics{count=4, sum=199, min=10, average=49.750000, max=79},
Group:A=IntSummaryStatistics{count=3, sum=53, min=12, average=17.666667, max=27},
Group:G=IntSummaryStatistics{count=4, sum=16, min=3, average=4.000000, max=5},
Group:D=IntSummaryStatistics{count=4, sum=1246, min=67, average=311.500000, max=894}
}
...