All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ence.1.3.0.source-code.overview.html Maven / Gradle / Ivy

There is a newer version: 2.3.0
Show newest version




A lightweight alternative to Java 8 sequential Stream

The Sequence library is a leaner alternative to sequential Java 8 Streams, used in similar ways but with a lighter step, and with better integration with the rest of Java.

It aims to be roughly feature complete with sequential Streams, with additional convenience methods for advanced traversal and transformation. In particular it allows easier collecting into common Collections without Collectors, better handling of Maps with Pair and Map.Entry as first-class citizens, tighter integration with the rest of Java by being implemented in terms of Iterable, and advanced partitioning, mapping and filtering methods, for example allowing you to peek at previous or next elements to make decisions during traversal. Sequences go to great lengths to be as lazy and late-evaluating as possible, with minimal overhead.

Sequences use Java 8 lambdas in much the same way as Streams do, but is based on readily available Iterables instead of a black box pipeline, and is built for convenience and compatibility with the rest of Java. It's for programmers wanting to perform common data processing tasks on moderately sized collections. If you need parallel iteration or are processing over 1 million or so entries, you might benefit from using a parallel Stream instead.

{@code
    List evens = Sequence.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                                 .filter(x -> x % 2 == 0)
                                 .map(Object::toString)
                                 .toList();

    assertThat(evens, contains("2", "4", "6", "8"));
}

See also: {@link org.d2ab.sequence.Sequence#of(Object...)}, {@link org.d2ab.sequence.Sequence#from(Iterable)}

Overview

The main Sequence package is {@link org.d2ab.sequence} where all the sequences reside. There are seven kinds of Sequences, each dealing with a different type of entry. The first is the regular {@link org.d2ab.sequence.Sequence} which is the general purpose stream of items. {@link org.d2ab.sequence.EntrySequence} and {@link org.d2ab.sequence.BiSequence} work directly on the constituent components of {@link java.util.Map.Entry} and {@link org.d2ab.util.Pair} objects. The last four are primitive sequences dealing with {@code char}, {@code int}, {@code long} and {@code double} primitives; {@link org.d2ab.sequence.CharSeq}, {@link org.d2ab.sequence.IntSequence}, {@link org.d2ab.sequence.LongSequence}, and {@link org.d2ab.sequence.DoubleSequence}. These work much the same as the regular {@link org.d2ab.sequence.Sequence} except they're adapted to work directly on primitives.

Iterable

Because each {@link org.d2ab.sequence.Sequence} is an {@link Iterable} you can re-use them safely after you have already traversed them, as long as they're not backed by an {@link java.util.Iterator} or {@link java.util.stream.Stream} which can only be traversed once.

{@code
    Sequence singulars = Sequence.range(1, 9); // Digits 1..9

    // using sequence of ints 1..9 first time to get odd numbers between 1 and 9
    Sequence odds = singulars.step(2);
    assertThat(odds, contains(1, 3, 5, 7, 9));

    // re-using the same sequence again to get squares of numbers between 4 and 8
    Sequence squares = singulars.startingFrom(4).endingAt(8).map(i -> i * i);
    assertThat(squares, contains(16, 25, 36, 49, 64));
}

Foreach

Also because each {@link org.d2ab.sequence.Sequence} is an {@link Iterable} they work beautifully in foreach loops:

{@code
    Sequence sequence = Sequence.ints().limit(5);

    int expected = 1;
    for (int each : sequence)
        assertThat(each, is(expected++));

    assertThat(expected, is(6));
}

FunctionalInterface

Because Sequence is a {@link FunctionalInterface} requiring only the {@link Iterable#iterator()} method to be implemented, it's very easy to create your own full-fledged {@link org.d2ab.sequence.Sequence} instances that can be operated on like any other {@link org.d2ab.sequence.Sequence} through the default methods on the interface that carry the bulk of the burden. In fact, this is how `Sequence's` own factory methods work. You could consider all of `Sequence` to be a smarter version of `Iterable`.

{@code
    List list = Arrays.asList(1, 2, 3, 4, 5);

    // Sequence as @FunctionalInterface of list's Iterator
    Sequence sequence = list::iterator;

    // Operate on sequence as any other sequence using default methods
    Sequence transformed = sequence.map(Object::toString);

    assertThat(transformed.limit(3), contains("1", "2", "3"));
}

Caching

Sequences can be created from Iterators or Streams but can then only be passed over once.

{@code
    Iterator iterator = Arrays.asList(1, 2, 3, 4, 5).iterator();

    Sequence sequence = Sequence.once(iterator);

    assertThat(sequence, contains(1, 2, 3, 4, 5));
    assertThat(sequence, is(emptyIterable()));
}

See also: {@link org.d2ab.sequence.Sequence#once(Iterator)}, {@link org.d2ab.sequence.Sequence#once(Stream)}

If you have an Iterator or Stream and wish to convert it to a full-fledged multi-iterable Sequence, use the caching methods on Sequence.

{@code
    Iterator iterator = Arrays.asList(1, 2, 3, 4, 5).iterator();

    Sequence cached = Sequence.cache(iterator);

    assertThat(cached, contains(1, 2, 3, 4, 5));
    assertThat(cached, contains(1, 2, 3, 4, 5));
}

See also: {@link org.d2ab.sequence.Sequence#cache(Iterable)}, {@link org.d2ab.sequence.Sequence#cache(Iterator)}, {@link org.d2ab.sequence.Sequence#cache(Stream)}

Updating

Sequences have full support for updating the underlying collection where possible, both through Iterator#remove() and by modifying the underlying collection directly in between iterations.

{@code
    List list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));

    Sequence.from(list).filter(x -> x % 2 != 0).clear();

    assertThat(list, contains(2, 4));
}
{@code
    List list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));

    Sequence sequence = Sequence.from(list).filter(x -> x % 2 == 0);
    assertThat(sequence, contains(2, 4));

    list.add(6);
    assertThat(sequence, contains(2, 4, 6));
}

