I am looking at a code that has deeply nested for loop that I wanted to rewrite in a pure functional form using java-8 streams but what I see is that there are multiple values that are needed at each level and I am not sure how to approach to solve this in a clean way.
List<Report> reports = new ArrayList();
for (DigitalLogic dl : digitalLogics){
for (Wizard wiz : dl.getWizards){
for(Vice vice : wiz.getVices()){
reports.add(createReport(dl, wiz, vice));
}
}
}
//
Report createReport(DigitalLogic dl, Wizard wiz, Vice vice){
//Gets certain elements from all parameters and creates a report object
}
My real case scenario is much more complicated than this but I am wondering if there is a cleaner pure functional way of writing this using streams. Below is my initial attempt
List<Report> reports = new ArrayList();
digitalLogics.stream()
.map(dl -> dl.getWizards())
.flatMap(List::stream())
.map(wiz -> wiz.getVices())
.flatMap(List::stream())
.forEach(vice -> reports.add(createReport(?, ?, vice));
Obviously, I have lost the DigitalLogic and Wizard references.
I will go with forEach method because stream solution makes this complicated
List<Report> reports = new ArrayList<>();
digitalLogics.forEach(dl->dl.getWizards()
.forEach(wiz->wiz.getVices()
.forEach(v->reports.add(createReport(dl, wiz, v)))));
Though currently what you have(for loops) is much cleaner than what it would be with streams, yet if you were to try it out :
public void createReports(List<DigitalLogic> digitalLogics) {
List<Report> reports = digitalLogics.stream()
.flatMap(dl -> dl.getWizards().stream()
.map(wizard -> new AbstractMap.SimpleEntry<>(dl, wizard)))
.flatMap(entry -> entry.getValue().getVices().stream()
.map(vice -> createReport(entry.getKey(), entry.getValue(), vice)))
.collect(Collectors.toList());
}
Related
I have a class with a collection of Seed elements. One of the method's return type of Seed is Optional<Pair<Boolean, String>>.
I'm trying to loop over all seeds, find if any boolean value is true and at the same time, create a set with all the String values. For instance, my input is in the form Optional<Pair<Boolean, String>>, the output should be Optional<Signal> where Signal is like:
class Signal {
public boolean exposure;
public Set<String> alarms;
// constructor and getters (can add anything to this class, it's just a bag)
}
This is what I currently have that works:
// Seed::hadExposure yields Optional<Pair<Boolean, String>> where Pair have key/value or left/right
public Optional<Signal> withExposure() {
if (seeds.stream().map(Seed::hadExposure).flatMap(Optional::stream).findAny().isEmpty()) {
return Optional.empty();
}
final var exposure = seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.anyMatch(Pair::getLeft);
final var alarms = seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.map(Pair::getRight)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
return Optional.of(new Signal(exposure, alarms));
}
Now I have time to make it better because Seed::hadExposure could become and expensive call, so I was trying to see if I could make all of this with only one pass. I've tried (some suggestions from previous questions) with reduce, using collectors (Collectors.collectingAndThen, Collectors.partitioningBy, etc.), but nothing so far.
It's possible to do this in a single stream() expression using map to convert the non-empty exposure to a Signal and then a reduce to combine the signals:
Signal signal = exposures.stream()
.map(exposure ->
new Signal(
exposure.getLeft(),
exposure.getRight() == null
? Collections.emptySet()
: Collections.singleton(exposure.getRight())))
.reduce(
new Signal(false, new HashSet<>()),
(leftSig, rightSig) -> {
HashSet<String> alarms = new HashSet<>();
alarms.addAll(leftSig.alarms);
alarms.addAll(rightSig.alarms);
return new Signal(
leftSig.exposure || rightSig.exposure, alarms);
});
However, if you have a lot of alarms it would be expensive because it creates a new Set and adds the new alarms to the accumulated alarms for each exposure in the input.
In a language that was designed from the ground-up to support functional programming, like Scala or Haskell, you'd have a Set data type that would let you efficiently create a new set that's identical to an existing set but with an added element, so there'd be no efficiency worries:
filteredSeeds.foldLeft((false, Set[String]())) { (result, exposure) =>
(result._1 || exposure.getLeft, result._2 + exposure.getRight)
}
But Java doesn't come with anything like that out of the box.
You could create just a single Set for the result and mutate it in your stream's reduce expression, but some would regard that as poor style because you'd be mixing a functional paradigm (map/reduce over a stream) with a procedural one (mutating a set).
Personally, in Java, I'd just ditch the functional approach and use a for loop in this case. It'll be less code, more efficient, and IMO clearer.
If you have enough space to store an intermediate result, you could do something like:
List<Pair<Boolean, String>> exposures =
seeds.stream()
.map(Seed::hadExposure)
.flatMap(Optional::stream)
.collect(Collectors.toList());
Then you'd only be calling the expensive Seed::hadExposure method once per item in the input list.
I'm trying to convert two foreach loops and an if statement into a stream.
Here is what I want to convert:
for (ViewFlightRouteAirportType associatedAirportType : etopsAndNonEtopsAssociatedAirportTypes) {
for (ViewFlightAirportDTO airport : flightRoute.getAirportsForType(associatedAirportType)) {
if ( airportIataCode.equals(airport.getIataCode()) ) {
addValueIfNotPresent(associatedFlights, associatedAirportType, flightData);
}
}
}
etopsAndNonEtopsAssociatedAirportTypes is an array.
airportIataCode is String
Here is what I wrote:
Arrays.stream(etopsAndNonEtopsAssociatedAirportTypes)
.forEach(associatedAirportType -> flightRoute.getAirportsForType(associatedAirportType)
.forEach(airport -> flightRoute.getAirportsForType(associatedAirportType)
.stream()
.filter(p -> p.equals(airport.getIataCode())).forEach(addValueIfNotPresent(associatedFlights,associatedAirportType,flightData);
It is not needed and doesn't work, but for me it looks ugly. How should it look like?
How about this? I expect your airport has a getViewFlightRouteAirportType() getter which makes this easier on us as we don't have to track the value of the current type of the etopsAndNonEtopsAssociatedAirportTypes array:
Arrays.stream(etopsAndNonEtopsAssociatedAirportTypes)
.map(flightRoute::getAirportsForType)
.flatMap(Collection::stream)
.filter(airport -> airportIataCode.equals(airport.getIataCode()))
.forEach(airport -> addValueIfNotPresent(associatedFlights, airport.getViewFlightRouteAirportType(), flightData));
EDIT: As discussed in the comments
for (ViewFlightRouteAirportType associatedAirportType : etopsAndNonEtopsAssociatedAirportTypes) {
flightRoute.getAirportsForType(associatedAirportType).stream()
.flatMap(Collection::stream)
.filter(airport -> airportIataCode.equals(airport.getIataCode()))
.forEach(airport -> addValueIfNotPresent(associatedFlights, associatedAirportType, flightData));
}
Here's an example. Since I don't have your classes, I tried to mock it this way. Imagine ll is your outer for loop data structure, and for each element in there you create another collection and loop through it. So, basically, we the list of those objects in the inner for loop as a whole collection so that we can filter them. That's where you can use flatMap which flattens a collection of collections. Then we just filter and collect those values.
List<String> ll = new ArrayList<>();
ll.stream()
.map(el-> Arrays.asList(el))
.flatMap(List::stream)
.filter(el->el.equals(str))
.collect(Collectors.toList());
Again, I want to mention, this is not the exact case of yours, but I think the situation is similar.
I have a HashMap that contains List<Dto> and List<List<String>>:
Map<List<Dto>, List<List<String>>> mapData = new HashMap();
and an Arraylist<Dto>.
I want to iterate over this map, get the keys-key1, key2 etc and get the value out of it and set it to the Dto object and thereafter add it to a List. So i am able to successfully iterate using foreach and get it added to lists but not able to get it correctly done using Java 8. So i need some help on that. Here is the sample code
List<DTO> dtoList = new ArrayList();
DTO dto = new DTO();
mapData.entrySet().stream().filter(e->{
if(e.getKey().equals("key1")){
dto.setKey1(e.getValue())
}
if(e.getKey().equals("key2")){
dto.setKey2(e.getValue())
}
});
Here e.getValue() is from List<List<String>>()
so first thing is I need to iterate over it to set the value.
And second is I need to add dto to a Arraylist dtoList. So how to achieve this.
Basic Snippet that i tried without adding to a HashMap where List has keys, multiList has values and Dto list is where finally i add into
for(List<Dto> dtoList: column) {
if ("Key1".equalsIgnoreCase(column.getName())) {
index = dtoList.indexOf(column);
}
}
for(List<String> listoflists: multiList) {
if(listoflists.contains(index)) {
for(String s: listoflists) {
dto.setKey1(s);
}
dtoList.add(dto);
}
}
See https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines. A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce.
So in your snippet above, filter isn't really doing anything. To trigger it, you'd add a collect operation at the end. Notice that the filter lambda function needs to return a boolean for your code to compile in the first place.
mapData.entrySet().stream().filter(entry -> {
// do something here
return true;
}).collect(Collectors.toList());
Of course you don't need to abuse intermediate operations - or generate a bunch of new objects - for straightforward tasks, something like this should suffice:
mapData.entrySet().stream().forEach(entry -> {
// do something
});
_logger.info("data size : "+saleData.size);
saleData.parallelStream().forEach(data -> {
SaleAggrData saleAggrData = new SaleAggrData() {
{
setCatId(data.getCatId());
setRevenue(RoundUpUtil.roundUpDouble(data.getRevenue()));
setMargin(RoundUpUtil.roundUpDouble(data.getMargin()));
setUnits(data.getUnits());
setMarginRate(ComputeUtil.marginRate(data.getRevenue(), data.getMargin()));
setOtd(ComputeUtil.OTD(data.getRevenue(), data.getUnits()));
setSaleDate(data.getSaleDate());
setDiscountDepth(ComputeUtil.discountDepth(data.getRegularPrice(), data.getRevenue()));
setTransactions(data.getTransactions());
setUpt(ComputeUtil.UPT(data.getUnits(), data.getTransactions()));
}
};
salesAggrData.addSaleAggrData(saleAggrData);
});
The Issue with code is that when I am getting an response from DB, and while iterating using a parallel stream, the data size is different every time, while when using a sequential stream it's working fine.
I can't use a sequential Stream because the data is huge and it's taking time.
Any lead would be helpful.
You are adding elements in parallel to salesAggrData which I'm assuming is some Collection. If it's not a thread-safe Collection, no wonder you get inconsistent results.
Instead of forEach, why don't you use map() and then collect the result into some Collection?
List<SaleAggrData> salesAggrData =
saleData.parallelStream()
.map(data -> {
SaleAggrData saleAggrData = new SaleAggrData() {
{
setCatId(data.getCatId());
setRevenue(RoundUpUtil.roundUpDouble(data.getRevenue()));
setMargin(RoundUpUtil.roundUpDouble(data.getMargin()));
setUnits(data.getUnits());
setMarginRate(ComputeUtil.marginRate(data.getRevenue(), data.getMargin()));
setOtd(ComputeUtil.OTD(data.getRevenue(), data.getUnits()));
setSaleDate(data.getSaleDate());
setDiscountDepth(ComputeUtil.discountDepth(data.getRegularPrice(), data.getRevenue()));
setTransactions(data.getTransactions());
setUpt(ComputeUtil.UPT(data.getUnits(), data.getTransactions()));
}
};
return saleAggrData;
})
.collect(Collectors.toList());
BTW, I'd probably change that anonymous class instance creation, and use a constructor of a named class to create the SaleAggrData instances.
If I have a List containing objects like
new MyObj("some"),
new MyObj("foo"),
new MyObj("bar"),
new MyObj("xyz");
and want to filter it with Java 8 streams after match some condition, say
myObj.prop = "foo";
how would that be accomplished?
The result of the above example should be
new MyObj("some"),
new MyObj("foo")
That would be the "traditional" way:
List<MyObj> results = new ArrayList<>();
for (MyObj myObj : myObjList) {
if (!myObj.prop.equals("foo")) {
results.add(myObj);
} else {
results.add(myObj);
break;
}
}
It is not exaclty a duplicate of Limit a stream by a predicate because that does not include the matched element. However, I should be able to adapt the takeWhile operation.
Unfortunately such scenario is not directly supported by Stream API. Even takeWhile, which is newly appeared method in Java 9 would not solve this. Some free third-party Stream API extensions have a method you need. For example, limitWhileClosed in jOOλ or takeWhileInclusive in StreamEx (the library I wrote). Here's an example, how to do it via StreamEx:
List<MyObj> results = StreamEx.of(myObjList)
.takeWhileInclusive(myObj -> !myObj.prop.equals("foo")).toList();
jOOλ version would look pretty similar.
Without using third-party libraries you can solve this in two passes:
int idx = IntStream.range(0, myObjList.size())
.filter(i -> myObjList.get(i).prop.equals("foo"))
.findFirst().orElse(myObjList.size());
List<MyObj> results = myObjList.stream().limit(idx+1).collect(Collectors.toList());