2
\$\begingroup\$

I'm implementing argument check in a method where one parameter is a List of LocalDate objects (from Joda-Time). I want to make sure that List contains at most one LocalDate per month.

public List<LocalDate> generateAllDates(List<LocalDate> firstYearDates, ...) { }

Thus, for example

Arrays.asList(
    new LocalDate(2012, 3, 25),
    new LocalDate(2012, 4, 30),
    new LocalDate(2013, 3, 26)
)

would be an illegal argument because there are two dates whose month is March.

Currently I have something like this (making use of Guava's Multimap):

Multimap<Integer, LocalDate> monthToDates = HashMultimap.create();
for (LocalDate date : firstYearDates) {
    monthToDates.put(date.getMonthOfYear(), date);
}
for (Integer i : monthToDates.keySet()) {
    if (monthToDates.get(i).size() > 1) {
        throw new IllegalArgumentException(
            "firstYearDates must contain at most one entry per month");
    }
}

Question is, how would you simplify that? Using any API features of JDK 1.6, Guava or Joda-Time is fine.

\$\endgroup\$
3
\$\begingroup\$

Bit late but there is Guava built-in method for that - Maps.uniqueIndex - given:

List<LocalDate> dates = Arrays.asList(
     new LocalDate(2012,3,25), new LocalDate(2012,4,30), new LocalDate(2013,3,26);

with JDK 8:

Maps.uniqueIndex(dates, LocalDate::getMonthOfYear);

or with JDK <7:

Maps.uniqueIndex(dates, new Function<LocalDate, Integer>() {
    @Override
    public Integer apply(final LocalDate date) {
      return date.getMonthOfYear();
    }
});

It will throw

java.lang.IllegalArgumentException: duplicate key: 3

in this example.

\$\endgroup\$
1
\$\begingroup\$

I understood that you only need to know if a month has been found before or not. I wouldn't then go through the trouble of using a Multimap if there is no other reason for it.

boolean[] monthFound = new boolean[12];
for(LocalDate date : firstYearDates) {
  if(monthFound[date.getMonthOfYear() - 1]) {
     throw new IllegalArgumentException(
            "firstYearDates must contain at most one entry per month");
  } else {
     monthFound[date.getMonthOfYear() - 1] = true;
  }
}

If you need the map then just check containsKey(month) before each put and throw an exception if the method returns true.

If you just want to use Guava then one way of doing this might be

Function<LocalDate, Integer> months = new Function<LocalDate, Integer>() {    
      public Integer apply(LocalDate date) { return date.getMonthOfYear(); }
};

if(new HashSet(Collections2.transform(firstYearDates, months)).size() < firstYearDates.size()) {
   throw new IllegalArgumentException(
            "firstYearDates must contain at most one entry per month");
}
\$\endgroup\$
  • \$\begingroup\$ +1. I guess I sometimes get carried away by Guava's fancy data structures and become blind to plain old Java options like boolean arrays. Good point about containsKey too. If fact, I'll probably use a plain map and containsKey, as I'm not a fan of array indexing minutiae [ date.getMonthOfYear() - 1 ] if it can be avoided. \$\endgroup\$ – Jonik Jul 18 '11 at 12:53
  • \$\begingroup\$ You can create a 13 element array if you are not starving for memory :) You could also use bit arrays if you want to go old school. \$\endgroup\$ – Aleksi Yrttiaho Jul 18 '11 at 13:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.