How to generate objects with complex condition in a functional manner? - java

I want to create a list of Java objects based on some conditions. I want to do this using functional programming principles.
I took a look at Stream.generate() with .limit() but I can't add a predicate to limit to stop with a condition. Plus I don't know how to pass the modified start OffSetDateTime to the new object to be created.
public class Test {
private String name;
private OffsetDateTime timestamp;
}
public List<Test> generateTestObjects(OffsetDateTime startTime) {
ArrayList<Test> tests = new ArrayList<>();
for(OffsetDateTime start = startTime; start.isBefore(startTime.plusMinutes(100)); start = start.plusMinutes(1)) {
tests.add(new Test(start));
}
return tests;
}

With Java 9, you can use the three-argument Stream#iterate with the condition that you already have in your for-loop:
public Stream<OffsetDateTime> generateTestObjects(OffsetDateTime startTime) {
return Stream.iterate(startTime, time -> time.isBefore(startTime.plusMinutes(100)),
time -> time.plusMinutes(1));
}
If you want to return a List, you can add .collect(Collectors.toList()):
public List<OffsetDateTime> generateTestObjects(OffsetDateTime startTime) {
return Stream.iterate(startTime, time -> time.isBefore(startTime.plusMinutes(100)),
time -> time.plusMinutes(1)).collect(Collectors.toList());
}
Note that I excluded your mapping to Test, which would be equivalent to adding .map(Test::new) before calling collect.

Related

Sort ArrayList of objects with "pinned" objects at top behaves differently each execution

So I am trying to display a list of groups in a recyclerview in Android.
The groups are custom objects (Group) with a small amount of values, stored in a public static Arraylist (allGroups).
I have a method to sort these groups by their "time" value, which is the time in milliseconds.
Method to sort:
public static ArrayList<Group> sort(ArrayList<Group> list) {
list.sort(Comparator.comparing(Group::getTime));
Collections.reverse(list);
ArrayList<Group> newSort = new ArrayList<>();
for(Group g: list) {
if(g.isPinned()) {
newSort.add(g);
}
}
for(Group g: list) {
if(!g.isPinned()) {
newSort.add(g);
}
}
list.clear();
return newSort;
}
When I run the app the first time, it works fine and sorts my groups perfectly by pin and date, but whenever I add a group using the method below, it ONLY sorts it by date
allGroups.add(new Group(
new BigInteger(130, new java.util.Random()).toString(32),
"PB",
(long) (Math.random() * 1649157582577L),
new BigInteger(260, new java.util.Random()).toString(32)
).makePinned(false));
allGroups = sort(allGroups);
groupsAdapter.notifyDataSetChanged();
I have no clue what might be causing this, it makes no sense to me.
Edit:
Implementation for makePinned:
public Group makePinned(boolean pinned) {
this.pinned = pinned;
return this;
}
Constructor of Group:
public Group(String name, String logo, long time, String message) {
this.id = groupAmount + 1;
this.name = name;
this.logo = logo;
this.time = time;
this.message = message;
}
Your "Found the Answer" is incorrect, because your second sort statement :
list.sort(Comparator.comparing(Group::isPinned));
totally overwrites the first sort. OK, for your test sample, it MIGHT be giving the results you desire (coincidentally preserving some of the order from the first sort), but that is undefined behaviour that is NOT to be relied upon.
What it looks like you might be after is better implemented as :
allGroups.sort(Comparator.comparing(Group::isPinned)
.thenComparing(Group::getTime).reversed());
This is explicitly sorting by isPinned first, and then by getTime in reverse order. Explicit is good.
I have written an example program that is available here : Online Java Compiler, that :
sorts as per your question
Randomises the list (ie, undoes the sorting)
Sorts as above

Averaging across multiple fields with IntSummaryStatistics

