Assignment 9Due Mar 24, on line
Part of
the homework for CS:2820, Spring 2021
|
Scores from HW9 are a problem due to a serious mistake on my part. I accidentally released the solutions at 6:42 PM Wednesday afternoon and did not discover I'd done so until around 10 PM. Students who discovered the posted solutions got perfect scores with no effort, while others found this to be a challenging assignment.
The obvious resolution to this is to simply drop the score from this assignment, but that penalizes those who didn't notice the posted solutions. The alternative is to drop perfect scores earned after 6:42 PM -- ICON keeps the date of submission to the minute. Perfect scores are sufficiently unusual on my homework/quizlets that this seems reasonable.
private void create( double time ) { Simulator.schedule( time + MyRandom.stream.nextExponential( interval ), (double t)-> create( t ) ); System.out.println( time + " created at " + this.toString() ); // BUG: must create a vehicle }
The lambda expression in this bit of code provides the body of the
trigger method of an implementation of the interface
Simulator.Action. What is the implicit parameter list for the
constructor for the new object passed to Simulator.schedule?
a) ()
b) ( t )
c) ( this )
– correct
d) ( time )
e) ( this, time )
The first step in answering this question is to identify the constructor call hidden in this line of code. A λ expression in Java does the following:
So what are the parameters to that constructor? As an inner class, the parameter list looks empty. You'd write new Anon() to create an instance. But, the body of the inner class is a method call to create, and create is not a static method. Therefore, a call to create is implicitly a call to this.create for the current object. The variable this is implicitly defined by the header to every constructor and non-static method, so to implement the inner class, it must be passed, as an implicit parameter, to the constructor. Answer c) does this.
Several students leanded toward answer e), but time is never mentioned inside the λ expression. Rather, the simulated time is passed as a parameter to the trigger method much later, when the action is triggered.
Simulator.schedule( endOfTime, (double t)->System.exit(0) );
This is rather stupid, it just kills the program, while really, we should
replace the call to System.exit( 0 ) with a call to
a routine to wrap up the simulation and print out a final report of the
results, perhaps summary statistics of the congestion on each road and
the waiting times to get through each intersection. We'll call the
method that does this wrapUp.
Which of the following would be the best replacement for the above line?
a) Simulator.schedule( endTime, (double t)->wrapUp( t ) );
– correct
b) Simulator.schedule( endTime, (double t)->this.wrapUp( t ) );
c) Simulator.schedule( endTime, (double t)->Simulator.wrapUp( t ) );
d) Simulator.schedule( endTime, (double t)->wrapUp() );
e) Simulator.schedule( endTime, (double t)->this.wrapUp() );
It's worth noting that there's a typo here, endTime and endOfTime should have been the same name, this adds a little confusion to the question, but does not change the reasoning about the answer.
Does the proposed wrapUp method need to know th time? Nothing is said about what it does other than "print out a final report of the results, perhaps summary statistics." That is enough, really, since summary statistics frequently include such things like averages over time. Therefore, d) and e) are poor choices.
Is the proposed wrapUp method likely to be a static method of class Simulator? Noting in that class knows about what is being simulated, therefore it would have a hard time gathering useful statistics about the model. Therefore c) is a poor choice.
Where would this call to Simulator.schedule take place? Probably either in the main method or in a static initialization method called from the main method. If the call is in a static method, this is undefined, so b) and e) are bad choices.
It's worth noting that there's a typo here, Double should have been double. That makes all of the alternatives c), d) and e) slower, but does not change the right answer.
We can rule out a) and b) because the log-normal distribution requires two parameters.
We can rule out a) and c) because the log-normal distribution needs to give a real-valued result (float or double in Java) in order to support log-normal distributions of things like the time of a disease state. In our older code, we used it only to compute capacities of places, rounding the result to the nearest integer, but with MP7, the general case matters.
That leaves the choice between d) and e). We can express the log-normal distribution either way. Either logNormal computes sigma (the standard deviation of the underlying normal distribution) or we require the caller to compute it. Therefore, efficiency has to be the deciding factor. If we make logNormal compute sigma for each call, that computation will be duplicated each time a number is drawn from the distribution. If we make the caller compute sigma, we can compute it only once, saving the result and passing it each time we need to draw yet another number from the same distribution.
int[] infectPop = new int[InfectStates.Dead.ordinal() + 1];
Now, if a person's infection state changes from oldState to
newState, you'll have to write this code to track the population
change:
a) infectPop[ oldState ]--;
infectPop[ newState ]++;
b) infectPop[ InfectStates.oldState ]--;
infectPop[ InfectStates.newState ]++;
c) infectPop[ oldState.ordinal() ]--;
infectPop[ newState.ordinal() ]++;
– correct
d) infectPop[ oldState.hashCode() ]--;
infectPop[ newState.hashCode() ]++;
e) infectPop[ InfectStates.valueOf( oldState ) ]--;
infectPop[ InfectStates.valueOf( newState ) ]++;
The variables oldState and newState hold objects of class InfectStates. Therefore, they cannot be used as array indexes. This rules out a). They are not names of static components of InfectStates either (the static components are named latent, asymptomatic etc., up to dead). That rules out b).
The hashCode() method, applied to any object, gives an integer, and integers can be used for array indexing, but the integers you get are huge and appear random, so alternative d) is unlikely to work.
The valueOf method applying to enum classes does not do anything like what the code above seems to be implying. If you look it up, you'll see that the number of parameters supplied is just plain wrong. Alternative e) is utter nonsense.
That leaves c). The ordinal() method of an enum gives a small integer, zero for the first member of the enumeration, one for the second, and so on. This lets us use enum constants as array indices.
How the sorting is actually done is irrelevant. Are object moved from here to there, or is the sorting dne in place. Do we sort by selecting the least element when we do remove from a queue or do we sort by keeping things in order whenever we do add to a queue. FIFO queues can be made both ways. So, a), b) and c) are irrelevant.
What matters is, does the queue preserve the order of elements with equal keys. In our case the last midnight report was scheduled at exactly the same time as the end-of-time event. Which happens first. That is a question of the stability of the sorting algorithm. It happens that class PriorityQueue uses a heap data structure, according to the Oracle documentation for the class. That is the exact same data structure underlying heapsort, a very high performance but unstable sorting algorithm.