1

This is more of an app design question so forgive me if it's not relevant.

The app I am working on is designed to show what shops are open in the area and I am not sure what is the best way to store this information. I get the information from a database (I didn't design) that supplies an array of boolean values that determine if the shop is open during that time or not. Each boolean value represents a 15 minute slot e.g. 07:00-07:15.

This is split down into days but not specific dates, for instance the shop could be open Mon-Fri 09:00-17:00 but Sat 09:00-20:00 so the model I keep will have to know what the day is but not necessarily the date.

What I'm not sure about is how to store all of this for ease/speed of lookup because I want the view to be able to update in real time (shops change from open to closed without refresh). I have considered adding a table to the DB that contains opening times for any shop (using proper times and days) and then let SQLite do all the hard work allowing the Java to assume the shop is closed unless it gets a record back but that feels a little clunky because it might be better to hold the opening times in memory somehow.

Any help you can give would be awesome.

das_weezul
  • 6,082
  • 2
  • 28
  • 33
Catharsis
  • 616
  • 4
  • 20

2 Answers2

2

I assume your DB looks like this:

("Bob's Bakery", [0,0,1,1,1,1, ... ])
("Aunt McGee's Gun-O-Rama", [0,0,0,0,1,1, ... ])
....

In my example above I define that the bit-array starts at 07:00 AM. The "..." express my lazyness to continue until 12:00 PM :)

You could populate a hash map with that data, so you end up with a mapping of "start-of-15-min-slot" to "open shop". something like this (simple pseudo code to visualize my idea):

openShops = {
   ("07:30",{"Bob's Bakery"}),
   ("07:45",{"Bob's Bakery"}),      
   ("08:00",{"Bob's Bakery","Aunt McGee's Gun-O-Rama"}),
   ("08:15",{"Bob's Bakery","Aunt McGee's Gun-O-Rama"}),
   ...
}

This way you only have to calculate the beginning of the current 15 min slot and perform one look-up. Let's says you want to open shops for 08:02, then you just have to divide the minutes by 15 (without remainder) and use the result as the value for the minutes in the query:

2 div 15 = 0 → 08:00 → openShops.get("08:00")("Bob's Bakery","Aunt McGee's Gun-O-Rama")

das_weezul
  • 6,082
  • 2
  • 28
  • 33
  • Thanks, I was thinking something along the same lines, in theory the String "Bob's Bakery" could even be an shop object. My current plan is to store the opening times in the shop object and then build the hashmap that you have spoke about from there. I think this is one of those things that there is more than one way to do it depending on how I want to access the data. – Catharsis Apr 16 '14 at 12:58
2

I will ignore the database side, as you claim to be getting an array of boolean primitive values from the array.

Enum, EnumSet, EnumMap

On the Java side, you can use the surprisingly powerful enum facility in Java along with an EnumSet.

Define an enum for all the time slots. I'll include only a few for brevity. I add a displayName for a prettier label for each enum item.

enum QuarterHour {
    T_0700_0715, T_0715_0730, T_0730_0745, T_0745_0800;

    public String displayName;
    public LocalTime start, stop;

    private QuarterHour () {
        // Soft-code the members of each enum item by parsing its name.
        this.displayName = this.name ().replace ( "T_" , "" ).replace ( "_" , "-" );
        String[] tokens = this.displayName.split ( "-" ); // Extract the start and stop times from the left and right parts of this string.
        String t1 = tokens[ 0 ].substring ( 0 , 2 ) + ":" + tokens[ 0 ].substring ( 2 );
        String t2 = tokens[ 1 ].substring ( 0 , 2 ) + ":" + tokens[ 1 ].substring ( 2 );
        this.start = LocalTime.parse ( t1 );
        this.stop = LocalTime.parse ( t2 );
        System.out.println ( "Debug construction of each QuarterHour enum item: " + this.name () + " with displayName: " + this.displayName + " from " + this.start + " to " + this.stop );
    }

}

