Подтвердить что ты не робот

Сплит java.util.stream.Stream

У меня есть текстовый файл, содержащий URL-адреса и электронные письма. Мне нужно извлечь все из них из файла. Каждый URL и адрес электронной почты можно найти более одного раза, но результат не должен содержать дубликатов. Я могу извлечь все URL-адреса, используя следующий код:

Files.lines(filePath).
    .map(urlPattern::matcher)
    .filter(Matcher::find)
    .map(Matcher::group)
    .distinct();

Я могу извлечь все электронные письма, используя следующий код:

Files.lines(filePath).
    .map(emailPattern::matcher)
    .filter(Matcher::find)
    .map(Matcher::group)
    .distinct();

Можно ли удалить все URL-адреса и электронные письма, считывающие поток, возвращенный Files.lines(filePath), только один раз? Что-то вроде разделения потоков строк на поток URL-адресов и поток писем.

4b9b3361

Ответ 1

Вы можете использовать коллекцию partitioningBy, хотя это еще не очень элегантное решение.

Map<Boolean, List<String>> map = Files.lines(filePath)
        .filter(str -> urlPattern.matcher(str).matches() ||
                       emailPattern.matcher(str).matches())
        .distinct()
        .collect(Collectors.partitioningBy(str -> urlPattern.matcher(str).matches()));
List<String> urls = map.get(true);
List<String> emails = map.get(false);

Если вы не хотите дважды применять регулярное выражение, вы можете сделать это с помощью промежуточного объекта-пары (например, SimpleEntry):

public static String classify(String str) {
    return urlPattern.matcher(str).matches() ? "url" : 
        emailPattern.matcher(str).matches() ? "email" : null;
}

Map<String, Set<String>> map = Files.lines(filePath)
        .map(str -> new AbstractMap.SimpleEntry<>(classify(str), str))
        .filter(e -> e.getKey() != null)
        .collect(Collectors.groupingBy(e -> e.getKey(),
            Collectors.mapping(e -> e.getValue(), Collectors.toSet())));

Используя мою бесплатную библиотеку StreamEx, последний шаг будет короче:

Map<String, Set<String>> map = StreamEx.of(Files.lines(filePath))
        .mapToEntry(str -> classify(str), Function.identity())
        .nonNullKeys()
        .grouping(Collectors.toSet());

Ответ 2

Вы можете выполнить сопоставление в пределах Collector:

Map<String,Set<String>> map=Files.lines(filePath)
    .collect(HashMap::new,
        (hm,line)-> {
            Matcher m=emailPattern.matcher(line);
            if(m.matches())
              hm.computeIfAbsent("mail", x->new HashSet<>()).add(line);
            else if(m.usePattern(urlPattern).matches())
              hm.computeIfAbsent("url", x->new HashSet<>()).add(line);
        },
        (m1,m2)-> m2.forEach((k,v)->m1.merge(k, v,
                                     (s1,s2)->{s1.addAll(s2); return s1;}))
    );
Set<String> mail=map.get("mail"), url=map.get("url");

Обратите внимание, что это легко может быть адаптировано для поиска нескольких совпадений внутри строки:

Map<String,Set<String>> map=Files.lines(filePath)
    .collect(HashMap::new,
        (hm,line)-> {
            Matcher m=emailPattern.matcher(line);
            while(m.find())
              hm.computeIfAbsent("mail", x->new HashSet<>()).add(m.group());
            m.usePattern(urlPattern).reset();
            while(m.find())
              hm.computeIfAbsent("url", x->new HashSet<>()).add(m.group());
        },
        (m1,m2)-> m2.forEach((k,v)->m1.merge(k, v,
                                     (s1,s2)->{s1.addAll(s2); return s1;}))
    );

Ответ 3

Поскольку вы не можете повторно использовать Stream, единственным вариантом будет "сделать это вручную", я думаю.

File.lines(filePath).forEach(s -> /** match and sort into two lists */ );

Если есть другое решение для этого, я был бы рад узнать об этом!

Ответ 4

Общий вопрос: зачем вы хотите передавать только один раз?

Извлечение URL-адресов и извлечение электронных писем - это разные операции и, следовательно, их следует обрабатывать в своих собственных потоковых операциях. Даже если исходный источник потока содержит сотни тысяч записей, время для итерации можно пренебречь по сравнению с операциями сопоставления и фильтрации.

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

List<String> allLines = Files.readAllLines(filePath);
allLines.stream() ... // here do the URLs
allLines.stream() ... // here do the emails

Конечно, для этого требуется некоторая память.