Сбор HashSet/Java 8/Regex Pattern/Stream API
Недавно я меняю версию JDK 8 вместо 7 моего проекта, и теперь я перезаписываю фрагменты кода, используя новые функции, которые поставляются с Java 8.
final Matcher mtr = Pattern.compile(regex).matcher(input);
HashSet<String> set = new HashSet<String>() {{
while (mtr.find()) add(mtr.group().toLowerCase());
}};
Как я могу написать этот код с помощью Stream API?
Ответы
Ответ 1
A Matcher
реализация spliterator может быть довольно простой, если вы повторно используете предоставленный JDK Spliterators.AbstractSpliterator
:
public class MatcherSpliterator extends AbstractSpliterator<String[]>
{
private final Matcher m;
public MatcherSpliterator(Matcher m) {
super(Long.MAX_VALUE, ORDERED | NONNULL | IMMUTABLE);
this.m = m;
}
@Override public boolean tryAdvance(Consumer<? super String[]> action) {
if (!m.find()) return false;
final String[] groups = new String[m.groupCount()+1];
for (int i = 0; i <= m.groupCount(); i++) groups[i] = m.group(i);
action.accept(groups);
return true;
}
}
Обратите внимание, что spliterator предоставляет все группы сопряжений, а не только полное совпадение. Также обратите внимание, что этот разделитель поддерживает parallelism, потому что AbstractSpliterator
реализует политику разделения.
Как правило, вы будете использовать удобный поток factory:
public static Stream<String[]> matcherStream(Matcher m) {
return StreamSupport.stream(new MatcherSpliterator(m), false);
}
Это дает вам мощную основу для краткой записи всех видов сложной логики, ориентированной на регулярное выражение, например:
private static final Pattern emailRegex = Pattern.compile("([^,]+?)@([^,]+)");
public static void main(String[] args) {
final String emails = "[email protected], [email protected], [email protected]";
System.out.println("User has e-mail accounts on these domains: " +
matcherStream(emailRegex.matcher(emails))
.map(gs->gs[2])
.collect(joining(", ")));
}
Какая печать
User has e-mail accounts on these domains: gmail.com, yahoo.com, tijuana.com
Для полноты ваш код будет переписан как
Set<String> set = matcherStream(mtr).map(gs->gs[0].toLowerCase()).collect(toSet());
Ответ 2
Ответ Марко показывает, как получить совпадения в потоке с помощью Spliterator
. Молодец, дайте этому человеку большой +1! Серьезно, убедитесь, что вы выдвинули свой ответ, прежде чем даже подумать о том, чтобы перенести это, так как он полностью является его производным.
У меня есть только небольшой бит для добавления в ответ Марко, который заключается в том, что вместо представления совпадений в виде массива строк (с каждым элементом массива, представляющим группу соответствия), матчи лучше представлены как MatchResult
, которые это тип, изобретенный для этой цели. Таким образом, результатом будет Stream<MatchResult>
вместо Stream<String[]>
. Код становится немного проще. Код tryAdvance
будет
if (m.find()) {
action.accept(m.toMatchResult());
return true;
} else {
return false;
}
Вызов map
в его примере сопоставления с электронной почтой изменится на
.map(mr -> mr.group(2))
и пример OP будет переписан как
Set<String> set = matcherStream(mtr)
.map(mr -> mr.group(0).toLowerCase())
.collect(toSet());
Использование MatchResult
дает немного большую гибкость в том, что оно также обеспечивает смещения групп соответствия внутри строки, что может быть полезно для некоторых приложений.
Ответ 3
Я не думаю, что вы можете превратить это в Stream
без написания собственного Spliterator, но я не знаю, почему вы хотели бы к.
Matcher.find()
- операция изменения состояния объекта Matcher
, поэтому запуск каждого поиска() в параллельном потоке приведет к противоречивым результатам. Запуск потока в последовательном режиме не будет иметь лучшую производительность, эквивалентную Java 7, и будет сложнее понять.
Ответ 4
Как насчет Pattern.splitAsStream
?
Stream<String> stream = Pattern.compile(regex).splitAsStream(input);
а затем сборщик, чтобы получить набор.
Set<String> set = stream.map(String::toLowerCase).collect(Collectors.toSet());
Ответ 5
Что насчет
public class MakeItSimple {
public static void main(String[] args) throws FileNotFoundException {
Scanner s = new Scanner(new File("C:\\Users\\Admin\\Desktop\\TextFiles\\Emails.txt"));
HashSet<String> set = new HashSet<>();
while ( s.hasNext()) {
String r = s.next();
if (r.matches("([^,]+?)@([^,]+)")) {
set.add(r);
}
}
set.stream().map( x -> x.toUpperCase()).forEach(x -> print(x));
s.close();
}
}
Ответ 6
Вот реализация с использованием интерфейса Spliterator.
// To get the required set
Set<String> result = (StreamSupport.stream(new MatcherGroupIterator(pattern,input ),false))
.map( s -> s.toLowerCase() )
.collect(Collectors.toSet());
...
private static class MatcherGroupIterator implements Spliterator<String> {
private final Matcher matcher;
public MatcherGroupIterator(Pattern p, String s) {
matcher = p.matcher(s);
}
@Override
public boolean tryAdvance(Consumer<? super String> action) {
if (!matcher.find()){
return false;
}
action.accept(matcher.group());
return true;
}
@Override
public Spliterator<String> trySplit() {
return null;
}
@Override
public long estimateSize() {
return Long.MAX_VALUE;
}
@Override
public int characteristics() {
return Spliterator.NONNULL;
}
}