Combine two streams with collation - java

I need to do some Matrix work in an efficient and flexible way and hoped I could practice my Java 8 using streams and lambdas and perhaps even get free parallelism out of it. One point I am struggling with is how to perform an operation on two streams putting the result into a third.
Consider the simple mechanism:
static final List<String> a = Arrays.asList("A", "A", "A");
static final List<String> b = Arrays.asList("B", "B", "B");
public void withoutStreams() {
// The boring old way.
List<String> c = new ArrayList<>();
for (Iterator<String> ai = a.iterator(), bi = b.iterator(); ai.hasNext() && bi.hasNext();) {
c.add(ai.next() + bi.next());
}
System.out.println(c);
}
Works fine but I want to use Streams.
private void withStreams() {
List<String> c = new ArrayList<>();
combine(a.stream(), b.stream(), c, (String x, String y) -> x + y);
}
private void combine(Stream<String> a, Stream<String> b, List<String> c, BinaryOperator<String> op) {
// What can I do here to make it happen?
}
I fully expect we will be filling c using a Consumer of some form but extra kudos for coming up with some way of referring to a specific cell of the matrix other than using (row,col) bearing in mind that the cells will be immutable.

You can use the IntStream class to simulate indexing and then .mapToObj to concatenate the corresponding to the index objects from a and b:
List<String> list = IntStream.range(0, Math.max(a.size(), b.size()))
.mapToObj(i -> a.get(i) + b.get(i))
.collect(Collectors.toList());
Applied to your method, this will look like:
private void combine(List<String> a, List<String> b, List<String> c, BinaryOperator<String> op) {
c = IntStream.range(0, Math.max(a.size(), b.size()))
.mapToObj(i -> op.apply(a.get(i), b.get(i)))
.collect(Collectors.toList());
}
However, if you don't want to change the method's signature, here is a solution which works for all possible combinations of infinite and finite streams:
private void combine(Stream<String> a, Stream<String> b, List<String> c, BinaryOperator<String> op) {
Iterator<String> i1 = a.iterator();
Iterator<String> i2 = b.iterator();
Iterable<String> i = () -> new Iterator<String>() {
public boolean hasNext() {
return i1.hasNext() && i2.hasNext();
}
public String next() {
return op.apply(i1.next(), i2.next());
}
};
c = StreamSupport.stream(i.spliterator(), false).collect(Collectors.toList());
}

Functional programming style, using recursive (no loop):
static Stream<String> combine(List<String> a, List<String> b) {
if(a.isEmpty() || b.isEmpty()) {
return Stream.empty();
}
return Stream.concat(
Stream.of(a.get(0) + b.get(0)),
combine(a.stream().skip(1).collect(Collectors.toList()),
b.stream().skip(1).collect(Collectors.toList()))
);
}
plus: I vote up kocko's answer, my answer is for fun.

Related

Remove first(with zero index) element from stream conditionally

