How to make this method more efficient? - java

So I'm writing a method called getThanksgiving. It works as is and it's part of a much larger class but I needed advice on how to make it more efficient. The getWeekDay method just returns what day of the week November 1 is on a user-inputted year.
public String getThanksgiving(){
String a = getWeekDay(11, 1);
int offset = 0;
if(a.equals("Friday")){
offset = 7;
}
if(a.equals("Saturday")){
offset = 6;
}
if(a.equals("Sunday")){
offset = 5;
}
if(a.equals("Monday")){
offset = 4;
}
if(a.equals("Tuesday")){
offset = 3;
}
if(a.equals("Wednesday")){
offset = 2;
}
if(a.equals("Thursday")){
offset = 1;
}
int date = 21 + offset;
thanksgiving = "Thursday, November " + date;
return thanksgiving;
}
I tried rewriting it as a for loop but it's not working.
public String getThanksgiving(){
String a = getWeekDay(11, 1);
int offset = 8;
String[] wTable = {"Friday", "Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday"};
for(int i = 1; i < 8; i++){
if(a.equals(wTable[i - 1])){
offset --;
}
}
}
Also, the idea of offset and adding 21 is just something my teacher wants us to do. Thanks in advance!

you can use switch case
like
switch(a )
{
case "Monday":
offset = 4;
break;
case "Tuesday":
offset = 3;
break;
}
reference
switch(n)
{
case 1:
execute code block 1
break;
case 2:
execute code block 2
break;
default:
code to be executed if n is different from case 1 and 2
}

I don't think you can make it "more efficient" (i.e. runtime performance). If you want to make your code more readable shorter, I think you're almost there
String[] wTable = {null, "Thursday", "Wednesday", "Tuesday", "Monday", "Sunday", "Saturday", "Friday"};
for(int i = 1, n = wTable.lenght; i < n; i++) {
if(a.equals(wTable[i])){
offset = i;
break;
}
}