Java offers the very fast and very small EnumSet and EnumMap classes to collect Enum instances. Each is respectively an implementation of Set and Map like any other implementation. What is special is that because they are dedicated to Enum instances, they can be highly optimized by using bitmaps internally. So they use very little memory and execute extremely quickly.

Ignoring your boolean array for a moment, here is some example code showing how to use the EnumSet and EnumMap.

DayOfWeek

Our map uses the built-in DayOfWeek enum that represents each of the seven days of the week, Monday-Sunday.

java.time

The DayOfWeek enum is part of the java.time framework built into Java 8 and later. These classes supplant the old troublesome date-time classes such as java.util.Date. See Oracle Tutorial. Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP.

Example code

In this example I have the weekdays opening the full hour 07:00 to 08:00 (four quarter-hours) and the weekend opening a half-hour later 07:30 to 08:00 (two quarter-hours).

EnumMap<DayOfWeek , EnumSet<QuarterHour>> days = new EnumMap<> ( DayOfWeek.class );
EnumSet quarterHoursWhenOpen = EnumSet.of ( QuarterHour.T_0700_0715 , QuarterHour.T_0715_0730 , QuarterHour.T_0730_0745 , QuarterHour.T_0745_0800 );
EnumSet quarterHoursWhenOpen2 = EnumSet.of ( QuarterHour.T_0730_0745 , QuarterHour.T_0745_0800 );

days.put ( DayOfWeek.MONDAY , quarterHoursWhenOpen );
days.put ( DayOfWeek.TUESDAY , quarterHoursWhenOpen );
days.put ( DayOfWeek.WEDNESDAY , quarterHoursWhenOpen );
days.put ( DayOfWeek.THURSDAY , quarterHoursWhenOpen );
days.put ( DayOfWeek.FRIDAY , quarterHoursWhenOpen );
days.put ( DayOfWeek.SATURDAY , quarterHoursWhenOpen2 );
days.put ( DayOfWeek.SUNDAY , quarterHoursWhenOpen2 );

Dump to console. By default the toString method being implicitly called on our QuarterHours enum by default uses the name of the element. For presentation to your users you would call the displayName method I defined rather than call the built-in toString. That toString method is defined as being intended for "programmer-friendly" string values rather than user-oriented presentation strings.

System.out.println ( "days: " + days );

days: {MONDAY=[T_0700_0715, T_0715_0730, T_0730_0745, T_0745_0800], TUESDAY=[T_0700_0715, T_0715_0730, T_0730_0745, T_0745_0800], WEDNESDAY=[T_0700_0715, T_0715_0730, T_0730_0745, T_0745_0800], THURSDAY=[T_0700_0715, T_0715_0730, T_0730_0745, T_0745_0800], FRIDAY=[T_0700_0715, T_0715_0730, T_0730_0745, T_0745_0800], SATURDAY=[T_0730_0745, T_0745_0800], SUNDAY=[T_0730_0745, T_0745_0800]}

By the way, this naming of start-stop times follows the common approach in handling spans of time called "Half-Open" where the start is inclusive while the ending is exclusive.

Conversion

All we have left to do is convert back-and-forth to/from EnumSet QuarterHours and boolean array. For more information, see my Question: Convert between EnumSet and array of boolean values. Here I use the simple for loop approach, but look at the other answers for the more compact Lambda & streams syntax available in Java 8 and later.

The following code calls the values method on an enum. This method is annoyingly not documented on the Enum class because the method is synthetically created by the compiler for tricky internal technicalities as discussed in this Question. The upshot is that the method returns an array of all the items in the enum.

Caveat: All this code is untested, only run this once. Use at your own risk.

From EnumSet to array

// From EnumSet to array of boolean primitives.
QuarterHour[] quarterHoursOfDay = QuarterHour.values ();
boolean[] quarterHoursIfOpen = new boolean[ quarterHoursOfDay.length ];
EnumSet<QuarterHour> enumSet = days.get ( DayOfWeek.SATURDAY );
for ( int i = 0 ; i < quarterHoursOfDay.length ; i ++ ) {
    quarterHoursIfOpen[ i ] = enumSet.contains ( quarterHoursOfDay[ i ] );
}

