Retrieving multiple min occurrences from a list - java

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;
});

Related

Return nested for loop first element by predicate using streams [duplicate]

This question already has answers here:
Difference between anyMatch and findAny in java 8
(2 answers)
Closed 2 years ago.
I have List<Item> that contains List<SubItem> objects inside and I need to return first date of SubItem. I'd like to use streams for this. I know that SubItem is never null or empty.
Logic behind is to retrieve date from SubItem which for all Item objects are the same. Loop through Item list then loop through SubItem (each Item has list of SubItem) and from the first SubItem return date.
Using non stream:
public LocalDate getSubItemDate(List<Item> items) {
LocalDate date = null;
for (Item item : items) {
List<SubItem> subItems = item.getSubItems().getSubItem();
for (SubItem si: subItems) {
XMLGregorianCalendar rawDate = si.getSubItemDate().getVal();
return rawDate.toGregorianCalendar().toZonedDateTime().toLocalDate();
}
}
return date;
}
And using stream I've tried so far and I want to have something like this:
// but it's boolean...
public LocalDate getSubItemDate(List<Item> items) {
return items
.stream()
.anyMatch(item ->
item.getSubItems().getSubItem()
.stream()
.anyMatch(si->
si.getSubItemDate().getVal().toGregorianCalendar().toZonedDateTime().toLocalDate()));
As per your first code snippet, there is no need to write a for loop. You can directly get the first item out of the list after checking the list size.
if (items.size() > 0) {
Item item = items.get(0);
List<SubItem> subItems = item.getSubItems().getSubItem();
if (subItems.size() > 0) {
XMLGregorianCalendar rawDate = subItems.get(0).getSubItemDate().getVal();
return rawDate.toGregorianCalendar().toZonedDateTime().toLocalDate();
}
}
return null;
Using streams,
items.stream()
.findFirst()
.flatMap(item -> item.getSubItems().getSubItem().stream().findFirst()
.map(si -> si.getSubItemDate().getVal().toGregorianCalendar().toZonedDateTime().toLocalDate()))
.orElse(null); //or use orElseThrow to throw an exception
Also, using getSubItems().getSubItem() sounds odd, it should have been just getSubItems().

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.

Trim Java ArrayList to distinct row with the newest date

I have a List of MyData as below.
It consist of multiple rows of MyData objects.
There are some with same key but different date and name
public class MyData {
String name;
String key;
String date;
// ... constructor and other codes omitted here
}
List<MyData> myDataList;
I'm thinking of trimming the list to distinct key based on the newest date
e.g. If I have
*Name* *key* *date*
ABC 12 2016-10-09
FGH 10 2016-10-18
IJK 10 2016-10-08
DEF 12 2016-10-19
then the trim result should be
*Name* *key* *date*
DEF 12 2016-10-19
FGH 10 2016-10-18
What's the best way to do this algorithmically?
Note: I'm on Java 7, can't use the Stream feature of Java 8. (This is for Android Development, Java 8 is not supported yet).
Assuming you use real date types for date attribute you could do this:
private Collection<MyData> trim(List<MyData> data) {
Map<String, MyData> result = new HashMap<>();
for (MyData item : data) {
MyData lastItem = result.get(item.getKey());
if (lastItem == null || lastItem.getDate().before(item.getDate())) {
result.put(item.getKey(), item);
}
}
return result.values();
}
You could probably reach same results using streams.
You can use a HashMap and update object corresponding to key only if it is more recent. I let you write the function to compare two dates.
HashMap<Integer, MyData> trimedData = new HashMap<>();
for (MyData d : myDataList){
MyData dataSaved= trimedData.get(d.key);
if (dataSaved!= null){
if(d.date > dataSaved.data){ // Here use correct method to compare date
trimedData.put(d.key, d);
}
}
else trimedData.put(d.key, key);
}
You can use streams, look at this example:
ArrayList<String> strings = new ArrayList<>();
strings.add("cbab");
strings.add("abab");
strings.add("dabab");
strings.add("ddabab");
Map<Integer, Optional<String>> collect = strings
.stream()
.collect(Collectors.groupingBy(String::length,
Collectors.maxBy(Comparator.comparingInt((c) -> (int) charAt(0)))));
System.out.println(collect);
Change String::length to MyData::key and comparator to comparing dates (Collectors.maxBy((MyData d1, Mydate d2) -> d1.getDate().compareTo(d2.getDate()).

Iterate through the list and grouping based on dates

I am trying to achieve, where I am looking for ways to group the list based on the dates. I have a lost of attribute as below:
List<Attribute> attributes
where
Attribute.java is as below
public class Attribute
{
Integer getValue();
List<String> getString();
Date getDate();
}
I am looking for a way such that , while iterating through the list of Attribute , I could create a List of elements (current date) and map of elements based on the dates (in the past) having the same IntegerValue.
My codes goes as below:
List<Attribute> currentElement = new ArrayList<Attribute>();
Map<Integer, List<Attribute>> historicalElement = new HashMap<Integer, List<Attribute>>();
//iterating the entire list
for(final Attribute attribute : attributes)
{
if(attribute.getDate() == currentDate)
{
currentElement.add(attribute);
}
if(attribute.getDate() < currentDate)
{
historicalElement.put(attribute.getValue(), attribute)
}
}
The statement
historicalElement.put(attribute.getValue(), attribute)
wont work beacuse
The method put(Integer, List<Attribute>) in the type Map<Integer,List<Attribute>> is not applicable for the arguments (Integer, Attribute).
Is there any way that we can achieve that map , rather than typing casting to List.
Thanks !!!
Casting to list won't help at all. You'll only get a ClassCastException. Simplest way might be this:
if(attribute.getDate() < currentDate)
{
List<Attribute> list = historicalElement.get(attribute.getValue());
if(list == null){
list = new ArrayList<>();
historicalElement.put(attribute.getValue() , list);
}
list.add(attribute);
}
edit: Paul's answer is better here.
Looks like a job for guava multimap where you can do:
Map<Integer, List<Attribute>> historicalElement = Multimaps.newListMultimap();
for(final Attribute attribute : attributes) {
historicalElement.put(attribute.getValue(), attribute)
}
should do it.
Well, except that you want to group by the date as well? That's slightly trickier.
Firstly you need to fix your date comparison. You don't compare dates using == operator.
Now while adding a new entry in map, you've to first check the existing key. If not there then create a new ArrayList with new value:
if(attribute.getDate().compareTo(currentDate) < 0) {
if (historicalElement.containsKey(attribute.getValue())) {
historicalElement.get(attribute.getValue()).add(attribute);
} else {
historicalElement.put(attribute.getValue(),
new ArrayList<Attribute>(Arrays.asList(attribute)));
}
}
If you're using Java 8, you can directly use Map#merge() method to avoid that extra testing:
if(attribute.getDate().compareTo(currentDate) < 0) {
historicalElement.merge(
attribute.getValue(),
new ArrayList<Attribute>(Arrays.asList(attribute)),
ArrayList::addAll);
}
You can also use Stream API and lambda here:
List<Attribute> currentElement = attributes.stream()
.filter(a -> a.getDate().compareTo(currentDate) == 0)
.collect(Collectors.toList());
Map<Integer, List<Attribute>> historicalElement = attributes.stream()
.filter(a -> a.getDate().compareTo(currentDate) < 0)
.collect(Collectors.groupingBy(Attribute::getValue));
Aren’t you trying to put a single Attribute in a Map where it requires a List of Attributes, here:
//iterating the entire list
for(final Attribute attribute : attributes)
{
if(attribute.getDate() == currentDate)
{
currentElement.add(attribute);
}
if(attribute.getDate() < currentDate)
{
historicalElement.put(attribute.getValue(), attribute) // HERE
}
}
If you want it to be single attribute, this should change:
From: Map<Integer, List<Attribute>> historicalElement = new HashMap<Integer, List<Attribute>>();
To: Map<Integer, List<Attribute>> historicalElement = new HashMap<Integer, Attribute>();

Compare two sets of different types

I'm looking for a way to tell if two sets of different element types are identical if I can state one-to-one relation between those element types. Is there a standard way for doing this in java or maybe guava or apache commons?
Here is my own implementation of this task. For example, I have two element classes which I know how to compare. For simplicity, I compare them by id field:
class ValueObject {
public int id;
public ValueObject(int id) { this.id=id; }
public static ValueObject of(int id) { return new ValueObject(id); }
}
class DTO {
public int id;
public DTO(int id) { this.id=id; }
public static DTO of(int id) { return new DTO(id); }
}
Then I define an interface which does the comparison
interface TwoTypesComparator<L,R> {
boolean areIdentical(L left, R right);
}
And the actual method for comparing sets looks like this
public static <L,R> boolean areIdentical(Set<L> left, Set<R> right, TwoTypesComparator<L,R> comparator) {
if (left.size() != right.size()) return false;
boolean found;
for (L l : left) {
found = false;
for (R r : right) {
if (comparator.areIdentical(l, r)) {
found = true; break;
}
}
if (!found) return false;
}
return true;
}
Example of a client code
HashSet<ValueObject> valueObjects = new HashSet<ValueObject>();
valueObjects.add(ValueObject.of(1));
valueObjects.add(ValueObject.of(2));
valueObjects.add(ValueObject.of(3));
HashSet<DTO> dtos = new HashSet<DTO>();
dtos.add(DTO.of(1));
dtos.add(DTO.of(2));
dtos.add(DTO.of(34));
System.out.println(areIdentical(valueObjects, dtos, new TwoTypesComparator<ValueObject, DTO>() {
#Override
public boolean areIdentical(ValueObject left, DTO right) {
return left.id == right.id;
}
}));
I'm looking for the standard solution to to this task. Or any suggestions how to improve this code are welcome.
This is what I would do in your case. You have sets. Sets are hard to compare, but on top of that, you want to compare on their id.
I see only one proper solution where you have to normalize the wanted values (extract their id) then sort those ids, then compare them in order, because if you don't sort and compare you can possibly skip pass over duplicates and/or values.
Think about the fact that Java 8 allows you to play lazy with streams. So don't rush over and think that extracting, then sorting then copying is long. Lazyness allows it to be rather fast compared to iterative solutions.
HashSet<ValueObject> valueObjects = new HashSet<>();
valueObjects.add(ValueObject.of(1));
valueObjects.add(ValueObject.of(2));
valueObjects.add(ValueObject.of(3));
HashSet<DTO> dtos = new HashSet<>();
dtos.add(DTO.of(1));
dtos.add(DTO.of(2));
dtos.add(DTO.of(34));
boolean areIdentical = Arrays.equals(
valueObjects.stream()
.mapToInt((v) -> v.id)
.sorted()
.toArray(),
dtos.stream()
.mapToInt((d) -> d.id)
.sorted()
.toArray()
);
You want to generalize the solution? No problem.
public static <T extends Comparable<?>> boolean areIdentical(Collection<ValueObject> vos, Function<ValueObject, T> voKeyExtractor, Collection<DTO> dtos, Function<DTO, T> dtoKeyExtractor) {
return Arrays.equals(
vos.stream()
.map(voKeyExtractor)
.sorted()
.toArray(),
dtos.stream()
.map(dtoKeyExtractor)
.sorted()
.toArray()
);
}
And for a T that is not comparable:
public static <T> boolean areIdentical(Collection<ValueObject> vos, Function<ValueObject, T> voKeyExtractor, Collection<DTO> dtos, Function<DTO, T> dtoKeyExtractor, Comparator<T> comparator) {
return Arrays.equals(
vos.stream()
.map(voKeyExtractor)
.sorted(comparator)
.toArray(),
dtos.stream()
.map(dtoKeyExtractor)
.sorted(comparator)
.toArray()
);
}
You mention Guava and if you don't have Java 8, you can do the following, using the same algorithm:
List<Integer> voIds = FluentIterables.from(valueObjects)
.transform(valueObjectIdGetter())
.toSortedList(intComparator());
List<Integer> dtoIds = FluentIterables.from(dtos)
.transform(dtoIdGetter())
.toSortedList(intComparator());
return voIds.equals(dtoIds);
Another solution would be to use List instead of Set (if you are allowed to do so). List has a method called get(int index) that retrieves the element at the specified index and you can compare them one by one when both your lists have the same size. More on lists: http://docs.oracle.com/javase/7/docs/api/java/util/List.html
Also, avoid using public variables in your classes. A good practice is to make your variables private and use getter and setter methods.
Instantiate lists and add values
List<ValueObject> list = new ArrayList<>();
List<DTO> list2 = new ArrayList<>();
list.add(ValueObject.of(1));
list.add(ValueObject.of(2));
list.add(ValueObject.of(3));
list2.add(DTO.of(1));
list2.add(DTO.of(2));
list2.add(DTO.of(34));
Method that compares lists
public boolean compareLists(List<ValueObject> list, List<DTO> list2) {
if(list.size() != list2.size()) {
return false;
}
for(int i = 0; i < list.size(); i++) {
if(list.get(i).id == list2.get(i).id) {
continue;
} else {
return false;
}
}
return true;
}
Your current method is incorrect or at least inconsistent for general sets.
Imagine the following:
L contains the Pairs (1,1), (1,2), (2,1).
R contains the Pairs (1,1), (2,1), (2,2).
Now if your id is the first value your compare would return true but are those sets really equal? The problem is that you have no guarantee that there is at most one Element with the same id in the set because you don't know how L and R implement equals so my advise would be to not compare sets of different types.
If you really need to compare two Sets the way you described I would go for copying all Elements from L to a List and then go through R and every time you find the Element in L remove it from the List. Just make sure you use LinkedList instead of ArrayList .
You could override equals and hashcode on the dto/value object and then do : leftSet.containsAll(rightSet) && leftSet.size().equals(rightSet.size())
If you can't alter the element classes, make a decorator and have the sets be of the decorator type.

Categories

Resources