6

What is a good way to access an element of a previous step of a stream?

The example here starts with a stream of parents (String) and flatMaps them to two children (Integer) for each parent. At the step where I am dealing with the Integer children (forEach) I would like to "remember" what the parent String was ('parentFromPreviousStep' compilation error).

The obvious solution is to introduce a holder class for a String plus an Integer and in the flatMap produce a stream of such holder objects. But this seems a bit clumsy and I was wondering whether there is a way to access a "previous element" in such a chain or to "pass down a parameter" (without introducing a special holder class).

    List<String> parents = new ArrayList();
    parents.add("parentA");
    parents.add("parentB");
    parents.add("parentC");

    parents.stream()
            .flatMap(parent -> {
                List<Integer> children = new ArrayList<>();
                children.add(1);
                children.add(2);
                return children.stream();
            }).forEach(child -> System.out.println(child + ", my parent is: ???" + parentFromPreviousStep));

Background: This actually happens in Nashorn with the idea to provide JavaScript scripting code for an application without having to deploy the Java application. This is why I do not want to introduce a special Java holder class for my two business objects (which are simplified here as String and Integer). [The Java code above can be written almost the same in Nashorn, and the stream functions are Java, not JavaScript.]

greg-449
  • 109,219
  • 232
  • 102
  • 145
StaticNoiseLog
  • 1,370
  • 17
  • 27
  • 5
    If you want to remember an association, you need an object storing that association. That’s the natural, unavoidable way. Well, until value types enter the Java scene. Apropos “clumsy”: you `flatMap` function could be simplified to `parent -> Stream.of(1, 2)`. I know, it’s just an example, but the underlying assumption that you have to create a `List` and fill it, just to get a `Stream`, should be corrected. If you want to avoid a new, dedicated holder class, you can use `AbstractMap.SimpleImmutableEntry` for holding two objects/values. – Holger Oct 12 '16 at 08:55
  • 1
    @Holger: This actually answers the question for me. And thanks for the good tips! I was not aware of "Stream.of" and "SimpleImmutableEntry". – StaticNoiseLog Oct 12 '16 at 11:38

4 Answers4

3

Holger's comment to the question provides valuable information and leads to the following solution. The existence of SimpleImmutableEntry in the JDK solves the problem that creating a proprietary holder class was not an option for me in this situation. (This is a community wiki because Holger provided the answer.)

Stream.of("parentA", "parentB", "parentC")
.flatMap(parent -> Stream.of(
            new AbstractMap.SimpleImmutableEntry<>(1, parent),
            new AbstractMap.SimpleImmutableEntry<>(2, parent))
).forEach(child -> System.out.println(child.getKey() + ", my parent is: " + child.getValue()));
Holger
  • 285,553
  • 42
  • 434
  • 765
StaticNoiseLog
  • 1,370
  • 17
  • 27
0

Child aggregates to a parent, hmm.. Smells like key-value pairs. Map!

So, I tried it in this way. and it worked. Hope this would be helpful.

    parents.stream()
            .flatMap(parent -> {
                List<Integer> children = new ArrayList<>();
                children.add(1);
                children.add(2);

                Map<String, List<Integer>> stringListMap = new HashMap<String, List<Integer>>();
                stringListMap.put(parent, children);
                return stringListMap.entrySet().stream();
            })
            .forEach(entry -> {
                entry.getValue().stream().forEach(val -> System.out.println(val + ", my parent is: ???" + entry.getKey()));
            });
Jude Niroshan
  • 4,280
  • 8
  • 40
  • 62
  • 1
    You are defeating the entire purpose of `flatMap`. It’s not intended to pass `List` instances downstream. – Holger Oct 12 '16 at 09:27
  • I am passing `Map>.Entry` objects down the stream. Inside the `flatMap()` it flatterns the child `List` and do some `map()` work to the initial stream. I think, that's what the misuse of flatMap() i'm doing. isn't it? – Jude Niroshan Oct 12 '16 at 09:46
  • 1
    You are returning a `Stream>>`. Each entry contains a list. You are not flattening the list. This is most obviously recognizable as you are performing a nested `forEach` on that list in the terminal action. – Holger Oct 12 '16 at 09:52
  • @Jude: Thanks for your answer. For practical purposes it solves the problem, too, and therefore has its merit. The suggestions of Holger and the availability of SimpleImmutableEntry allow for a shorter version, however and therefore I have added that as an answer. – StaticNoiseLog Oct 12 '16 at 12:32
0

This is possible in the reducer method of the stream as follows, this code actually gives you Lists of the current element and the previous two elements. I was a little surprised this solution was NOT in this baeldung article;

https://www.baeldung.com/java-stream-last-element

public List<Integer> mainFunPreviousAccess(int n) {
    Supplier<Integer> s = new Supplier<Integer>() {
        int next = 0;
        
        @Override
        public Integer get() {
            return next++;
        }
    };
    return Stream.generate(s).takeWhile(j -> j <= n).map(j -> List.of(j)).reduce((x,y) -> {
        System.out.println("\naccumulator x " + x);
        System.out.println("y " + y);
        int xs = x.size();
        if (xs >= 2) {
            return List.of(x.get(xs - 2), x.get(xs -1), y.get(0));
        }
        return List.of(x.get(0), y.get(0));
    }).get();
}

@Adligo :)

0

Here is another solutions to the same problem, that allows continuing use of the stream since this is not done through a terminal operation;

public void mainFunPreviousAccess2(int n) {
    //Stream.generate(s).takeWhile(j -> j <= i).forEach(j -> System.out.println("j " + j));
    Supplier<Integer> s = new Supplier<Integer>() {
        int next = 0;
        
        @Override
        public Integer get() {
            return next++;
        }
    };

    Function<Integer, List<Integer>> s2 = new Function<Integer,List<Integer>>() {
        Integer m1 = null;
        
        @Override
        public List<Integer> apply(Integer t) {
            if (m1 == null) {
                m1 = t;
                return List.of(t);
            }
            List<Integer> r =  List.of(m1, t);
            m1 = t;
            return r;
        }
    };
    Stream.generate(s).takeWhile(k -> k <= n).map(s2).forEach(k -> System.out.println("hey " + k));
}

@Adligo :)