10
\$\begingroup\$

I don't like to post programming contest code in here, but I really like the question and want to share it with you all.

It has been years since Superman started his enmity with the super-genius Lex Luthor. After years of intense battle, Luthor finally decides to end it all by going into the past and capturing Superman the moment he lands on Earth. Using the time warp machine that he built, Luthor goes back in time. Superman, wanting to thwart his evil intentions, asks The Flash to push him back into time by breaking the time barrier.

The Flash obliges and throws Superman back in time. But Superman senses that something is fishy and the Earth doesn't feel the same. After an hour of intense detective work (which he isn’t used to) he finds out that he has been sent into a parallel anti-universe where everything is reverse to that of our Universe. Looking at his watch, Superman realizes that he'll first need to correct the time on his watch in order to reach Kansas, the place where his ship landed, on time.

Superman, not being as smart as you, asks for your help in fixing the time on his watch. You know that the actual time on the other anti-Universe is the mirror image of the time on Superman's watch, considering the mirror to be a vertical line running from 12 to 6 on the watch.

INPUT

The first line contains an integer T, denoting the number of test cases. T lines follow with the time given in the 12-hour format: HH:MM am/pm

OUTPUT

The output should consist of T lines with the mirrored time of each test case in the 12-hour format: HH:MM am/pm

SAMPLE I/O

Input

3
05:00 am
06:00 pm
03:15 pm

Output

07:00 pm
06:00 am
08:45 am

TL;DR

You will be given a time as XX:XX am/pm (12 hour format) and you have to print the time that you will see if you hold the analog clock beside the mirror.

A picture worth thousands words

enter image description here

package foo.competition;
import java.util.Scanner;

public class Parallel {

    public static void main(String[] args) {
        try (Scanner input = new Scanner(System.in)) {
            // number of testcases
            int t = input.nextInt();
            // feeding newline
            input.nextLine();
            while(t-- > 0) {
                String actualTime = input.nextLine();
                String parallelUniverseTime = parallelUniverseTime(actualTime);
                System.out.println(parallelUniverseTime);
            }
        }
    }

    /**
     * 
     * @param actualTimeStamp - actual time. format XX.XX am/pm (12 hour clock)
     * @return - a String formatted as "XX:XX am/pm" as the time in parallel universe
     */
    public static String parallelUniverseTime(String actualTimeStamp) {
        String []time = parseTimeAndReturnAsArray(actualTimeStamp);
        String mirrorHour = mirroredHour(time[0], time[1]);
        String mirrorMinute = mirroredMinute(time[1]);
        String mirrorMedian = mirroredMedian(time[2]);
        return formattedMirroredTime(mirrorHour, mirrorMinute, mirrorMedian);
    }

    /**
     * 
     * @param timeStamp - actual time. format XX:XX am/pm (12 hour clock)
     * @return - an array as {hour, minute, median}
     */
    private static String[] parseTimeAndReturnAsArray(String timeStamp) {
        // timeStamp example - "07:30 pm"
        String hour = timeStamp.charAt(0) + "" + timeStamp.charAt(1);
        String minute = timeStamp.charAt(3) + "" + timeStamp.charAt(4);
        String median = timeStamp.charAt(6) + "" + timeStamp.charAt(7);
        return new String[]{hour, minute, median};
    }

    private static String mirroredHour(final String hour, final String minute) {
        String hourAndMinute = hour + ":" + minute;
        if(hourAndMinute.equals("12:00") || hourAndMinute.equals("06:00")) {
            return hour;
        }

        final int minuteAsInt = Integer.parseInt(minute);
        final int hourAsInt = Integer.parseInt(hour);

        // after 12 min the hour hand moves one place
        // so before XX:12 the mirrored hour will be (12 - hour)
        if(minuteAsInt < 12) {
            return (12 - hourAsInt) + "";
        }

        // otherwise hour hand already moved so mirrored hour hand
        // will near to 6.
        else {
            return (12 - hourAsInt - 1) + "";
        }

    }

    private static String mirroredMinute(final String minute) {
        if(minute.equals("00")) {
            return minute;
        }

        final int minuteAsInt = Integer.parseInt(minute);
        // return the mirrored minute hand
        return (60 - minuteAsInt) + "";

    }

    private static String mirroredMedian(final String median) {
        if(median.equals("am")) return "pm";
        else return "am";
    }

    private static String formattedMirroredTime(String hour, String minute, String median) {
        if(hour.length() == 1) { hour = "0" + hour; }
        return (hour + ":" + minute + " " + median);
    }
}

There are many pitfalls of the code:

  1. I didn't handle the user input error case.
  2. All methods are static and in under one class.
  3. Bad documentation.