I'm trying to use Java 8 streams to create a single CarData object, which consists of an average of all the CarData fields in the list coming from getCars;
CarData = new CarData();
CarData.getBodyWeight returns Integer
CarData.getShellWeight returns Integer
List<CarData> carData = carResults.getCars();
IntSummaryStatistics averageBodyWeight = carData.stream()
.mapToInt((x) -> x.getBodyWeight())
.summaryStatistics();
averageBodyWeight.getAverage();
IntSummaryStatistics averageShellWeight = carData.stream()
.mapToInt((x) -> x.getShellWeight())
.summaryStatistics();
getShellWeight.getAverage();
I don't want to have to put each of these back together in my final returned result.
Visually, this is my list
getCars() : [
{CarData: { getBodyWeight=10, getShellWeight=3 } }
{CarData: { getBodyWeight=6, getShellWeight=5 } }
{CarData: { getBodyWeight=8, getShellWeight=19 } }
]
and the output I'm trying to achieve is a single object that has the average of each of the fields I specify. not sure If I need to use Collectors.averagingInt or some combo of IntSummaryStatistics to achieve this. Easy to do across one field for either of these techniques, just not sure what I'm missing when using multiple integer fields.
{CarData: { getBodyWeight=8, getShellWeight=9 } }
Starting with JDK 12, you can use the following solution:
CarData average = carData.stream().collect(Collectors.teeing(
Collectors.averagingInt(CarData::getBodyWeight),
Collectors.averagingInt(CarData::getShellWeight),
(avgBody, avgShell) -> new CarData(avgBody.intValue(), avgShell.intValue())));
For older Java versions, you can do either, add the teeing implementation of this answer to your code base and use it exactly as above or create a custom collector tailored to your task, as shown in Andreas’ answer.
Or consider that streaming twice over a List in memory is not necessarily worse than doing two operations in one stream, both, readability- and performance-wise.
Note that calling intValue() on Double objects has the same behavior as the (int) casts in Andreas’ answer. So in either case, you have to adjust the code if other rounding behavior is intended.
Or you consider using a different result object, capable of holding two floating point values for the averages.
You need to write your own Collector, something like this:
class CarDataAverage {
public static Collector<CarData, CarDataAverage, Optional<CarData>> get() {
return Collector.of(CarDataAverage::new, CarDataAverage::add,
CarDataAverage::combine,CarDataAverage::finish);
}
private long sumBodyWeight;
private long sumShellWeight;
private int count;
private void add(CarData carData) {
this.sumBodyWeight += carData.getBodyWeight();
this.sumShellWeight += carData.getShellWeight();
this.count++;
}
private CarDataAverage combine(CarDataAverage that) {
this.sumBodyWeight += that.sumBodyWeight;
this.sumShellWeight += that.sumShellWeight;
this.count += that.count;
return this;
}
private Optional<CarData> finish() {
if (this.count == 0)
return Optional.empty();
// adjust as needed if averages should be rounded
return Optional.of(new CarData((int) (this.sumBodyWeight / this.count),
(int) (this.sumShellWeight / this.count)));
}
}
You then use it like this:
List<CarData> list = ...
Optional<CarData> averageCarData = list.stream().collect(CarDataAverage.get());

Java Collection sort

I am trying to sort a list of Dates and it's not working.
Here is the declaration and get function in AttEnt
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "end_time")
private Date endTime;
public Date getEndTime() {
return endTime;
}
Here is the sorting code that isn't doing anything. GetAttempts() gets the list of all the attempts for called. They aren't in order, and I just want to be able to get whatever attempt has the latest endTime.
List<AttEnt> attempts = called.getAttempts();
Collections.sort(attempts, new Comparator<AttEnt>() {
#Override
public int compare(AttEnt a1, AttEnt a2) {
if (a1.getEndTime() == null || a2.getEndTime() == null)
return 0;
return a1.getEndTime().compareTo(a2.getEndTime());
}
});
I believe that the code above should sort attempts, and then after it attempts should be sorted, so the latest end time would be attempts.get(attempts.size()-1).getEndTime()
Comparator<AttEnt> comparator = Comparator.comparing(AttEnt::getEndTime).reversed();
attempts.sort(comparator);
Java static methods in interfaces are your friend
Click HERE for more awesomeness

Modifying local variable from inside lambda

