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?
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().
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 {
}
Related
I have some concatenated observable using flatMap such as
api.call1()
.flatMap{a-> api.call2(a)}
.flatMap{b-> api.call3(a,b) //<- no access to a}
the issue is that In third concatenated call I need what I got from previous one but also the result from the first one
this is the scheme:
A->(a)->B->(b)-> C( needs b but also a)->...
I try to solve it out like that :
1 approach ( nested 3rd observable inside the 2nd one)
api.call1()
.flatMap{a-> api.call2(a).flatMap{b-> api.call3(a,b)}}
it works but I know that this is not a good approach (because of passing parms from outside of the pipe)
2 approach (better but a lot of boilerplate)
api.call1().flatMap{ a->
Observable.combineLatest(Observable.just(a),api.call2(a),
BiFunction{ a,b -> Pair(a,b)})
.flatMap(pair:Pair<A,B> -> api.call3(pair.first,pair.second))
}}
anybody know better approach (some fashion operator)
Thanks everybody sory for my poor ingles.
There is a second overload of flatMap which takes both the input and the output. You can then combine these into a Pair to send to the next flatMap
api.call1()
.flatMap(
{ a -> api.call2(a) },
{ a, b -> new Pair(a, b) }
)
.flatMap { pair-> api.call3(pair.first, pair.second) }
I have an original collection, List<Reviewers>, and a new one List<ReviewPerson> where some fields from Reviewers will be copied to ReviewPerson.
The new list is constructed in a special way and not directly from reviewers.stream().map(...). But at the end, I need to copy 2 additional columns that exist in each bean, status and comments.
List<Reviewers> originalList = ... // from DAO
if (!originalList.isEmpty()) {
List<ReviewPerson> newList = new ArrayList<ReviewPerson>();
// this fills out some columns of the ReviewPerson, not all;
// must use this partial construction from service class
newList.addAll(service.initialPopulation());
// At the end, need to copy: (1) status, (2) comments
// ...
}
The problem is I can't do this,
originalList.stream()
.map(obj -> new ReviewPerson(obj.getField1(), obj.getField2(),
// ...
obj.getStatus(), obj.getComments()))
.collect(Collectors.toList());
because I'm not constructing new objects in the collection. What should I do?
One common solution is to stream the (presumably corresponding) indexes of the lists and use the same index to access both lists:
IntStream.range(0, originalList.size()).forEach(i -> {
newList.get(i).setFieldA(originalList.get(i).getFieldA();
newList.get(i).setFieldB(originalList.get(i).getFieldB();
// etc...
});
But to be honest, this may be streaming for the sake of streaming. Sometimes a good old-fashioned straight-forward for loop is just a better solution.
If you don't mind an extra dependency, and if Reviewers and ReviewPersons are indeed corresponding, I'd suggest using the jOOλ library and its Seq.zip() method (Seq is a subtype of Stream) together with a Tuple.consumer overload.
With the above, you can end up with such a concise piece of code:
Seq.zip(newList, originalList).forEach(Tuple.consumer((reviewPerson, reviewer) -> {
reviewPerson.setStatus(reviewer.getStatus());
reviewPerson.setComments(reviewer.getComments());
}));
I have some data stored in a JPA Repository that I am trying to process. I would like to be able to use Java 8 Streams to do so, but can not figure out how to get the required information. This particular 'Entity' is actually only for recovery, so it holds items that would need to be processed after something like a power-fail/restart.
Using pre-Java 8 for-loops the code would look like:
List<MyEntity> deletes = myEntityJpaRepository.findByDeletes();
for (MyEntity item : deletes) {
String itemJson = item.getData();
// use a Jackson 'objectMapper' already setup to de-serialize
MyEventClass deleteEvent = objectMapper.readValue(itemJson, MyEventClass.class);
processDelete(deleteEvent, item.getId());
}
The problem arises from the two parameter method called at the very end. Using Streams, I believe I would do:
// deletes.stream()
// .map(i -> i.getData())
// .map(event -> objectMapper.readValue(event, MyEventClass.class))
// .forEach(??? can't get 'id' here to invoke 2 parameter method);
I have a solution (without Streams) that I can live with. However I would think this problem comes up a lot, thus my question is: IN GENERAL, is there a way using Streams to accomplish what I am trying to do?
Why not a Pair return on your map operation:
.map(i -> new Pair<>(i.getData(), i.getId()))
.map(pair -> new Pair<>(objectMapper.readValue(pair.getLeft(), MyEventClass.class), pair.getRight())
.forEach(p -> processDelete(pair.getLeft(), pair.getRight()))
I did not compile this, so there might be minor things to fix. But in general, you would need a Holder to pass your objects to the next stage in such a case. Either a Pair or some type or even a array.
Why not doing it simply this way?
deletes.forEach(item ->
processDelete(objectMapper.readValue(item.getData(), MyEventClass.class),
item.getId()));
This is a start at least, I guess it is dependent on why you want to use stream and how much you want to make it more functional
List<MyEntity> deletes = myEntityJpaRepository.findByDeletes();
deletes.stream().foreach(item -> {
String itemJson = item.getData();
// use a Jackson 'objectMapper' already setup to de-serialize
MyEventClass deleteEvent = objectMapper.readValue(itemJson, MyEventClass.class);
processDelete(deleteEvent, item.getId());
});
I am pretty new to streams.
I would like to stream the geometries EC_Geometry arraylist and if the EC_Geometry element is not present (or better equals never returns true), then I add it.
public void init(GL3 gl3, EC_Mesh mesh) {
geometries.stream()
.filter(geometry -> mesh.getGeometry().equals(geometry))
.findAny()
.orElse(..?);
}
But I am stuck at the last line
How can I solve it using streams?
Please note that equals is a method I wrote checking if the geometry is the same (i.e: if the triangles correspond)
orElse will always run even if the value returned isn't used so it is preferable to use orElseGet here which will only run if nothing is found.
geometries.stream()
.filter(geometry -> mesh.getGeometry().equals(geometry))
.findAny()
.orElseGet(() -> {
geometries.add(mesh.getGeometry());
return mesh.getGeometry();
});
.findAny().orElse(..?);
is for Optional - if you would like to get first element found.
For what you would like to achieve the best approach would be just to:
meshG = mesh.getGeometry();
if (!geometries.contains(meshG)) {
geometries.add(meshG);
}
No need to overuse Stream API.
Sorry about the title of the question; it was kind of hard for me to make sense of it. If you guys have a better title, let me know and I can change it.
I have two types of objects, Bookmark and Revision. I have one large Map, like so:
Map<Long, Bookmark> mapOfBookmarks;
it contains key: value pairs like so:
1L: Bookmark1,
2L: Bookmark2,
...
Each Bookmark has a 'getRevisions()' method that returns a Map
public Map<Long, Revision> getRevisions();
I want to create a Stream that contains all revisions that exist under mapOfBookmarks. Essentially I want to do this:
List<Revision> revisions = new ArrayList<>();
for (Bookmark bookmark : mapOfBookmarks.values()) { // loop through each bookmark in the map of bookmarks ( Map<Long, Bookmark> )
for (Revision revision : bookmark.getRevisions().values()) { // loop through each revision in the map of revisions ( Map<Long, Revision> )
revisions.add(revision); // add each revision of each map to the revisions list
}
}
return revisions.stream(); // return a stream of revisions
However, I'd like to do it using the functionality of Stream, so more like:
return mapOfBookmarks.values().stream().everythingElseThatIsNeeded();
Which would essentially be like saying:
return Stream.of(revision1, revision2, revision3, revision4, ...);
How would I write that out? Something to note is that the dataset that it is looping through can be huge, making the list method a poor approach.
I'm using Windows 7 and Java 8
A flatmap is what you looking for. When you have streams contained within a stream that you wish to flatten, then flatmap is the answer,
List<Revision> all =
mapOfBookmarks.values().stream()
.flatMap(c -> c.getRevisions().values().stream())
.collect(Collectors.toList());
You are looking for the flatMap(mapper) operation:
Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element.
In this case, we're making a Stream<Bookmark> by calling stream(), flat mapping it to the revisions of each bookmark and, finally, collecting that into a list with toList().
List<Revision> revisions =
mapOfBookmarks.values()
.stream()
.flatMap(bookmark -> boormark.getRevisions().values().stream())
.collect(Collectors.toList());
Note that your current code could also be improved by calling addAll instead of looping over each revisions:
for (Bookmark bookmark : mapOfBookmarks.values()) { // loop through each bookmark in the map of bookmarks ( Map<Long, Bookmark> )
revisions.addAll(bookmark.getRevisions().values());
}