Extensions to Iterable

The de.fhg.fokus.xtensions.iteration.IterableExtensions class provides extension methods to java.lang.Iterable

Unfortunately the java.lang.Iterable interface does not provide a (default) method for creating a java.lang.Stream. It does provide a method to obtain a Spliterator which can be used to create a stream, but this is rather unpleasant to use.
The IterableExtensions class provides the stream extension method to easily create a stream from an iterable. This method will first check if the given iterable is instance of java.util.Collection, since this class does provide a default stream method, otherwise it will construct a stream from the spliterator provided by the iterable.

Example:

import static extension de.fhg.fokus.xtensions.iteration.IterableExtensions.*
import java.util.OptionalDouble
//...
#["foo", null, "BAR", "bazzzz"]
	.filterNull
	.averageSize
	.ifPresent [
		println('''The average string lenght is «it»''')
	]

//...

private def OptionalDouble averageSize(Iterable<String> strings) {
	strings.stream.mapToInt[length].average (1)
}
  1. In this line the extension method stream is called on the iterable strings.

Analogous to the stream method the IterableExtensions class also provides a parallelStream method.

It is also possible to map an iterable to a primitive iterable (see Primitve Iterables / From Iterables).

The JDK since Java 8 provides the class java.util.stream.Collector which can be used with streams to perform a reduction operation over all elements in a stream. The class java.util.stream.Collectors already provides constructor methods for a bunch of useful collectors. The IterableExtensions class of this library provides a collect extension method directly for Iterable to easily reduce the elements of the iterable.

Example:

import static java.util.stream.Collectors.*
import static extension de.fhg.fokus.xtensions.iteration.IterableExtensions.*
// ...
val Iterable<String> strings = #["fooooo", "baar", "baz"]
val summary = strings.collect(summarizingInt[length])
println("Average length: " + summary.average)
println("Max length: " + summary.max)

A fairly common task in Model-to-Model transformations is to find objects of one type that are not referenced by some other objects. This is usually done by navigation over all elements in a model, finding all elements of both types and then finding the elements that are actually not referenced. This is typically done by traversing the complete model twice to find the elements of both types. This can be an expensive operation if the input model is large. The solution is to traverse the model just once and group the elements according to their type.

This library provides a method allowing exactly this. The method groupIntoListBy and groupIntoSetBy groups elements of an iterable by their type.

Example:

val foo = "foo"
val bar = "bar"
val baz = "baz"
val traverseMe = #[foo, #[bar], baz, #[foo], bar]
val groups = traverseMe.groupIntoSetBy(String, List)

val Set<String> strings = groups.get(String)
val Set<List> lists = groups.get(List)
val inNoList = strings
	.filter[str|
		!lists.exists[it.contains(str)]
	].toList
println("Elements contained in no list: " + inNoList)

To exclude elements of one Iterable from another, the method withoutAll can be used.

val s = #["I", "boo", "pity", "char", "the", "fool"]
	.withoutAll(#{"boo", "char"})
	.join(" ")
println(s)
Caution
Performance of withoutAll is best when using an appropriate java.util.Set, such as HashSet for the elements to exclude.

To partition the elements of an Iterable based on a predicate or the class (elements are tested to be instance of) into two parts: selected and rejected. The selected part will contain the elements for which the predicate evaluates to true or elements are instance of the given partitioning class. The rejected part will contain the other elements.

Example:

import static extension de.fhg.fokus.xtensions.iteration.IterableExtensions.*
// ...
val char[] chars = #[0x0020 as char, 0x0034 as char]
val List<CharSequence> list = #[
	"Hello",
	new StringBuilder().append(chars),
	"Xtend",
	new StringBuilder().append(0x0032 as char)
]
list.partitionBy(String) => [
	println('''Selected: "«selected.join(" ")»"''')
	println('''Rejected: "«rejected.join("")»"''')
]

For both versions of the partitionBy method there exist overloads that take instances of Collector.

Example:

import static extension de.fhg.fokus.xtensions.iteration.IterableExtensions.*
import static java.util.stream.Collectors.*
import java.util.Set
import java.util.List
// ...
val list = #["foo", "bla", "foo", "hui", "fun"]
val partition = list.partitionBy([startsWith("f")], toSet, toList)
val Set<String> selected = partition.selected
val List<String> rejected = partition.rejected
println("Unique words starting with 'f' : " + selected.join(", "))
println("Other words: " + rejected.join(", "))

Note that the Collectors#partitioningBy Collector from the JDK aggregates into a Map<Boolean,List<T>> where T is the type of the elements in a collected stream. Another partitioningBy overload from Collectors aggregates the map values and returns a Map<Boolean,D> where D is the aggregation of a "downstream" Collector.

import static java.util.stream.Collectors.*
import java.util.Set
// ...
val list = #["foo", "bla", "foo", "hui", "fun"]
val partition = list.stream.collect(partitioningBy([startsWith("f")], toSet))
val Set<String> selected = partition.get(true)
val Set<String> rejected = partition.get(false)
println("Unique words starting with 'f' : " + selected.join(", "))
println("Other words: " + rejected.join(", "))

To allow a similar workflow to the JDK version, for the Partition of this library a asMap extension method is provided for Partitions having the same type for the selected and rejected part. The other way around an extension method is provided to wrap a Map<Boolean,X> into a Partition<X,X>.

To add elements from an Iterable to one or more collections, the into extension method is provided by the class IterableExtensions.

Example:

val namesWithB = newArrayList("Barbara", "Bob", "Brian")
val newNames = #["Justin", "Anna", "Bruce", "Chris", "Becky"]
newNames
	.filter[it.toFirstLower.startsWith("b")]
	.into(namesWithB)

namesWithB.forEach[
	println(it)
]
Tip

Related JavaDocs:

results matching ""

    No results matching ""