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()).
Related
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;
});
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.
I want to filter a single array into parts. Suppose I have an ArrayList of String :
Array1 = ["2015-01-06","2015-04-06",
"2016-06-06","2016-01-06",
"2016-05-06","2017-02-06",
"2017-04-06","2017-03-06",
"2015-03-06","2016-04-06",
"2016-02-06","2015-05-06",
"2015-01-06","2016-06-06"]
I want it to filter in new arrays according to their years, so that the output will look like :
arrayA = ["2015-01-06","2015-04-06","2015-03-06","2015-05-06","2015-01-06"]
arrayB = ["2016-06-06","2016-01-06","2016-05-06","2016-04-06","2016-02-06","2016-06-06"]
arrayC = [""2017-02-06","2017-04-06","2017-03-06""]
The arrays are based on years now. I don't know the proper way for accomplishing this task.
The question was edited to include the Android tag, making this inapplicable for the OP since Android doesn't support Java 8 yet, I'll leave it in case it helps someone not using Android though.
Using the Stream API, you can easily build a Map<Integer, List<String>> where each key is a year. You can use groupingBy(classifier) where the classifier extracts the year from the date. In this case, the date is parsed with LocalDate.parse but you could easily extend that by giving your own formatter.
public static void main(String[] args) {
String[] array = {"2015-01-06","2015-04-06",
"2016-06-06","2016-01-06",
"2016-05-06","2017-02-06",
"2017-04-06","2017-03-06",
"2015-03-06","2016-04-06",
"2016-02-06","2015-05-06",
"2015-01-06","2016-06-06"};
Map<Integer, List<String>> map =
Arrays.stream(array)
.collect(Collectors.groupingBy(s -> LocalDate.parse(s).getYear()));
// or a simple s -> Integer.valueOf(s.substring(0, s.indexOf('-'))) in this case
}
You can then access the list you want from that map.
This will create an ArrayList each year
ArrayList<String> array1 = new ArrayList<String>();
Map<String, List<String>> mapForYear = new TreeMap<String, List<String>>();
for(String date : array1)
{
String year = date.substring(0,4);
if(!mapForYear.containsKey(year))
mapForYear.put(year, new ArrayList<String>());
mapForYear.get(year).add(date);
}
That's easy if you use java 8 and Stream API. I wrote the following piece of code:
public List<String> getListByYear(String[] array, int year) {
return Arrays.stream(array)
.filter(s -> s.contains(Integer.toString(year)))
.collect(Collectors.toList());
}
As to the question, there is such solution:
for (String s : array) {
int key = LocalDate.parse(s).getYear();
List<String> value = map.get(key);
map.put(key, value == null
? new ArrayList<String>() { { add(s); } }
: new ArrayList<String>(value) { { add(s); } }
);
}
The same result as #Tunaki provided, but a less pretty. If Java 8 is not supported, it will be as the alternative as the most compact solution.
Output is here:
{
2016 = [2016-06-06, 2016-01-06, 2016-05-06, 2016-04-06, 2016-02-06, 2016-06-06],
2017 = [2017-02-06, 2017-04-06, 2017-03-06],
2015 = [2015-01-06, 2015-04-06, 2015-03-06, 2015-05-06, 2015-01-06]
}
Using the Java 8 Stream package I want to transform a List of arrays of type object into a List of a specific class object. The arrays on the first list contain field of the class loaded from the database.
This is the List of arrays of type object loaded from the DB:
List<Object[]> results = loadFromDB();
Every element Object[] present in the list contains fields that I want to map to the following class:
class DeviationRisk {
Timestamp plannedStart;
Timestamp plannedEnd;
String rcsName;
BigDecimal riskValue;
BigDecimal mediumThreshold;
BigDecimal highThreshold;
Interval interval;
String getRcsName() {
return rcsName;
}
DeviationRisk(Object[] res) {
this((Timestamp) res[0], (Timestamp) res[1], (String) res[2], (BigDecimal) res[3], (BigDecimal) res[4], (BigDecimal) res[5]);
}
DeviationRisk(Timestamp start, Timestamp end, String rcs, BigDecimal risk, BigDecimal medium, BigDecimal high) {
plannedStart = start;
plannedEnd = end;
rcsName = rcs;
riskValue = risk;
mediumThreshold = medium;
highThreshold = high;
interval = new Interval(plannedStart.getTime(), plannedEnd.getTime());
}
DeviationRisk create(Object[] res) {
return new DeviationRisk(res);
}
List<DateTime> getRange() {
return DateUtil.datesBetween(new DateTime(plannedStart), new DateTime(plannedEnd));
}
}
As you can see every element Object[] present in the original list results it's just the array representation of the object DeviationRisk
Now, I know how to do this using loops it's just 3 lines of code as you can see below:
List<DeviationRisk> deviations = new ArrayList<>();
for (Object[] res : results) {
deviations.add(new DeviationRisk(res));
}
How to achieve the same result using Java 8 Streams?
You can try with:
List<DeviationRisk> result = results.stream()
.map(DeviationRisk::new)
.collect(Collectors.toList())
This will do it:
final List<DeviationRisk> l = results.stream()
.map(DeviationRisk::new).collect(Collectors.toList());
This work because DeviationRisk::new is viewed as a Function<Object[], DeviationRisk> by the compiler.
I have an Array of Hashmap and each hashmap contain 24 hour time as key-value pair.
I want to sort this array in ascending order of time. how can i achieve this?
here is snippet of my code:
HashMap[] arr = new HashMap[100];
for(int i=0;i<100;i++) {
HashMap<String,String> child=new HashMap<String,String>();
child.put("some_time","21:09"); //time changes per iteration(time is in 24-hour format)
arr[i]=child;
}
Here is the full code that will sort the array on time which is in hh:mm format:
HashMap<String,String>[] harr = new HashMap[10];
final DateFormat df = new SimpleDateFormat("kk:mm");
// prepare your data
for(int i=0;i<harr.length;i++) {
HashMap<String,String> child=new HashMap<String,String>();
int ss = (int)(Math.random() * (59 + 1));
//time changes per iteration(time is in 24-hour format)
child.put("some_time", String.format("21:%02d", ss));
harr[i]=child;
}
System.out.printf("map array is: %s%n", Arrays.deepToString(harr));
// now apply sort using a custom method
Arrays.sort(harr, new Comparator<HashMap<String,String>>() {
public int compare(HashMap<String,String> o1, HashMap<String,String> o2) {
String t1 = o1.get("some_time");
String t2 = o2.get("some_time");
try {
Date dt1 = df.parse(t1);
Date dt2 = df.parse(t2);
return dt1.compareTo(dt2);
} catch (ParseException e) {
e.printStackTrace();
}
return 0;
}
});
System.out.printf("sorted map array is: %s%n", Arrays.deepToString(harr));
You can use Arrays.sort(T[], Comparator<T>). This allows you to pass an array of any type and write your own custom comparator method like this:
Arrays.sort(arr, new Comparator<HashMap>() {
public int compare(HashMap o1, HashMap o2) {
// Compare values you're interested in and return int as specified by Comparator API
}
});
See the API for details on what to return.
Before proceeding with this approach, do think about the comments and decide whether the array of hashmaps is the right way to go. If, as I pointed out, you have a bunch of maps, each containing large amounts of information, with one entry being your dates, then this may be the right thing to do, in which case the easiest way to sort the array would be to use Arrays.sort method:
HashMap[] arr=new Hashmap[100];
for(int i=0;i<100;i++){
HashMap<String,String> child=new HashMap<String,String>();
... // put all the info into the HashMap
child.put("some_time","21:09"); //time changes per iteration(time is in 24-hour format)
arr[i]=child;
}
Arrays.sort(arr, new Comparator<HashMap>() {
public int compare(HashMap o1, HashMap o2) {
String d1 = o1.get("some_time");
String d2 = o2.get("some_time");
//compare the two dates. If you're always in the same format, e.g. HH:MM (24 hours, two-digit hour, two-digit year), you might even be able to simply compare strings:
return d1.compareTo(d2);
}
});
The general approach is to write a Comparator to order a pair of your HashMap objects based on the key, and then pass that as a parameter to the Arrays.sort(T[], Comparator<T>) method.
Th comparator would look something like this:
Comparator<HashMap> DATE_ORDER = new Comparator<HashMap>() {
public int compare(Comparator<HashMap>h1, Comparator<HashMap>h2) {
String time1 = h1.get("some_time");
String time2 = h2.get("some_time");
return time1.compareTo(time2); // assuming that the time strings
// can be ordered that way
}
};
Having said that, your problem has the "smell" of trying to use Maps when they should really be writing custom classes.
As Bhavik points out, you may not be using the JDK to it's full potential - have a look at SortedMap which may be just what you're looking for; possibly with your own implementation of a Comparator.
SortedMap arr = new TreeMap<String,HashMap<String,String>>();
for ( int i=0 ; i<100 ; i++ )
{
Map<String,String> child = HashMap<String,String>();
child.put( "some_time" , "21:09" );
arr.put( "21:09" , child );
}
then you can use arr.values().iterator() to get your sorted children.
Cheers,