and many more...

But I wrote the whole code under 1 hour, so I'd like to get a review my code as I wrote the first time. All kind of reviewing and criticization are welcomed.

I didn't find any bugs in my code, but if you find any, please comment.

\$\endgroup\$
1
  • \$\begingroup\$ One thing I found out : median should be meridiem or period. \$\endgroup\$ Commented Dec 24, 2013 at 21:39

4 Answers 4

14
\$\begingroup\$

The challenge states that Parallel Universe Time is Normal Time, mirrored on the vertical axis of an analog clock; the examples suggest that AM/PM should be swapped. I initially found it ambiguous how noon and midnight should be treated, and assumed that 12:00 am should map to 12:00 pm and vice versa, consistent with @tintinmj's original code.

However, on further thought, I believe @abuzittingillifirca is right, that noon should map to noon and midnight should map to midnight. Consider the following diagram of the mapping. The blue lines are undisputed (for example, 1 am ⟷ 11 pm). By a continuity argument, it's more plausible that noon and midnight should be fixed points of the transformation (red lines).

Mapping of Normal Time to Parallel Universe Time

Here is the revised code to implement that.

private static final Pattern TIME_FMT = Pattern.compile("(0?[1-9]|1[0-2]):([0-5]\\d)\\s*([ap]m)");
private static final int MINUTES_PER_DAY = 24 * 60;

/**
 * Parses time as minutes since midnight.
 */
private static int parse(String time) {
    Matcher m = TIME_FMT.matcher(time);
    if (!m.matches()) {
        throw new IllegalArgumentException(time);
    }

    int hour = Integer.parseInt(m.group(1)) % 12;
    if ("pm".equals(m.group(3))) hour += 12;
    int minute = Integer.parseInt(m.group(2));

    return 60 * hour + minute;
}

/**
 * Formats time, in minutes since midnight.
 */
private static String format(int minutes) {
    String ampm = (minutes % MINUTES_PER_DAY < MINUTES_PER_DAY / 2) ? "am" : "pm";
    int hour = (minutes / 60) % 12;
    if (hour == 0) hour = 12;
    int minute = minutes % 60;

    return String.format("%02d:%02d %s", hour, minute, ampm);
}

public static String parallelUniverseTime(String time) {
    return format(MINUTES_PER_DAY - parse(time));
}
\$\endgroup\$
2
  • \$\begingroup\$ Sure going to nominate this answer as *Thousand Words*. ^_^ \$\endgroup\$ Commented Dec 26, 2013 at 14:03
  • \$\begingroup\$ You really wanted to get that "Thousand words" award, didn't you ;) +1 \$\endgroup\$
    – syb0rg
    Commented Dec 27, 2013 at 18:04
8
\$\begingroup\$

For writing all of that in under an hour you should feel pretty good about yourself (with partial documentation to boot!). Here are some things I found though:


You currently aren't following Java package naming standards.

package com.foo.competition;

