как сделать корректную реализацию Collectors

В каждом предложении текста без использования предварительного разбиения на предложения определить разницу между количеством согласных и гласных букв и сформировать соответствующий Map (key: номер предложения, value: определена разница между количеством согласных и гласных букв).

final int[] sentence = {0, 0, 0};
final HashMap<Integer, Integer> task3 = Arrays.stream(text.split(" "))
        .collect(HashMap::new,
                (map, str) -> {
                    str = str.toLowerCase();
                    for (int i = 0; i < str.length(); i++) {
                        char ch = str.charAt(i);
                        if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u') ++sentence[1];
                        else if ((ch >= 'a' && ch <= 'z')) ++sentence[2];
                    }
     
                    if (str.endsWith(".")) {
                        map.put(++sentence[0], sentence[2] - sentence[1]);
                        sentence[1] = 0;
                        sentence[2] = 0;
                    }
                }
                , HashMap::putAll
        );

Ответы (1 шт):

Автор решения: Nowhere Man

В принципе, показанный пример работает достаточно корректно, и с учётом ограничения, что исходный текст нельзя предварительно разбить на предложения, использование внешнего по отношению к стриму массива sentence для сохранения состояния стрима (номера предложения и статистики букв) представляется неизбежным.

Представленный код можно несколько улучшить:

  • привести сразу строку в нужный регистр
  • добавить обработку !, ? в конце предложения
  • использовать поток символов chars, убрав циклы
  • использовать switch + case expression (если допускается Java 12+)
  • вместо массива sentence использовать AtomicInteger
AtomicInteger id = new AtomicInteger(1);
AtomicInteger diff = new AtomicInteger(0);

String text = "Hello world!!  How are you?! You are welcome.";

Map<Integer, Integer> stats = text.toLowerCase().replaceAll("[!?.]+", ".")
    .chars()
    .filter(c -> Character.isLetter(c) || c == '.')
    .collect(
        HashMap::new,
        (map, c) -> { switch(c) {
            case '.' -> map.put(id.getAndIncrement(), diff.getAndSet(0));
            case 'a', 'e', 'i', 'o', 'u' -> diff.decrementAndGet();
            default -> diff.incrementAndGet();
        }},
        HashMap::putAll
    );
System.out.println(stats);
// -> {1=4, 2=-1, 3=-1}

Если бы исходный текст можно было предварительно разбить на предложения, тогда для каждого предложения было бы гораздо проще вычислить требуемую разницу. Решение с заготовленным сетом гласных может выглядеть так:

Set<Integer> vowels = "aeiou".chars().boxed().collect(Collectors.toSet());
String[] sentences = text.toLowerCase().split("[!?.]+");
Map<Integer, Integer> stats2 = IntStream.range(0, sentences.length)
    .boxed()
    .collect(Collectors.toMap(
        i -> i + 1,
        i -> sentences[i].chars()
            .filter(Character::isLetter)
            .map(c -> vowels.contains(c) ? -1 : 1)
            .sum()
    ));
System.out.println(stats2);
// -> {1=4, 2=-1, 3=-1}

или результат можно было бы представить в виде списка (индексация от 0, а не 1), а не мапы.

→ Ссылка