Modifying a local variable in forEach gives a compile error:
Normal
int ordinal = 0;
for (Example s : list) {
s.setOrdinal(ordinal);
ordinal++;
}
With Lambda
int ordinal = 0;
list.forEach(s -> {
s.setOrdinal(ordinal);
ordinal++;
});
Any idea how to resolve this?
Use a wrapper
Any kind of wrapper is good.
With Java 10+, use this construct as it's very easy to setup:
var wrapper = new Object(){ int ordinal = 0; };
list.forEach(s -> {
s.setOrdinal(wrapper.ordinal++);
});
With Java 8+, use either an AtomicInteger:
AtomicInteger ordinal = new AtomicInteger(0);
list.forEach(s -> {
s.setOrdinal(ordinal.getAndIncrement());
});
... or an array:
int[] ordinal = { 0 };
list.forEach(s -> {
s.setOrdinal(ordinal[0]++);
});
Note: be very careful if you use a parallel stream. You might not end up with the expected result. Other solutions like Stuart's might be more adapted for those cases.
For types other than int
Of course, this is still valid for types other than int.
For instance, with Java 10+:
var wrapper = new Object(){ String value = ""; };
list.forEach(s->{
wrapper.value += "blah";
});
Or if you're stuck with Java 8 or 9, use the same kind of construct as we did above, but with an AtomicReference...
AtomicReference<String> value = new AtomicReference<>("");
list.forEach(s -> {
value.set(value.get() + s);
});
... or an array:
String[] value = { "" };
list.forEach(s-> {
value[0] += s;
});
This is fairly close to an XY problem. That is, the question being asked is essentially how to mutate a captured local variable from a lambda. But the actual task at hand is how to number the elements of a list.
In my experience, upward of 80% of the time there is a question of how to mutate a captured local from within a lambda, there's a better way to proceed. Usually this involves reduction, but in this case the technique of running a stream over the list indexes applies well:
IntStream.range(0, list.size())
.forEach(i -> list.get(i).setOrdinal(i));
If you only need to pass the value from the outside into the lambda, and not get it out, you can do it with a regular anonymous class instead of a lambda:
list.forEach(new Consumer<Example>() {
int ordinal = 0;
public void accept(Example s) {
s.setOrdinal(ordinal);
ordinal++;
}
});
As the used variables from outside the lamda have to be (implicitly) final, you have to use something like AtomicInteger or write your own data structure.
See
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables.
An alternative to AtomicInteger is to use an array (or any other object able to store a value):
final int ordinal[] = new int[] { 0 };
list.forEach ( s -> s.setOrdinal ( ordinal[ 0 ]++ ) );
But see the Stuart's answer: there might be a better way to deal with your case.
Yes, you can modify local variables from inside lambdas (in the way shown by the other answers), but you should not do it. Lambdas have been made for functional style of programming and this means: No side effects. What you want to do is considered bad style. It is also dangerous in case of parallel streams.
You should either find a solution without side effects or use a traditional for loop.
If you are on Java 10, you can use var for that:
var ordinal = new Object() { int value; };
list.forEach(s -> {
s.setOrdinal(ordinal.value);
ordinal.value++;
});
You can wrap it up to workaround the compiler but please remember that side effects in lambdas are discouraged.
To quote the javadoc
Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement
A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care
I had a slightly different problem. Instead of incrementing a local variable in the forEach, I needed to assign an object to the local variable.
I solved this by defining a private inner domain class that wraps both the list I want to iterate over (countryList) and the output I hope to get from that list (foundCountry). Then using Java 8 "forEach", I iterate over the list field, and when the object I want is found, I assign that object to the output field. So this assigns a value to a field of the local variable, not changing the local variable itself. I believe that since the local variable itself is not changed, the compiler doesn't complain. I can then use the value that I captured in the output field, outside of the list.
Domain Object:
public class Country {
private int id;
private String countryName;
public Country(int id, String countryName){
this.id = id;
this.countryName = countryName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCountryName() {
return countryName;
}
public void setCountryName(String countryName) {
this.countryName = countryName;
}
}
Wrapper object:
private class CountryFound{
private final List<Country> countryList;
private Country foundCountry;
public CountryFound(List<Country> countryList, Country foundCountry){
this.countryList = countryList;
this.foundCountry = foundCountry;
}
public List<Country> getCountryList() {
return countryList;
}
public void setCountryList(List<Country> countryList) {
this.countryList = countryList;
}
public Country getFoundCountry() {
return foundCountry;
}
public void setFoundCountry(Country foundCountry) {
this.foundCountry = foundCountry;
}
}
Iterate operation:
int id = 5;
CountryFound countryFound = new CountryFound(countryList, null);
countryFound.getCountryList().forEach(c -> {
if(c.getId() == id){
countryFound.setFoundCountry(c);
}
});
System.out.println("Country found: " + countryFound.getFoundCountry().getCountryName());
You could remove the wrapper class method "setCountryList()" and make the field "countryList" final, but I did not get compilation errors leaving these details as-is.
To have a more general solution, you can write a generic Wrapper class:
public static class Wrapper<T> {
public T obj;
public Wrapper(T obj) { this.obj = obj; }
}
...
Wrapper<Integer> w = new Wrapper<>(0);
this.forEach(s -> {
s.setOrdinal(w.obj);
w.obj++;
});
(this is a variant of the solution given by Almir Campos).
In the specific case this is not a good solution, as Integer is worse than int for your purpose, anyway this solution is more general I think.

Problem with Priority Queue

I'm trying to use a priority queue in my code, and for some reason when I remove the objects, they aren't in order. Do you know what i"m doing wrong?
Here's my code:
the contructor:
recordedsong = new PriorityQueue<recordedNote>(50, new Comparator<recordedNote>()
{
public int compare(recordedNote n1, recordedNote n2)
{
long l = n1.rt()-n2.rt();
int i = (int)l;
return i;
}
});
where each recordedNotehas a long value that is returned my the method rt().
But when I call
while (!Song.isEmpty())
{
recordedNote temp = (recordedNote)Song.remove();
and then print temp.rt() for each one, all the numbers are out of order. And not just like reverse order, but all over the place, like 1103, 0, 500, 0, 220 orders like that.
Can you see if there's anything wrong with my contructor?
Thanks!
remove should work, and in fact it does work fine in a small example program that I created to help answer this question:
import java.util.Comparator;
import java.util.PriorityQueue;
public class TestPriorityQueue {
public static void main(String[] args) {
long[] noteTimes = {1103L, 0L, 500L, 0L, 220L, 1021212812012L};
PriorityQueue<RecordedNote> noteQueue = new PriorityQueue<RecordedNote>(10,
new Comparator<RecordedNote>() {
#Override
public int compare(RecordedNote o1, RecordedNote o2) {
Long time1 = o1.getTime();
Long time2 = o2.getTime();
// uses Long's built in compareTo method, so we
//don't have to worry as much about edge cases.
return time1.compareTo(time2);
}
});
for (int i = 0; i < noteTimes.length; i++) {
RecordedNote note = new RecordedNote(noteTimes[i]);
System.out.println(note);
noteQueue.add(note);
}
System.out.println();
while (noteQueue.size() > 0) {
System.out.println(noteQueue.remove());
}
}
}
class RecordedNote {
private long time;
public RecordedNote(long time) {
this.time = time;
}
public long getTime() {
return time;
}
#Override
public String toString() {
return "[Time: " + time + "]";
}
}
So this begs the question, why isn't it working for you? Myself, I don't see enough coherent code in your question to be able to answer this. We're not sure what is Song as I don't see this declared as a class or a variable, and I also don't see where you're using your PriorityQueue variable, recordedsong, anywhere. So I suggest you do the same thing as I: create a small compilable runnable program that we can run and modify and that demonstrates your problem, an http://sscce.org
I guess there is a possibility for i getting 0. So modify compare method so that it returns a positive value rather than the result.
Reading the API docs for PriorityQueue, it states the following:
The Iterator provided in method iterator() is not guaranteed to traverse the elements of the priority queue in any particular order. If you need ordered traversal, consider using Arrays.sort(pq.toArray()).
My guess is that remove() is not obligated to follow the natural ordering, either.

Categories

Resources