Succeeds: streams
Summary
Terminal -> triggers the evaluation
Intermediate -> returns another Stream
Stateful -> stores information, might need to processes the whole stream before producing an output
Bounded -> only works on finite streams
Streams
- java.util.Stream
- can only be operated once, even by intermediate operations
- best to use a single pipeline
java
Stream<Integer> s = Stream.iterate(0, x -> x + 1);
s.map(x -> x * 2);
s.map(x -> x + x); // error, IllegalStateException
Reduce
- evaluates left-to-right, unlike accumulate from #cs1101s
java
Stream.iterate(1, i -> i + 1).limit(5).reduce(0, (x, y) -> 2 * x + y)
// 57 = 2(2(2(2(2(0)+1)+2)+3)+4)+5
Filter
- lazily keeps entries for which the predicate returns true
Map/flatMap
- lazily applies a function to all entries
- for flatMap the resulting stream is flattened into the current stream
Concept
Factory methods
java
Stream.generate(Supplier<T> s)
Stream.iterate(seed, Function<T, T> next)
Stream.empty()
Stream.of(T...)
Stream.concat(Stream<? extends T> a, Stream<? extends T> b)
Terminal operations
java
Stream::head
Stream::tail
Stream::reduce
Stream::count
Stream::forEach
Stream::toList
// bounded, fails on infinite streams
Stream::anyMatch
Stream::noneMatch
Stream::allMatch
Intermediate operations
java
Stream::filter
Stream::map
Stream::flatMap
Stream::peek
// truncating/short-circuiting
Stream::limit
Stream::takeWhile
Stream::skip
// bounded, fails on infinite streams
Stream::sorted
Stream::distinct
Application
isPrime
java
// method implementation
boolean isPrime(int x) {
for (int i = 2; i <= x - 1; i++) {
if (x % i == 0) { // check if every integer in the range [2, x) is a multiple
return false;
}
}
return true;
}
boolean isPrime(int x) {
return IntStream.range(2, x) // range from 2 to x-1
.noneMatch(i -> x % i == 0);
}
InfiniteList
java
package cs2030s.fp;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
public class InfiniteList<T> {
private final Lazy<Maybe<T>> head;
private final Lazy<InfiniteList<T>> tail;
private static final InfiniteList<?> SENTINEL = new Sentinel();
private InfiniteList() {
this.head = null;
this.tail = null;
}
// generates the head lazily
public static <T> InfiniteList<T> generate(Producer<T> producer) {
return new InfiniteList<>(
Lazy.of(() -> Maybe.some(producer.produce())),
Lazy.of(() -> InfiniteList.generate(producer)));
}
// generates the head eagerly
public static <T> InfiniteList<T> iterate(T seed, Transformer<T, T> next) {
return new InfiniteList<>(seed, () -> InfiniteList.iterate(next.transform(seed), next));
}
private InfiniteList(T head, Producer<InfiniteList<T>> tail) {
this.head = Lazy.of(Maybe.some(head));
this.tail = Lazy.of(tail);
}
private InfiniteList(Lazy<Maybe<T>> head, Lazy<InfiniteList<T>> tail) {
this.head = head;
this.tail = tail;
}
public T head() {
// doesn't work since the arg is evaluated first, we only want it to evaluate if its Maybe.None
// return this.head.get().orElse(this.tail.get().head());
return this.head.get().orElseGet(() -> this.tail.get().head());
}
public InfiniteList<T> tail() {
// funny thing to ensure that head has been evaluated first
return this.head.get().map(x -> this.tail.get()).orElseGet(() -> this.tail.get().tail());
}
public <R> InfiniteList<R> map(Transformer<? super T, ? extends R> mapper) {
return new InfiniteList<>(this.head.map(m -> m.map(mapper)), this.tail.map(i -> i.map(mapper)));
}
public InfiniteList<T> filter(BooleanCondition<? super T> predicate) {
return new InfiniteList<>(
this.head.map(m -> m.filter(predicate)), this.tail.map(i -> i.filter(predicate)));
}
private static final class Sentinel extends InfiniteList<Object> {
// protected Sentinel() {
// super(Lazy.of(() -> Maybe.none()),
// Lazy.of(() -> InfiniteList.sentinel()));
// }
@Override
public String toString() {
return "-";
}
@Override
public boolean isSentinel() {
return true;
}
@Override
public Object head() {
throw new NoSuchElementException();
}
@Override
public InfiniteList<Object> tail() {
throw new NoSuchElementException();
}
@Override
public <U> InfiniteList<U> map(Transformer<? super Object, ? extends U> mapper) {
return InfiniteList.sentinel();
}
@Override
public InfiniteList<Object> filter(BooleanCondition<? super Object> predicate) {
return this;
}
@Override
public InfiniteList<Object> limit(long n) {
return this;
}
@Override
public List<Object> toList() {
return List.of();
}
@Override
public InfiniteList<Object> takeWhile(BooleanCondition<? super Object> predicate) {
return this;
}
@Override
public <U> U reduce(U identity, Combiner<U, ? super Object, U> accumulator) {
return identity;
}
}
public static <T> InfiniteList<T> sentinel() {
// SENTINEL, which is InfiniteList<?> can be cast to InfiniteList<T>
@SuppressWarnings("unchecked")
InfiniteList<T> out = (InfiniteList<T>) SENTINEL;
return out;
}
public InfiniteList<T> limit(long n) {
return n <= 0
? InfiniteList.sentinel()
: new InfiniteList<>(
this.head,
Lazy.of(() -> this.tail.get().limit(this.head.get().map(v -> n - 1).orElse(n))));
}
public InfiniteList<T> takeWhile(BooleanCondition<? super T> predicate) {
// true if predicate evaluates to true or is a filtered out element
Lazy<Boolean> check = Lazy.of(() -> this.head.get().map(v -> predicate.test(v)).orElse(true));
return new InfiniteList<>(
Lazy.of(() -> check.get() ? this.head.get() : Maybe.none()),
Lazy.of(
() -> check.get() ? this.tail.get().takeWhile(predicate) : InfiniteList.sentinel()));
}
public boolean isSentinel() {
return false;
}
public <U> U reduce(U identity, Combiner<U, ? super T, U> accumulator) {
return this.tail
.get()
.reduce(
this.head.get().map(v -> accumulator.combine(identity, v)).orElse(identity),
accumulator);
}
public long count() {
return this.reduce(0L, (x, y) -> x + 1L);
}
public List<T> toList() {
List<T> list = new ArrayList<>();
InfiniteList<T> inf = this;
while (!inf.isSentinel()) {
// System.out.println(inf.head());
inf.head.get().ifPresent(list::add);
inf = inf.tail.get();
}
return list;
}
public String toString() {
return "[" + this.head + " " + this.tail + "]";
}
}