Descriptive names
private static void printSortedArr (int[] arr) {
I don't like these names. I would rather write out array if that's what's being said. E.g.
private static void printSortedArray(int[] numbers) {
Now I don't have to figure out that Arr
is short for array. And numbers
is more descriptive than array anyway.
Favor arrays over numbered variables
int count0 = 0, count1 = 0, count2 = 0;
So here you have three variables that hold different values based on their names. However, we have a data type that enforces that relation, the array.
As proposed here:
private static void printSortedArray(int[] array, int maxElement) {
int[] counts = new int[maxElement + 1];
Now you have a more reusable method. It's no longer limited to just sorting an array of 0s, 1s, and 2s. It can sort any array of 0, ..., maxElement
. Further, it is now self-organizing. Instead of
if (elem == 0) {
count0++;
}
else if (elem == 1) {
count1++;
}
else {
count2++;
}
You can just say
counts[element]++;
You no longer have to expand the if
/else
each time you add a new element value.
In a comment, you said
Isn't storing the values in an array a space overhead?
The difference between your original three variables and this array is the length
variable. Other than that, both use the same space. The cost of the extra variable is more than made up with the reduction in errors from replacing the if
/else
monster with the simple array update.
For example, your original has a bug if the array contains numbers other than 0, 1, or 2. It silently counts any other number as a 2. The revised version would throw an array out of bounds exception in that case, and it will do so without any added checking on your part.
In another comment, you say
I thought that according to the problem statement, there was no explicit need to store the sorted elements in the array causing me to just print them. Also, I thought that using an array would be a space overhead.
In that case, the array duplicated the original array and was a space overhead. But the thing that you gain there is that you can reuse the created array for other things.
You are correct in pointing out that the current problem statement does not require separate action and display. What we are trying to tell you though is that in the real world, problem statements change. Your code is fragile. If the problem statement changes, you have to rewrite it. We want to encourage you to write robust code that will continue to work even if the goal changes. This is because the goal in a project tends to change a lot.
Donald Knuth said:
The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.
You are generally going to be better off writing for good engineering principles and then, in those rare instances where it matters, optimizing. Don't fall in love with your optimized solutions, as user requirements will stomp all over them.
What if you were asked to print out the counts for each element? You would have to modify this code to do that. However, if your method was
// numbers can only contain values from 0 to maximumValue
private static int[] countElementsByValue(int[] numbers, int maximumValue) {
int[] counts = new int[maximumValue + 1];
for (int element : numbers) {
counts[element]++;
}
return counts;
}
Then you could easily display the counts or the array. Or both. Because you have that information right there. With the original method, you would either need to rewrite it like this or recount the elements to do that display. Because the original method discards that information immediately. Or you could modify the original method. But then what happens when someone says that they want just the array without the counts?
In general, we prefer small methods that do one thing and only one thing over more complicated methods that do multiple things. This is especially true about mixing generation and display. That fails quite often, as we have found that requiring multiple displays over the same basic data is quite common. So there is a specific instance (don't mix generation and display) of the more general rule to keep difference actions separate.
Your original method might become something like
int[] counts = countElementsByValue(numbers, 2);
int[] sorted = expandCounts(counts);
System.out.println(Arrays.toString(sorted));
Note how the last line doesn't have to know that it's displaying a sorted array. It doesn't care. Whatever the array is, it displays.
We could probably improve performance by writing a special display routine that works off the counts directly. But it wouldn't be that much of an improvement. And most of the time, it won't matter. With your example input, the time difference is going to be trivial. We'll spend more time compiling the program than running it.
Performance
If you're really worried about storage performance, then why read the data into an array at all?
String line3 = br.readLine();
String[] inps = line3.split(" ");
for (int j = 0; j < size; j++) {
arr[j] = Integer.parseInt(inps[j]);
}
printSortedArr(arr);
Could be
for (String token : br.readline().split(" ")) {
char digit = token.charAt(0);
if (digit == '0') {
System.out.print("0 ");
} else {
counts[digit - '0']++;
}
}
int value = '0';
for (int count : counts) {
while (count > 0) {
System.out.print(value + " ");
count--;
}
value++;
}
System.out.println();
One reason not to do this is that, counter-intuitively, it is actually slower to mix input and output like this than to use more storage. In particular, System.out.print
is not high performance with printing two characters at a time. You'd usually be better off using a StringBuilder
to generate the string and then outputting that.
Why use readline
? The resulting array could potentially be huge, and you are committing yourself to two arrays, one of strings and one of integers. Consider
int read = br.read();
while (read >= 0) {
if (read != ' ') {
counts[read - '0']++;
}
read = br.read();
}
This saves us two arrays and a string at the cost of one smaller array. If you are really space constrained, mixing input and generation like this is better than mixing generation and output.
Of course, it is quite possible that this is slower than your original. That's a common trade-off, speed for space.
Then with Java 8, you can use something like (from Stack Overflow):
for (i = 0; i < counts.length; i++) {
String value = Integer.toString(i);
System.out.print(String.join(" ", Collections.nCopies(counts[i], value)));
}
System.out.println();
This will display with only four print statements.
Or if that uses too much space (it's unclear to me if it creates a temporary collection or not), stick to your original output method. That may be slow, but it uses very little space.
Remember though, that this kind of optimization is something you should do after you establish that you need it. Most of the time this is going to be unnecessary and it may even be counter-productive. E.g. if you're worrying about space constraints when you are actually time constrained.