Sorting list of objects by date property - java

I have an object that contains two LocalDate properties:
public class SomeObject {
private LocalDate startDate;
private LocalDate endDate;
}
Constructor and stuff ommitted for brevity. I want to sort a list of these objects by their startdate and then assign the startdate of the next object to the previous object's enddate. To clarify, I start with a list of these objects:
SomeObject object1 = new SomeObject(LocalDate.parse("2015-01-01"), null);
SomeObject object2 = new SomeObject(LocalDate.parse("2014-01-01"), null);
SomeObject object3 = new SomeObject(LocalDate.parse("2016-01-01"), null);
List<SomeObject> list = Arrays.asList(object1, object2, object3);
And after sorting it should return this:
for (SomeObject object : list) {
System.out.println(object.startDate.toString() + " " + object.endDate.toString() );
}
2014-01-01 2015-01-01
2015-01-01 2016-01-01
2016-01-01 null
Each list will only contain 3 or 4 of these objects at most, but the code might have to process tens of thousands of these lists, so I'm looking for an efficient way to do this.

You can use Collections.sort with a Comparator. In Java 8 with Lambdas it looks like this:
Collections.sort(list, (x, y) -> x.startDate.compareTo(y.startDate));
for (int i = 0; i < (list.size() - 1); i++) {
list.get(i).endDate = list.get(i + 1).startDate;
}

As an enhancement to the accepted answer:
Collections.sort(list, Comparator.comparing(SomeObject::getStartDate));

As you mentioned that you didn't really care whether it is startDate or endDate and just order all of them, maybe the following will help you:
List<LocalDate> dates = list.stream()
.flatMap(s -> Stream.of(s.startDate, s.endDate))
.filter(Objects::nonNull) // maybe... if nulls are required too, then skip that part here... (but also check the sorting variant then); note that I use null now if the last date is reached (check the printing part for that)
.distinct()
.sorted() // natural order
// alternatively: natural order + nulls last
// .sorted(Comparator.nullsLast(Comparator.comparing(Function.identity())))
.collect(Collectors.toList());
// printing part:
IntStream.range(0, dates.size())
.mapToObj(i -> {
String from = Objects.toString(dates.get(i));
String upto = Objects.toString(i < dates.size() - 1 ? dates.get(i + 1) : null); // exchange null with the end date you are expecting
return from + " - " + upto;
})
.forEach(System.out::println);
EDIT: There was that endDate set on one of your samples before... as that isn't the case anymore, here an update how you can set the right date ranges. It's basically similar to what also Ralf Renz has used in his answer:
list.sort(Comparator.comparing(SomeObject::getStartDate));
IntStream.range(0, list.size() - 1)
.forEach(i -> list.get(i).endDate = list.get(i + 1).startDate);
// or if you care about performance, just do the same as Ralf did:
for (int i = 0; i < (list.size() - 1); i++) {
list.get(i).endDate = list.get(i + 1).startDate;
}

Make use of the fact that LocalDate already implements Comparable and make your SomeObject do as well. Additionally, give it a proper toString() method, which handles null values in order to represent your object as a String:
public class SomeObject implements Comparable<SomeObject> {
private LocalDate startDate;
private LocalDate endDate;
public SomeObject(LocalDate startDate, LocalDate endDate) {
this.startDate = startDate;
this.endDate = endDate;
}
#Override
public int compareTo(SomeObject anotherObject) {
return this.startDate.compareTo(anotherObject.startDate);
}
#Override
public String toString() {
String start = startDate == null ? "null" : startDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
String end = endDate == null ? "null" : endDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
StringBuilder sb = new StringBuilder();
sb.append(start).append(" ").append(end);
return sb.toString();
}
}
By doing so, you can easily just call Collections.sort(list); and have your data sorted by startDate:
public class SomeObjectSorting {
public static void main(String[] args) {
SomeObject object1 = new SomeObject(LocalDate.parse("2015-01-01"), null);
SomeObject object2 = new SomeObject(LocalDate.parse("2014-01-01"), LocalDate.parse("2017-01-01"));
SomeObject object3 = new SomeObject(LocalDate.parse("2016-01-01"), null);
List<SomeObject> list = Arrays.asList(object1, object2, object3);
System.out.println("———— BEFORE SORTING ————");
list.forEach(object -> {
System.out.println(object.toString());
});
Collections.sort(list);
System.out.println("———— AFTER SORTING ————");
list.forEach(object -> {
System.out.println(object.toString());
});
}
}

