#java #foreach #java-stream
#java #foreach #java-stream
Вопрос:
Во время упражнений с анаграммами я обнаружил, что мой код после выполнения имеет дубликаты на выходе в зависимости от момента использования sorted()
метода из Stream
класса.
Когда я выполняю сортировку перед filter()
и forEach()
методами
words.stream()
.sorted()
.filter(s1 -> !alreadyFound.contains(s1) amp;amp; words.stream()
.filter(s2 -> isAnagram.apply(s1, s2))
.count() == maxAnagrams)
.forEach(s1 -> {
Это дает эти результаты:
abel able bale bela elba
alger glare lager large regal
angel angle galen glean lange
caret carte cater crate trace
elan lane lean lena neal
evil levi live veil vile
Но когда я использую методы sorted()
after filter()
и before forEach()
words.stream()
.filter(s1 -> !alreadyFound.contains(s1) amp;amp; words.stream()
.filter(s2 -> isAnagram.apply(s1, s2))
.count() == maxAnagrams)
.sorted()
Затем он выдает эти результаты:
abel able bale bela elba
abel able bale bela elba
alger glare lager large regal
angel angle galen glean lange
angel angle galen glean lange
abel able bale bela elba
abel able bale bela elba
caret carte cater crate trace
caret carte cater crate trace
caret carte cater crate trace
caret carte cater crate trace
elan lane lean lena neal
abel able bale bela elba
evil levi live veil vile
angel angle galen glean lange
alger glare lager large regal
angel angle galen glean lange
alger glare lager large regal
elan lane lean lena neal
angel angle galen glean lange
alger glare lager large regal
elan lane lean lena neal
elan lane lean lena neal
evil levi live veil vile
evil levi live veil vile
elan lane lean lena neal
alger glare lager large regal
caret carte cater crate trace
evil levi live veil vile
evil levi live veil vile
Кажется, что при втором подходе программа дублирует результаты и добавляет уже найденные слова к выходным данным. Интересно, почему это происходит?
Я использую:
jdk1.8.0_201
Пример кода:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Main2 {
public static void main(String[] args) {
List<String> words = new ArrayList<>();
List<String> alreadyFound = new ArrayList<>();
BiFunction<String, String, Boolean> isAnagram = (s1, s2) -> {
if (s1.length() != s2.length()) return false;
char[] c1 = s1.toCharArray();
char[] c2 = s2.toCharArray();
Arrays.sort(c1);
Arrays.sort(c2);
return Arrays.equals(c1, c2);
};
try (InputStream inputStream = new URL("http://wiki.puzzlers.org/pub/wordlists/unixdict.txt").openStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
Stream<String> stream = new BufferedReader(inputStreamReader).lines()) {
stream.forEach(words::add);
} catch (IOException e1) {
e1.printStackTrace();
}
long maxAnagrams = Collections.max(words.stream()
.map(s1 -> words.stream()
.filter(s2 -> isAnagram.apply(s1, s2))
.count())
.collect(Collectors.toList())
);
words.stream()
// .sorted()
.filter(s1 -> !alreadyFound.contains(s1) amp;amp; words.stream()
.filter(s2 -> isAnagram.apply(s1, s2))
.count() == maxAnagrams)
// .sorted()
.forEach(s1 -> {
alreadyFound.add(s1);
words.stream()
.filter(s2 -> isAnagram.apply(s1, s2))
.forEach(s2 -> {
alreadyFound.add(s2);
System.out.print(" " s2);
});
System.out.println();
});
}
}
// РЕДАКТИРОВАТЬ: НЕ ПО ТЕМЕ
Я считаю, что это лучший способ достичь желаемых результатов:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Main4 {
public static void main(String[] args) {
long start = System.currentTimeMillis();
try (InputStream inputStream = new URL("http://wiki.puzzlers.org/pub/wordlists/unixdict.txt").openStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
Stream<String> stream = new BufferedReader(inputStreamReader).lines()) {
List<List<String>> anagrams = new ArrayList<>(stream
.collect(Collectors.groupingBy(o -> {
char[] chars = o.toCharArray();
Arrays.sort(chars);
return new String(chars);
}))
.values());
int maxAnagrams = anagrams.parallelStream()
.mapToInt(List::size)
.max()
.orElse(0);
anagrams.stream()
.filter(strings -> strings.size() == maxAnagrams)
.sorted(Comparator.comparing(o -> o.get(0)))
.forEach(strings -> {
strings.forEach(s -> System.out.print(s " "));
System.out.println();
});
} catch (IOException e1) {
e1.printStackTrace();
}
long stop = System.currentTimeMillis();
System.out.println(stop - start);
}
}
Комментарии:
1. Если бы я мог правильно посчитать скобки, у вас есть поток внутри потоковой операции, и я также вижу 2
System.out
. Чего вы ожидаете в противном случае?2. Это может немного облегчить вашу жизнь, если вы разделите этот внутренний поток на его собственную функцию. Мне нравится использование бифункции, но без этого условия фильтрации в качестве предиката это немного утомительно для анализа.
3. Я полагаю, что в вашем фильтре что-то происходит, что заставляет его удалять дубликаты, если вы сортируете его первым, но во втором этого не происходит. Я немного поиграл с этим, чтобы переписать его, но я получаю то же дублирование, которое вы видите.
4. Вы создали код, полагающийся на порядок обработки потока; ваш предикат filter предполагает, что он может предполагать, что предыдущий элемент уже был обработан
forEach
. Это нарушено по определению и, как вы можете видеть, будет нарушаться на практике в некоторых сценариях, например, при использованииsorted()
или параллельного потока. Кроме того, ваш код, выполняющий повторяющиеся линейные поиски, даже избыточно, ужасно неэффективен. Как правило, вам следует пересмотреть свою привычку использоватьforEach
для всего, начиная с того, как собирать поток вList
…5. Спасибо за все ответы, что касается «эффективности», я добавил лучшее решение для этой проблемы 🙂 Что касается проблемы, описанной ниже, алгоритм, представленный выше, чувствителен к такой операции,
sorted()
которая выполняет обход потока.
Ответ №1:
Я не уверен на 100%, но я полагаю, что причина, по которой вы видите некоторые из этих действий в зависимости от того, где вы сортируете свой поток, заключается в «синхронизации» данных, проходящих через поток.
Ваш foreach добавляет их в список слов, которые уже были найдены — но имейте в виду, что никакие данные не оцениваются до вызова foreach. Это означает, что элементы добавляются в список только по мере прохождения всего набора через foreach. Ваш основной фильтр, который выполняется перед foreach, скорее всего, увидит некоторое дублирование в результате попытки фильтрации по списку, который на самом деле еще не существует.
Сортировка запускает все механизмы, потому что она может задержать поток данных — нельзя гарантировать, что список будет отсортирован, пока не будет просмотрен каждый элемент. Это не совсем завершающая операция, но она задерживает выполнение, пока не пройдут все элементы. Отложенная оценка — замечательная вещь, но это может быть сложно, и часто вам действительно нужно убедиться, что что-то вроде сортировки — это то, что вы хотите, прежде чем идти по этому маршруту.
При всем сказанном, если вы пытаетесь избежать дублирования, есть способы получше (у stream есть .distinct()
функция, которая сделает это за вас).
Я написал другую реализацию того, что вы пытаетесь сделать здесь, которая выдает следующий результат.
abel able bale bela elba
alger glare lager large regal
angel angle galen glean lange
caret carte cater crate trace
elan lane lean lena neal
evil levi live veil vile
Если вы хотите, я могу поделиться этим с вами — но у меня складывается впечатление, что это для какого-то назначения, и вы пытаетесь разобраться в этом самостоятельно. Молодец, если это так.
Если вам нужны какие-то советы или вы хотите задать кому-то вопросы, не стесняйтесь обращаться ко мне в DM, и я буду рад помочь. После этого я отредактирую этот пост и опубликую код, который я использовал для генерации блока выше.
Комментарии:
1. Спасибо за ответ, я уже нашел «лучший» (я думаю, по эффективности) алгоритм для этой проблемы и прикрепил его к основному сообщению. Я все еще отчасти не понимаю, как работают потоки внизу (мне просто нужно разобраться с этим), но после прочтения некоторых документов Java ( docs.oracle.com/javase/8/docs/api/java/util/stream / … ) и «примитивной» отладки кажется, что ваш вывод верен.
2. В документах мы можем видеть, что
sorted()
этоa stateful intermediate operation
, который описывается как: > Операциям с сохранением состояния может потребоваться обработать весь ввод перед выдачей результата. Например, невозможно получить какие-либо результаты от сортировки потока, пока не будут просмотрены все элементы потока. Таким образом, он блокирует обход дляforeach()
, и в моем втором случае это дает странные результаты.