I need to write a daymanagerr that assigns time slots for orders in a restaurant.
That's the daymanager:
public DayManager (LocalDate date, int numberOfTimeSlots, int capacityPerSlot) {
this.date = date;
this.capacityPerSlot = capacityPerSlot;
this.numberOfTimeSlots = numberOfTimeSlots;
For each day I can choose the number of available slots and the capacity per slot (so if I choose 3 slots and a capacity of 3 per slot, that's 9 slots in total).
Every customer can state their preferred time slot (here: 0, 1 or 2) with their order.
Here's my class for adding orders:
public Optional<Integer> addOrder(Order order, int preferredSlot) {
int givenSlot = 0, count = 1;
List<Integer> slots = new ArrayList<Integer>();
List<Integer> slotsPerSlot = new ArrayList<Integer>();
if ((slots.size() * slotsPerSlot.size()) <= (numberOfTimeSlots * capacityPerSlot)) {
if (slots.contains(preferredSlot) == false) {
givenSlot = preferredSlot;
slots.add(preferredSlot);
slotsPerSlot.add(count);
count++;
} else if (slotsPerSlot.size() <= capacityPerSlot) {
givenSlot = preferredSlot;
slots.add(preferredSlot);
slotsPerSlot.add(slotsPerSlot.size() + 1);
} else {
givenSlot = slots.get(slots.lastIndexOf(count));
}
return Optional.of(givenSlot);
}
return Optional.empty();
}
What I need help with:
With every new order I get, I need to check if there is still capacity left in that customers preferred slot. If there is capacity left, I assign that slot to him. If there is no capacity left in that slot, I assign the slot with the next lowest index (and available capacity). If there is no capacity left for that day, I simply return nothing.
I just can't figure out how to create a slotsPerSlot list for each slot and additionally I don't really know, how I could get the next lowest slot number.
Multimap
You could use a multimap. A Map is a pairing of key that leads to a value. In a multimap, the value is actually a collection of values rather than a single value.
Imagine, for example, a hierarchy of three time slots where the first and last contain an empty list (no orders yet), while the middle time slot has a list of a single order.
2022-01-23T13:00-07:00[America/Edmonton]
[]
2022-01-23T14:00-07:00[America/Edmonton]
[Order[id=2d8e5cc2-26ac-474d-a081-2c71207fd6c5, customerName=Basil]]
2022-01-23T15:00-07:00[America/Edmonton]
[]
(Minor detail: The 2d8e5cc2-26ac-474d-a081-2c71207fd6c5 text is a hexadecimal string representation of a 128-bit UUID value used to identify that particular order.)
Example code
Here is some example code. This code is incomplete, but will get you going in the right direction.
First, define our Order class. We will make it a record, using the new feature in Java 16. You could just as well define a conventional class, but a record is so much briefer. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString.
package work.basil.example.orders;
import java.util.UUID;
public record Order(UUID id , String customerName)
{
}
Define the DayManager class.
The constructor is the place to set up your data structure for tracking orders. You were doing that work inside your addOrder method which makes no sense.
The goal of the constructor is to populate a NavigableMap, a Map that maintains its keys it a sorted order. We use TreeMap as the concrete implementation of the NavigableMap interface.
The keys to our map are ZonedDateTime, a date with time-of-day within the context of a time zone. Each ZonedDateTime object is the start of each time slot. I use this approach rather than your mere integer number to identify each slot.
The values of our map are a list of Order objects. As we add orders to our tracking system, they land in one of these lists. Each list is tied to a ZonedDateTime object as the time slot in our map. The problem with using a List is that the class is meant to be a resizable collection. So our code here manages the size limits, checking the current size to get a count of how many orders are already present in the list. We compare that count to our capacity-per-time-slot number we keep as a member field on DayManager class.
If we are under capacity, we add our order to the list, and return an Optional containing the ZonedDateTime to identify the time slot.
If we are at capacity, we need to move on to the next time slot — this I leave as an exercise for the reader.
Hint: We have a ZonedDateTime in hand, the current key used to access our map. So if we add the Duration stored as a member field, we will obtain the next key in our map. Use that key to get the next list of orders. Lather, rinse, repeat, until moving past the last time slot of the day.
By the way, in real work, I would look to a third-party library such as Eclipse Collections or Google Guava for a fixed-size list class. Better to rely on code that is already written and tested than rely on our size-checking code here.
We pre-populate our map with time slots and empty lists in the constructor. So we have a data structure in place when we begin adding orders.
The addOrder method looks for the requested time slot as the key in the map. Doing a get on the map returns a List of orders for us to inspect.
package work.basil.example.orders;
import java.time.*;
import java.util.*;
public class DayManager
{
// Member fields.
final ZoneId zoneId;
final LocalDate workDate;
final LocalTime startTime;
final Duration timeSlice;
final int numberOfTimeSlots, capacityPerTimeSlot;
private final NavigableMap < ZonedDateTime, List < Order > > ordersPerTimeSlot;
// Constructor
public DayManager ( final ZoneId zoneId , final LocalDate localDate , final LocalTime startTime , final Duration timeSlice , final int numberOfTimeSlots , final int capacityPerSlot )
{
this.zoneId = zoneId;
this.workDate = localDate;
this.startTime = startTime;
this.timeSlice = timeSlice;
this.numberOfTimeSlots = numberOfTimeSlots;
this.capacityPerTimeSlot = capacityPerSlot;
this.ordersPerTimeSlot = new TreeMap <>();
this.populateMap();
}
// Subroutine.
private void populateMap ( )
{
ZonedDateTime start = ZonedDateTime.of( this.workDate , this.startTime , this.zoneId );
for ( int i = 0 ; i < this.numberOfTimeSlots ; i++ )
{
Duration d = this.timeSlice.multipliedBy( i );
ZonedDateTime zdt = start.plus( d );
List < Order > list = new ArrayList <>( this.capacityPerTimeSlot );
this.ordersPerTimeSlot.put( zdt , list );
}
System.out.println( "this.ordersPerTimeSlot = " + this.ordersPerTimeSlot );
}
// Business logic.
public Optional < ZonedDateTime > addOrder ( final Order order , final ZonedDateTime zdt )
{
List < Order > orders = this.ordersPerTimeSlot.get( zdt );
if ( Objects.isNull( orders ) ) { return Optional.empty(); }
if ( orders.size() > this.capacityPerTimeSlot )
{
String msg = "ERROR - Capacity per time slot exceeded. ";
System.out.println( msg );
throw new IllegalStateException( msg );
} else if ( orders.size() == this.capacityPerTimeSlot )
{
String msg = "INFO - This time slot filled.";
System.out.println( msg );
throw new IllegalStateException( msg );
} else if ( orders.size() < this.capacityPerTimeSlot )
{
// Room in this time slot to place order.
orders.add( order );
return Optional.of( zdt );
} else
{
String msg = "ERROR - Should never reach this point. Error in IF-THEN logic of adding orders.";
System.out.println( msg );
throw new IllegalStateException( msg );
}
}
// Debugging
public String dumpOrders ( )
{
return this.ordersPerTimeSlot.toString();
}
}
Here is an App class for exercising our DayManager & Order classes.
package work.basil.example.orders;
import java.time.*;
import java.util.Optional;
import java.util.UUID;
public class App
{
public static void main ( String[] args )
{
ZoneId z = ZoneId.of( "America/Edmonton" );
LocalDate ld = LocalDate.of( 2022 , Month.JANUARY , 23 );
DayManager dm = new DayManager( z , ld , LocalTime.of( 13 , 0 ) , Duration.ofHours( 1 ) , 3 , 3 );
Order order = new Order( UUID.fromString( "2d8e5cc2-26ac-474d-a081-2c71207fd6c5" ) , "Basil" );
Optional < ZonedDateTime > optionalTimeSlot = dm.addOrder( order , ZonedDateTime.of( ld , LocalTime.of( 14 , 0 ) , z ) );
System.out.println( "order = " + order );
System.out.println( "optionalTimeSlot.toString() = " + optionalTimeSlot );
System.out.println( dm.dumpOrders() );
}
}
When run.
this.ordersPerTimeSlot = {2022-01-23T13:00-07:00[America/Edmonton]=[], 2022-01-23T14:00-07:00[America/Edmonton]=[], 2022-01-23T15:00-07:00[America/Edmonton]=[]}
order = Order[id=2d8e5cc2-26ac-474d-a081-2c71207fd6c5, customerName=Basil]
optionalTimeSlot.toString() = Optional[2022-01-23T14:00-07:00[America/Edmonton]]
{2022-01-23T13:00-07:00[America/Edmonton]=[], 2022-01-23T14:00-07:00[America/Edmonton]=[Order[id=2d8e5cc2-26ac-474d-a081-2c71207fd6c5, customerName=Basil]], 2022-01-23T15:00-07:00[America/Edmonton]=[]}
Related
As you can see, it's an ArrayList of objects and I need each of these objects to count their province attributes based on men and women
class Registro {
String region;
String province;
String gender;
}
List<Registro> reg = new ArrayList<Registro>();
reg.add(new Registro("China","Beigin","male"));
reg.add(new Registro("China","Beigin","female"));
reg.add(new Registro("China","x","male"));
reg.add(new Registro("China","x","female"));
reg.add(new Registro("China","x","male"));
reg.add(new Registro("EEUU","SB","female"));
reg.add(new Registro("EEUU","SB","female"));
reg.add(new Registro("EEUU","CAL","male"));
reg.add(new Registro("EEUU","CAL","male"));
The desired output
/*
China, Beigin, 1 male, 1 female
China, x , 2 male, 1 female
EEUU, SB , 0 male, 2 female
EEUU, CAL , 2 male, 0 female */
A Map for each sex
One quick and dirty approach would be a pair of Map objects, for male and female each, where the key is a combination of your country and region while the value is an Integer for the count of elements found.
Map< String , Integer > females = new TreeMap<>() ;
Map< String , Integer > males = new TreeMap<>() ;
Loop your collection of Registro objects. For each Registro object, get the country and region, and concatenate. Use some arbitrary character as a delimiter, such as a pipe | (VERTICAL LINE), so you can later pull country and region apart (if need be). That string is the key in your map.
The value in the map, the Integer object, is a count of Registro objects for that country-region in that sex. See this Answer to learn about using Map::merge to increment the map’s Integer value using a method reference, Integer::sum.
String key = registro.getCountry() + "|" + registro.getRegion() ;
if( registro.getSex().equals( "female" ) )
{
females.merge( key , 1 , Integer::sum ) ;
}
else if( registro.getSex().equals( "male" ) )
{
males.merge( key , 1 , Integer::sum ) ;
}
else // defensive programming
{
… handle error condition, illegal state.
}
To build your report, get a distinct set of all keys from both maps. Use a Set for this, as a set eliminates duplicates. The Set returned by Map::keySet is actually a view onto the map rather than a separate collection. So we pass that set for one sex to constructor of a new and separate set. For the other sex, we call Set::addAll. Thus we combine the two maps’ keys into a single distinct set.
We use a TreeSet to keep the keys in natural order, matching the natural order used by the TreeMap.
Set< String > keys = new TreeSet<>( females.keySet() ) ; // Build a fresh independent set from the set returned by `Map::keySet`.
keys.addAll( males.keySet() ) ; // Add in all the keys from the other map, automatically eliminating duplicates.
To build your report, loop the keys set. For each key, call females.getKey to get back a count, and call males.getKey to get back a count. If either returns a null, report zero for that sex in that country-region.
for( String key : keys ){
Integer countFemales = Objects.requireNonNullElse( females.getKey( key ) , new Integer( 0 ) ) ;
Integer countMales = Objects.requireNonNullElse( males.getKey( key ) , new Integer( 0 ) ) ;
String output = key + " = " + countFemales + " females, " + countMales + " males." ;
}
China|Beigin = 13 females, 7 males.
The approach described here assumes your collection of Registro objects is not being modified by another thread.
I expect there are other ways to solve your problem, likely more elegant no doubt. But this should get the job. I would use this approach myself.
By the way, here is a graphic table I made showing the attributes of various Map implementations.
I'm making a program for expense statistics over a month; however, I don't know what I should do in the for loop in order to make it initialize each MonthlyExpensePeriods with the corresponding name of that month in the correct order?
for(int i = 0; i < 12; i++){
monthlyExpensePeriods[i] = new MonthlyExpensePeriods(month's name);
}
You could use DateFormatSymbols.getMonths():
String[] monthNames = new DateFormatSymbols().getMonths();
for(int i = 0; i < 12; i++){
monthlyExpensePeriods[i] = new MonthlyExpensePeriods(monthNames[i]);
}
tl;dr
Use Month enum objects rather than mere text of name of month.
for(int i = 1; i <= 12; i++){
monthlyExpensePeriods[i] = new MonthlyExpensePeriod( Month.of( i ) );
}
Use smart objects, not dumb strings
Using strings for special values, such as tracking each month of the year, is fragile and error-prone.
Month
Instead, use objects.
In Java 8 and later, we have the Month enum class built-in. This enum predefines a dozen objects, one for each month of the year, each assigned to a named constant.
Your class MonthlyExpensePeriod should hold a member variable of type Month.
public class MonthlyExpensePeriod {
// Member fields
public Month month ;
…
// Constructor
public MonthlyExpensePeriod( final Month monthArg ) {
this.month = monthArg ;
…
}
}
To use that class and its constructor, pass one of the named constants.
new MonthlyExpensePeriod ( Month.MARCH )
You can retrieve one of the Month objects by month number. Notice the sane numbering, 1-12 for January-December.
Month month = Month.of( 3 ) ; // Month.MARCH
So your example code would look like the following. Change your for loop to count 1-12.
for(int i = 1; i <= 12; i++){
monthlyExpensePeriods[i] = new MonthlyExpensePeriod( Month.of( i ) );
}
Collections
You may want to use Java Collections rather than mere arrays.
List< MonthlyExpensePeriod > periods = new ArrayList<>( 12 ) ;
for(int i = 1; i <= 12; i++){
MonthlyExpensePeriod period = new MonthlyExpensePeriod( Month.of( i ) ) ;
periods.add( period );
}
Streams
I suppose we could get fancy and use streams. But in this case I do not see any added-value.
Calling Month.values() returns an array of all the objects defined on the enum, in the order in which they were defined. From that, we can generate a stream by calling the utility class method Arrays.stream.
List < MonthlyExpensePeriod > periods =
Arrays.stream( Month.values() )
.map( month -> new MonthlyExpensePeriod( month ) )
.collect( Collectors.toList() )
;
Or as a single-line of code.
List < MonthlyExpensePeriod > periods = Arrays.stream( Month.values() ).map( month -> new MonthlyExpensePeriod( month ) ).collect( Collectors.toList() );
You might want to make an unmodifiable copy of your list.
List < MonthlyExpensePeriod > periodsUnmod = List.copyOf( periods );
Or make the original list unmodifiable, by calling Collectors.toUnmodifiableList(), as discussed here.
List < MonthlyExpensePeriod > periods =
Arrays
.stream( Month.values() )
.map( month -> new MonthlyExpensePeriod( month ) )
.collect( Collectors.toUnmodifiableList() )
;
Generating display text
You may want to display the name of the month.
Calling Month::toString generates text of the name in English in all caps. Instead you can automatically localize.
To localize, pass a TextStyle object to signal how long or abbreviated you want the name. Notice that TextStyle offers STANDALONE variations used in some languages (not English) where the month name appears outside the context of a date.
And pass a Locale to determine the human language and cultural norms to use in localizing.
Locale locale = Locale.CANADA_FRENCH ; // Or Locale.US and so on.
String output = Month.MARCH.getDisplayName( TextStyle.FULL , locale ) ;
I am facing a validation that get's my head smoking quite a bit.
I have an Object which I call Downtime for now and it looks like this:
public class Downtime {
/** The start date of the downtime. */
private ZonedDateTime downtimeFrom;
/** The end date of the downtime. */
private ZonedDateTime downtimeTo;
/**
* Gets the downtime from.
*
* #return the downtime from
*/
public ZonedDateTime getDowntimeFrom()
{
return downtimeFrom;
}
/**
* Gets the downtime to.
*
* #return the downtime to
*/
public ZonedDateTime getDowntimeTo()
{
return downtimeTo;
}
/**
* Sets the downtime from.
*
* #param downtimeFrom the new downtime from
*/
protected void setDowntimeFrom( ZonedDateTime downtimeFrom )
{
this.downtimeFrom = downtimeFrom;
}
/**
* Sets the downtime to.
*
* #param downtimeTo the new downtime to
*/
protected void setDowntimeTo( ZonedDateTime downtimeTo )
{
this.downtimeTo = downtimeTo;
}
}
When I create a new downtime over a CRUD implementation, I already validate that the start time is actually before the end time and such things.
Now I have to add the validation that when I create a new Downtime, it doesn't interfere with the already created downtimes. Meaning the start date of the new downtime is not already existent and is not in another downtime. ( between the start and end of an already created downtime ).
So my way of doing this right now, since I am terrible at date/time oriented things when it comes to localization, would be something like this:
private boolean isNewDowntimeValid(Downtime newDowntime, List<Downtime> createdDowntimes){
// let's just assume I already filtered out that the list only contains the same day. That's actually pretty easy.
List<ZonedDateTime> dateRange = new LinkedList<>();
ZonedDateTime newTime = newDowntime.getDowntimeFrom();
for(Downtime downtime : createdDowntimes){
ZonedDateTime downtimeStart = downtime.getDowntimeFrom();
ZonedDateTime downtimeEnd = downtime.getDowntimeTo();
for(ZonedDateTime start = downtimeStart; !start.isAfter(downtimeEnd); start = start.plusHours(1)){
dateRange.add(start);
}
}
if(dateRange.contains(newTime)){
return false;
}
return true;
}
The code is written out of my head in here, so there might be syntax errors but I think you can get the idea of what I want.
And now to my question.
This code above seems like such an overhead and I would like to know how I can validate it faster and with less code.
EDIT:
Let me provide a clear Example
I have a List of Downtimes like this:
List<Downtime> createdDowntimes = [
{
start:2015-01-10T00:00Z,
end:2015-01-10T02:00Z
},
{
start:2015-01-10T04:00Z,
end:2015-01-10T06:00Z
},
{
start:2015-01-10T07:00Z,
end:2015-01-10T09:00Z
}
]
and then I have the new Downtime I want to create:
Downtime newDowntime =
{
start:2015-01-10T05:00Z,
end:2015-01-10T05:30Z
}
In this example the new downtime is not valid since it actually is during a period that is already in another already created downtime.
Hopefully it makes things more clear.
EDIT 2:
While the marked duplicate consists of the reason and offers a solution I also want to give credits to Hugo who provided a good answer with considering my criterias.
Here is another solution I prepared which offers a lot of more detailed exception and information handling
/*
* Collision 1 = the new downtime starts before the created ones but ends in their span
* Collision 2 = the new downtime starts after created ones and also ends after their span
* Collision 3 = the new downtime starts after created ones and ends in their span
*/
List<Downtime> collision1 = createdDowntimes.stream().filter( e -> e.getDowntimeFrom().isAfter( newTimeStart ) )
.filter( e -> e.getDowntimeTo().isAfter( newTimeEnd ) ).collect( Collectors.toList() );
List<Downtime> collision2 = createdDowntimes.stream().filter( e -> e.getDowntimeFrom().isBefore( newTimeStart ) )
.filter( e -> e.getDowntimeTo().isBefore( newTimeEnd ) ).collect( Collectors.toList() );
List<Downtime> collision3 = createdDowntimes.stream().filter( e -> e.getDowntimeFrom().isBefore( newTimeStart ) )
.filter( e -> e.getDowntimeTo().isAfter( newTimeEnd ) ).collect( Collectors.toList() );
Keep in mind that my "solution" is one of many and also pretty intensive in terms of performance since streams are heavy operations. So if you don't need to know exactly how many collided and why they collided consider Hugo's answer.
Considering that:
Meaning the start date of the new downtime is not already existent and is not in another downtime. (between the start and end of an already created downtime).
In this case, you need to compare the startDate (downtimeFrom) of the new Downtime with all the existent Downtime's:
private boolean isNewDowntimeValid(Downtime newDowntime, List<Downtime> createdDowntimes) {
// the start of the new downtime
ZonedDateTime newStartTime = newDowntime.getDowntimeFrom();
for (Downtime downtime : createdDowntimes) {
ZonedDateTime downtimeStart = downtime.getDowntimeFrom();
ZonedDateTime downtimeEnd = downtime.getDowntimeTo();
if (newStartTime.equals(downtimeStart)) {
// start date of new downtime already exists
return false;
}
// start date of new downtime is in the existent downtime
// (existent startDate < new startDate and new startDate < existent endDate)
if (downtimeStart.isBefore(newStartTime) && newStartTime.isBefore(downtimeEnd)) {
return false;
}
}
// no invalid cases found, it's valid
return true;
}
Note:
In this code, the Downtime is valid if the new startDate is equals the end of an existent Downtime. If you don't want that, you can change the second if to:
if (downtimeStart.isBefore(newStartTime) && (! newStartTime.isAfter(downtimeEnd))) {
return false;
}
Check this question
Or have this in mind: two periods will overlap if and only if
(StartA <= EndB) and (EndA >= StartB)
So you would have to check your new Downtime against all the previous one you have
suppose you have to LocalTime objects,
endA represents when the event-A ends and
beginB represents when the event-B begins
then
LocalTime endA = ...;//LocalTime.of(13, 00, 00);
LocalTime beginB = ...;//LocalTime.of(12, 00, 00);
Duration duration = Duration.between(endA, beginB);
System.out.println(duration.getSeconds());
if getSeconds() is negative, then they overlap
List of dates are as below (The list can be in any order):
3-Jan to 31-Mar, 2-Apr to 30-Jun, 1-Jul to 30-Sep, 4-Oct to 31-Dec
Current Date is: 19-Feb
Can someone please help me with the logic?
My approach is:
if(the given date should be greater than start date and less than end date){//this gives current quarter}else if(difference of the month of current date from the end date of each object should be less than or equal to 5)
i am hard coding the condition less than 5, which may break if in future the range of date will be of 4 months
Second approach is:
we can sort the list in ascending order and can get the current quarter index by comparing with current date and the next quarter will be of next index. But the complexity will be more.
I tried below code, but it gives only current quarter date. I am not able to get next quarter considering there would be only 3 objects and current date month is feb.
public static List getCurrentQtrOffr(List detail,Date currentDate) throws ParseException{
int currentQuarter = 9999, diff1;
int nextquarter = 9999, diff2;
Detail detail1;
Detail detail2;
Detail detail3 = null;
Detail detail4 = null;
Iterator<Detail> iterator = detail.iterator();
List<Detail> list = new ArrayList<Detail>();
while(iterator.hasNext()){
detail1 = iterator.next();
diff1 = getDiff(currentDate,detail1.startTime());
if(diff1>0){
if(iterator.hasNext()){
detail2 = iterator.next();
}else{
detail2 = null;
}
if(detail2 != null){
diff2 = getDiff(currentDate,detail2.startTime());
if(diff1 < diff2 ){
if(currentQuarter > diff1){
nextquarter = currentQuarter;
currentQuarter = diff1;
//how to assign detail3 before updating it with next minimum value, as if there will be only 3 object and flow comes in this if block then detail4 will be null
detail4=detail3;
detail3=detail1;
}else if(nextquarter > diff1){
nextquarter = diff1;
detail4=detail1;
}
}else{
if(currentQuarter > diff2){
nextquarter = currentQuarter;
currentQuarter = diff2;
detail4=detail3;
detail3=detail1;
}else if(nextquarter > diff2){
nextquarter = diff2;
detail4=detail1;
}
}
}else{
if(currentQuarter > diff1){
nextquarter = currentQuarter;
currentQuarter = diff1;
detail4=detail3;
detail3=detail1;
}else if(nextquarter > diff1){
nextquarter = diff1;
detail4=detail1;
}
}
}else{
System.out.println("skipped "+diff1);
}
}
list.add(detail3);
list.add(detail4);
return list;
}
If the periods are mutually exclusive (not overlapping) the you simply check for the first occurrence where:
The target is equal to or later than the start, and…
The target is before the stop.
This logic follows the Half-Open approach commonly used in date-time work where the beginning is inclusive while the ending is exclusive.
A shorter way of saying "the target is equal to or later than the start" is "not before start". The exclamation mark ! means not in Java syntax.
Boolean periodContainsTarget = ( ! target.isBefore( start ) ) && target.isBefore( stop ) ;
The above logic would be used with LocalDate if you meant date with a year. If you literally meant a month and day without a year, use the MonthDay class. The logic works for both.
Use Period class to represent the span of time between a pair of LocalDate objects. See Tutorial.
You might also find useful the Interval class in the ThreeTen-Extra project that supplements java.time.
I have never used Joda-Time before but I have ArrayList which contains objects with LocalDate and count. So I have count for each day in ArrayList and each day is only once in ArrayList.
I need to calculate counts for each month of year, which is in list.
My data:
E.g.:
dd.MM.yyyy
17.01.1996 (count 2)
18.01.1996 (count 3)
19.02.1996 (count 4)
19.03.1996 (count 1)
18.05.1997 (count 3)
Now I want outpur like this:
MM.yyyy
01.1996 -> 2 (17.1.1996) + 3 (18.1.1996) = 5
02.1996 -> 4 (19.2.1996) = 4
03.1996 -> 1 (19.3.1996) = 1
05.1997 -> 3 (18.5.1997) = 3
Simply I need to get count for each month, but I do not know what would be best way to achieve this.
Data class:
private class Info{
int count;
LocalDate day;
}
And result I would put in some class which contains Month and Year date + count.
In Joda-Time, there is class that represents Year + Month information, named YearMonth.
What you need to do is mostly construct a Map<YearMonth, int> to store the count of each YearMonth, by looping through your original List which contains LocalDate and count, and update the map accordingly.
Conversion from LocalDate to YearMonth should be straight forward: YearMonth yearMonth = new YearMonth(someLocalDate); should work
in pseudo-code, it looks like:
List<Info> dateCounts = ...;
Map<YearMonth, Integer> monthCounts = new TreeMap<>();
for (Info info : dateCounts) {
YearMonth yearMonth = new YearMonth(info.getLocalDate());
if (monthCounts does not contains yearMonth) {
monthCounts.put(yearMonth, info.count);
} else {
oldCount = monthCounts.get(yearMonth);
monthCounts.put(yearMonth, info.count + oldCount);
}
}
// feel free to output content of monthCounts now.
// And, with TreeMap, the content of monthCounts are sorted
You are looking for the getMonthOfYear and getYear methods on the LocalDate class in Joda-Time 2.3.
for ( Info info : infos ) {
int year = info.day.getYear();
int month = info.day.getMonthOfYear();
}
From there, write code to roll-up the count in any way that suits you. You could keep a map of years as keys leading to a map of months. You could create a string in the format of "YYYY-MM" as a key to map.