Related

Getting list of all consecutive intervals from series of dates in Java

I have a list of LocalDates lets say
14-06-2020, 15-06-2020, 17-06-2020, 19-06-2020, 20-06-2020, 21-06-2020
and I want to have all consecutive intervals from above dates. So the output would be like
Interval 1 = [14-06-2020, 15-06-2020]
Interval 2 = [17-06-2020, 17-06-2020]
Interval 3 = [19-06-2020, 21-06-2020]
What would be the most efficient way to do in Java
So I have create an Interval class that would hold start and enddate
public class Interval{
private LocalDate startDate;
private LocalDate endDate;
}
I can iterate over each element in list of dates and then check for logic if two dates are consecutive something in below line,
public static void main(String args[]){
List<LocalDate> dates = new ArrayList<>();
dates.add(LocalDate.of(2020,6,14));
dates.add(LocalDate.of(2020,6,15));
dates.add(LocalDate.of(2020,6,17));
dates.add(LocalDate.of(2020,6,19));
dates.add(LocalDate.of(2020,6,21));
dates.add(LocalDate.of(2020,6,20));
Collections.sort(dates);
//Handle if empty or null
List<Interval> intervals = new ArrayList<>();
if(dates==null || dates.size()==0){
throw new IllegalArgumentException("list cannot be empty");
}
//If only one date then the interval starts and ends with same date
if(dates.size()==1){
Interval interval = new Interval();
interval.setStartDate(dates.get(0));
interval.setEndDate(dates.get(0));
}
LocalDate firstDate = dates.get(0);
for(int i =1;i<dates.size(); i++){
LocalDate endDate = dates.get(i);
LocalDate nextDate = endDate.plusDays(1);
//iterate over to get the desired list of interval
while(my condition satisfies){
//create new interval
}
//intervals.add(interval
}
}
I wanted to check if there is something better using stream api or can i group dates by consecutive days and then collect them using interval
A "workaround" towards an approach based on Stream upon the sorted collection could be to use markers for range lookup -
List<Integer> rangeMarkers = new ArrayList<>();
rangeMarkers.add(0);
rangeMarkers.addAll(IntStream.range(0, dates.size() - 1)
.filter(i -> !dates.get(i).plusDays(1).equals(dates.get(i + 1)))
.mapToObj(i -> i + 1)
.collect(Collectors.toList()));
rangeMarkers.add(dates.size());
System.out.println(rangeMarkers);
and then use those markers to map dates to Interval -
List<Interval> intervals = IntStream.range(0, rangeMarkers.size() - 1)
.mapToObj(i -> new Interval(dates.get(rangeMarkers.get(i)),
dates.get(rangeMarkers.get(i + 1) - 1)))
.collect(Collectors.toList());
System.out.println(intervals);
Stream API isn't your friend in this case. you can do it as I displayed the below but I think it isn't readable.
without non-stream API makes it more readable.
After sorting the list loop over the list by skipping the first index. current localDate is the first element in the list. by checking its equality with the other elements change the current value and merge intervalMap as you see. for current localDate in the loop if equality isn't matched put it on the map with a new key. (++index). because you just want to have start localDate and end localDate in the merge function I just set the endLocalDate of the first interVal with the second interval endLocalDate value.
LocalDate current = dates.get(0);
Map<Integer, Interval> intervalMap2 = new HashMap<>();
int index = 1;
intervalMap2.put(1, new Interval(current,current));
for (LocalDate localDate : dates.subList(1, dates.size())) {
if (current.plusDays(1).equals(localDate)) {
current = localDate;
intervalMap2.merge(index, new Interval(localDate,localDate),
(val, val2) -> {val.setEndDate(val2.getEndDate());return val; });
} else {
intervalMap2.merge(index, new Interval(current,current),
(val, val2) -> {val.setEndDate(val2.getEndDate());return val; });
intervalMap2.put(++index, new Interval(localDate,localDate));
current = localDate;
}
}
however, if you interested in do it with stream version you can do like:
Map<Integer, Interval> intervalMap = dates.stream().sorted()
.collect(HashMap::new, (hashMap, localDate) -> {
if (hashMap.get(hashMap.size()) != null &&
hashMap.get(hashMap.size()).getEndDate()
.equals(localDate.minusDays(1))) {
hashMap.merge(hashMap.size(),
new Interval(localDate, localDate),
(val, val2) -> {val.setEndDate(val2.getEndDate());return val; });
} else {
if (hashMap.size() > 1)
hashMap.merge(hashMap.size(), hashMap.get(hashMap.size()),
(val, val2) -> { val.setEndDate(val2.getEndDate());return val;});
hashMap.put(hashMap.size() + 1, new Interval(localDate, localDate));
}
}, HashMap::putAll);

