Pages

HackerRank Fraudulent Activity Notifications

Don't be too impressed by its name, this problem boils down to calculating the median for a moving window in a list of integers. It is not conceptually difficult, it just asks some attention in choosing the algorithm, since they are doing some check on the resulting performances. I do suggest you to try it yourself going to Sorting - Fraudulent Activity Notifications on HackerRank and then compare your solution with mine.

The structure of the algorithm is quite simple. Get the initial window from the input list, then loop on all the other elements checking if there is a fraud alert (i.e. calculate the median) and moving the window on. Let's see my result code. Since in these days I'm mainly work in Java, that is the language I've used for the implementation.

public static int activityNotifications(List<Integer> expenditures, int d) {
    if (expenditures == null || d == 0 || expenditures.size() <= d) { // 1
        return 0;
    }

    List<Integer> window = new ArrayList<>(expenditures.subList(0, d)); // 2
    window.sort(null); // 3

    int result = 0;
    for (int i = d; i < expenditures.size(); i++) { // 4
        Integer current = expenditures.get(i); // 5
        if (fraudAlert(window, current)) { // 6
            result += 1;
        }
        move(window, expenditures.get(i - d), current); // 7
    }

    return result;
}
  1. Check on the input parameters, not really required in a problem like this one, still I would feel bad not to have them
  2. Setting up the window, getting the first "d" elements from the input list. I used List.subList() to get them, and I push them in a new list, so that I can work freely on them - remember that subList() returns a view of the original list, and we don't want to mess with it
  3. It is very easy to get the median from an ordered list, just go to the center of it
  4. Loop on all the elements after the initial window
  5. Get the element to be checked for frauds
  6. When it is the case, increase the number of alerts
  7. The oldest element goes out of windows, the current one goes in

Good, right?

I don't think there is much to say on the fraudAlert() method, it just calculates the median and performs the check as required by the problem. If you want, please have a look at it on GitHub.

It is interesting the move() method. For performance reasons, we need a way to modify the window in a way that won't require to sort it again from scratch. My first idea was writing a custom selection sort that would take in account all the information we have to reduce its temporal complexity to the minimum. Alas, it didn't work. Besides, the code was getting too unreadable.

So I decided to move in another direction. It easy and cheap (logarithmic) to find the position of an element in a sorted collection - or the position where it should go, if not in.

private static void move(List<Integer> window, Integer exiting, Integer entering) {
    int posExit = Collections.binarySearch(window, exiting); // 1
    if (posExit == -1) { // 2
        throw new IllegalStateException("Can't find " + exiting + " in window");
    }
    int posEnter = lowerBound(window, entering); // 3

    if (posEnter > posExit) { // 4
        window.add(posEnter, entering);
        window.remove(posExit);
    } else if (posExit > posEnter) {
        window.remove(posExit);
        window.add(posEnter, entering);
    } else {
        window.set(posExit, entering);
    }
}
  1. Collections.binarySearch() assumes the collection is sorted and returns us the index of the element we search in logarithmic
  2. I would never expect this to happen in this class. Still, I could not help to write this check
  3. I needed something like the C++ lower_bound() function. Nothing like that in standard Java, to my knowledge. So I wrote it
  4. Then it is just a matter of removing and adding values

Since I used an ArrayList, I feared the possibly high number of writes for adding and removing could have a negative impact on performances, and I was ready to think of using a linked list (maybe a custom one) to reduce them. However this solution passed all the test on HackerRank, so I didn't bother.

I don't have much to say on lowerBound(), too. Very close to a "normal" binary search.

Well, that's it. I guess. Here is the link to the class on GitHub.

Go to the full post

Heroku Web App Basic setting

Shopping list for setting up a Java EE Web App to be deployed on Heroku.

Assuming Maven, Java 11, Tomcat 9 as web server.

pom.xml: execution

Add to the build - plugins - plugin for maven-dependency-plugin, in the executions

<execution>
<phase>package</phase>
<goals>
  <goal>copy</goal>
</goals>
<configuration>
  <artifactItems>
	<artifactItem>
	  <groupId>com.heroku</groupId>
	  <artifactId>webapp-runner</artifactId>
	  <version>9.0.41.0</version>
	  <destFileName>webapp-runner.jar</destFileName>
	</artifactItem>
  </artifactItems>
</configuration>
</execution>

The element "version" is the Tomcat version we want to use

System properties

Root level, the system.properties should specify the Java version

java.runtime.version=11

Procfile

A one-liner, I split it here to improve readability

web: java $JAVA_OPTS
        -jar target/dependency/webapp-runner.jar
        --port $PORT target/*.war

Go to the full post

HackerRank Array Manipulation

Think of an array of 64-bit integers initialized to zero. It could be quite large, up to ten million elements. We change it applying up to 20 thousand times a series of simple operation: add to all its elements in a specified range an increase that could be in the order of billions. At the end, we want to know the max value in the array.

Please, try it on HackerRank before checking the Java solution here below. It is tagged as hard but, as you get its point, it is not actually so complicated.

The naive solution of applying the required operations just as stated in the problem description would obviously work, but a too high cost. It would be too slow, and a few test cases are bound to fail on a timeout.

We have to be smarter than that, we should avoid modifying all those elements in the array all over again. If you know what a partial sum is, you have already seen the (green) light to this problem.

We could keep track of just the increase and the interval! When asked to add 10 to all the elements between, say, 1_000 and 2_001_000, we could just add 10 to the element 1_000, and then subtract 10 to the element 2_001_001. The elements in the array won't store the actual value but the variation from the previous one.

We just need to do a final single pass on every element to apply the partial sum algorithm to convert our array of variations in an actual array of values, as expected by the user. And then check what is the max in it.

Once the approach is clear, the code is pretty simple.

long[] data = new long[n + 1]; // 1

for(int[] query: queries) { // 2
    data[query[0]-1] += query[2]; // 3
    data[query[1]] -= query[2]; // 4
}

for(int i = 1; i < data.length; i++) { // 5
    data[i] += data[i-1];
}

return LongStream.of(data).max().orElseThrow(); // 6
  1. The caller specify the size of the array as n, it simplify the partial sum algorithm having one element more at the right
  2. Looping on all the queries passed in. First two components are the one-based indeces of the subinterval on which we should apply the change. The third value is the increase
  3. Being one-based, remember to decrease the index
  4. Remember also that we should decrease the first element outside the interval on the right
  5. Unfortunately, I don't know any standard Java functionality to generate the partial sum of an array, like C++ partial_sum() or Python accumulate(). So I did it "by hand"
  6. It is not strictly a necessity using streams here. In any case I guess it is clear what the code means. By the way, orElseThrow() is Java 10, if you want to run this code on Java 8 (as HackerRank asks) you'd better fallback to getAsLong()

Full Java code and relative test case pushed to GitHub.

Go to the full post