как сделать корректную реализацию 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 шт):
В принципе, показанный пример работает достаточно корректно, и с учётом ограничения, что исходный текст нельзя предварительно разбить на предложения, использование внешнего по отношению к стриму массива 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), а не мапы.