Getting the Set with the most elements nested in a HashMap using Java Streams

So here is the situation:
I need to register people's vote for certain dates. In short, a date is proposed and people vote for the date they want.
The data structure is the following:
private HashMap<LocalDateTime, Set<Vote>> votes;
A vote is:
public class Vote {
private String name;
private VoteType vote;
public Vote(String name, VoteType vote) {
super();
this.name = name;
this.vote = vote;
}
}
Where VoteType is just an enum:
public enum VoteType {YES, NO, MAYBE}
Now I already made a stream that returns the amount of votes for the availability (VoteType):
public Map<LocalDateTime, Integer> voteCount(VoteType targetVote) {
return this.votes.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> new Integer(
e.getValue().stream().filter(v -> v.getVote() == targetVote).collect(Collectors.toList()).size())));
}
So my question is:
How can I get, using Java Streams, the date that got the most 'YES'.
/* Returns the date that got the most 'YES' votes */
public LocalDateTime winningDate() {
// TODO
}
Thank you for the help!
So my question is: How can I get, using Java Streams, the date that
got the most 'YES'.
This is going to be a lengthy one...
we need to get to a position where we have a Stream<LocalDateTime> so we can later group by date applying a counting downstream collector to get the number of votes on that specific date and we can accomplish this structure via flatMap.
we need to retain only the objects where the vote type is YES
we group the results by the date and have the values as the number of YES votes on that specific date.
we stream over the entrySet and find the max date by vote
Code:
/* Returns the date that got the most 'YES' votes */
public Optional<LocalDateTime> getWinningDate() {
return votes.entrySet() // Set<Entry<LocaleDateTime, Set<Vote>>
.stream() // Stream<Entry<LocaleDateTime, Set<Vote>>
.flatMap(e -> e.getValue().stream().filter(a -> a.getVote() == VoteType.YES)
.map(x -> e.getKey())) // Stream<LocalDateTime>
.collect(groupingBy(Function.identity(), counting())) // Map<LocaleDateTime, Long>
.entrySet() // Set<Entry<LocaleDateTime, Long>>
.stream() // Stream<Entry<LocaleDateTime, Long>>
.max(Comparator.comparingLong(Map.Entry::getValue)) // Optional<Entry<LocaleDateTime, Long>>
.map(Map.Entry::getKey); // Optional<LocalDateTime>
}
note that I've changed the method return type to
Optional<LocaleDateTime>, I could have returned
.map(Map.Entry::getKey).orElse(null) thus you've be able to maintain your current method return type of LocalDateTime but that just feels bad and so
I've decided to defer the decision upon what to do in the "no value
case" to the client.
I've changed the method name to getWinningDate to enhance readability.
As for dealing with Optional<T>, in your case, if you want to have a null value in the case of getWinningDate returning an empty Optional, you can unwrap it safely as:
LocalDateTime winningDate = getWinningDate().orElse(null);
or if you want to provide a default date:
LocalDateTime winningDate = getWinningDate().orElse(defaultDate);
or if you're sure there will always be a result then simply call get().
LocalDateTime winningDate = getWinningDate().get();
etc..
You can do it this way:
private LocalDateTime winningDate(Map<LocalDateTime, Integer> mapGroup) {
Integer max = mapGroup
.values().stream()
.max(Comparator.naturalOrder())
.get();
return mapGroup
.entrySet()
.stream()
.filter(e -> e.getValue().equals(max))
.map(Map.Entry::getKey)
.findFirst().orElse(null);
}
This answer shows a way to do it without your voteCount method but just in case you may want to write some logic in your winningDate method that would integrate with your already made voteCount method.
in which case we can do:
/* Returns the date that got the most 'YES' votes */
public Optional<LocalDateTime> getWinningDate() {
return voteCount(VoteType.YES).entrySet() // call voteCount and stream over the entries
.stream()
.max(Comparator.comparingLong(Map.Entry::getValue))
.map(Map.Entry::getKey);
}
first we invoke the voteCount(VoteType.YES) method to get a mapping of dates and the number of YES votes on the date.
second we find the max LocalDateTime by the vote count
note that I've changed the method return type to
Optional<LocaleDateTime>, I could have returned
.map(Map.Entry::getKey).orElse(null) thus you'll be able to maintain
your current method return type of LocalDateTime but that just feels
bad and so I've decided to defer the decision upon what to do in the
"no value case" to the client.
I've changed the method name to getWinningDate to enhance
readability.
Further, the voteCount method can be improved to:
public Map<LocalDateTime, Long> voteCount(VoteType targetVote) {
return this.votes.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().stream().filter(v -> v.getVote() == targetVote).count()));
}
this avoids the overhead of constructing a list of all the elements that pass the filter simply to retrieve the count via size(), instead just filter and invoke count.
Use your first method which counts up the YES votes, returns a map of the yes counts which is passed into the winning date method:
/* Returns the date that got the most 'YES' votes */
public LocalDateTime winningDate(Map<LocalDateTime, Integer> yesVotes) {
return yesVotes.entrySet().stream().max(Map.Entry.comparingByValue()).get().getKey();
}
I can't help but thinking this was the intention here, but what do I know.
You asked how to do it with streams, here's another way:
class Max { long value = Long.MIN_VALUE; LocalDateTime date; }
Max max = new Max();
votes.forEach((d, vs) -> {
long count = vs.stream().filter(v -> VoteType.YES == v.getVote()).count();
if (count > max.value) {
max.value = count;
max.date = d;
}
});
LocalDateTime maxDate = max.date;
And to get the set of votes:
Set<Vote> maxVotesForYes = votes.get(maxDate);
This solution iterates the map entries and counts YES votes for each date. If this count is greater than the current max count, the max count (along with its corresponding date) is changed.
In order to be able to modify the max count and its corresponding date, we need a local class Max that keeps track of these values (otherwise, we wouldn't be able to change variables from within a lambda).
The question asked about how to solve this "using Java Streams". The following is using streams. And a for-loop.
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class VoteCountTest
{
public static void main(String[] args)
{
Map<LocalDateTime, Set<Vote>> votes =
new LinkedHashMap<LocalDateTime, Set<Vote>>();
Set<Vote> yes0 = votesWith(VoteType.NO, VoteType.NO);
Set<Vote> yes1 = votesWith(VoteType.YES, VoteType.NO);
Set<Vote> yes2 = votesWith(VoteType.YES, VoteType.YES);
votes.put(LocalDateTime.of(2000, 1, 1, 1, 1), yes1);
votes.put(LocalDateTime.of(2000, 1, 2, 1, 1), yes0);
votes.put(LocalDateTime.of(2000, 1, 3, 1, 1), yes2);
votes.put(LocalDateTime.of(2000, 1, 4, 1, 1), yes1);
System.out.println(getWinningDateA(votes));
System.out.println(getWinningDateB(votes));
}
public static Optional<LocalDateTime> getWinningDateA(
Map<LocalDateTime, Set<Vote>> votes)
{
LocalDateTime bestDate = null;
long maxCount = -1;
Predicate<Vote> votedYes = v -> v.getVote() == VoteType.YES;
for (Entry<LocalDateTime, Set<Vote>> entry : votes.entrySet())
{
long count = entry.getValue().stream().filter(votedYes).count();
if (count > maxCount)
{
maxCount = count;
bestDate = entry.getKey();
}
}
return Optional.ofNullable(bestDate);
}
// As of https://stackoverflow.com/a/53771478/3182664
public static Optional<LocalDateTime> getWinningDateB(Map<LocalDateTime, Set<Vote>> votes)
{
return votes.entrySet() // Set<Entry<LocaleDateTime, Set<Vote>>
.stream() // Stream<Entry<LocaleDateTime, Set<Vote>>
.flatMap(e -> e.getValue().stream().filter(a -> a.getVote() == VoteType.YES)
.map(x -> e.getKey())) // Stream<LocalDateTime>
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) // Map<LocaleDateTime, Long>
.entrySet() // Set<Entry<LocaleDateTime, Long>>
.stream() // Stream<Entry<LocaleDateTime, Long>>
.max(Comparator.comparingLong(Map.Entry::getValue)) // Optional<Entry<LocaleDateTime, Long>>
.map(Map.Entry::getKey); // Optional<LocalDateTime>
}
//=========================================================================
enum VoteType {YES, NO, MAYBE}
static class Vote {
private String name;
private VoteType vote;
public Vote(String name, VoteType vote) {
super();
this.name = name;
this.vote = vote;
}
public VoteType getVote()
{
return vote;
}
}
private static Set<Vote> votesWith(VoteType... voteTypes)
{
Set<Vote> votes = new LinkedHashSet<Vote>();
for (int i = 0; i < voteTypes.length; i++)
{
votes.add(new Vote("v" + i, voteTypes[i]));
}
return votes;
}
}
Compare this to the "pure-stream" solution, and think about which code you'd rather like to read, understand and maintain in the future. Then choose wisely.
(I know that this may, strictly speaking, not be the desired answer to the question. But some people seem to intentionally over-use streams and derive some sort of geeky pride from that. I also enjoy that as a challenge occasionally. But imagining that I might be the one who has to maintain these abominations of functional programming in the future makes me shudder...)