I have following code:
Stream<String> lines = reader.lines();
If fist string equals "email" I want to remove first string from the Stream. For other strings from the stream I don't need this check.
How could I acheve it?
P.S.
Sure I can transform it to the list, then use old school for loop but further I need stream again.
While the reader will be in an unspecified state after you constructed a stream of lines from it, it is in a well defined state before you do it.
So you can do
String firstLine = reader.readLine();
Stream<String> lines = reader.lines();
if(firstLine != null && !"email".equals(firstLine))
lines = Stream.concat(Stream.of(firstLine), lines);
Which is the cleanest solution in my opinion. Note that this is not the same as Java 9’s dropWhile, which would drop more than one line if they match.
If you cannot have the list and must do it with only a Stream, you can do it with a variable.
The thing is that you can only use a variable if it is "final" or "effectively final" so you cannot use a literal boolean. You can still do it with an AtomicBoolean :
Stream<String> stream = Arrays.asList("test", "email", "foo").stream();
AtomicBoolean first = new AtomicBoolean(true);
stream.filter(s -> {
if (first.compareAndSet(true, false)) {
return !s.equals("email");
}
return true;
})
// Then here, do whatever you need
.forEach(System.out::println);
Note : I don't like using "external variables" in a Stream because side effects are a bad practice in the functional programming paradigm. Better options are welcome.
To avoid checking the condition on each line of the file, I'd simply read and check the first line separately, then run the pipeline on rest of lines without checking the condition:
String first = reader.readLine();
Stream<String> firstLines = Optional.of(first)
.filter(s -> !"email".equals(s))
.map(s -> Stream.of(s))
.orElseGet(() -> Stream.empty());
Stream<String> lines = Stream.concat(firstLines, reader.lines());
Simpler on Java 9+:
Stream<String> firstLines = Optional.of(first)
.filter(s -> !"email".equals(s))
.stream();
Stream<String> lines = Stream.concat(firstLines, reader.lines());
To filter elements based on their index, you can use AtomicInteger to store and increment index while processing a Stream:
private static void filter(Stream<String> stream) {
AtomicInteger index = new AtomicInteger();
List<String> result = stream
.filter(el -> {
int i = index.getAndIncrement();
return i > 0 || (i == 0 && !"email".equals(el));
})
.collect(toList());
System.out.println(result);
}
public static void main(String[] args) {
filter(Stream.of("email", "test1", "test2", "test3"));
//[test1, test2, test3]
filter(Stream.of("test1", "email", "test2", "test3"));
//[test1, email, test2, test3]
filter(Stream.of("test1", "test2", "test3"));
//[test1, test2, test3]
}
This approach allows to filter elements at any index, not only the first one.
A little more convoluted, getting some inspiration from this snippet.
You can create a Stream<Integer> that will represent indexes and "zip" it with your Stream<String> to create a Stream<Pair<String, Integer>>
Then filter using the index and map it back to a Stream<String>
public static void main(String[] args) {
Stream<String> s = reader.lines();
Stream<Integer> indexes = Stream.iterate(0, i -> i + 1);
zip(s, indexes)
.filter(pair -> !(pair.getKey().equals("email") && pair.getValue() == 0))
.map(Pair::getKey)
.forEach(System.out::println);
}
private static Stream<Pair<String,Integer>> zip(Stream<String> stringStream, Stream<Integer> indexesStream){
Iterable<Pair<String,Integer>> iterable = () -> new ZippedWithIndexIterator(stringStream.iterator(), indexesStream.iterator());
return StreamSupport.stream(iterable.spliterator(), false);
}
static class ZippedWithIndexIterator implements Iterator<Pair<String, Integer>> {
private final Iterator<String> stringIterator;
private final Iterator<Integer> integerIterator;
ZippedWithIndexIterator(Iterator<String> stringIterator, Iterator<Integer> integerIterator) {
this.stringIterator = stringIterator;
this.integerIterator = integerIterator;
}
#Override
public Pair<String, Integer> next() {
return new Pair<>(stringIterator.next(), integerIterator.next());
}
#Override
public boolean hasNext() {
return stringIterator.hasNext() && integerIterator.hasNext();
}
}
#Arnouds answer is correct. You can create one stream for first line and then compare as below,
Stream<String> firstLineStream = reader.lines().limit(1).filter(line -> !line.startsWith("email"));;
Stream<String> remainingLinesStream = reader.lines().skip(1);
Stream.concat(firstLineStream, remainingLinesStream);
Here is example with Collectors.reducing. But in the end creates a list anyway.
Stream<String> lines = Arrays.asList("email", "aaa", "bbb", "ccc")
.stream();
List reduceList = (List) lines
.collect(Collectors.reducing( new ArrayList<String>(), (a, v) -> {
List list = (List) a;
if (!(list.isEmpty() && v.equals("email"))) {
list.add(v);
}
return a;
}));
reduceList.forEach(System.out::println);
Try this:
MutableBoolean isFirst = MutableBoolean.of(true);
lines..dropWhile(e -> isFirst.getAndSet(false) && "email".equals(e))

How to Get common Items from a stream of objects in Java8

I have 2 streams of Coll objects and i want to find the common objects on the basis of one the instance variable say i here. I need to do this using Java 8 streams.
Further I need to update the j variable by say a multiplier of 1000 for the common elements.
class Coll
{
Integer i;
Integer j;
public Coll(Integer i, Integer j) {
this.i = i;
this.j = j;
}
public Integer getI() {
return i;
}
public void setI(Integer i) {
this.i = i;
}
public Integer getJ() {
return j;
}
public void setJ(Integer j) {
this.j = j;
}
}
I am wring something like :
public static void main(String args[])
{
Stream<Coll> stream1 = Stream.of(new Coll(1,10),new Coll(2,20),new Coll(3,30) );
Stream<Coll> stream2 = Stream.of(new Coll(2,20),new Coll(3,30),new Coll(4,40) );
Stream<Coll> common = stream1
.filter(stream2
.map(x->x.getI())
.collect(Collectors.toList())
::equals(stream2
.map(x->x.getI()))
.collect(Collectors.toList()));
common.forEach( x-> x.setJ(x.getJ()*1000));
common.forEach(x -> System.out.println(x));
}
Am doing something wrong around equals method!! I guess Java8 doesn't support methods with parameters like equals does!!
I am getting a compilation error: expected a ')' or ';' around equals method
You can do it like so,
Map<Integer, Coll> colsByI = listTwo.stream()
.collect(Collectors.toMap(Coll::getI, Function.identity()));
List<Coll> commonElements = listOne.stream()
.filter(c -> Objects.nonNull(colsByI.get(c.getI())) && c.getI().equals(colsByI.get(c.getI()).getI()))
.map(c -> new Coll(c.getI(), c.getJ() * 1000))
.collect(Collectors.toList());
Move the logic to collect all the i of Stream2 outside. Then filter all Coll in stream1 if it's i is present in the other list.
List<Integer> secondCollStreamI = stream2
.map(Coll::getI)
.collect(Collectors.toList());
Stream<Coll> common = stream1
.filter(coll -> secondCollStreamI.contains(coll.getI()));
common.forEach( x-> x.setJ(x.getJ()*1000));
common.forEach(x -> System.out.println(x));
The last statement will result in an IllegalStateException (stream has already been operated upon or closed) since you cannot reuse the stream. You need to somewhere collect it to a List<Coll>... Something like...
List<Coll> common = stream1
.filter(coll -> secondCollStreamI.contains(coll.getI()))
.collect(Collectors.toList());
common.forEach(x -> x.setJ(x.getJ() * 1000));
common.forEach(System.out::println);
Or, if you want to do everything on the fly without collecting it
stream1
.filter(coll -> secondCollStreamI.contains(coll.getI()))
.forEach(x-> {
x.setJ(x.getJ()*1000);
System.out.println(x);
});

