Reputation: 6976
I have an array in Java containing a set of random dates:
{ January 20 2015, February 12 2015, February 20 2015, June 21 2015, July 12 2015, July 28 2015, July 30 2015, September 24 2015, December 31 2015 }
How do I split this array into multiple arrays by month?
I would want
{ {January 20 2015}, {February 12 2015, February 20 2015}, {June 21 2015}, {July 12 2015, July 28 2015, July 30 2015}, {September 24 2015}, {December 31 2015} }
I could iterate through the entire array and checking if the next date is still within the same month and then add it to the sub array if it is. However I was wondering if there was a more succinct or efficient method.
Edit:
Additionally, I need to sort by year and month so, for example, January 15 2014 and January 23 2015 should not be combined.
Here's a method I came up with but it doesn't look terribly efficient:
private void splitListByMonth(){
ArrayList<ArrayList<Homework>> mainArrayList = new ArrayList<>();
ArrayList<String> titleList = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
SimpleDateFormat dateFormat = new SimpleDateFormat("MMMM yyy");
for(Homework homework:mList){
calendar.setTimeInMillis(homework.getDate());
String monthString = dateFormat.format(calendar.getTime());
if(titleList.contains(monthString)){
int index = titleList.indexOf(monthString);
mainArrayList.get(index).add(homework);
} else {
titleList.add(monthString);
int index = titleList.indexOf(monthString);
mainArrayList.get(index).add(homework);
}
}
Log.d("Tag",""+titleList);
Log.d("Tag",""+mainArrayList);
}
Upvotes: 6
Views: 2549
Reputation: 339342
For Android, you should be using the Joda-Time library rather than the old java.util.Date/.Calendar classes that have proven to be so troublesome. Note that some alternate editions of Joda-Time have been released to workaround an Android problem with initial slowness.
Joda-Time includes a class YearMonth
, just what we need to represent the year and month as a key to tracking the date values. Joda-Time also has a class LocalDate
to represent a date-only value without any time-of-day or time zone.
We define a formatter with pattern "MMMM dd yyyy"
to parse the strings. Note that we specify a Locale with English language on the formatter so this code will run successfully on JVMs where the current default Locale has a language other than English. Than language applies when parsing the names of the months "January", "February", and so on.
We collect the LocalDate values as a SortedSet
which fulfills two purposes, (a) eliminates duplicates, and (b) keeps the dates sorted. Our implementation of SortedSet
is a TreeSet
. Each set object is assigned to a YearMonth
object. A TreeMap
tracks which YearMonth
has which set of dates. We use a TreeMap
rather than HashMap
to keep the keys in sorted order, as it implements SortedMap
. If you had enormous numbers of elements and sorting by key was not critical, then HashMap might be a better choice for performance.
String[] input = { "January 20 2015" , "February 12 2015" , "February 20 2015" , "June 21 2015" , "July 12 2015" , "July 28 2015" , "July 30 2015" , "September 24 2015" , "December 31 2015" };
Map<YearMonth , SortedSet<LocalDate>> map = new TreeMap<>();
DateTimeFormatter formatter = DateTimeFormat.forPattern( "MMMM dd yyyy" ).withLocale( Locale.ENGLISH );
for ( String string : input ) {
LocalDate localDate = formatter.parseLocalDate( string );
YearMonth yearMonth = new YearMonth( localDate );
if ( ! map.containsKey( yearMonth ) ) { // If this is the first encounter with such a year-month, make a new entry.
map.put( yearMonth , new TreeSet<>() );
}
map.get( yearMonth ).add( localDate );
}
Dump to console.
System.out.println( "input: " + Arrays.toString( input ) );
System.out.println( "map: " + map );
When run.
input: [January 20 2015, February 12 2015, February 20 2015, June 21 2015, July 12 2015, July 28 2015, July 30 2015, September 24 2015, December 31 2015]
map: {2015-01=[2015-01-20], 2015-02=[2015-02-12, 2015-02-20], 2015-06=[2015-06-21], 2015-07=[2015-07-12, 2015-07-28, 2015-07-30], 2015-09=[2015-09-24], 2015-12=[2015-12-31]}
In Java 8 and later, you can use the new built-in java.time framework. Joda-Time provided the inspiration for that framework. The code would be very similar in the case of this Answer.
Upvotes: 0
Reputation: 159135
You're on the right track, but stringifying the year/month is the slow way, just track the year and month:
@SuppressWarnings("null")
private static List<List<Date>> splitByMonth(Date ... dates) {
List<List<Date>> datesByMonth = new ArrayList<>();
List<Date> monthList = null;
int currYear = 0, currMonth = -1;
Calendar cal = Calendar.getInstance();
for (Date date : dates) {
cal.setTime(date);
if (cal.get(Calendar.YEAR) != currYear || cal.get(Calendar.MONTH) != currMonth) {
monthList = new ArrayList<>();
datesByMonth.add(monthList);
currYear = cal.get(Calendar.YEAR);
currMonth = cal.get(Calendar.MONTH);
}
monthList.add(date);
}
return datesByMonth;
}
Note that the parameter must be pre-sorted. The question + comments were a bit unclear on that point.
Test code
public static void main(String[] args) throws Exception {
// Build list of all dates
String[] txtDates = { "January 20 2015", "February 12 2015", "February 20 2015", "June 21 2015",
"July 12 2015", "July 28 2015", "July 30 2015", "September 24 2015", "December 31 2015",
"January 15 2014", "January 15 2015" };
SimpleDateFormat fmt = new SimpleDateFormat("MMMM d yyyy");
Date[] allDates = new Date[txtDates.length];
for (int i = 0; i < txtDates.length; i++)
allDates[i] = fmt.parse(txtDates[i]);
// Sort dates, then split them by month
Arrays.sort(allDates);
List<List<Date>> datesByMonth = splitByMonth(allDates);
// Print result
for (List<Date> dates : datesByMonth) {
StringBuilder buf = new StringBuilder();
for (Date date : dates) {
if (buf.length() != 0)
buf.append(", ");
buf.append(fmt.format(date));
}
System.out.println(buf);
}
}
Output
January 15 2014
January 15 2015, January 20 2015
February 12 2015, February 20 2015
June 21 2015
July 12 2015, July 28 2015, July 30 2015
September 24 2015
December 31 2015
Upvotes: 2
Reputation: 4544
Using java 8 Collectors.groupingBy, it return a map, then get values toList.
List<List<Date>> partitions = dates.stream().collect(Collectors.groupingBy(e -> e.getYear() * 100 + e.getMonth(), TreeMap::new, Collectors.toList())).values().stream().collect(Collectors.toList());
Upvotes: 0