Retrieving multiple min occurrences from a list

I have a list of custom objects:
List<CustomObject> customObjects;
from which I would like to extract all the objects that have the have the earliest datetime value set.
So the class would look something like this:
class CustomObject {
LocalDateTime date;
public LocalDateTime getDateTime() {
return date;
}
}
and I'm able to successfully find the object in the list with the earliest date with a custom comparator function like this:
private static LocalDateTime getDate(CustomObject customObject) {
return customObject.getDateTime();
}
CustomObject customObjectMin = customObjects.stream().
min(Comparator.comparing(MyUtilClass::getDate));
However, it is possible to have multiple custom objects with the same date, but it looks like there is no way to get multiple occurrences in that scenario with the min. Is there an easy solution to finding all the objects in the list with the earliest date set ? Something like this:
List<CustomObject> customObjectsMin = customObjects.stream().
minWithAllOccurences(Comparator.comparing(MyUtilClass::getDate));
You can do two selections.
one to find the min date
one to find those with that date
e.g.
LocalDate min = customObjects.stream()
.map(CustomObject::getDateTime)
.min(Comparator.naturalOrder());
List<CustomObject> objs = customObjects.stream()
.filter(c -> min.equals(c.getDateTime()))
.collect(Collectors.toList());
Or you can use Collectors.groupingBy into a TreeMap and take the first entry.
Other than Peter Lawrey's excellent answer, I would like to point out that it is possible to do this with a single stream while avoiding the memory costs of collecting every element into a TreeMap. How? One way would be to use reduce(), as follows:
List<SampleJava> customObjectsMin = customObjects.stream()
.reduce(new ArrayList<>(), // identity
(List<SampleJava> list, SampleJava item) -> { // accumulate
if(list.isEmpty() || getDate(item).compareTo(getDate(list.get(0))) < 0) {
return new ArrayList<>(Arrays.asList(item));
} else if(getDate(item).equals(getDate(list.get(0)))) {
list.add(item);
}
return list;
},
(list1, list2) -> { // combine
if(list1.isEmpty()) return list2;
if(list2.isEmpty()) return list1;
int cmp = getDate(list1.get(0)).compareTo(getDate(list2.get(0)));
if(cmp < 0) return list1;
if(cmp > 0) return list2;
list1.addAll(list2);
return list1;
});