Dump to console.

System.out.println ( "For " + DayOfWeek.SATURDAY + " array quarterHoursIfOpen: " + Arrays.toString ( quarterHoursIfOpen ) );

For SATURDAY array quarterHoursIfOpen: [false, false, true, true]

From array to EnumSet

Going the other direction, from the array of boolean quarterHoursIfOpen above to a fresh EnumSet named enumSet2 below.

// From array of boolean to EnumSet.
EnumSet<QuarterHour> enumSet2 = EnumSet.noneOf ( QuarterHour.class );
QuarterHour[] quarterHoursOfDay2 = QuarterHour.values ();
for ( int i = 0 ; i < quarterHoursOfDay2.length ; i ++ ) {
    boolean isOpen = quarterHoursIfOpen[ i ];
    if ( isOpen ) {
        enumSet2.add ( quarterHoursOfDay[ i ] );
    }
}

Dump to console.

System.out.println ( "enumSet2: " + enumSet2 );

enumSet2: [T_0730_0745, T_0745_0800]

Generate source code

Here is some source code to generate source code to name all 24-hours worth of quarter hour enum names.

StringBuilder enums = new StringBuilder ();
LocalTime start = LocalTime.MIN;
for ( int i = 0 ; i < ( 24 * 4 ) ; i ++ ) {
    if ( enums.length () > 0 ) {
        enums.append ( " , " );
    }
    LocalTime stop = start.plusMinutes ( 15 );
    String name = "T_" + start.toString () + "_" + stop.toString ();
    name = name.replaceAll ( ":" , "" );
    enums.append ( name );
    // Setup next loop
    start = stop;
}
System.out.println ( "enums: " + enums );

enums: T_0000_0015 , T_0015_0030 , T_0030_0045 , T_0045_0100 , T_0100_0115 , T_0115_0130 , T_0130_0145 , T_0145_0200 , T_0200_0215 , T_0215_0230 , T_0230_0245 , T_0245_0300 , T_0300_0315 , T_0315_0330 , T_0330_0345 , T_0345_0400 , T_0400_0415 , T_0415_0430 , T_0430_0445 , T_0445_0500 , T_0500_0515 , T_0515_0530 , T_0530_0545 , T_0545_0600 , T_0600_0615 , T_0615_0630 , T_0630_0645 , T_0645_0700 , T_0700_0715 , T_0715_0730 , T_0730_0745 , T_0745_0800 , T_0800_0815 , T_0815_0830 , T_0830_0845 , T_0845_0900 , T_0900_0915 , T_0915_0930 , T_0930_0945 , T_0945_1000 , T_1000_1015 , T_1015_1030 , T_1030_1045 , T_1045_1100 , T_1100_1115 , T_1115_1130 , T_1130_1145 , T_1145_1200 , T_1200_1215 , T_1215_1230 , T_1230_1245 , T_1245_1300 , T_1300_1315 , T_1315_1330 , T_1330_1345 , T_1345_1400 , T_1400_1415 , T_1415_1430 , T_1430_1445 , T_1445_1500 , T_1500_1515 , T_1515_1530 , T_1530_1545 , T_1545_1600 , T_1600_1615 , T_1615_1630 , T_1630_1645 , T_1645_1700 , T_1700_1715 , T_1715_1730 , T_1730_1745 , T_1745_1800 , T_1800_1815 , T_1815_1830 , T_1830_1845 , T_1845_1900 , T_1900_1915 , T_1915_1930 , T_1930_1945 , T_1945_2000 , T_2000_2015 , T_2015_2030 , T_2030_2045 , T_2045_2100 , T_2100_2115 , T_2115_2130 , T_2130_2145 , T_2145_2200 , T_2200_2215 , T_2215_2230 , T_2230_2245 , T_2245_2300 , T_2300_2315 , T_2315_2330 , T_2330_2345 , T_2345_0000

Community
  • 1
  • 1
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154