I have a use case need to sum different feilds in this object so I code like this, can I put it in one stream?
int totalUnits = records.stream()
.mapToInt(DynamoSalesAggregateSummaryRecord::getUnits).sum();
int totalReturns = records.stream()
.mapToInt(DynamoSalesAggregateSummaryRecord::getReturns).sum();
int totalCancellations = records.stream()
.mapToInt(DynamoSalesAggregateSummaryRecord::getCancellations).sum();
BigDecimal totalRevenue = records.stream()
.map(DynamoSalesAggregateSummaryRecord::getRevenue)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal totalRoyalties = records.stream()
.map(DynamoSalesAggregateSummaryRecord::getRoyalties)
.reduce(BigDecimal.ZERO, BigDecimal::add);
Honestly, I would not do that. Instead: get rid of the code duplication in the first place: write a little helper that invokes:
return records.stream().mapToInt(accessorMethod).sum();
(where you pass the accessor to that method).
Then you could go for:
int totals = fetch(DynamoSalesAggregateSummaryRecord::getUnits) + fetch(DynamoSalesAggregateSummaryRecord::getReturns) + ...
But beyond that - it is actually not clear what you intend to do here. The above example assumed that you would build a "total" sum of all entries. If that is the not the case - and you want "distinct" sums, my advise would be to give up on using mapToInt(). And instead do something like:
class SumHelper {
private int unitTotals = 0;
private int returnTotals = 0;
...
public add(DynamoSalesAggregateSummaryRecord record) {
unitTotals += record.getUnits();
...
to be used like:
SumHelper helper = new SumHelper;
records.forEach(r -> helper.add(r));
In other words: you want to call multiple different methods on a record; to build different sums. You can't do that with streams - as one operation is creating multiple results. So you need some other kind of "storage" to keep track of the different results.
Another approach would be to create a separate method getTotalSum either in DynamoSalesAggregateSummaryRecord class or (better) - external helper. That method sums up all required fields of a single DynamoSalesAggregateSummaryRecord instance, then just sum it up:
int totalSum =
records.stream()
.mapToInt(DynamoSalesAggregateSummaryRecord::getTotalSum).sum()
java.util.stream.IntStream.sum() is a terminal operation.
You cannot invoke another stream task after a terminal operation.
If you don't have lot of elements in the collection, the GhostCat solution is fine.
If it is not the case, to avoid iterating 5 times on the records collection, another approach should be used.
You could use streams to do it but I am not sure that you will gain in readability as you should probably write your own code to compute aggregates.
A classic loop seems fine in this context :
int totalUnits = 0;
int totalReturns = 0;
...
for (DynamoSalesAggregateSummaryRecord record : records){
totalUnits += record.getUnits();
totalReturns += record.getReturns();
...
}
Related
I have a bit of a long winded question. Say I have a method which makes a math calculation and this method needs to keep state (i.e. every time it gets called with parameters its result is affected by its previous results).
int sum
def add(int x, int y) {
sum = sum + x + y
return sum
}
On top of this, its state is dependent on where it was called. (i.e. if I call this method twice from different places the method must only work with its own state). I was thinking about Closures in groovy but closures doesn't keep state and if you store your state in a variable outside of the closure a second call to this method won't have its own state.
Lets also assume for reasons that I won't go into right now I cannot keep this method in a object to keep state. i.e.
class MyObject {
private int sum
MyObject() {}
public int sum(int x, int y) {
sum = sum + x + y
return sum
}
}
I want to avoid doing this
MyObject mObj = new MyObject()
.... some code later
def result = mObj.add(1,2)
instead I just want to have to do this
for (int i = 0; i<5;i++){
def result1 = add(1,2)
}
some code later
for (int i = 0; i<5;i++){
def result2 = add(1,2)
}
In the case above the two 'add' methods should have its own state.
Is there any other tricks in either groovy or java to achieve something like this?
What you are describing here is a closure. The Groovy code there does
already that. If you want put that "global" state into the function, you
could be explicit about it and "build" the add function. E.g.
def add = { ->
def total = 0
return { a, b ->
return total += a + b
}
}()
println(add(1,2))
// -> 3
println(add(1,2))
// -> 6
println(add(1,2))
// -> 9
Above code creates a function, that holds the state for total and
returns a closure, that does the actual add. It captures the local
total. Then this function is called and the result (the closure) is
assigned to add.
edit
As stated in the comments, the expectation is a "magic" boundary that
isolates the state of add over the course of the execution of the
program. Assuming that this question aims for a DSL, users might be
able to know the implicit boundaries. Yet I doubt there is a way with
Groovy (or Java?) to know, that a function is called from inside
a for-loop unless you write your own for.
One way around this would be to create an explicit boundary like this:
def withTotal(c) {
def total = 0
c.add = { total += it }
c.call()
total
}
println withTotal{
add(1)
add(2)
}
// -> 3
println withTotal{
10.times{ add it }
}
// -> 45
I have a java class with 3 boolean property like this
boolean isActive;
boolean isEnable;
boolean isNew;
every property is related to an enum (e.g. ACTIVE,ENABLE,NEW).
I want to have 2 lists of enum. One which has only the enums related to true property value and one for the false one.
just to be clear. using if-else statement I could have
Set<FlagEnum> flagSet = new HashSet<>();
Set<FlagEnum> falseFlagSet = new HashSet<>();
if (object.isActive()) {
flagSet.add(ACTIVE);
} else {
falseFlagSet.add(ACTIVE);
}
if (object.isEnable()) {
flagSet.add(ENABLE);
} else {
falseFlagSet.add(ENABLE);
}
if (object.isNew()) {
flagSet.add(NEW);
} else {
falseFlagSet.add(NEW);
}
is there a way to avoid all these if-else?
I tried with something like
Map<boolean, List<Pair<boolean, FlagEnum>>> res = Stream.of(
new Pair<>(object.isActive(), ACTIVE),
new Pair<>(object.isNew(), NEW),
new Pair<>(object.isEnable(), ENABLE))
.collect(Collectors.partitioningBy(Pair::getKey));
but the resulted structure is an additional complexity which I would like to avoid.
In my real case, I have more than 15 boolean properties...
You can simplify this in various ways. Which of them make sense, depends on your exact requirements.
You can derive the falseFlagSet trivially from the flagSet using EnumSet.complementOf after populating the flagSet:
EnumSet<FlagEnum> falseFlagSet = EnumSet.complementOf(flagSet);
This assumes that all FlagEnum values have corresponding flags. If that's not the case then you need to construct a EnumSet with all enums that have flags and subtract flagSet from that using removeAll.
#1 already removes the need for the else in your cascade, simplifying the code to
if (object.isActive()) {
flagSet.add(ACTIVE);
}
if (object.isEnable()) {
flagSet.add(ENABLE);
}
if (object.isNew()) {
flagSet.add(NEW);
}
If you have enough different flags, then you can create a mapping from getter method to FlagEnum value like this:
Map<Function<YourClass,Boolean>,FlagEnum> GETTERS = Map.of(
YourClass::isActive, FlagEnum.ACTIVE,
YourClass::isNew, FlagEnum.NEW,
YourClass::isEnable, FlagEnum.ENABLE);
Then you can use this to make the whole process data-driven:
EnumSet<FlagEnum> getFlagSet(YourClass yourObject) {
EnumSet<FlagEnum> result = EnumSet.noneOf(FlagEnum.class);
for (Map.Entry<Function<YourClass,Boolean>, FlagEnum> getter : GETTERS.entrySet()) {
if (getter.getKey().apply(yourObject)) {
result.add(getter.getValue());
}
}
return result;
}
If the number of flags is very big, then you could switch entirely to reflection and detect the flags and matching getters dynamically using string comparison, but I would not suggest that approach. If you need something like that then you probably should switch to a framework that supports that kind of feature and not implement it yourself.
That last two obviously only makes sense when the number of flags is big. If it's actually just 3 flags, then I wouldn't mind and just have 3 simple if statements.
As a slight tangent: GETTERS above should definitely be an immutable map (wrap it in Collections.unmodifiableMap or use something like Guava ImmutableMap) and it could be argued that the same applies to the return value of the getFlagSet method. I've left those out for succinctness.
You can use a private helper method for this.
private void addFlagSet(boolean condition, FlagEnum flagEnum,
Set<FlagEnum> flagSet, Set<FlagEnum> falseFlagSet) {
Set<FlagEnum> chosenFlagSet = condition ? flagSet: falseFlagSet;
chosenFlagSet.add(flagEnum);
}
Call it as:
addFlagSet(object.isActive(), FlagEnum.ACIVE, flagSet, falseFlagSet);
addFlagSet(object.isNew(), FlagEnum.NEW, flagSet, falseFlagSet);
addFlagSet(object.isEnable(), FlagEnum.ENABLE, flagSet, falseFlagSet);
You could probably use Reflection to get all methods, then check if a getReturnType() == boolean.class. Problem is the connection between the method's name and the enum. If every single one is named like the method without the 'is', you could use FlagEnum.valueOf() to retrieve the enum value from the method name and use it.
I think this could be the easiest and clearest way to do what I need
Map<Boolean, Set<FlagEnum>> flagMap = new HashMap<>();
flagMap.computeIfAbsent(object.isActive(), h -> new HashSet()).add(ACTIVE);
flagMap.computeIfAbsent(object.isEnabled(), h -> new HashSet()).add(ENABLE);
flagMap.computeIfAbsent(object.isNew(), h -> new HashSet()).add(NEW);
//to get TRUE set simply :
flagMap.get(true);
what do you think?
List<Mt4Strategy> openStrategies = ...
OrderType sample = openStrategies.get(0).calculate().getOrderType();
boolean success = true;
for (int i = 1; i < openStrategies.size(); i++) {
Mt4Action calculate = openStrategies.get(i).calculate();
if (calculate.getOrderType() != sample) {
success = false;
break;
}
}
OrderType is an enum.
I don't know what the first element contains and as a result am forced to make openStrategies.get(0).... I want to get rid of this get(0), but how?
I tried to use lambda like this:
OrderType sample = openStrategies.get(0).calculate().getOrderType();
boolean success = IntStream.range(1, openStrategies.size()).mapToObj(i ->
openStrategies.get(i).calculate()).noneMatch(calculate ->
calculate.getOrderType() != sample);
It's a good start but does not resolve my get(0).
Can using a lambda get rid of it? How I can write this to check success without get(0)? Lambda solution in priority something similar to last case .noneMatch.
You apparently want to determine whether all the input list elements have the same order type. A stream ought to make this pretty simple. For example,
boolean success = openStrategies.stream()
.map(s -> s.calculate().getOrderType())
.distinct()
.limit(2)
.count() == 1;
Note here the distinctness comparisons are done with equals rather than ==. If you really need ==, it's more difficult.
This checks for exactly one value in the list. If the input can be empty and you want the result to be true in that case, change == 1 to <= 1.
The limit(2) isn't needed for correctness but allows the search to stop as soon as a second distinct value is found, so it's more efficient.
There are other ways to do this.
Responding to comment
There are various hacky ways you could get the common value without calling .get(0), but none that would be clearer (at least that I can think of). It's silly to code things in oddball ways just to avoid a call you don't like the looks of.
Below function creates a Map, gets the count of passengers where passengers are > minTrips. The code works completely fine. Please see below
fun List<Trip>.filter(minTrips : Int): Set<Passenger> {
var passengerMap: HashMap<Passenger, Int> = HashMap()
this.forEach { it: Trip ->
it.passengers.forEach { it: Passenger ->
var count: Int? = passengerMap.get(it)
if (count == null) {
count = 1
passengerMap.put(it, count)
} else {
count += 1
passengerMap.put(it, count)
}
}
}
val filteredMinTrips: Map<Passenger, Int> = passengerMap.filterValues { it >= minTrips }
println (" Filter Results = ${filteredMinTrips}")
return filteredMinTrips.keys
}
Even though this is written in Kotlin, it seems like the code was first written in Java and then converted over to Kotlin. If it was truly written in Kotlin I am sure this wouldnt have been so many lines of code. How can I reduce the lines of Code? What would be a more funtional approach to solve this? What function or functions can I use to extract the Passengers Set directly where Passengers are > minTrips? This is too much of a code and seems crazy. Any pointers would be helpful here.
One way you could do this is to take advantage of Kotlin's flatmap and grouping calls. By creating a list of all passengers on all trips, you can group them, count them, and return the ones that have over a certain number.
Assuming you have data classes like this (essential details only):
data class Passenger(val id: Int)
data class Trip(val passengers: List<Passenger>)
I was able to write this:
fun List<Trip>.frequentPassengers(minTrips: Int): Set<Passenger> =
this
.flatMap { it.passengers }
.groupingBy { it }
.eachCount()
.filterValues { it >= minTrips }
.keys
This is nice because it is a single expression. Going through it, we look at each Trip and extract all of its Passengers. If we had just done map here, we would have List<List<Passenger>>, but we want a List<Passenger> so we flatmap to achieve that. Next, we groupBy the Passenger objects themselves, and call eachCount() on the returned object, giving us a Map<Passenger, Int>. Finally we filter the map down the Passengers we find interesting, and return the set of keys.
Note that I renamed your function, List already has a filter on it, and even though the signatures are different I found it confusing.
You basically want to count the trips for each passenger, so you can put all passengers in a list and then group by them and afterwards count the occurences in each group:
fun List<Trip>.usualPassengers(minTrips : Int) = // 1
flatMap(Trip::passengers) // 2
.groupingBy { it } // 3
.eachCount() // 4
.filterValues { it >= minTrips } // 5
.keys // 6
Explanation:
return type Set<Passenger> can be inferred
this can be ommitted, a list of the form [p1, p2, p1, p5, ...] is returned
a Grouping is created, which looks like this [p1=[p1, p1], p2=[p2], ...]]
the number of occurences in each group will be counted: [p1=2, p2=1, ...]
all elementes with values which less than minTrips will be filtered out
all keys that are left will be returned [p1, p2, ...]
p1...pn are Passenger instances
I'm trying to refactoring some for-loops into lambda expressions, most of them are working fine, but I'm struggling with for-loops that contains two if-statements.
Code
for (B2KTransactionDTO b2kTransactionDTO : result) {
//Generate loyaltyMatchId based on transaction input
String loyaltyMatchId = getLoyaltyMatchIdBasedOnTransactionDTO(b2kTransactionDTO);
if (loyaltyMatchIdAmountMap.containsKey(loyaltyMatchId)) {
BigDecimal cashback = loyaltyMatchIdAmountMap.get(loyaltyMatchId);
b2kTransactionDTO.addLoyaltyPoints(cashback);
}
String loyaltyMatchInsuranceId = getLoyaltyMatchInsuranceIdBasedOnTransactionDTO(b2kTransactionDTO);
if (loyaltyMatchInsuranceIdAmountMap.containsKey(loyaltyMatchInsuranceId)) {
BigDecimal cashback = loyaltyMatchInsuranceIdAmountMap.get(loyaltyMatchInsuranceId);
b2kTransactionDTO.addLoyaltyPoints(cashback);
}
}
I refactored this to the following code:
result.forEach(b2kTransactionDTO -> {
//Generate loyaltyMatchId based on transaction input
String loyaltyMatchId = getLoyaltyMatchIdBasedOnTransactionDTO(b2kTransactionDTO);
if (loyaltyMatchIdAmountMap.containsKey(loyaltyMatchId)) {
BigDecimal cashback = loyaltyMatchIdAmountMap.get(loyaltyMatchId);
b2kTransactionDTO.addLoyaltyPoints(cashback);
}
String loyaltyMatchInsuranceId = getLoyaltyMatchInsuranceIdBasedOnTransactionDTO(b2kTransactionDTO);
if (loyaltyMatchInsuranceIdAmountMap.containsKey(loyaltyMatchInsuranceId)) {
BigDecimal cashback = loyaltyMatchInsuranceIdAmountMap.get(loyaltyMatchInsuranceId);
b2kTransactionDTO.addLoyaltyPoints(cashback);
}
});
Is it possible to even futher lambda-nize this?
Thanks
This is not the answer you are looking for.
I would say, don't use lambda's here.
Lambda's can help you to utilise parralelism features of cpu's to speed up processing of large datasets.
In my experience it's not worth the hassle of utilising a lambda with smaller datasets(<10(0).000);
Keep your code readable and maintainable and pluggable.
Learn to make objects that handle purposes. Don't put all the things in one object and end up with veryLongVariableNamesThatDescribeAPurpose.
When long variable names occur it should be a signal to you that you can refactor that code into several objects that handle a single purpose.
If you really want to use lambda's try the following:
Seperate objects for value holders and processors and methods for seperate tasks, it will be easier in to utilise lambda's when needed because you can simply plug in the methods for the tasks required and pass on the value holder objects to get your desired results.
So, not the answer you're looking for, but this was too long really to explain in a comment.
I think you also should consider refactor your code because it looks like there's a lot of duplication between the match and matchInsurance stuff.
private static void addLoyaltyPoints(Collection<B2KTransactionDTO> result, Map<String, BigDecimal> ids, Function<B2KTransactionDTO, String> extractId)
{
result.stream()
.filter(b -> ids.containsKey(extractId.apply(b)))
.forEach(b -> b.addLoyaltyPoints(ids.get(extractId.apply(b))));
}
Then the whole code you posted could become:
addLoyaltyPoints(result, loyaltyMatchIdAmountMap, Use::getLoyaltyMatchIdBasedOnTransactionDTO);
addLoyaltyPoints(result, loyaltyMatchInsuranceIdAmountMap, Use::getLoyaltyMatchInsuranceIdBasedOnTransactionDTO);
Edit: another version of addLoyaltyPoints (more concise, less expressive):
private static void addLoyaltyPoints(Collection<B2KTransactionDTO> result, Map<String, BigDecimal> ids, Function<B2KTransactionDTO, String> extractId)
{
result.stream()
.forEach(b -> b.addLoyaltyPoints(ids.getOrDefault(extractId.apply(b), new BigDecimal(0))));
}