Java 8 Map a List to another list and count

I am looking to create a list of history values for an existing list so that I can save it in DB to be displayed later in a table
Class Data {
Date date;
int int1;
int int2;
}
class DataHistory {
Date date;
int sum_Int1_beforeOrEqualDate;
int sum_Int2_beforeOrEqualDate;
String someOtherValues;
}
For example I have several lines perDate with all values. What I would like to achieve is :
My input :
date, int1, int2
01/01/18, 2, 3
01/01/18, 0, 1
02/01/18, 0, 1
02/01/18, 3, 0
03/01/18, 1, 3
...
My output :
date, sum_Int1_beforeOrEqualDate, sum_Int2_beforeOrEqualDate
01/01/18, 2, 4
02/01/18, 3, 1
03/01/18, 1, 3
...
I have tried several things, mainly with Map, but has never been able to do it with List-->List.
What I have tried to do is :
Edit: My lastAttempt, which clearly shows I don't know what i am doing..
List<OutputList> outputList =
inputlist
.stream()
.map( e -> new DataHistory())
.collect(Collectors.groupingBy(int1));
I believe you're trying to simply sum the values grouping by date. So assuming you have parsed data as a List
List<Data> list = getDataAsList();
List<DataHistory> historyList = list.stream()
.collect(Collectors.groupingBy(data -> data.date)).entrySet().stream()
.map((entry) -> {
DataHistory history = new DataHistory();
history.date = entry.getKey();
List<Data> dataList = entry.getValue();
history.sum_Int1_beforeOrEqualDate = dataList.stream().mapToInt(data -> data.int1).sum();
history.sum_Int2_beforeOrEqualDate = dataList.stream().mapToInt(data -> data.int2).sum();
return history;
})
.collect(Collectors.toList());
Tell me if I got the logic correct.
What you could do is use Collections.reducing which works pretty good.
List<DataHistory> dataHistories =
list.stream()
.collect(Collectors.groupingBy(Data::getDate,
Collectors.reducing(DataHistory::new,
DataHistoryHelper::merge)))
.values();
This solution assumes you have a constructor in DataHistory taking a Data as parameter.
public DataHistory(Data o) {
this.date = o.getDate();
// and so on
}
And that you have a method (anywhere) that takes care of merging two DataHistory objects
public DataHistory merge(DataHistory o1, DataHistory o2) {
DataHistory merged = new DataHistory();
merged.setSum_Int1_beforeOrEqualDate(o1.getSum_Int1_beforeOrEqualDate + o2.getSum_Int1_beforeOrEqualDate);
// and so on
return merged;
}
You can accomplish the task at hand using the toMap collector:
Collection<DataHistory> resultSet =
myList.stream()
.collect(Collectors.toMap(Data::getDate,
e -> new DataHistory(e.getDate(), e.getInt1(), e.getInt2(), null),
DataHistory::merge)).values();
This assumes you have a constructor defined as follows in your DataHistory class:
public DataHistory(Date date, int sum_Int1_beforeOrEqualDate,
int sum_Int2_beforeOrEqualDate, String someOtherValues) {
this.date = date;
this.sum_Int1_beforeOrEqualDate = sum_Int1_beforeOrEqualDate;
this.sum_Int2_beforeOrEqualDate = sum_Int2_beforeOrEqualDate;
this.someOtherValues = someOtherValues;
}
and a merge function defined as such:
public DataHistory merge(DataHistory other){
this.sum_Int1_beforeOrEqualDate += other.getSum_Int1_beforeOrEqualDate();
this.sum_Int2_beforeOrEqualDate += other.getSum_Int2_beforeOrEqualDate();
return this;
}
in the DataHistory class.
Further, if you explicitly require a List<DataHistory> as opposed to a Collection<DataHistory> then you can do:
List<DataHistory> historyList = new ArrayList<>(resultSet);
Note that I am passing null to the DataHistory constructor for the fourth parameter simply because I don't know what data to pass, so I'll leave that for you to decide upon.