The Scanner.nextInt() method does not read the last newline character of your input, and thus a newline could be consumed if you call Scanner.nextLine. I prefer to use Scanner.nextLine since you can input any valid String, and then just parse it into the type you need (as long as it's a valid type to parse it to).

I would be especially wary of this since you have multiple .nextInt() and .nextLine() method calls scattered throughout your code.

int t = Integer.parseInt(input.nextLine());

You aren't consistent in your if condition statements. You should choose a way to write with them and stick with it so you don't confuse yourself and other people reading your code.

if(minute.equals("00")) {
        return minute;
}
...
if(median.equals("am")) return "pm";
...
if(hour.length() == 1) { hour = "0" + hour; }

There is an if condition statement that you have on one line, with braces. You don't really need those braces since if you added another statement to the condition, it would be pretty noticeable and you would add braces anyways. You already did this for one of your if condition statements anyways.

if(hour.length() == 1) { hour = "0" + hour; }

if(median.equals("am")) return "pm";

This is more of a nitpick, but I don't like how you are declaring arrays with a space in-between the object and and the brackets. It is a String array with the variable name time. But keeping it that way is up to you.

String []time = parseTimeAndReturnAsArray(actualTimeStamp);

String[] time = parseTimeAndReturnAsArray(actualTimeStamp);
\$\endgroup\$
1
  • \$\begingroup\$ Yes inconsistency of if blocks, I didn't notice that. \$\endgroup\$ Commented Dec 24, 2013 at 21:49
6
\$\begingroup\$

You're working too hard.

Parsing is easier using a regular expression (import java.util.regex.*).

Figuring out the mirrored time is easier if you convert everything into minutes first. Then the code would model how an analog clock works. You don't want your code to be littered with special cases.

Formatting the output is easier using String.format().

private static final int MINUTES_PER_HALF_DAY = 12 * 60;

private static final Pattern TIME_FMT = Pattern.compile(
    "(0?[1-9]|1[0-2])" +  // Group 1: Hour 01-12 (optional leading zero)
    ":" +                 //  colon
    "([0-5]\\d)" +        // Group 2: Minute 00-59 (0-5 followed by any 2nd digit)
    "\\s*" +              //  optional whitespace
    "([ap]m)"             // Group 3: am or pm
    , Pattern.CASE_INSENSITIVE);

public static String parallelUniverseTime(String time) {
    Matcher m = TIME_FMT.matcher(time);
    if (!m.matches()) {
        return null;
    }

    // Hour as an int, but mapping "12" to 0
    int hour = Integer.parseInt(m.group(1)) % 12;
    int minute = Integer.parseInt(m.group(2));
    String ampm = m.group(3).toLowerCase();

    int minutes = 60 * hour + minute;

    //  ^^^ Normal universe ^^^
    //        ** WARP! **
    // vvv Parallel universe vvv

    minutes = MINUTES_PER_HALF_DAY - minutes;

    // Likely 0 ≤ hour < 12, but possibly hour = 12 if minutes == 0.
    // We'll canonicalize hour = 0 and hour = 12 to "12" on output.
    hour = minutes / 60;
    minute = minutes % 60;
    ampm = ("pm".equals(ampm)) ? "am" : "pm";

    return String.format("%02d:%02d %s", (hour == 0 ? 12 : hour), minute, ampm);
}
\$\endgroup\$
9
  • \$\begingroup\$ Very nice (+1), but there is one exception I would point out with using regex for parsing; don't do it with HTML. \$\endgroup\$
    – syb0rg
    Commented Dec 25, 2013 at 0:04
  • \$\begingroup\$ Your am/pm is wrong for 12:00. Noon and midnight should map to itself. You should always test for edge cases. \$\endgroup\$ Commented Dec 25, 2013 at 7:21
  • \$\begingroup\$ @abuzittingillifirca I did consider noon and midnight. The challenge description was ambiguous on how they should be handled, so I opted to stay compatible with the OP's code. If you want noon and midnight to map to themselves, it's even easier: calculate minutes using a 24-hour clock, and subtract it from MINUTES_PER_DAY instead. \$\endgroup\$ Commented Dec 25, 2013 at 8:46
  • 1
    \$\begingroup\$ @200_success Well I don't like regular expression! But can you explain how are you using regex? And what is the ambiguousness in the question? \$\endgroup\$ Commented Dec 25, 2013 at 11:35
  • \$\begingroup\$ @abuzittingillifirca Yes in a [12 hour clock] 12:00 am mirrored time will be 12:00 pm and vice versa. \$\endgroup\$ Commented Dec 25, 2013 at 11:36
6
\$\begingroup\$

I found some major bugs in my code and also in 200_success's code. Since bug-determining and killing also a part of code-review I want to post this as an answer not as a comment. I think it's OK? right?

  Input        Correct output      My output      200_success's output [No bugs found]

  12:01 am     11:59 pm            00:59 pm x     11:59 pm 
  11:32 am     12:28 pm            00:28 pm x     12:28 pm
  12:34 am     11:26 pm            -1:26 pm x     11:26 pm
  06:01 am     05:59 pm            06:59 pm x     05:59 pm 
  11:02 am     12:58 pm            01:58 pm x     12:58 pm
  12:59 am     11:01 pm            -1:1 pm  x     11:01 pm

x is marked as wrong output.

So in my code the line

    if(minuteAsInt < 12) {
        return (12 - hourAsInt) + "";
    }

is making all kind of troubles. When I wrote the code, I made a theoretical assumption which doesn't stand in practical usage. What I thought was that

enter image description here

If the hour hand travels 30° in 60 minutes, then it will travel 6° in 12 minutes. So after XX:12 the hour hand will move.

But in practical cases we assume that hour hand moves continuously after XX:00.

Also there are corner cases for 12:XX and 11:XX. As 12 - hourAsInt - 1 will give -ve or 00:XX value respectively.

\$\endgroup\$
2
  • \$\begingroup\$ The hours hand might be moving continuously. Depends on the model of the clock ;) \$\endgroup\$ Commented Dec 25, 2013 at 17:38
  • \$\begingroup\$ @retailcoder he he now I've to find Clark K.. oops, Superman to see his clock model ;) \$\endgroup\$ Commented Dec 25, 2013 at 17:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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