waylonion
waylonion

Reputation: 6976

Java sort an array of months into multiple arrays by month

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

Answers (3)

Basil Bourque
Basil Bourque

Reputation: 339342

Joda-Time

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]}

java.time

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

Andreas
Andreas

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

Feiyu Zhou
Feiyu Zhou

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

Related Questions