To specifically address your question on 'how to make this method more efficient', one thing to note is that the method evaluates every if statement even in the case when you already found your solution. Using 'bare bones' java if's you could add a condition to check when the day has been found in this way:
int offset = 0;
boolean found = false;
if(!found && a.equals("Friday")){
offset = 7;
found = true;
}
if(!found && a.equals("Saturday")){
offset = 6;
found = true;
}
This flag will marginally reduce runtime by virtue of shortcut evaluation of the && (and) operator, only executing the string comparison until a match is found. You can achieve a similar performance result with a for and using break to get out of the loop when you have found a matching element.
A better alternative would be using a Map data structure:
Map<String, Integer> daysByOffset = new HashMap<String,Integer>();
// this 'setup' part you only do once
daysByOffset.put("Friday", 7);
daysByOffset.put("Saturday", 6);
...
Then the lookup part is very efficient, as the lookup in a hashmap is O(1):
int offset = daysByOffset.get(day);
An elegant alternative would be using enums that encapsulate the offset information:
public enum DaysWithOffset {
FRIDAY(7), SATURDAY(6),..., THURSDAY(1);
private final offset;
private DaysWithOffset(int offset) {
this.offset = offset;
}
public int getOffset() {
return offset;
}
}
After the enum is defined, each enum constant will contain the corresponding offset info:
FRIDAY.getOffset() // = 7
You can calculate the offset by resolving the enum from the provided String and asking the offset value from it:
...
String a = getWeekDay(11, 1);
int offset = DaysWithOffset.valueOf(day.toUpperCase()).getOffset();
...
Coming back to the question on which option is more efficient, both map and enum have an O(1) lookup, (the Enum lookup being slightly better by the optimized internal enum dictionary. Yet, the enum operation requires a toUpperCase(), while the map doesn't) both these options will perform a lot better than a list of if's (the original version) or a for loop.
I include these options for completeness of the answer and also for you to have a 'sneak preview' of the possibilities that the Java language offers.
Now a teaser: If you were doing this in Scala, you'd write something like this:
val daysByOffset = Map("Friday" -> 7, "Saturday" -> 6,...,"Thursday" ->1)
def thanksGiving(day:String):String = "Thursday, November " + (daysByOffset(day)+21)
If I were learning a language today, it would be Scala.

how about this?
private final static Map<String, int> dayMap = new HashMap<String,int>()
{
dayMap.put("Monday", 0);
// do for the rest
};
in your method:
public String getThanksgiving(){
String a = getWeekDay(11, 1);
//do a lookup
int result = dayMap.get(a);
// do somthing with it. and return
return "blah "+ result;
}

Related

Format for number with floating point

I want to implement format with dynamic floating point for different length of input data in specified length for display. For example x.xxxx, xx.xxxx, xxx.xx, xxxx.x.
In other words,
if I have 1.4, I need 1.4000.
if 13.4 then I need 13.400, for every case length should be 5 digits (with no dot).
I'm using
DecimalFormat df2 = new DecimalFormat("000000");
but can't build a correct pattern. Is there any solution for this?
Thanks for helping.
The following is not production code. It doesn’t take a leading minus into account, nor very high values of the noDigits constant. But I believe you can use it as a starting point. Thanks to Mzf for inspiration.
final static int noDigits = 5;
public static String myFormat(double d) {
if (d < 0) {
throw new IllegalArgumentException("This does not work with a negative number " + d);
}
String asString = String.format(Locale.US, "%f", d);
int targetLength = noDigits;
int dotIx = asString.indexOf('.');
if (dotIx >= 0 && dotIx < noDigits) {
// include dot in result
targetLength++;
}
if (asString.length() < targetLength) { // too short
return asString + "0000000000000000000000".substring(asString.length(), targetLength);
} else if (asString.length() > targetLength) { // too long
return asString.substring(0, targetLength);
}
// correct length
return asString;
}

Find missing date ranges in timeline using java

I have a custom java sync that fetch data by date range thoght SOAP service running on tomcat.
Ex:
getDataByDateRange(startDate,endDate)
getDataByDateRange('2016-01-01 10:00:00.00000','2016-01-01 11:00:00.00000')
I want to write a control program to check if any range has been missed by any kind of runtime or server error.
How can I find the missing date ranges?
Thanks.
Visually Example:
TimeLine : [------------------------------------------------------------------]
Processed Dates: [----1---][---2----]---[-3-][--4---]---[----5---][---6--]-----------
Missing Dates : -------------------[-1-]-----------[-2-]----------------[-----3----]
TimeLine:
1: '2016-01-01 10:00:00.00000','2016-02-01 09:00:00.00000'
Processed Dates:
1: '2016-01-01 10:00:00.00000','2016-01-01 11:00:00.00000'
2: '2016-01-01 11:00:00.00000','2016-01-01 12:00:00.00000'
3: '2016-01-01 13:00:00.00000','2016-01-01 13:30:00.00000'
4: '2016-01-01 13:30:00.00000','2016-01-01 14:30:00.00000'
5: '2016-01-01 15:30:00.00000','2016-01-01 16:30:00.00000'
6: '2016-01-01 16:30:00.00000','2016-01-01 17:00:00.00000'
Missing Dates:
1: '2016-01-01 12:00:00.00000','2016-01-01 13:00:00.00000'
2: '2016-01-01 14:30:00.00000','2016-01-01 15:30:00.00000'
3: '2016-01-01 17:00:00.00000','2016-01-02 09:00:00.00000'
According to your comment I post my previous comment as answer. This solution uses my library Time4J (including the range-module):
// prepare parser
ChronoFormatter<PlainTimestamp> f =
ChronoFormatter.ofTimestampPattern( // five decimal digits
"uuuu-MM-dd HH:mm:ss.SSSSS", PatternType.CLDR, Locale.ROOT);
// parse input to intervals - here the overall time window
TimestampInterval timeline =
TimestampInterval.between(
f.parse("2016-01-01 10:00:00.00000"),
f.parse("2016-02-01 09:00:00.00000"));
// for more flexibility - consider a for-each-loop
TimestampInterval i1 =
TimestampInterval.between(f.parse("2016-01-01 10:00:00.00000"), f.parse("2016-01-01 11:00:00.00000"));
TimestampInterval i2 =
TimestampInterval.between(f.parse("2016-01-01 11:00:00.00000"), f.parse("2016-01-01 12:00:00.00000"));
TimestampInterval i3 =
TimestampInterval.between(f.parse("2016-01-01 13:00:00.00000"), f.parse("2016-01-01 13:30:00.00000"));
TimestampInterval i4 =
TimestampInterval.between(f.parse("2016-01-01 13:30:00.00000"), f.parse("2016-01-01 14:30:00.00000"));
TimestampInterval i5 =
TimestampInterval.between(f.parse("2016-01-01 15:30:00.00000"), f.parse("2016-01-01 16:30:00.00000"));
TimestampInterval i6 =
TimestampInterval.between(f.parse("2016-01-01 16:30:00.00000"), f.parse("2016-01-01 17:00:00.00000"));
// apply interval arithmetic
IntervalCollection<PlainTimestamp> icoll =
IntervalCollection.onTimestampAxis().plus(Arrays.asList(i1, i2, i3, i4, i5, i6));
List<ChronoInterval<PlainTimestamp>> missed = icoll.withComplement(timeline).getIntervals();
// result
System.out.println(missed);
// [[2016-01-01T12/2016-01-01T13), [2016-01-01T14:30/2016-01-01T15:30), [2016-01-01T17/2016-02-01T09)]
The core of the whole interval arithmetic is just done by the code fragment icoll.withComplement(timeline). The rest is only about creation of intervals. By applying a for-each-loop you can surely minimize again the count of lines in presented code.
The output is based on the canonical description of the intervals implicitly using toString(), for example: [2016-01-01T12/2016-01-01T13) The square bracket denotes a closed boundary while the round bracket to the right end denotes an open boundary. So we have here the standard case of half-open timestamp intervals (without timezone). While other interval types are possible I have chosen that type because it corresponds to the type of your input strings.
If you plan to combine this solution with Joda-Time in other parts of your app then keep in mind that a) there is not yet any special bridge between both libraries available and b) the conversion looses microsecond precision (Joda-Time only supports milliseconds) and c) Time4J has much more power than Joda-Time (for almost everything). Anyway, you can do this as conversion (important if you don't want to do the effort of bigger rewriting of your app):
ChronoInterval<PlainTimestamp> missed0 = missed.get(0);
PlainTimestamp tsp = missed0.getStart().getTemporal();
LocalDateTime ldt = // joda-equivalent
new LocalDateTime(
tsp.getYear(), tsp.getMonth(), tsp.getDayOfMonth(),
tsp.getHour(), tsp.getMinute(), tsp.getSecond(), tsp.get(PlainTime.MILLI_OF_SECOND));
System.out.println(ldt); // 2016-01-01T10:00:00.000
About a Joda-only solution:
Joda-Time does only support instant intervals, not timestamp intervals without timezone. However, you could simulate that missing interval type by hardwiring the timezone to UTC (using fixed offset).
Another problem is missing support for five decimal digits. You can circumvent it by this hack:
DateTime start =
DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS")
.withZoneUTC()
.parseDateTime("2016-01-01 16:30:00.00000".substring(0, 23));
System.out.println(start); // 2016-01-01T16:30:00.000Z
DateTime end = ...;
Interval interval = new Interval(start, end);
The other more critical element of a solution is almost missing - interval arithmetic. You have to sort the intervals first by start instant (and then by end instant). After sorting, you can iterate over all intervals such that you find the gaps. The best thing Joda-Time can do for you here is giving you methods like isBefore(anotherInstant) etc. which you can use in your own solution. But it gets pretty much bloated.
Given that the frequency of date ranges is one hour, you can start with range start date, iterate till range end date and write a method that checks for an entry with dates. You can use DateUtils to add hour to date, as shown in the below pseudo code:
Date startDate = startDate;
Date endDate = endDate;
while (startDate.before(endDate){
if(!exists(startDate, DateUtils.addHours(startDate, 1), entries)){
//Add into missing entries
}
startDate = DateUtils.addHours(startDate, 1);
}
I posted my IntervalTree a while ago - it seems to work well with this kind of problem.
See the minimise method for what you are looking for.
/**
* Title: IntervlTree
*
* Description: Implements a static Interval Tree. i.e. adding and removal are not possible.
*
* This implementation uses longs to bound the intervals but could just as easily use doubles or any other linear value.
*
* #author OldCurmudgeon
* #version 1.0
* #param <T> - The Intervals to work with.
*/
public class IntervalTree<T extends IntervalTree.Interval> {
// My intervals.
private final List<T> intervals;
// My center value. All my intervals contain this center.
private final long center;
// My interval range.
private final long lBound;
private final long uBound;
// My left tree. All intervals that end below my center.
private final IntervalTree<T> left;
// My right tree. All intervals that start above my center.
private final IntervalTree<T> right;
public IntervalTree(List<T> intervals) {
if (intervals == null) {
throw new NullPointerException();
}
// Initially, my root contains all intervals.
this.intervals = intervals;
// Find my center.
center = findCenter();
/*
* Builds lefts out of all intervals that end below my center.
* Builds rights out of all intervals that start above my center.
* What remains contains all the intervals that contain my center.
*/
// Lefts contains all intervals that end below my center point.
final List<T> lefts = new ArrayList<>();
// Rights contains all intervals that start above my center point.
final List<T> rights = new ArrayList<>();
long uB = Long.MIN_VALUE;
long lB = Long.MAX_VALUE;
for (T i : intervals) {
long start = i.getStart();
long end = i.getEnd();
if (end < center) {
lefts.add(i);
} else if (start > center) {
rights.add(i);
} else {
// One of mine.
lB = Math.min(lB, start);
uB = Math.max(uB, end);
}
}
// Remove all those not mine.
intervals.removeAll(lefts);
intervals.removeAll(rights);
uBound = uB;
lBound = lB;
// Build the subtrees.
left = lefts.size() > 0 ? new IntervalTree<>(lefts) : null;
right = rights.size() > 0 ? new IntervalTree<>(rights) : null;
// Build my ascending and descending arrays.
/**
* #todo Build my ascending and descending arrays.
*/
}
/*
* Returns a list of all intervals containing the point.
*/
List<T> query(long point) {
// Check my range.
if (point >= lBound) {
if (point <= uBound) {
// Gather all intersecting ones.
List<T> found = intervals
.stream()
.filter((i) -> (i.getStart() <= point && point <= i.getEnd()))
.collect(Collectors.toList());
// Gather others.
if (point < center && left != null) {
found.addAll(left.query(point));
}
if (point > center && right != null) {
found.addAll(right.query(point));
}
return found;
} else {
// To right.
return right != null ? right.query(point) : Collections.<T>emptyList();
}
} else {
// To left.
return left != null ? left.query(point) : Collections.<T>emptyList();
}
}
/**
* Blends the two lists together.
*
* If the ends touch then make them one.
*
* #param a
* #param b
* #return
*/
static List<Interval> blend(List<Interval> a, List<Interval> b) {
// Either empty - return the other.
if (a.isEmpty()) {
return b;
}
if (b.isEmpty()) {
return a;
}
// Where does a end and b start.
Interval aEnd = a.get(a.size() - 1);
Interval bStart = b.get(0);
ArrayList<Interval> blended = new ArrayList<>();
// Do they meet/cross?
if (aEnd.getEnd() >= bStart.getStart() - 1) {
// Yes! merge them.
// Remove the last.
blended.addAll(a.subList(0, a.size() - 1));
// Add a combined one.
blended.add(new SimpleInterval(aEnd.getStart(), bStart.getEnd()));
// Add all but the first.
blended.addAll(b.subList(1, b.size()));
} else {
// Just join them.
blended.addAll(a);
blended.addAll(b);
}
return blended;
}
static List<Interval> blend(List<Interval> a, List<Interval> b, List<Interval>... more) {
List<Interval> blended = blend(a, b);
for (List<Interval> l : more) {
blended = blend(blended, l);
}
return blended;
}
List<Interval> minimise() {
// Calculate min of left and right.
List<Interval> minLeft = left != null ? left.minimise() : Collections.EMPTY_LIST;
List<Interval> minRight = right != null ? right.minimise() : Collections.EMPTY_LIST;
// My contribution.
long meLeft = minLeft.isEmpty() ? lBound : Math.max(lBound, minLeft.get(minLeft.size() - 1).getEnd());
long meRight = minRight.isEmpty() ? uBound : Math.min(uBound, minRight.get(0).getEnd());
return blend(minLeft,
Collections.singletonList(new SimpleInterval(meLeft, meRight)),
minRight);
}
private long findCenter() {
//return average();
return median();
}
protected long median() {
if (intervals.isEmpty()) {
return 0;
}
// Choose the median of all centers. Could choose just ends etc or anything.
long[] points = new long[intervals.size()];
int x = 0;
for (T i : intervals) {
// Take the mid point.
points[x++] = (i.getStart() + i.getEnd()) / 2;
}
Arrays.sort(points);
return points[points.length / 2];
}
/*
* What an interval looks like.
*/
public interface Interval {
public long getStart();
public long getEnd();
}
/*
* A simple implemementation of an interval.
*/
public static class SimpleInterval implements Interval {
private final long start;
private final long end;
public SimpleInterval(long start, long end) {
this.start = start;
this.end = end;
}
#Override
public long getStart() {
return start;
}
#Override
public long getEnd() {
return end;
}
#Override
public String toString() {
return "{" + start + "," + end + "}";
}
}
/**
* Not called by App, so you will have to call this directly.
*
* #param args
*/
public static void main(String[] args) {
/**
* #todo Needs MUCH more rigorous testing.
*/
// Test data.
long[][] data = {
{1, 4}, {2, 5}, {5, 7}, {10, 11}, {13, 20}, {19, 21},};
List<Interval> intervals = new ArrayList<>();
for (long[] pair : data) {
intervals.add(new SimpleInterval(pair[0], pair[1]));
}
// Build it.
IntervalTree<Interval> test = new IntervalTree<>(intervals);
// Test it.
System.out.println("Normal test: ---");
for (long i = 0; i < 10; i++) {
List<Interval> intersects = test.query(i);
System.out.println("Point " + i + " intersects:");
intersects.stream().forEach((t) -> {
System.out.println(t.toString());
});
}
// Check minimise.
List<Interval> min = test.minimise();
System.out.println("Minimise test: ---");
System.out.println(min);
// Check for empty list.
intervals.clear();
test = new IntervalTree<>(intervals);
// Test it.
System.out.println("Empty test: ---");
for (long i = 0; i < 10; i++) {
List<Interval> intersects = test.query(i);
System.out.println("Point " + i + " intersects:");
intersects.stream().forEach((t) -> {
System.out.println(t.toString());
});
}
}
}
This gets close to what you are looking for. Here's some code to minimise your ranges into just 3.
static String[][] dates = {{"2016-01-01 10:00:00.00000", "2016-01-01 11:00:00.00000"}, {"2016-01-01 11:00:00.00000", "2016-01-01 12:00:00.00000"}, {"2016-01-01 13:00:00.00000", "2016-01-01 13:30:00.00000"}, {"2016-01-01 13:30:00.00000", "2016-01-01 14:30:00.00000"}, {"2016-01-01 15:30:00.00000", "2016-01-01 16:30:00.00000"}, {"2016-01-01 16:30:00.00000", "2016-01-01 17:00:00.00000"}};
static List<IntervalTree.SimpleInterval> ranges = new ArrayList<>();
static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
static {
for (String[] pair : dates) {
try {
ranges.add(new IntervalTree.SimpleInterval(df.parse(pair[0]).getTime(), df.parse(pair[1]).getTime()));
} catch (ParseException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public void test() {
IntervalTree tree = new IntervalTree<>(ranges);
List<IntervalTree.Interval> min = tree.minimise();
//System.out.println("min->" + min);
for (IntervalTree.Interval i : min) {
System.out.println(df.format(new Date(i.getStart())) + " - " + df.format(new Date(i.getEnd())));
}
}
which prints
2016-01-01 10:00:00.0 - 2016-01-01 12:00:00.0
2016-01-01 13:00:00.0 - 2016-01-01 14:30:00.0
2016-01-01 15:30:00.0 - 2016-01-01 17:00:00.0
which is all of your Processed Dates joined into three date ranges.

How to use an if function to call elements of an array

Basically, I have a variable 'prime'. It can only take values between 0 and 6. Based on its value, I want a string 'result' to be Sunday if prime is 0, Monday if 1, etc. Currently, it's coded this way:
String result = new String();
if (prime == 0)
{
result = "Sunday";
}
if (prime == 1)
{
result = "Monday";
}
if (prime == 2)
{
result = "Tuesday";
}
if (prime == 3)
{
result = "Wednesday";
}
if (prime == 4)
{
result = "Thursday";
}
if (prime == 5)
{
result = "Friday";
}
if (prime == 6)
{
result = "Saturday";
}
else
{
result = "Check your code.";
}
I'm wondering if there's a faster way to do this? I've created an array with the days of the week:
String[] days = new String[7];
days [0] = "Sunday";
days [1] = "Monday";
days [2] = "Tuesday";
days [3] = "Wednesday";
days [4] = "Thursday";
days [5] = "Friday";
days [6] = "Saturday";
How do I quickly and elegantly code it so that if the value of prime is 0, the string 'result' is the first element of the array, and so on until if prime is 6, the string 'result' is the seventh element?
You already have all the valid values stored in a simple lookup table, you just need to ensure that the requested value is within the range of available values.
The basic answer would be to do something like...
if (prime >= 0 && prime < days.length) {
result = days[prime];
} else {
result = prime + " is not within a valid range";
// Or throw an exception
}
What this does is makes sure that the prime value is within the valid range of acceptable values (0..days.length - 1), other wise it returns an error message (or you could throw an exception).
Remember, arrays are 0 indexed, hence the need to use < days.length (less then) and not <= days.length (less then or equals to)
You were close. For those saying Switch or if chains, it's overkill for this problem.
result = days[Math.abs(prime % days.length)];
The array acts like a switch statement, and the modulus operator wrapped in the Math.abs (absolute value) acts like a safety net to stay with in the valid range of the array 0-6.
Credits to #MadProgrammer for the Math.abs
From Tutorials Point
Modulus - Divides left hand operand by right hand operand and returns remainder
Why don't you use the DayOfWeek class?
import java.time.DayOfWeek;
and try this...
try {
DayOfWeek dayOfWeek = DayOfWeek.of(++prime);
System.out.println(dayOfWeek.getDisplayName(TextStyle.FULL, Locale.getDefault()));
} catch (java.time.DateTimeException ex) {
System.out.println("Invalid value for DayOfWeek");
}
Note that we have to do ++prime since your code starts in zero and the enum in one.
If you have to set Sunday as the first day (instead of Monday that is the first in the enum)... the minus method will do the trick:
DayOfWeek dayOfWeek = DayOfWeek.of(++prime).minus(1);
EDIT: advantages and disadvantages of the solution
Pros:
Don't require an object to maintain your days.
Don't use a conditional statement.
The text style and language can easily be changed.
Cons:
java 1.8 is required
You could simply do:
if (prime >= 0 && prime < days.length) result = days[prime];
else result = "Check your code."
Because prime is essentially the index of the day that you want.
You can use Enum and define yourself
public enum Week {
SUNDAY(1, "Sunday"), Monday(2, "Monday"), TUESDAY(3, "Tuesday"), WEDNESDAY(
4, "Wednesday"), THURSDAY(6, "Thursday"), FRIDAY(6, "Friday"), SATURDAY(
7, "Saturday");
private int id;
private String name;
static Map<Integer, String> map = new HashMap<Integer, String>();
static {
for (Week w : Week.values()) {
map.put(w.getId(), w.name);
}
}
private Week(int id, String name) {
this.setId(id);
this.setName(name);
}
public static String getNameById(int id) {
return map.get(id);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
}

Sort algorithm problems on java comparable

I want to do a specific sort. I am using java's comparable interface which means the return of my compare method must return -1 +1 or 0 depending on the equality of the two compared, then I am sorting using Collections. My trouble comes from how I wish to compare.
I have a key that is made up of either of the following
[keyName]
[siteName].[keyName]
[siteName].[pageName].[keyName]
so as an example "mysite.alampshade.color"
the tricky part is the sites must be sorted first, followed by keyname, followed by pageName. but firstly by the keynames, then site name, in the order of the number of sections to the property. Sorry. its a little complicated, an example may help. here is the order they must be:
alpha
beta
charlie
sitea.alpha
sitea.charlie
sitea.pagea.beta
sitea.pageb.beta
sitea.pagea.charlie
siteb.alpha
siteb.delta
siteb.pagef.alpha
siteb.pageb.echo
siteb.pageb.golf
siteb.pagea.hotel
siteb.pageb.hotel
siteb.pagec.hotel
I have tried many different ways and have thrown away code a few times but still cant get it perfect. some pseudocode would be of great help if not some java.
EDIT:
to add another possibly simplier to understand example
the following is sorted how I need it
a
b
c
z
a.b
a.c
a.d
a.z
a.b.a
a.c.a
a.b.b
a.b.c
a.c.c
a.a.d
b.a
b.b
b.z
b.a.a
b.b.a
b.a.b
c.c.f
Another option, making it recursive you avoid the problem if there is ever more entries.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class SortTest {
public static void main(String[] args) {
String[] test = new String[]{
"a",
"b",
"b.a",
"b.a.a",
"a.a.a",
"a.b.a",
"a.a",
"a.b",
"b.a.b",
"b.b.a"
};
Arrays.sort(test, new Comparator<String>() {
int compareComplexList(List<String> a, List<String> b, List<int[]> positions, int order ) {
int minimum = a.size() < b.size() ? a.size() - 1 : b.size() - 1;
if (a.get(positions.get(minimum)[order]).compareTo(b.get(positions.get(minimum)[order])) != 0)
return a.get(positions.get(minimum)[order]).compareTo(b.get(positions.get(minimum)[order]));
else if (order < minimum - 1) return compareComplexList(a,b, positions, ++order);
else return Double.compare(a.size(),b.size());
}
public int compare(String a, String b) {
List<String> partsA = Arrays.asList(a.split("\\."));
List<String> partsB = Arrays.asList(b.split("\\."));
List<int[]> orders = new ArrayList<int[]>();
orders.add(new int[] {0});
orders.add(new int[] {0,1});
orders.add(new int[] {0,2,1});
return compareComplexList(partsA, partsB, orders,0);
}
});
System.out.println("Sorted: "+Arrays.toString(test));
}
}
Should be good now.
public int compare(String a, String b) {
String[] partsA = a.split("\\.");
String[] partsB = b.split("\\.");
// If first term is different, we exit.
if (partsA[0].compareTo(partsB[0]) != 0) return partsA[0].compareTo(partsB[0]);
// Else, first term is identical.
else {
// Same number of parts
if (partsA.length == partsB.length) {
// 2 parts, we compare the 2nd part.
if (partsA.length == 2) {
return partsA[1].compareTo(partsB[1]);
// 3 parts, we compare the 3rd part first, then the 2nd part
} else {
if (partsA[2].compareTo(partsB[2]) != 0) return partsA[2].compareTo(partsB[2]);
return partsA[1].compareTo(partsB[1]);
}
// Different number of parts
} else {
// If A has only 1 part, it's first
if (partsA.length == 1) return -1;
// If B has only 1 part, it's first
if (partsB.length == 1) return 1;
// Case 2 vs 3 parts, we compare the 3rd part with the 2nd part of the other. If it's equal, the shorter is first.
if (partsA.length == 3) {
if (partsA[2].compareTo(partsB[1]) != 0) return partsA[2].compareTo(partsB[1]);
else return 1;
} else {
if (partsA[1].compareTo(partsB[2]) != 0) return partsA[1].compareTo(partsB[2]);
else return -1;
}
}
}
}
My other answer started getting too gnarly. Here's a better, more natural solution:
public class StrangeComparator {
private static class Entry implements Comparable<Entry> {
// What to split with.
static final String dot = Pattern.quote(".");
// The parts.
final String key;
final String page;
final String site;
public Entry(String s) {
String [] parts = s.split(dot);
switch (parts.length) {
case 1:
key = parts[0];
page = "";
site = "";
break;
case 2:
key = parts[1];
page = "";
site = parts[0];
break;
case 3:
key = parts[2];
page = parts[1];
site = parts[0];
break;
default:
throw new IllegalArgumentException("There must be at least one part to an entry.");
}
}
#Override
public int compareTo(Entry t) {
int diff = site.compareTo(t.site);
if ( diff == 0 ) {
diff = page.compareTo(t.page);
}
if ( diff == 0 ) {
diff = key.compareTo(t.key);
}
return diff;
}
#Override
public String toString () {
return (site.length() > 0 ? site + "." : "")
+ (page.length() > 0 ? page + "." : "")
+ key;
}
}
public void test() {
String[] test = new String[]{
"alpha",
"beta",
"charlie",
"zeta", // Added to demonstrate correctness.
"sitea.alpha",
"sitea.charlie",
"sitea.pagea.beta",
"sitea.pageb.beta",
"sitea.pagea.charlie",
"siteb.alpha",
"siteb.delta",
"siteb.pagef.alpha",
"siteb.pageb.echo",
"siteb.pageb.golf",
"siteb.pagea.hotel",
"siteb.pageb.hotel",
"siteb.pagec.hotel"
};
Arrays.sort(test);
System.out.println("Normal sort: " + Separator.separate("\n", "\n", test));
Entry[] entries = new Entry[test.length];
for ( int i = 0; i < test.length; i++ ) {
entries[i] = new Entry(test[i]);
}
Arrays.sort(entries);
System.out.println("Special sort: " + Separator.separate("\n", "\n", entries));
}
public static void main(String args[]) {
new StrangeComparator().test();
}
}
Output order is:
alpha
beta
charlie
zeta
sitea.alpha
sitea.charlie
sitea.pagea.beta
sitea.pagea.charlie
sitea.pageb.beta
siteb.alpha
siteb.delta
siteb.pagea.hotel
siteb.pageb.echo
siteb.pageb.golf
siteb.pageb.hotel
siteb.pagec.hotel
siteb.pagef.alpha
Which kinda does what you say but doesn't match your example.
Here's an alternative - if a component is found to contain less that 3 parts then parts are added at the start to take up the slack. It then uses a sort order array to define which columns should be compared next:
public void test() {
String[] test = new String[]{
"alpha",
"beta",
"charlie",
"zeta", // Added to demonstrate correctness.
"sitea.alpha",
"sitea.charlie",
"sitea.pagea.beta",
"sitea.pageb.beta",
"sitea.pagea.charlie",
"siteb.alpha",
"siteb.delta",
"siteb.pagef.alpha",
"siteb.pageb.echo",
"siteb.pageb.golf",
"siteb.pagea.hotel",
"siteb.pageb.hotel",
"siteb.pagec.hotel"
};
Arrays.sort(test);
System.out.println("Normal sort: "+Arrays.toString(test));
Arrays.sort(test, new Comparator<String>() {
// How many columns to pad to.
final int padTo = 3;
// What to pad with.
final String padWith = "";
// What order to compare the resultant columns in.
final int[] order = {0, 2, 1};
#Override
public int compare(String s1, String s2) {
String[] s1parts = padArray(s1.split(Pattern.quote(".")), padTo, padWith);
String[] s2parts = padArray(s2.split(Pattern.quote(".")), padTo, padWith);
int diff = 0;
for ( int i = 0; diff == 0 && i < order.length; i++ ) {
diff = s1parts[order[i]].compareTo(s2parts[order[i]]);
}
return diff;
}
String [] padArray(String[] array, int padTo, String padWith) {
String [] padded = new String[padTo];
for ( int i = 0; i < padded.length; i++ ) {
padded[padded.length - i - 1] = i < array.length ? array[i]: padWith;
}
return padded;
}
});
System.out.println("Special sort: "+Arrays.toString(test));
}
prints (more or less):
Normal sort: [alpha,
beta,
charlie,
sitea.alpha,
sitea.charlie,
sitea.pagea.beta,
sitea.pagea.charlie,
sitea.pageb.beta,
siteb.alpha,
siteb.delta,
siteb.pagea.hotel,
siteb.pageb.echo,
siteb.pageb.golf,
siteb.pageb.hotel,
siteb.pagec.hotel,
siteb.pagef.alpha,
zeta]
Special sort: [alpha,
beta,
charlie,
sitea.alpha,
sitea.charlie,
siteb.alpha,
siteb.delta,
zeta,
siteb.pagef.alpha,
sitea.pagea.beta,
sitea.pageb.beta,
sitea.pagea.charlie,
siteb.pageb.echo,
siteb.pageb.golf,
siteb.pagea.hotel,
siteb.pageb.hotel,
siteb.pagec.hotel]
There does seem to be some ambiguity in your requirements but this code is structured so you can, with trivial tweaks, achieve most interpretations of your comparison quite simply.

getting the index of a string in a string array

My array looks something like this
String[] dayNames = new String[DAYS_IN_WEEK];
dayNames[0] = "Sunday";
dayNames[1] = "Monday";
dayNames[2] = "Tuesday";
dayNames[3] = "Wednesday";
dayNames[4] = "Thursday";
dayNames[5] = "Friday";
dayNames[6] = "Saturday";
I need to print the index of the array using a method findDay.
so if "Saturday" was selected, i would need 6 to be returned.
Thanks for your time =D
P.s. No answers please? Just suggestions =)
UPPDATE**
my array will not compile. This is exactly what i have:
private static final int DAYS_IN_WEEK = 7;
String[] dayNames;
dayNames = new String[DAYS_IN_WEEK]
// Declare an array of Strings named dayNames
dayNames[0] = "Sunday";
dayNames[1] = "Monday";
dayNames[2] = "Tuesday";
dayNames[3] = "Wednesday";
dayNames[4] = "Thursday";
dayNames[5] = "Friday";
dayNames[6] = "Saturday";
and I get multiple errors starting with:
Weekdays.java:12: error: <identifier> expected
dayNames = new String[DAYS_IN_WEEK]
I don't understand why. I literally copied the EXACT format from
http://docs.oracle.com/javase/tutorial/java/nutsandbolts/arrays.html
You could do this...
int index = Arrays.asList(dayNames).indexOf("Saturday");
I'll leave printing the index value as an exercise for you.
Use a map your key is day and value is index.
Key => Sunday , Monday , Tuesday .....
Value = > 0,1,2 ..
Map the value against the key you required.
Well, one way to do it would be like this:
public int findDay(String dayString) {
if (dayString.equals("Sunday") {
return 0;
}
else if (dayString.equals("Monday") {
...
You get the gist of it - but that's a fairly crufty solution.
An alternative would be to use Java's enumerated values:
public enum Day {
SUNDAY(0), MONDAY(1), TUESDAY(2), WEDNESDAY(3) ...
private final int i;
private Day(int value) {
i = value;
}
public int getNumericRepresentation() {
return i;
}
}
Then you can actually have an array of enumerations, like:
Day[] days = new Day[Day.values().size()];
int i = 0;
for (Day day : Day.values()) {
days[i] = day;
i++;
}
And to print out a day's numeric representation you just use:
day.getNumericRepresentation();

Categories