Функция фильтра не ленилась
Я делаю свою собственную версию библиотеки Java Stream для развлечения. Здесь моя подпись класса:
class Stream<T> {
Supplier<T> head;
Supplier<Stream<T>> tail;
...
}
Кроме того, я написал базовый бесконечный потоковый итератор, который будет генерировать бесконечный список, основанный на данной функции:
public static <T> Stream<T> iterate(T first, Function<T, T> f) {
return new Stream<T>(
() -> first,
() -> {
T nextElem = f.apply(first);
if (nextElem == null) {
return generate(() -> null);
} else {
return iterate(nextElem, f);
}
}
);
}
generate
функции - это особый случай итерации, повторяющий данный элемент навсегда. В вышеприведенной функции я генерирую бесконечную последовательность null
чтобы указать конец потока (я не думаю, что я буду хранить нулевые значения в потоке).
Затем я написал функцию сокращения, где функция уменьшения ленив по второму аргументу:
public <U> U reduce(U acc, Function<T, Function<Supplier<U>, U>> f) {
System.out.println("REDUCE CALL");
T elem = head.get();
if (elem != null) {
return f.apply(elem).apply(() -> this.tail.get().reduce(acc, f));
} else {
return acc;
}
}
Основываясь на функции сокращения, я написал функцию фильтра.
public Stream<T> filter(Predicate<T> p) {
System.out.println("FILTER");
return reduce(generate(() -> null), elem -> acc -> {
if (p.test(elem)) {
return new Stream<>(
() -> elem,
() -> acc.get()
);
} else {
return acc.get();
}
});
}
Наконец, я продолжил использовать свой собственный класс Stream:
public static void main(String[] args) {
Stream<Integer> ilist =
Stream
.iterate(1, x -> x + 1)
.filter(x -> x >= 5);
}
Но фильтр не ленив! Из приведенного ниже результата, я думаю, фильтр оценивает элементы, пока не найдет тот, который соответствует данному предикату.
FILTER
REDUCE CALL
REDUCE CALL
REDUCE CALL
REDUCE CALL
REDUCE CALL
Что не так с моим кодом, и как я могу снова отключить функцию фильтра?
Обновление. Основываясь на замечаниях Sweeper, я попробовал еще одну функцию фильтра без использования сокращения.
public Stream<T> filter2(Predicate<T> p) {
System.out.println("FILTER2");
T elem = head.get();
if (elem == null) {
return generate(() -> null);
} else {
if (p.test(elem)) {
return new Stream<>(
() -> elem,
() -> this.tail.get().filter2(p)
);
} else {
return this.tail.get().filter2(p);
}
}
}
Однако эта функция также не ленива. Выход моей основной функции с использованием filter2
выглядит следующим образом:
FILTER2
FILTER2
FILTER2
FILTER2
FILTER2
Как я могу это исправить, и есть ли способ реализовать ленивый фильтр через ленивое сокращение?
Благодарности: Это упражнение и реализация вышеуказанных функций были вдохновлены книгой "Функциональное программирование" в "Scala" от Чиусано и Бьярнасона.
Ответы
Ответ 1
В версии, которую вы написали без reduce
, случай, когда элемент существует, но не удовлетворяет предикату, не ленив. Вместо того, чтобы обернуть рекурсивный вызов в lambda поставщика, как вы это делали в другом случае, вы с нетерпением получите хвост и немедленно его фильтруете.
public Stream<T> filter2(Predicate<T> p) {
System.out.println("FILTER2");
T elem = head.get();
if (elem == null) {
return generate(() -> null);
} else {
if (p.test(elem)) {
return new Stream<>(
() -> elem,
() -> this.tail.get().filter2(p)
);
} else {
return this.tail.get().filter2(p); // <- not lazy!
}
}
}
То, что вам нужно, это способ создания потока таким образом, чтобы решение о его пустом или нет отложено до следующего.
public class Stream<T> {
// private constructor(s)
public static <T> Stream<T> empty() { /* ... */ }
public static <T> Stream<T> cons(Supplier<T> head, Supplier<Stream<T> tail) { /* ... */ }
public static <T> Stream<T> lazy(Supplier<Stream<T>> stream) { /* ... */ }
public Stream<T> filter(Predicate<T> p) {
if ( /* this stream is empty */ ) {
return Stream.empty();
} else if ( /* head element satisfies predicate */ ) {
// lazily filter tail, cons head element
} else {
return Stream.lazy(() -> this.tail.get().filter(p));
}
}
}
Что-то в этом роде.