Returning values from other Stream inside a Stream - for-loop

The task
I have a list of Objects "Point" and a filtered version of it: allPoints and pointsFromStepTwo, where stepTwo is an other method. I need to add to list, that i got from stepTwo, all Point, which are match to condition that aplied to allPoints and pointsFromStepTwo at the same time.
The code looks kind like:
public List<Point> stepThree(List<Point> pointsFromStepTwo, List<Point> allPoints) {
return allPoints.stream()
.filter(point -> point.getRadius() + {pointsFromStepTwo.stream().forEach(point1 -> point1.getRadius()); > smth })
}.collect(Collectors.toList());
where "smth" is a special condition.
The problem
I can't find a correct way to return values from pointsFromStepTwo to points from allPoint every time.
Basically it is a for loop inside a for loop. I think that will work:
public List<Point> stepThree(List<Point> pointsFromStepTwo, List<Point> allPoints) {
Set<Point> tmp = new HashSet<>();
for (Point point1 : allPoints) {
for (Point point2 : pointsFromStepTwo) {
if (point1.equals(point2) ||
point1.getRadius() + point2.getRadius() + getGap() + getErr() >= getL(point1, point2)) {
tmp.add(point2);
}
}
}
return new ArrayList<>(tmp);
}
where getL(point1, point2) is a special condition

Use anyMatch instead of forEach:
public List<Point> stepThree(List<Point> pointsFromStepTwo, List<Point> allPoints)
{
return allPoints.stream()
.filter(point2 -> pointsFromStepTwo.stream()
.anyMatch(point1 -> point1.getRadius() + point2.getRadius() >= getL(point1, point2)))
.collect(Collectors.toList());
}
EDIT: It looks like you want the output List to contain all the points of pointsFromStepTwo. If you don't care about the order, then (assuming all the points of pointsFromStepTwo belong to `allPoints), you can add a condition to the filter:
public List<Point> stepThree(List<Point> pointsFromStepTwo, List<Point> allPoints)
{
return allPoints.stream()
.filter(point2 -> pointsFromStepTwo.stream()
.anyMatch(point1 -> point2.equals(point1) || (point1.getRadius() + point2.getRadius() >= getL(point1, point2))))
.collect(Collectors.toList());
}

Related

Collect groupBy on deep property

private Map<String, Set<Square>> populateZuloSquare(List<Square> squares) {
if (squares == null || squares.isEmpty()) {
return emptyMap();
}
Map<String, Set<Square>> res = new HashMap<>();
squares.stream()
.filter(square -> {
if (square.getZuloCodes().isEmpty()) {
LOG("Ignored {}", square.id);
return false;
}
return true;
})
.forEach(square -> {
square.getZuloCodes()
.forEach(code -> {
res.putIfAbsent(code, new HashSet<>());
res.get(code).add(square);
}));
});
return Collections.unmodifiableMap(res);
}
The code above receives a list of Squares, and those squares may contain ZuloCodes inside. The output should be a immutable Map zuloCode and value all the squares with that UniquePrefix.
As you can see I cannot figure out a way to remove the auxiliar collection res and make the code easily readable, is there a way to explode that collection into a [zuloCode, square] and then collect.groupBy ? Also that if inside the filter is so unreadable, how would you tackle it?
The standard approach is using flatMap before collecting using groupingBy, but since you need the original Square for each element, you need to map to an object holding both, the Square instance and the zulo code String.
Since there is no standard pair or tuple type in Java (yet), a work-around is to use a Map.Entry instance, like this
private Map<String, Set<Square>> populateZuloSquare0(List<Square> squares) {
if (squares == null || squares.isEmpty()) {
return emptyMap();
}
return squares.stream()
.filter(square -> logMismatch(square, !square.getZuloCodes().isEmpty()))
.flatMap(square -> square.getZuloCodes().stream()
.map(code -> new AbstractMap.SimpleEntry<>(code, square)))
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toSet())),
Collections::unmodifiableMap));
}
private static boolean logMismatch(Square square, boolean match) {
if(!match) LOG("Ignored {}", square.id);
return match;
}
An alternative is to use a custom collector which will iterate over the keys:
private Map<String, Set<Square>> populateZuloSquare(List<Square> squares) {
if (squares == null || squares.isEmpty()) {
return emptyMap();
}
return squares.stream()
.filter(square -> logMismatch(square, !square.getZuloCodes().isEmpty()))
.collect(Collector.of(
HashMap<String, Set<Square>>::new,
(m,square) -> square.getZuloCodes()
.forEach(code -> m.computeIfAbsent(code, x -> new HashSet<>()).add(square)),
(m1,m2) -> {
if(m1.isEmpty()) return m2;
m2.forEach((key,set) ->
m1.merge(key, set, (s1,s2) -> { s1.addAll(s2); return s1; }));
return m1;
},
Collections::unmodifiableMap)
);
}
Note that this custom collector can be seen as a parallel capable variant of the following looping code:
private Map<String, Set<Square>> populateZuloSquare(List<Square> squares) {
if (squares == null || squares.isEmpty()) {
return emptyMap();
}
Map<String, Set<Square>> res = new HashMap<>();
squares.forEach(square -> {
if(square.getZuloCodes().isEmpty()) LOG("Ignored {}", square.id);
else square.getZuloCodes().forEach(
code -> res.computeIfAbsent(code, x -> new HashSet<>()).add(square));
});
return Collections.unmodifiableMap(res);
}
which might not look so bad now, when you don’t need the code to be parallel capable…
How about this. You may use map merge operation to get this thing done. I have updated the filter and simplified it too.
squares.stream().filter(s -> !s.getZuloCodes().isEmpty())
.forEach(s -> s.getZuloCodes().stream().forEach(z -> res.merge(z, new HashSet<>(Arrays.asList(s)),
(s1, s2) -> Stream.of(s1, s2).flatMap(Collection::stream).collect(Collectors.toSet()))));

