When code golfing, it’s important to establish the constraints you’re supposed to respect. A looser interpretation of the rules may lead to a shorter solution, but you don’t want to end up with a class that works only with a specific use case. Let’s establish the boundaries of this exercise:
In my solution, I represent a group of connected containers using a circular list, just like Speed2. Instance field n (for next) is a pointer to the next container in the group.
Also like Speed2, when you add water with addWater, the amount is stored locally in the instance field, a, and is never actually distributed among the other connected containers. As a consequence, every call to getAmount needs to scan the whole group, sum up the amounts that every container holds, and finally return the total amount divided by the size of the group.
Before I present the actual code, here’s a legend for the five instance fields:
Take a look at the following code for the compact Container implementation. I left some basic white space and indentation for readability. If I remove all unnecessary white space, the class measures 223 bytes and still works as intended. For a comparison, Reference takes 1322 bytes, including white space.
public class Container { float a,s,t; 1 s,t are used like local variables Container n=this,c=n; public float getAmount() { for(s=t=0;s<1||c!=n;c=c.n,s++) 2 Notice the comma t+=c.a; return t/s; } public void connectTo(Container o) { c=o.n; o.n=o.c=n; n=c; 3 Swaps next pointers } public void addWater(float w) { a+=w; 4 Just accumulates locally } }
To understand this obscure implementation, start by reading the addWater method, which is the easiest. The newly added water is summed to the a (for amount) field, and no other line modifies that field. Hence, the a field of a given container indicates the amount of water ever added to that container.
Then, move to the method connectTo. Recall from chapter 3 that merging two circular lists starting from arbitrary nodes is particularly easy: it suffices to swap their “next” pointers. The method connectTo does exactly that. Moreover, it updates the value of the support variables c and o.c to be equal to the new (that is, swapped) value of n and o.n, respectively.
As in C, you can concatenate multiple assignments in Java. Such a sequence is evaluated right to left, so all variables in the sequence are assigned the value of the rightmost expression.
Finally, there’s the rather daunting loop in getAmount. Its purpose is to compute the total amount in all containers in this group, while at the same time measuring the size of the group. After the loop, you can find the total amount in the variable t and the size of the group in s, which explains why the method returns t/s.
In C, the expression exp1,exp2 evaluates both expressions in order and then returns the value of the second one. In Java, a similar syntax is allowed only inside the first and third clause of a for loop. It’s meant to gracefully support loops with multiple indices:
for (i=0, j=n; i<n; i++, j-{}-) ...
With this in mind, the loop initialization and update parts should be quite clear. Both the size and the total amount start at zero. For each iteration, the size is incremented by one, and the container pointer c moves to the next container in the group.
The staying condition requires some explanation. The loop must stop when it has visited the whole circular list, that is, when the container pointer goes back to its original value. In our case, the container pointer c starts with the value of the next pointer n. As a result, the loop must continue as long as c!=n. But there’s a catch: the for loop checks its staying condition before each iteration. To force the loop to perform at least one iteration, I had to add the staying condition s<1.
It’s very likely that shorter solutions exist. Can you find one? If you do, drop me a line, I’d be glad to hear about it!
18.226.187.24