How to apply multiple Filters on Java Stream?

I have to filter a Collection of Objects by a Map, which holds key value pairs of the Objects field names and field values. I am trying to apply all filters by stream().filter().
The Objects are actually JSON, therefore the Map holds the names of its variables as well as the value they have to contain in order to be accepted, but for simplicity reasons and because its not relevant to the question I wrote a simple Testclass for simulating the behaviour:
public class TestObject {
private int property1;
private int property2;
private int property3;
public TestObject(int property1, int property2, int property3) {
this.property1 = property1;
this.property2 = property2;
this.property3 = property3;
}
public int getProperty(int key) {
switch(key) {
case 1: return property1;
case 2: return property2;
default: return property3;
}
}
}
What I have tried so far:
public static void main(String[] args) {
List<TestObject> list = new ArrayList<>();
Map<Integer, Integer> filterMap = new HashMap<>();
list.add(new TestObject(1, 2, 3));
list.add(new TestObject(1, 2, 4));
list.add(new TestObject(1, 4, 3));
filterMap.put(3, 3); //Filter property3 == 3
filterMap.put(2, 2); //Filter property2 == 2
//Does not apply the result
filterMap.forEach((key, value) -> list.stream()
.filter(testObject -> testObject.getProperty(key) == value)
.collect(Collectors.toList())
);
/* Gives error: boolean can not be converted to void
list = list.stream()
.filter(testObject -> filterMap.forEach((key, value) -> testObject.getProperty(key) == value))
.collect(Collectors.toList()
);
*/
//Printing result
list.forEach(obj -> System.out.println(obj.getProperty(1) + " " + obj.getProperty(2) + " " + obj.getProperty(3)));
}
I tried putting forEach of the Map first and the stream of the Collection first, but both solutions did not work as intended. The desired output of this example would be only to print the object with the values property1=1, property2=2 and property3=3.
How can I apply all filters correctly like when you would put them one after another in the code with a fixed amount of filters?
With a known amount of filters:
list.stream().filter(...).filter(...)
Edit:
Sweeper summed my question up very well in his answer, so just for clarification (and probably future readers) here again: I want to keep all Objects that satisfy all filters.
I suppose you want to keep all the TestObjects that satisfy all the conditions specified by the map?
This will do the job:
List<TestObject> newList = list.stream()
.filter(x ->
filterMap.entrySet().stream()
.allMatch(y ->
x.getProperty(y.getKey()) == y.getValue()
)
)
.collect(Collectors.toList());
Translated into "English",
filter the list list by keeping all the elements x that:
all of the key value pairs y of filterMap must satisfy:
x.getProperty(y.getKey()) == y.getValue()
(I don't think I did a good job at making this human readable...) If you want a more readable solution, I recommend Jeroen Steenbeeke's answer.
To apply a variable number of filter steps to a stream (that only become known at runtime), you could use a loop to add filter steps.
Stream<TestObject> stream = list.stream();
for (Predicate<TestObject> predicate: allPredicates) {
stream = stream.filter(predicate);
}
list = stream.collect(Collectors.toList());
It has nothing to do with filter. Actually the filter never work as per your code. Look at
//Does not apply the result
filterMap.forEach((key, value) -> list.stream()
.filter(testObject -> testObject.getProperty(key) == value)
.collect(Collectors.toList())
);
List has been filtered but nothing is changed here. No element has been deleted and No object address has been changed either. Try removeIf
// Does not apply the result
filterMap.forEach((key, value) -> list.removeIf(testObject -> testObject.getProperty(key) != value));
output is
1 2 3
A more general approach is to create a multi filter (Predicate) which is concatenated using Predicate.and(...) or Predicate.or(...). This is applicable to anything using Predicate - first of all Stream and Optional.
Since the result is a Predicate itself one can continue with Predicate.and(...) or Predicate.or(...) or with building more complex predicates using MultiPredicate again.
class MultiPredicate {
public static <T> Predicate<T> matchingAll(Collection<Predicate<T>> predicates) {
Predicate<T> multiPredicate = to -> true;
for (Predicate<T> predicate : predicates) {
multiPredicate = multiPredicate.and(predicate);
}
return multiPredicate;
}
#SafeVarargs
public static <T> Predicate<T> matchingAll(Predicate<T> first, Predicate<T>... other) {
if (other == null || other.length == 0) {
return first;
}
Predicate<T> multiPredicate = first;
for (Predicate<T> predicate : other) {
multiPredicate = multiPredicate.and(predicate);
}
return multiPredicate;
}
public static <T> Predicate<T> matchingAny(Collection<Predicate<T>> predicates) {
Predicate<T> multiPredicate = to -> false;
for (Predicate<T> predicate : predicates) {
multiPredicate = multiPredicate.or(predicate);
}
return multiPredicate;
}
#SafeVarargs
public static <T> Predicate<T> matchingAny(Predicate<T> first, Predicate<T>... other) {
if (other == null || other.length == 0) {
return first;
}
Predicate<T> multiPredicate = first;
for (Predicate<T> predicate : other) {
multiPredicate = multiPredicate.or(predicate);
}
return multiPredicate;
}
}
Applying to the question:
public static void main(String... args) {
List<TestObject> list = new ArrayList<>();
Map<Integer, Integer> filterMap = new HashMap<>();
list.add(new TestObject(1, 2, 3));
list.add(new TestObject(1, 2, 4));
list.add(new TestObject(1, 4, 3));
filterMap.put(3, 3); // Filter property3 == 3
filterMap.put(2, 2); // Filter property2 == 2
List<Predicate<TestObject>> filters = filterMap.entrySet().stream()
.map(filterMapEntry -> mapToFilter(filterMapEntry))
.collect(Collectors.toList());
Predicate<TestObject> multiFilter = MultiPredicate.matchingAll(filters);
List<TestObject> filtered = list.stream()
.filter(multiFilter)
.collect(Collectors.toList());
for (TestObject to : filtered) {
System.out.println("(" + to.getProperty(1) + "|" + to.getProperty(2) + "|" + to.getProperty(3) + ")");
}
}
private static Predicate<TestObject> mapToFilter(Entry<Integer,Integer> filterMapEntry) {
return to -> to.getProperty(filterMapEntry.getKey()) == filterMapEntry.getValue();
}
In this case all filters have to match. The result is:
(1|2|3)
If we use MultiPredicate.matchingAny(...) the result is:
(1|2|3)
(1|2|4)
(1|4|3)

How to zip two Java Lists

I have 2 Lists:
List<String> subjectArr = Arrays.asList<String>("aa", "bb", "cc");
List<Long> numArr = Arrays.asList<Long>(2L, 6L, 4L);
How do I create new List and zip two Lists into it?
List<?> subjectNumArr = zip(subjectArr, numArr);
// subjectNumArr == [{'aa',2},{'bb',6},{'cc',4}]
Here's Java-8 solution using the Pair class (like in #ZhekaKozlov answer):
public static <A, B> List<Pair<A, B>> zipJava8(List<A> as, List<B> bs) {
return IntStream.range(0, Math.min(as.size(), bs.size()))
.mapToObj(i -> new Pair<>(as.get(i), bs.get(i)))
.collect(Collectors.toList());
}
In Java 9 onwards you can use Map.entry():
public static <A, B> List<Map.Entry<A, B>> zipJava8(List<A> as, List<B> bs) {
return IntStream.range(0, Math.min(as.size(), bs.size()))
.mapToObj(i -> Map.entry(as.get(i), bs.get(i)))
.collect(Collectors.toList());
}
As per related question, you can use Guava (>= 21.0) to do this:
List<String> subjectArr = Arrays.asList("aa", "bb", "cc");
List<Long> numArr = Arrays.asList(2L, 6L, 4L);
List<Pair> pairs = Streams.zip(subjectArr.stream(), numArr.stream(), Pair::new)
.collect(Collectors.toList());
Note that the guava method is annotated as #Beta, though what that means in practice is up to interpretation, the method has not changed since version 21.0.
To get an Iterator<C> from an Iterator<A>, an Iterator<B>, and a BiFunction<A, B, C>:
public static <A, B, C> Iterator<C> map(Iterator<A> a, Iterator<B> b, BiFunction<A, B, C> f) {
return new Iterator<C>() {
public boolean hasNext() {
return a.hasNext() && b.hasNext(); // This uses the shorter of the two `Iterator`s.
}
public C next() {
return f.apply(a.next(), b.next());
}
};
}
Use an ArrayList of Map.Entry<String, Long>, checking that both arraylists have equal size (as it seems to be your requirement), like that:
List<Map.Entry<String,Long>> subjectNumArr = new ArrayList<>(numArr.size());
if (subjectArr.size() == numArr.size()) {
for (int i = 0; i < subjectArr.size(); ++i) {
subjectNumArr.add(new AbstractMap.SimpleEntry<String, Long>(subjectArr.get(i), numArr.get(i));
}
}
That's all the code you need!
Then, to iterate over the results, use something like:
for (Map.Entry<String, Long> entry : subjectNumArr) {
String key = entry.getKey();
Long value = entry.getValue();
}
or, you can simply get the pair at position i (keeping insertion order), by:
Map.Entry<String, Long> entry = subjectNumArr.get(i);
This can also hold duplicate entries, unlike the Map solution that I initially suggested, without requiring to define your own (Pair) class.
The operation you want is called zipping.
You need to implement a method zip:
public static <A, B> List<Pair<A, B>> zip(List<A> as, List<B> bs) {
Iterator<A> it1 = as.iterator();
Iterator<B> it2 = bs.iterator();
List<Map.Entry<A, B>> result = new ArrayList<>();
while (it1.hasNext() && it2.hasNext()) {
result.add(Map.entry(it1.next(), it2.next()));
}
return result;
}
And you use it like this:
zip(subjectArr, numArr);
I agree with vefthym however if you have to do using list then create a class like below -:
class DirtyCoding{
String subject;
int numbr;
}
Then iterate over the your list, create object of DirtyCoding, populate it and add then add it to List<DirtyCoding>.
My ideas:
Define a class for your pairs. This makes your code extendable (i.e. if you want to add a third field).
Define your Lists with the convinient method Arrays.asList. It is easy to understand, short and automatically generates generic collections.
Use superclasses or interfaces as variable types. I used List in the example, maybe Collection would be even better. Only declare variables as ArrayList if you need the list to be so specific. That will give you the possibility to use other implementations, without having to change much code.
I would create Pair objects like this:
import java.util.*;
class Pair {
String subject;
Long num;
}
public class Snippet {
public static void main(String[] args) {
List<String> subjectArr = Arrays.asList("aa", "bb", "cc");
List<Long> numArr = Arrays.asList(2l,6l,4l);
// create result list
List<Pair> pairs = new ArrayList<>();
// determine result size
int length = Math.min(subjectArr.size(), numArr.size());
// create pairs
for (int position = 0; position < length; position++) {
Pair pair = new Pair();
pair.subject = subjectArr.get(position);
pair.num = numArr.get(position);
pairs.add(pair);
}
}
}
Use one of the answers from Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip)
to zip and apply a function at the same time
e.g. Using a zipped Stream:
<A,B,C> Stream<C> zipped(List<A> lista, List<B> listb, BiFunction<A,B,C> zipper){
int shortestLength = Math.min(lista.size(),listb.size());
return IntStream.range(0,shortestLength).mapToObject( i -> {
return zipper.apply(lista.get(i), listb.get(i));
});
}
for which you may also use Guava's Streams.zip()
You should create an ArrayList of List:
ArrayList<List> subjectNumArr = new ArrayList<>();
Iterator iter = subjectArr.iterator();
int count=0;
while(iter.hasNext()){
subjectNumArr.add(Arrays.asList(iter.next(),numArr.get[count++]);
}
In Java 8:
You can do this in one line using Stream and Collectors class.
In Java 7/6/5:
List list = new ArrayList();
if(subjectArr.size() == numArr.size())
{
for (int i = 0; i < subjectArr.size(); i++) { // Loop through every subject/name
list.add(subjectArr.get(i) + " " + numArr.get(i)); // Concat the two, and add it
}
}

Is there a concise way to iterate over a stream with indices in Java 8?

Is there a concise way to iterate over a stream whilst having access to the index in the stream?
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList;
Stream<Integer> indices = intRange(1, names.length).boxed();
nameList = zip(indices, stream(names), SimpleEntry::new)
.filter(e -> e.getValue().length() <= e.getKey())
.map(Entry::getValue)
.collect(toList());
which seems rather disappointing compared to the LINQ example given there
string[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
var nameList = names.Where((c, index) => c.Length <= index + 1).ToList();
Is there a more concise way?
Further it seems the zip has either moved or been removed...
The cleanest way is to start from a stream of indices:
String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
IntStream.range(0, names.length)
.filter(i -> names[i].length() <= i)
.mapToObj(i -> names[i])
.collect(Collectors.toList());
The resulting list contains "Erik" only.
One alternative which looks more familiar when you are used to for loops would be to maintain an ad hoc counter using a mutable object, for example an AtomicInteger:
String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
AtomicInteger index = new AtomicInteger();
List<String> list = Arrays.stream(names)
.filter(n -> n.length() <= index.incrementAndGet())
.collect(Collectors.toList());
Note that using the latter method on a parallel stream could break as the items would not necesarily be processed "in order".
The Java 8 streams API lacks the features of getting the index of a stream element as well as the ability to zip streams together. This is unfortunate, as it makes certain applications (like the LINQ challenges) more difficult than they would be otherwise.
There are often workarounds, however. Usually this can be done by "driving" the stream with an integer range, and taking advantage of the fact that the original elements are often in an array or in a collection accessible by index. For example, the Challenge 2 problem can be solved this way:
String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList =
IntStream.range(0, names.length)
.filter(i -> names[i].length() <= i)
.mapToObj(i -> names[i])
.collect(toList());
As I mentioned above, this takes advantage of the fact that the data source (the names array) is directly indexable. If it weren't, this technique wouldn't work.
I'll admit that this doesn't satisfy the intent of Challenge 2. Nonetheless it does solve the problem reasonably effectively.
EDIT
My previous code example used flatMap to fuse the filter and map operations, but this was cumbersome and provided no advantage. I've updated the example per the comment from Holger.
Since guava 21, you can use
Streams.mapWithIndex()
Example (from official doc):
Streams.mapWithIndex(
Stream.of("a", "b", "c"),
(str, index) -> str + ":" + index)
) // will return Stream.of("a:0", "b:1", "c:2")
I've used the following solution in my project. I think it is better than using mutable objects or integer ranges.
import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Collector.Characteristics;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.util.Objects.requireNonNull;
public class CollectionUtils {
private CollectionUtils() { }
/**
* Converts an {#link java.util.Iterator} to {#link java.util.stream.Stream}.
*/
public static <T> Stream<T> iterate(Iterator<? extends T> iterator) {
int characteristics = Spliterator.ORDERED | Spliterator.IMMUTABLE;
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, characteristics), false);
}
/**
* Zips the specified stream with its indices.
*/
public static <T> Stream<Map.Entry<Integer, T>> zipWithIndex(Stream<? extends T> stream) {
return iterate(new Iterator<Map.Entry<Integer, T>>() {
private final Iterator<? extends T> streamIterator = stream.iterator();
private int index = 0;
#Override
public boolean hasNext() {
return streamIterator.hasNext();
}
#Override
public Map.Entry<Integer, T> next() {
return new AbstractMap.SimpleImmutableEntry<>(index++, streamIterator.next());
}
});
}
/**
* Returns a stream consisting of the results of applying the given two-arguments function to the elements of this stream.
* The first argument of the function is the element index and the second one - the element value.
*/
public static <T, R> Stream<R> mapWithIndex(Stream<? extends T> stream, BiFunction<Integer, ? super T, ? extends R> mapper) {
return zipWithIndex(stream).map(entry -> mapper.apply(entry.getKey(), entry.getValue()));
}
public static void main(String[] args) {
String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
System.out.println("Test zipWithIndex");
zipWithIndex(Arrays.stream(names)).forEach(entry -> System.out.println(entry));
System.out.println();
System.out.println("Test mapWithIndex");
mapWithIndex(Arrays.stream(names), (Integer index, String name) -> index+"="+name).forEach((String s) -> System.out.println(s));
}
}
In addition to protonpack, jOOλ's Seq provides this functionality (and by extension libraries that build on it like cyclops-react, I am the author of this library).
Seq.seq(Stream.of(names)).zipWithIndex()
.filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
.toList();
Seq also supports just Seq.of(names) and will build a JDK Stream under the covers.
The simple-react equivalent would similarly look like
LazyFutureStream.of(names)
.zipWithIndex()
.filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
.toList();
The simple-react version is more tailored for asynchronous / concurrent processing.
Just for completeness here's the solution involving my StreamEx library:
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
EntryStream.of(names)
.filterKeyValue((idx, str) -> str.length() <= idx+1)
.values().toList();
Here we create an EntryStream<Integer, String> which extends Stream<Entry<Integer, String>> and adds some specific operations like filterKeyValue or values. Also toList() shortcut is used.
I found the solutions here when the Stream is created of list or array (and you know the size). But what if Stream is with unknown size? In this case try this variant:
public class WithIndex<T> {
private int index;
private T value;
WithIndex(int index, T value) {
this.index = index;
this.value = value;
}
public int index() {
return index;
}
public T value() {
return value;
}
#Override
public String toString() {
return value + "(" + index + ")";
}
public static <T> Function<T, WithIndex<T>> indexed() {
return new Function<T, WithIndex<T>>() {
int index = 0;
#Override
public WithIndex<T> apply(T t) {
return new WithIndex<>(index++, t);
}
};
}
}
Usage:
public static void main(String[] args) {
Stream<String> stream = Stream.of("a", "b", "c", "d", "e");
stream.map(WithIndex.indexed()).forEachOrdered(e -> {
System.out.println(e.index() + " -> " + e.value());
});
}
With a List you can try
List<String> strings = new ArrayList<>(Arrays.asList("First", "Second", "Third", "Fourth", "Fifth")); // An example list of Strings
strings.stream() // Turn the list into a Stream
.collect(HashMap::new, (h, o) -> h.put(h.size(), o), (h, o) -> {}) // Create a map of the index to the object
.forEach((i, o) -> { // Now we can use a BiConsumer forEach!
System.out.println(String.format("%d => %s", i, o));
});
Output:
0 => First
1 => Second
2 => Third
3 => Fourth
4 => Fifth
If you happen to use Vavr(formerly known as Javaslang), you can leverage the dedicated method:
Stream.of("A", "B", "C")
.zipWithIndex();
If we print out the content, we will see something interesting:
Stream((A, 0), ?)
This is because Streams are lazy and we have no clue about next items in the stream.
Here is code by abacus-common
Stream.of(names).indexed()
.filter(e -> e.value().length() <= e.index())
.map(Indexed::value).toList();
Disclosure: I'm the developer of abacus-common.
There isn't a way to iterate over a Stream whilst having access to the index because a Stream is unlike any Collection. A Stream is merely a pipeline for carrying data from one place to another, as stated in the documentation:
No storage. A stream is not a data structure that stores elements; instead, they carry values from a source (which could be a data structure, a generator, an IO channel, etc) through a pipeline of computational operations.
Of course, as you appear to be hinting at in your question, you could always convert your Stream<V> to a Collection<V>, such as a List<V>, in which you will have access to the indexes.
With https://github.com/poetix/protonpack
u can do that zip:
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList;
Stream<Integer> indices = IntStream.range(0, names.length).boxed();
nameList = StreamUtils.zip(indices, stream(names),SimpleEntry::new)
.filter(e -> e.getValue().length() <= e.getKey()).map(Entry::getValue).collect(toList());
System.out.println(nameList);
If you don't mind using a third-party library, Eclipse Collections has zipWithIndex and forEachWithIndex available for use across many types. Here's a set of solutions to this challenge for both JDK types and Eclipse Collections types using zipWithIndex.
String[] names = { "Sam", "Pamela", "Dave", "Pascal", "Erik" };
ImmutableList<String> expected = Lists.immutable.with("Erik");
Predicate<Pair<String, Integer>> predicate =
pair -> pair.getOne().length() <= pair.getTwo() + 1;
// JDK Types
List<String> strings1 = ArrayIterate.zipWithIndex(names)
.collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings1);
List<String> list = Arrays.asList(names);
List<String> strings2 = ListAdapter.adapt(list)
.zipWithIndex()
.collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings2);
// Eclipse Collections types
MutableList<String> mutableNames = Lists.mutable.with(names);
MutableList<String> strings3 = mutableNames.zipWithIndex()
.collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings3);
ImmutableList<String> immutableNames = Lists.immutable.with(names);
ImmutableList<String> strings4 = immutableNames.zipWithIndex()
.collectIf(predicate, Pair::getOne);
Assert.assertEquals(expected, strings4);
MutableList<String> strings5 = mutableNames.asLazy()
.zipWithIndex()
.collectIf(predicate, Pair::getOne, Lists.mutable.empty());
Assert.assertEquals(expected, strings5);
Here's a solution using forEachWithIndex instead.
MutableList<String> mutableNames =
Lists.mutable.with("Sam", "Pamela", "Dave", "Pascal", "Erik");
ImmutableList<String> expected = Lists.immutable.with("Erik");
List<String> actual = Lists.mutable.empty();
mutableNames.forEachWithIndex((name, index) -> {
if (name.length() <= index + 1)
actual.add(name);
});
Assert.assertEquals(expected, actual);
If you change the lambdas to anonymous inner classes above, then all of these code examples will work in Java 5 - 7 as well.
Note: I am a committer for Eclipse Collections
You can use IntStream.iterate() to get the index:
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList = IntStream.iterate(0, i -> i < names.length, i -> i + 1)
.filter(i -> names[i].length() <= i)
.mapToObj(i -> names[i])
.collect(Collectors.toList());
This only works for Java 9 upwards in Java 8 you can use this:
String[] names = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> nameList = IntStream.iterate(0, i -> i + 1)
.limit(names.length)
.filter(i -> names[i].length() <= i)
.mapToObj(i -> names[i])
.collect(Collectors.toList());
If you are trying to get an index based on a predicate, try this:
If you only care about the first index:
OptionalInt index = IntStream.range(0, list.size())
.filter(i -> list.get(i) == 3)
.findFirst();
Or if you want to find multiple indexes:
IntStream.range(0, list.size())
.filter(i -> list.get(i) == 3)
.collect(Collectors.toList());
Add .orElse(-1); in case you want to return a value if it doesn't find it.
One possible way is to index each element on the flow:
AtomicInteger index = new AtomicInteger();
Stream.of(names)
.map(e->new Object() { String n=e; public i=index.getAndIncrement(); })
.filter(o->o.n.length()<=o.i) // or do whatever you want with pairs...
.forEach(o->System.out.println("idx:"+o.i+" nam:"+o.n));
Using an anonymous class along a stream is not well-used while being very useful.
If you need the index in the forEach then this provides a way.
public class IndexedValue {
private final int index;
private final Object value;
public IndexedValue(final int index, final Object value) {
this.index = index;
this.value = value;
}
public int getIndex() {
return index;
}
public Object getValue() {
return value;
}
}
Then use it as follows.
#Test
public void withIndex() {
final List<String> list = Arrays.asList("a", "b");
IntStream.range(0, list.size())
.mapToObj(index -> new IndexedValue(index, list.get(index)))
.forEach(indexValue -> {
System.out.println(String.format("%d, %s",
indexValue.getIndex(),
indexValue.getValue().toString()));
});
}
you don't need a map necessarily
that is the closest lambda to the LINQ example:
int[] idx = new int[] { 0 };
Stream.of(names)
.filter(name -> name.length() <= idx[0]++)
.collect(Collectors.toList());
You can create a static inner class to encapsulate the indexer as I needed to do in example below:
static class Indexer {
int i = 0;
}
public static String getRegex() {
EnumSet<MeasureUnit> range = EnumSet.allOf(MeasureUnit.class);
StringBuilder sb = new StringBuilder();
Indexer indexer = new Indexer();
range.stream().forEach(
measureUnit -> {
sb.append(measureUnit.acronym);
if (indexer.i < range.size() - 1)
sb.append("|");
indexer.i++;
}
);
return sb.toString();
}
This question (Stream Way to get index of first element matching boolean) has marked the current question as a duplicate, so I can not answer it there; I am answering it here.
Here is a generic solution to get the matching index that does not require an external library.
If you have a list.
public static <T> int indexOf(List<T> items, Predicate<T> matches) {
return IntStream.range(0, items.size())
.filter(index -> matches.test(items.get(index)))
.findFirst().orElse(-1);
}
And call it like this:
int index = indexOf(myList, item->item.getId()==100);
And if using a collection, try this one.
public static <T> int indexOf(Collection<T> items, Predicate<T> matches) {
int index = -1;
Iterator<T> it = items.iterator();
while (it.hasNext()) {
index++;
if (matches.test(it.next())) {
return index;
}
}
return -1;
}
String[] namesArray = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
String completeString
= IntStream.range(0,namesArray.length)
.mapToObj(i -> namesArray[i]) // Converting each array element into Object
.map(String::valueOf) // Converting object to String again
.collect(Collectors.joining(",")); // getting a Concat String of all values
System.out.println(completeString);
OUTPUT : Sam,Pamela,Dave,Pascal,Erik
String[] namesArray = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
IntStream.range(0,namesArray.length)
.mapToObj(i -> namesArray[i]) // Converting each array element into Object
.map(String::valueOf) // Converting object to String again
.forEach(s -> {
//You can do various operation on each element here
System.out.println(s);
}); // getting a Concat String of all
To Collect in the List:
String[] namesArray = {"Sam","Pamela", "Dave", "Pascal", "Erik"};
List<String> namesList
= IntStream.range(0,namesArray.length)
.mapToObj(i -> namesArray[i]) // Converting each array element into Object
.map(String::valueOf) // Converting object to String again
.collect(Collectors.toList()); // collecting elements in List
System.out.println(listWithIndex);
As jean-baptiste-yunès said, if your stream is based on a java List then using an AtomicInteger and its incrementAndGet method is a very good solution to the problem and the returned integer does correspond to the index in the original List as long as you do not use a parallel stream.
Here's solution for standard Java:
In-line solution:
Arrays.stream("zero,one,two,three,four".split(","))
.map(new Function<String, Map.Entry<Integer, String>>() {
int index;
#Override
public Map.Entry<Integer, String> apply(String s) {
return Map.entry(index++, s);
}
})
.forEach(System.out::println);
and more readable solution with utility method:
static <T> Function<T, Map.Entry<Integer, T>> mapWithIntIndex() {
return new Function<T, Map.Entry<Integer, T>>() {
int index;
#Override
public Map.Entry<Integer, T> apply(T t) {
return Map.entry(index++, t);
}
};
}
...
Arrays.stream("zero,one,two,three,four".split(","))
.map(mapWithIntIndex())
.forEach(System.out::println);
If the list is unique, we can make use of indexOf method.
List<String> names = Arrays.asList("Sam", "Pamela", "Dave", "Pascal", "Erik");
names.forEach(name ->{
System.out.println((names.indexOf(name) + 1) + ": " + name);
});

Categories

Resources