Java8 calculate average of list of objects in the map

Initial data:
public class Stats {
int passesNumber;
int tacklesNumber;
public Stats(int passesNumber, int tacklesNumber) {
this.passesNumber = passesNumber;
this.tacklesNumber = tacklesNumber;
}
public int getPassesNumber() {
return passesNumber;
}
public void setPassesNumber(int passesNumber) {
this.passesNumber = passesNumber;
}
public int getTacklesNumber() {
return tacklesNumber;
}
public void setTacklesNumber(int tacklesNumber) {
this.tacklesNumber = tacklesNumber;
}
}
Map<String, List<Stats>> statsByPosition = new HashMap<>();
statsByPosition.put("Defender", Arrays.asList(new Stats(10, 50), new Stats(15, 60), new Stats(12, 100)));
statsByPosition.put("Attacker", Arrays.asList(new Stats(80, 5), new Stats(90, 10)));
I need to calculate an average of Stats by position. So result should be a map with the same keys, however values should be aggregated to single Stats object (List should be reduced to single Stats object)
{
"Defender" => Stats((10 + 15 + 12) / 3, (50 + 60 + 100) / 3),
"Attacker" => Stats((80 + 90) / 2, (5 + 10) / 2)
}
I don't think there's anything new in Java8 that could really help in solving this problem, at least not efficiently.
If you look carefully at all new APIs, then you will see that majority of them are aimed at providing more powerful primitives for working on single values and their sequences - that is, on sequences of double, int, ? extends Object, etc.
For example, to compute an average on sequence on double, JDK introduces a new class - DoubleSummaryStatistics which does an obvious thing - collects a summary over arbitrary sequence of double values.
I would actually suggest that you yourself go for similar approach: make your own StatsSummary class that would look along the lines of this:
// assuming this is what your Stats class look like:
class Stats {
public final double a ,b; //the two stats
public Stats(double a, double b) {
this.a = a; this.b = b;
}
}
// summary will go along the lines of:
class StatsSummary implements Consumer<Stats> {
DoubleSummaryStatistics a, b; // summary of stats collected so far
StatsSummary() {
a = new DoubleSummaryStatistics();
b = new DoubleSummaryStatistics();
}
// this is how we collect it:
#Override public void accept(Stats stat) {
a.accept(stat.a); b.accept(stat.b);
}
public void combine(StatsSummary other) {
a.combine(other.a); b.combine(other.b);
}
// now for actual methods that return stuff. I will implement only average and min
// but rest of them are not hard
public Stats average() {
return new Stats(a.getAverage(), b.getAverage());
}
public Stats min() {
return new Stats(a.getMin(), b.getMin());
}
}
Now, above implementation will actually allow you to express your proper intents when using Streams and such: by building a rigid API and using classes available in JDK as building blocks, you get less errors overall.
However, if you only want to compute average once somewhere and don't need anything else, coding this class is a little overkill, and here's a quick-and-dirty solution:
Map<String, Stats> computeAverage(Map<String, List<Stats>> statsByPosition) {
Map<String, Stats> averaged = new HashMap<>();
statsByPosition.forEach((position, statsList) -> {
averaged.put(position, averageStats(statsList));
});
return averaged;
}
Stats averageStats(Collection<Stats> stats) {
double a, b;
int len = stats.size();
for(Stats stat : stats) {
a += stat.a;
b += stat.b;
}
return len == 0d? new Stats(0,0) : new Stats(a/len, b/len);
}
There is probably a cleaner solution with Java 8, but this works well and isn't too complex:
Map<String, Stats> newMap = new HashMap<>();
statsByPosition.forEach((key, statsList) -> {
newMap.put(key, new Stats(
(int) statsList.stream().mapToInt(Stats::getPassesNumber).average().orElse(0),
(int) statsList.stream().mapToInt(Stats::getTacklesNumber).average().orElse(0))
);
});
The functional forEach method lets you iterate over every key value pair of your given map.
You just put a new entry in your map for the averaged values. There you take the key you have already in your given map. The new value is a new Stats, where the arguments for the constructor are calculated directly.
Just take the value of your old map, which is the statsList in the forEach function, map the values from the given stats to Integer value with mapToInt and use the average function.
This function returns an OptionalDouble which is nearly the same as Optional<Double>. Preventing that anything didn't work, you use its orElse() method and pass a default value (like 0). Since the average values are double you have to cast the value to int.
As mentioned, there doubld probably be a even shorter version, using reduce.
You might as well use custom collector. Let's add the following methods to Stats class:
public Stats() {
}
public void accumulate(Stats stats) {
passesNumber += stats.passesNumber;
tacklesNumber += stats.tacklesNumber;
}
public Stats combine(Stats acc) {
passesNumber += acc.passesNumber;
tacklesNumber += acc.tacklesNumber;
return this;
}
#Override
public String toString() {
return "Stats{" +
"passesNumber=" + passesNumber +
", tacklesNumber=" + tacklesNumber +
'}';
}
Now we can use Stats in collect method:
System.out.println(statsByPosition.entrySet().stream().collect(
Collectors.toMap(
entity -> entity.getKey(),
entity -> {
Stats entryStats = entity.getValue().stream().collect(
Collector.of(Stats::new, Stats::accumulate, Stats::combine)
); // get stats for each map key.
// get average
entryStats.setPassesNumber(entryStats.getPassesNumber() / entity.getValue().size());
// get average
entryStats.setTacklesNumber(entryStats.getTacklesNumber() / entity.getValue().size());
return entryStats;
}
))); // {Attacker=Stats{passesNumber=85, tacklesNumber=7}, Defender=Stats{passesNumber=12, tacklesNumber=70}}
If java-9 is available and StreamEx, you could do :
public static Map<String, Stats> third(Map<String, List<Stats>> statsByPosition) {
return statsByPosition.entrySet().stream()
.collect(Collectors.groupingBy(e -> e.getKey(),
Collectors.flatMapping(e -> e.getValue().stream(),
MoreCollectors.pairing(
Collectors.averagingDouble(Stats::getPassesNumber),
Collectors.averagingDouble(Stats::getTacklesNumber),
(a, b) -> new Stats(a, b)))));
}