Merge 2 lists in a Functional Reactive way

Let's imagine the following object :
class People {
public int id;
public String name;
public Date dateOfDeath;
}
I have 2 lists of people.
In the first one, a People object has its ID and NAME properly set. In the second one, a People object has its ID and DATEOFDEATH properly set.
I need to combine the 2 lists in order to have a single list with a full People object (name and date of death).
In a full procedural way, this could be done with a double for loop like this :
for (People fullPeople : firstList) {
for (People peopleWithDateOfDeath : secondList) {
if (peopleWithDateOfDeath.id == fullPeople.id) {
fullPeople.dateOfDeath = peopleWithDateOfDeath.dateOfDeath;
break;
}
}
}
secondList = null;
// first list is good :)
How can I implement this in a functional way? I am using Rx-Java but any example with Java 8 Streams is easily convertible.
You can avoid O(n2) complexity by building a map of id to dateOfDeath:
Map<Integer, Date> deaths = secondList.stream()
.collect(toMap(p -> p.id, p -> p.dateOfDeath));
fullPeople.stream()
.filter(p -> deaths.containsKey(p.id))
.forEach(p -> p.dateOfDeath = deaths.get(p.id));
Or, if you want to avoid mutating existing people:
List<People> mergedPeople = fullPeople.stream()
.map(p -> deaths.containsKey(p.id)
? new People(p.id, p.name, deaths.get(p.id))
: p
).collect(toList());
You could do it like this:
List<People> persons =
names.stream()
.map(p -> new People(p.id, p.name, dates.stream()
.filter(pd -> pd.id == p.id)
.map(pd -> pd.dateOfDeath)
.findFirst()
.orElse(null))
)
.collect(Collectors.toList());
where names is the list of persons having the names and dates is the list of persons having the date of death. This assumes that the People class has a 3 argument constructor taking the id, name and date of death.
For all person with names, the person having the same id is looked up in the other list with filter and we map the result to the dateOfDeath. If a match is found, the date is returned, otherwise, orElse is invoked and null is returned.
Note that this will not merge any person that is present in the dates list but not in the names list.
Sample code:
List<People> names = new ArrayList<>();
List<People> dates = new ArrayList<>();
names.add(new People(1, "Name 1", null));
names.add(new People(2, "Name 2", null));
dates.add(new People(1, null, new Date()));
dates.add(new People(3, null, new Date()));
List<People> peoples = codeFromAbove();
System.out.println(peoples);
// prints
// [[id=1, name=Name 1, date=Sun Oct 18 19:48:58 CEST 2015],
// [id=2, name=Name 2, date=null]]
with:
class People {
public int id;
public String name;
public Date dateOfDeath;
public People(int id, String name, Date dateOfDeath) {
this.id = id;
this.name = name;
this.dateOfDeath = dateOfDeath;
}
#Override
public String toString() {
return "[id="+id+", name="+name+", date="+dateOfDeath+"]";
}
}

Categories

Resources