Как улучшить проверку, является ли строка (массив) подмножеством другой строки(массива), при помощи Stream API?
Я решал задачу leetcode 383. Ransom Note, в которой нужно было определить, является ли строка (массив символов) подмножеством другой строки (массива).
Задачу я решил при помощи Stream API, вот код:
public static boolean canConstruct(String ransomNote, String magazine) {
boolean result = false;
Map<Character, Long> letters = magazine.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.toMap(c -> c, c -> 1L, Long::sum));
ransomNote.chars()
.mapToObj(c -> (char) c)
.forEach(c -> {
long count = letters.getOrDefault(c, 0L) - 1L;
letters.put(c, count);
});
if (letters.entrySet()
.stream()
.filter(entry -> entry.getValue() < 0L)
.count() == 0L) {
result = true;
}
return result;
}
вопросы:
- Хочется узнать существует ли способ решить эту проблему при единожды открытом стриме (сейчас стрим "открывается" трижды), возможно ли это вообще?
- Если есть эксперты в Stream API, подскажите пожалуйста топ 5 или топ 10 методов в стримах, чтобы особое внимание им уделить))
Ответы (1 шт):
1. Хочется узнать существует ли способ решить эту проблему при единожды открытом стриме (сейчас стрим "открывается" трижды), возможно ли это вообще?
В представленном решении открываются два разных стрима для каждой из двух входных строк, чтобы построить карту частот символов. Затем в третьем стриме проверяется, не содержит ли карта частот хотя бы одно отрицательное значение частоты для какого-то символа (не слишком оптимальным образом).
Следует отметить, что для некоторых входных данных не понадобится открывать ни один стрим -- если строка magazine заведомо короче, чем строка ransomNote.
Затем, не нужно проверять всю строку ransomNote целиком, достаточно будет завершить проверку сразу же, как только в ней будет обнаружен символ, отсутствующий в карте частот, используя терминальную операцию с коротким замыканием noneMatch.
Поскольку в условии задачи явно сказано, что обе строки имеют длину до 105 и содержат только строчные английские буквы, вместо Map<Character, Long> можно использовать целочисленный массив на 26 элементов.
Таким образом, будут открыты только два стрима вместо трёх, по одному для каждой входной строки:
public boolean canConstruct(String ransomNote, String magazine) {
int n = magazine.length();
int m = ransomNote.length();
if (m > n) {
return false;
}
int[] freq = new int[26]; // заполнен 0-ми по умолчанию
// Заполняем карту частот по строке magazine
magazine
.chars()
.forEach(c -> freq[c - 'a']++);
// При проверке символов в ransomNote сразу же выходим, если найден отсутствующий символ
return ransomNote
.chars()
.map(c -> --freq[c - 'a']) // вернёт -1, если символ не найден в мапе
.noneMatch(q -> q == -1); // возвращаем false из метода
}
Если очень хочется использовать только один стрим, то отдельные стримы для каждой строки можно заменить одним целочисленным стримом по индексам, используя при этом метод String::charAt для доступа к элементам каждой строки.
Получится что-то вроде:
public boolean canConstruct(String ransomNote, String magazine) {
int n = magazine.length();
int m = ransomNote.length();
if (m > n) {
return false;
}
int[] freq = new int[26];
return IntStream.range(0, m + n)
.map(i -> i < n
? ++freq[magazine.charAt(i) - 'a']
: --freq[ransomNote.charAt(i - n) - 'a']
)
.noneMatch(f -> f == -1);
}
Более общее решение с использованием карты частот символов, а не массива, может выглядеть так:
public boolean canConstruct(String ransomNote, String magazine) {
int n = magazine.length();
int m = ransomNote.length();
if (m > n) {
return false;
}
Map<Character, Integer> freq = new HashMap<>(32);
return IntStream.range(0, m + n)
.map(i -> i < n
? freq.merge(magazine.charAt(i), 1, Integer::sum)
: freq.merge(ransomNote.charAt(i - n), -1, Integer::sum)
)
.noneMatch(f -> f == -1);
}
2. Если есть эксперты в Stream API, подскажите пожалуйста топ 5 или топ 10 методов в стримах, чтобы особое внимание им уделить))
Ответы на подобного рода вопросы про хит-парады не приветствуются на Stackoverflow, так как они провоцируют обмен мнениями, а не фактами (см. Справка: Какие вопросы лучше не задавать?).
Наиболее популярными скорее всего являются базовые методы Stream API:
- промежуточные операции
map/flatMap,filter,sorted/distinct,limit/skip, - терминальные операции
collect/reduce,findFirst/findAny,allMatch/noneMatch/anyMatch - фабричные методы для разнообразных коллекторов
Collectors.toList,Collectors.groupingBy,Collectors.toMap,Collectors.toSet,Collectors.joiningи др. - различные способы получения/объединения стримов типа
Stream.of,Stream.concat.