See also: {@link org.d2ab.sequence.Sequence#clear()}

Streams

Sequences interoperate beautifully with Stream, through the once(Stream) and .stream() methods.

{@code
    Sequence paired = Sequence.once(Stream.of("a", "b", "c", "d")).pairs().flatten();

    assertThat(paired.stream().collect(Collectors.toList()), contains("a", "b", "b", "c", "c", "d"));
}

See also: {@link org.d2ab.sequence.Sequence#once(Stream)}, {@link org.d2ab.sequence.Sequence#cache(Stream)}, {@link org.d2ab.sequence.Sequence#stream()}

Recursion

There is full support for infinite recursive Sequences, including termination at a known value.

{@code
    Sequence fibonacci = BiSequence.recurse(0, 1, (i, j) -> Pair.of(j, i + j))
                                            .toSequence((i, j) -> i)
                                            .endingAt(34);

    assertThat(fibonacci, contains(0, 1, 1, 2, 3, 5, 8, 13, 21, 34));
}
{@code
    Exception exception = new IllegalStateException(new IllegalArgumentException(new NullPointerException()));

    Sequence exceptionAndCauses = Sequence.recurse(exception, Throwable::getCause).untilNull();

    assertThat(exceptionAndCauses, contains(instanceOf(IllegalStateException.class),
                                            instanceOf(IllegalArgumentException.class),
                                            instanceOf(NullPointerException.class)));

    exceptionAndCauses.last(IllegalArgumentException.class).ifPresent(Throwable::printStackTrace);
}
{@code
    Iterator delimiter = Sequence.of("").append(Sequence.of(", ").repeat()).iterator();

    StringBuilder joined = new StringBuilder();
    for (String number : Arrays.asList("One", "Two", "Three"))
        joined.append(delimiter.next()).append(number);

    assertThat(joined.toString(), is("One, Two, Three"));
}
{@code
    CharSeq hexGenerator = CharSeq.random("0-9", "A-F").limit(8);

    String hexNumber1 = hexGenerator.asString();
    String hexNumber2 = hexGenerator.asString();

    assertTrue(hexNumber1.matches("[0-9A-F]{8}"));
    assertTrue(hexNumber2.matches("[0-9A-F]{8}"));
    assertThat(hexNumber1, is(not(hexNumber2)));
}

See also:

  • {@link org.d2ab.sequence.Sequence#recurse(Object, UnaryOperator)}
  • {@link org.d2ab.sequence.Sequence#recurse(Object, Function, Function)}
  • {@link org.d2ab.sequence.Sequence#generate(Supplier)}
  • {@link org.d2ab.sequence.Sequence#until(Object)}
  • {@link org.d2ab.sequence.Sequence#until(Predicate)}
  • {@link org.d2ab.sequence.Sequence#untilNull()}
  • {@link org.d2ab.sequence.Sequence#endingAt(Object)}
  • {@link org.d2ab.sequence.Sequence#endingAt(Predicate)}
  • {@link org.d2ab.sequence.Sequence#endingAtNull()}

Reduction

The standard reduction operations are available as per Stream:

{@code
    Sequence thirteen = Sequence.longs().limit(13);

    long factorial = thirteen.reduce(1L, (r, i) -> r * i);

    assertThat(factorial, is(6227020800L));
}

See also: {@link org.d2ab.sequence.Sequence#reduce(BinaryOperator)}, {@link org.d2ab.sequence.Sequence#reduce(Object, BinaryOperator)}

Maps

Maps are handled as Sequences of Entry, with special transformation methods that convert to/from Maps.

{@code
    Sequence keys = Sequence.of(1, 2, 3);
    Sequence values = Sequence.of("1", "2", "3");

    Map map = keys.interleave(values).toMap();

    assertThat(map, is(equalTo(Maps.builder(1, "1").put(2, "2").put(3, "3").build())));
}

See also:

  • {@link org.d2ab.sequence.Sequence#interleave(Iterable)}
  • {@link org.d2ab.sequence.Sequence#pairs()}
  • {@link org.d2ab.sequence.Sequence#toMap()}
  • {@link org.d2ab.sequence.Sequence#toMap(Function, Function)}
  • {@link org.d2ab.sequence.Sequence#toMap(Supplier)}
  • {@link org.d2ab.sequence.Sequence#toMap(Supplier, Function, Function)}
  • {@link org.d2ab.sequence.Sequence#toSortedMap()}
  • {@link org.d2ab.sequence.Sequence#toSortedMap(Function, Function)}

You can also map Entry Sequences to Pairs which allows more expressive transformation and filtering.

{@code
    Map map = Maps.builder("1", 1).put("2", 2).put("3", 3).put("4", 4).build();

    Sequence> sequence = Sequence.from(map)
                                                       .map(Pair::from)
                                                       .filter(p -> p.test((s, i) -> i != 2))
                                                       .map(p -> p.map((s, i) -> Pair.of(s + " x 2", i * 2)));

    assertThat(sequence.toMap(), is(equalTo(Maps.builder("1 x 2", 2).put("3 x 2", 6).put("4 x 2", 8).build())));
}

See also: {@link org.d2ab.util.Pair}

You can also work directly on Entry keys and values using EntrySequence.

{@code
    Map original = Maps.builder("1", 1).put("2", 2).put("3", 3).put("4", 4).build();

    EntrySequence oddsInverted = EntrySequence.from(original)
                                                               .filter((k, v) -> v % 2 != 0)
                                                               .map((k, v) -> Maps.entry(v, k));

    assertThat(oddsInverted.toMap(), is(equalTo(Maps.builder(1, "1").put(3, "3").build())));
}

See also: {@link org.d2ab.sequence.EntrySequence}

Pairs

When iterating over sequences of Pairs of item, BiSequence provides native operators and transformations:

{@code
    BiSequence presidents = BiSequence.ofPairs("Abraham Lincoln", 1861, "Richard Nixon", 1969,
                                                                "George Bush", 2001, "Barack Obama", 2005);

    Sequence joinedOffice = presidents.toSequence((n, y) -> n + " (" + y + ")");

    assertThat(joinedOffice, contains("Abraham Lincoln (1861)", "Richard Nixon (1969)", "George Bush (2001)",
                                      "Barack Obama (2005)"));
}

See also: {@link org.d2ab.sequence.BiSequence}

Primitive

There are also primitive versions of Sequence for char, int, long and double processing: CharSeq, IntSequence, LongSequence and DoubleSequence.

{@code
    CharSeq snakeCase = CharSeq.from("Hello Lexicon").map(c -> (c == ' ') ? '_' : c).map(Character::toLowerCase);

    assertThat(snakeCase.asString(), is("hello_lexicon"));
}
{@code
    IntSequence squares = IntSequence.positive().map(i -> i * i);

    assertThat(squares.limit(5), contains(1, 4, 9, 16, 25));
}
{@code
    LongSequence negativeOdds = LongSequence.negative().step(2);

    assertThat(negativeOdds.limit(5), contains(-1L, -3L, -5L, -7L, -9L));
}
{@code
    DoubleSequence squareRoots = IntSequence.positive().toDoubles().map(Math::sqrt);

    assertThat(squareRoots.limit(3), contains(sqrt(1), sqrt(2), sqrt(3)));
}

See also: {@link org.d2ab.sequence.CharSeq}, {@link org.d2ab.sequence.IntSequence}, {@link org.d2ab.sequence.LongSequence}, {@link org.d2ab.sequence.DoubleSequence}

Peeking

Sequences also have mapping and filtering methods that peek on the previous and next elements:

{@code
    CharSeq titleCase = CharSeq.from("hello_lexicon")
                               .mapBack('_', (p, c) -> p == '_' ? toUpperCase(c) : c)
                               .map(c -> (c == '_') ? ' ' : c);

    assertThat(titleCase.asString(), is("Hello Lexicon"));
}

See also:

  • {@link org.d2ab.sequence.Sequence#peekBack(BiConsumer)}
  • {@link org.d2ab.sequence.Sequence#peekBack(Object, BiConsumer)}
  • {@link org.d2ab.sequence.Sequence#peekForward(BiConsumer)}
  • {@link org.d2ab.sequence.Sequence#peekForward(Object, BiConsumer)}
  • {@link org.d2ab.sequence.Sequence#filterBack(BiPredicate)}
  • {@link org.d2ab.sequence.Sequence#filterBack(Object, BiPredicate)}
  • {@link org.d2ab.sequence.Sequence#filterForward(BiPredicate)}
  • {@link org.d2ab.sequence.Sequence#filterForward(Object, BiPredicate)}
  • {@link org.d2ab.sequence.Sequence#mapBack(BiFunction)}
  • {@link org.d2ab.sequence.Sequence#mapBack(Object, BiFunction)}
  • {@link org.d2ab.sequence.Sequence#mapForward(BiFunction)}
  • {@link org.d2ab.sequence.Sequence#mapForward(Object, BiFunction)}

Partitioning

Both regular and primitive Sequences have advanced windowing and partitioning methods, allowing you to divide up Sequences in various ways, including a partitioning method that uses a BiPredicate to determine which two elements to create a batch between.

{@code
    Sequence> batched = Sequence.of(1, 2, 3, 4, 5, 6, 7, 8, 9).batch(3);

    assertThat(batched, contains(contains(1, 2, 3), contains(4, 5, 6), contains(7, 8, 9)));
}
{@code
    String vowels = "aeoiuy";

    Sequence consonantsVowels = CharSeq.from("terrain")
                                               .batch((a, b) -> (vowels.indexOf(a) == -1) !=
                                                                (vowels.indexOf(b) == -1))
                                               .map(CharSeq::asString);

    assertThat(consonantsVowels, contains("t", "e", "rr", "ai", "n"));
}

See also:

  • {@link org.d2ab.sequence.Sequence#window(int)}
  • {@link org.d2ab.sequence.Sequence#window(int, int)}
  • {@link org.d2ab.sequence.Sequence#batch(int)}
  • {@link org.d2ab.sequence.Sequence#batch(BiPredicate)}
  • {@link org.d2ab.sequence.Sequence#split(Object)}
  • {@link org.d2ab.sequence.Sequence#split(Predicate)}

Reading

Primitive sequences can be read from `Readers` or `InputStreams` into a `CharSeq` or `IntSequence` respective. These can also be converted back to `Readers` and `InputStreams` respectively, allowing for filtering or transformation of these streams.

{@code
    Reader reader = new StringReader("hello world\ngoodbye world\n");

    Sequence titleCase = CharSeq.read(reader)
                                        .mapBack('\n',
                                                 (p, n) -> p == '\n' || p == ' ' ?
                                                           Character.toUpperCase(n) : n)
                                        .split('\n')
                                        .map(phrase -> phrase.append('!'))
                                        .map(CharSeq::asString);

    assertThat(titleCase, contains("Hello World!", "Goodbye World!"));

    reader.close();
}
{@code
    Reader original = new StringReader("hello world\ngoodbye world\n");

    BufferedReader transformed = new BufferedReader(CharSeq.read(original).map(Character::toUpperCase).asReader());

    assertThat(transformed.readLine(), is("HELLO WORLD"));
    assertThat(transformed.readLine(), is("GOODBYE WORLD"));

    transformed.close();
    original.close();
}
{@code
    InputStream inputStream = new ByteArrayInputStream(new byte[]{0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF});

    String hexString = IntSequence.read(inputStream)
                                  .toSequence(Integer::toHexString)
                                  .map(String::toUpperCase)
                                  .join();

    assertThat(hexString, is("DEADBEEF"));

    inputStream.close();
}

See also: {@link org.d2ab.sequence.CharSeq#read(java.io.Reader)}, {@link org.d2ab.sequence.IntSequence#read(java.io.InputStream)}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy