Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java streaming higher order function

I am trying to process an object that has nested lists 2 levels deep. For example my object can be broken down to something like this:

TopLevel: [
    MidLevel: [
        LowLevel,
        LowLevel,
        .
        .
    ],
    MidLevel: [
        LowLevel,
        LowLevel,
        .
        .
    ]
    .
    .
]

Essentially TopLevel contains a list of MidLevel objects, which in turn each contain a list of LowLevel objects. At the end of the processing I would like to build SomeObj for each of the LowLevel objects. However SomeObj requires information from TopLevel, MidLevel, and LowLevel.

I have been trying to write code in a more functional style over the last several months so my first thought was to create a higher order function that I could build up over each level in the object. The function looks like this:

Function<MidLevel, Function<LowLevel, SomeObj>> buildObjects(TopLevel topLevel) {
    return midLevel ->
        lowLevel -> {
            return buildSomeObj(topLevel, midLevel, lowLevel);
        };
}

I intend to use this function in some way like the following (assume I have utility functions that provide a stream of the lists):

Function<MidLevel, Function<LowLevel, SomeObj>> topBuilder = buildObjects(topLevel);
List<SomeObj> objs = topLevel.streamMid()
    .map(topBuilder)
    .streamLow()
    .map(Function::apply)
    .collect(/*collect to list*/);

However, this obviously does not work because once I apply the MidLevel objects to the topBuilder function my stream is now a stream of functions and not MidLevel objects, thus I do not have access to the list of LowLevel objects in the stream anymore.

Is there any solution to this or am I trying to solve this functionally when it's not well suited to it? Is there a way to both apply the function, and also have access to the original object that was applied to that function?

like image 964
wearebob Avatar asked Mar 04 '23 08:03

wearebob


2 Answers

flatMap() and nesting are the way to go. Try this:

topLevelStream() //create a stream of top level elements
  .flatMap( top -> top.streamMid() //create a stream of mid level elements
    .flatMap( mid -> mid.streamLow() //create a stream of low level elements
      .map(low -> "use top, mid and low here") 
    ) 
  )
  .collect( ... );

By nesting like this you still have access to the elements in the outer functions and the combination of flatMap() and map() exposes the stream that map() is called upon to collect().

like image 90
Thomas Avatar answered Mar 08 '23 15:03

Thomas


You could simply use flatMap as:

List<SomeObj> objs = topLevel.getMidLevels().stream()
        .flatMap(a -> a.getLowLevels().stream().map(b -> topBuilder.apply(a).apply(b)))
        .collect(Collectors.toList());

with your entities analogous to:

class TopLevel {
    List<MidLevel> midLevels;
}

class MidLevel {
    List<LowLevel> lowLevels;
}

class LowLevel {
}

class SomeObj {
}
like image 42
Naman Avatar answered Mar 08 '23 15:03

Naman