How to use stream to apply multiple filters and convert to a List

here is my problem,
I have a List<A> lista1, what I should do is :
map<key, List<A>> m = groupby(lista1);
m = lista.stream.collect(Collectors.groupingBy);
for every group, return one element with some condition, and make a new list List<A> lista2 = MakeListfromGroup(m)
List<A> lista2;
for (Map.Entry<key, List<A>> entry : m.entrySet()) {
A theOne;
for (A a : entry.getValue()) {
if(condition){
theOne = a;
}
}
lista2.add(theOne);
}
return lista2;
List<B> listb1 = makeListB(lista2); //here, I cant use stream.map.collect
List<Integer> b1Key;
List<Integer> b2Key;
for(A a : lista2){
b1key.add(a.b1key);
b2key.add(a.b2key);
}
mapb1 = gerfromBD(b1key);
mapb2 = gerfromBD(b2key);
List<B> listb1;
for(A a : lista2){
listb1.add(new B(mapb1.get(a.b1key),mapb2.get(a.b2key));
}
return listb1
B has member B1 b1 and B2 b2, create a new List<B> listb2= applyFilter(list<predicate<B1>>,list<predicate<B2>>)
List<B> listb2;
nextb:
for(B b : listb1){
for(Predicate p: filtreB1){
if(!p.accept(b.b1)){
continue nextb;
}
}
for(Predicate p: filtreB2){
if(!p.accept(b.b2)){
continue nextb;
}
}
listb2.add(b);
}
return listb2;
Is it possible to put all in one stream? or do the step 4 in a stream way?
thanks in advance
For the next question, please provide real code instead of incomplete, typo ridden pseudo code.
As you need the complete list of As for step 3, you have to collect any potential stream from step 2 into a list anyway. Step 2 and 4 can be simplified by using streams, though:
public static List<A> makeListfromGroup(Map<Object, List<A>> m, Predicate<A> condition)
{
return m.values()
.stream()
.map(as -> as.stream().filter(condition).findAny().orElse(null))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
For step 4, you can read how to apply multiple conditions at once here:
public static List<B> applyFilter(List<B> list, List<Predicate<B1>> filtreB1, List<Predicate<B2>> filtreB2)
{
return list.stream()
.filter(b -> filtreB1.stream().allMatch(p -> p.test(b.getB1())))
.filter(b -> filtreB2.stream().allMatch(p -> p.test(b.getB2())))
.collect(Collectors.toList());
}

How can I write the following method in Java 8 streams?

How can I write the following method in Java 8 streams? I couldn't find a way to do it. This is my code:
public static List<ObjectB> getFilteredList(List<ObjectA> list, LocalTime startTime, LocalTime endTime, int quantity) {
List<ObjectA> objectAList = new LinkedList<>();
List<ObjectB> objectBList = new LinkedList<>();
for (ObjectA object : list) {
if (object.getDateTime().toLocalTime().isAfter(startTime) && object.getDateTime().toLocalTime().isBefore(endTime)) {
objectAList.add(object);
}
}
for (ObjectA objectA : objectAList) {
int total = 0;
for (ObjectA object : list) {
if (object.getDateTime().toLocalDate().equals(objectA.getDateTime().toLocalDate())) {
total += object.getQuantity();
}
}
if (total > quantity) {
objectBList.add(new ObjectB(objectA.getDateTime(), objectA.getDescription(), objectA.getQuantity(), true));
} else {
objectBList.add(new ObjectB(objectA.getDateTime(), objectA.getDescription(), objectA.getQuantity(), false));
}
}
return objectBList;}
I have a list of objects with two fields: date and quantity. I need to return a list with one object for each date, but with one more feild - boolean, which should be true if the total sum of all quantites per day is more than 16, and false if it's not.
Let's do this step-by-step.
for (ObjectA object : list) {: a for loop is usually replaced with stream(), so start with list.stream().
if (...) {: condition is usually replaced with filter(), so continue with .filter(object -> object.getDateTime()...)
objectAList.add(object);: adding the results to the container is usually replaced with collect(). You are using LinkedList(), but any other List would be fine here, so we will simply use collect(Collectors.toList()).
So here's first loop:
List<ObjectA> objectAList = list.stream()
.filter(object -> object.getDateTime().toLocalTime().isAfter(startTime) &&
object.getDateTime().toLocalTime().isBefore(endTime))
.collect(Collectors.toList());
Now let's look into the inner loop which calculates the total:
int total = 0;
for (ObjectA object : list) {
if (object.getDateTime().toLocalDate().equals(objectA.getDateTime().toLocalDate())) {
total += object.getQuantity();
}
}
It's also stream-filter-collect sequence, but here you want to collect the sum. So you may use IntStream here which already has the sum() method:
int total = list.stream()
.filter(object -> object.getDateTime().toLocalDate().equals(
objectA.getDateTime().toLocalDate())
.mapToInt(ObjectA::getQuantity).sum();
To make your code less crowded I would extract this to the separate method:
private static int getQuantityByDate(List<ObjectA> list, LocalDate date) {
return list.stream().filter(object -> object.getDateTime().toLocalDate().equals(date))
.mapToInt(ObjectA::getQuantity).sum();
}
Now the next if statement. It just changes the last boolean argument, so I would rewrite it (even without Stream API):
objectBList.add(new ObjectB(objectA.getDateTime(), objectA.getDescription(),
objectA.getQuantity(), total > quantity));
So now we see that the outer loop becomes stream-map-collect chain and could be rewritten this way:
List<ObjectB> objectBList = objectAList.stream()
.map(objectA ->
new ObjectB(objectA.getDateTime(), objectA.getDescription(), objectA.getQuantity(),
getQuantityByDate(list, objectA.getDateTime().toLocalDate()) > quantity))
.collect(Collectors.toList());
Now you can notice that collecting into objectAList is unnecessary as we just use it to create another stream. So we can merge both loops into single pipeline, resulting in the following final code:
private static int getQuantityByDate(List<ObjectA> list, LocalDate date) {
return list.stream().filter(object -> object.getDateTime().toLocalDate().equals(date))
.mapToInt(ObjectA::getQuantity).sum();
}
public static List<ObjectB> getFilteredList(
List<ObjectA> list, LocalTime startTime, LocalTime endTime, int quantity) {
return list.stream()
.filter(object -> object.getDateTime().toLocalTime().isAfter(startTime) &&
object.getDateTime().toLocalTime().isBefore(endTime))
.map(objectA -> new ObjectB(
objectA.getDateTime(), objectA.getDescription(), objectA.getQuantity(),
getQuantityByDate(list, objectA.getDateTime().toLocalDate()) > quantity))
.collect(Collectors.toList());
}

What does ExpressionVisitor.Visit<T> Do?

Before someone shouts out the answer, please read the question through.
What is the purpose of the method in .NET 4.0's ExpressionVisitor:
public static ReadOnlyCollection<T> Visit<T>(ReadOnlyCollection<T> nodes, Func<T, T> elementVisitor)
My first guess as to the purpose of this method was that it would visit each node in each tree specified by the nodes parameter and rewrite the tree using the result of the elementVisitor function.
This does not appear to be the case. Actually this method appears to do a little more than nothing, unless I'm missing something here, which I strongly suspect I am...
I tried to use this method in my code and when things didn't work out as expected, I reflectored the method and found:
public static ReadOnlyCollection<T> Visit<T>(ReadOnlyCollection<T> nodes, Func<T, T> elementVisitor)
{
T[] list = null;
int index = 0;
int count = nodes.Count;
while (index < count)
{
T objA = elementVisitor(nodes[index]);
if (list != null)
{
list[index] = objA;
}
else if (!object.ReferenceEquals(objA, nodes[index]))
{
list = new T[count];
for (int i = 0; i < index; i++)
{
list[i] = nodes[i];
}
list[index] = objA;
}
index++;
}
if (list == null)
{
return nodes;
}
return new TrueReadOnlyCollection<T>(list);
}
So where would someone actually go about using this method? What am I missing here?
Thanks.
It looks to me like a convenience method to apply an aribitrary transform function to an expression tree, and return the resulting transformed tree, or the original tree if there is no change.
I can't see how this is any different of a pattern that a standard expression visitor, other than except for using a visitor type, it uses a function.
As for usage:
Expression<Func<int, int, int>> addLambdaExpression= (a, b) => a + b;
// Change add to subtract
Func<Expression, Expression> changeToSubtract = e =>
{
if (e is BinaryExpression)
{
return Expression.Subtract((e as BinaryExpression).Left,
(e as BinaryExpression).Right);
}
else
{
return e;
}
};
var nodes = new Expression[] { addLambdaExpression.Body }.ToList().AsReadOnly();
var subtractExpression = ExpressionVisitor.Visit(nodes, changeToSubtract);
You don't explain how you expected it to behave and why therefore you think it does little more than nothing.

Resources