abstract
class DataSource
, which allows data to be read one floating point number at a time.DataSource
, namely
ArraySource
which draws its input from an array; SineSource
which produces a sine-wave output;
and DataFilter
, which draws its input from another
DataSource
.
DataFilter
called ScaleFilter
, which scales (multiplies) the input by a constant; and SineScaleFilter
, which scales the input by a sine wave.
DataFilter
called BufferedFilter
, which uses a circular buffer to remember a range of previous input.
BufferedFilter
called EchoFilter
which echoes the input with a certain delay.
main
method will not be tested; you may use it any way you want.private
or protected
. This will be verified manually.
public abstract class DataSource implements java.util.Iterator<Double>
We can begin with a class that represents a generic source of data. The data we will be dealing with is a stream of Double
data (remember that Double
is just the reference version of the double
primitive type). The data from the stream is to be read one floating point number at a time, for as long as there is data left on the stream. This class is abstract
-
our data-reading methods are left there as a placeholder for future
classes to override. The class should implement the following:
public DataSource()
we don't need to do anything at this point, but it's still nice to have a constructor as a placeholder.
@Override public abstract Double next();
this would pull the next unit of data from the stream; not implemented yet.
@Override public abstract boolean hasNext();
this would tell us if there is more data left in the stream; not implemented yet.
public void display()
while hasNext()
indicates that there is more data left in this source, println
the result of toString()
(hint: there's nothing fancy about this method, and it can be written in as little as one line of code). The result will be a visual depiction of the data stream.
@Override public String toString()
this pulls a value from the source using next()
and uses it to produce a text string containing a single asterisk surrounded by whitespace. Use the following code:
@Override public String toString() {
double d = next();
if (d < -5 || d > 5) return "";
int i = (int)(7*d + 40);
return String.format("%" + i + "s", "*");
}
public class ArraySource extends DataSource
We already have data sources which have no data! Let's do something about that. This basic data source will allow you to enter an array of Double
values to use as input. Calls to next()
will begin with the first value in the array, and continue in order until there are no more values in the array, at which point hasNext()
will begin to return false
. The only new method in this class will be the constructor, although next()
and hasNext()
must be overriden so that the class is no longer abstract:
public ArraySource(Double[] a)
initializes the source using the array a
(assume that it's not null
.
@Override public Double next()
.
@Override public boolean hasNext()
.
public class SineSource extends DataSource
The SineSource
is another source of data, but this time it generates its own data. As you may be able to guess from the name, it generates a sine wave. A sine wave has several parameters: the amplitude a
represents how large it is; the frequency f
represents how quickly it oscillates; and the offset d
represents the moment it begins. Together, they form an equation like the following:
a×sin(f×t + d)
t
above represents the time step: the first time you call next()
corresponds to zero, the next time to 1, etc. Assume that the angle is in radians, to be consistent with Java's built in Math
functionality. We want the input to be finite so our constructor will include a maximum number of time steps before the sine wave runs out. You will implement:
public SineSource(Double a, Double f, Double d, int steps)
initializes the source using the given parameters; a
, f
, and d
correspond to parameters in the sine equation, while steps
is the total number of steps for which the SineSource
will have data.
@Override public Double next()
Retrieves the next unit of data from the sine wave.
@Override public boolean hasNext()
return false once the SineSource
has exceeded all of its allotted steps.
new SineSource(3.0, Math.PI/20, 0.0, 100).display();
public class DataFilter extends DataSource
This class takes another DataSource as input, and sends it directly through to the next()
method. This will not be useful in and of itself, but we will derive classes later which modify this functionality in order to be able to do something interesting with the data. Like with the other sources, you have the following to implement:
public DataFilter(DataSource src)
initializes so that the output of src is being used to send data to the output of this filter.
@Override public Double next()
initializes so that the output of src is being used to send data from input to output.
@Override public boolean hasNext()
this should depend on whether the input filter object still has data in it or not.
public class ScaleFilter extends DataFilter
This filter doesn't just pass the input to the output: it also multiplies it by a scaling constant before doing so. So if it has a scaling constant of c=5.0
and it recieves an input of 0.2
, then the output it produces with next()
will be 10.0
. This filter will implement the following:
public ScaleFilter(DataSource src, Double c)
initializes the filter with the given data source and scaling constant c
.
@Override public Double next()
.
public class SineScaleFilter extends DataFilter
This filter is similar to the ScaleFilter
, except that instead of multiplying by a constant at every step, it multiplies by a sine wave (see SineSource
) at every step. This filter will implement the following:
public SineScaleFilter(DataSource src, Double a, Double f, Double d)
initializes the filter with the given data source and sine wave parameters a
for amplitude, f
for frequency, and d
for offset.
@Override public Double next()
.
DataFilter
, you are not required to derive directly from DataFilter
. You can either leave the declaration as-is, or use a variant (for example, derive SineScaleFilter
from ScaleFilter
, or derive both of them from some intermediate class).
BufferedFilter: (20p)public class BufferedFilter extends DataFilter
While you're reading data through the filter, it's possible to use a buffer (in this case an array of Double
values) to remember input which you've already seen. For example, if you have an array of 20 Double
values, then you can remember the past 20 input values which have passed through your filter. If you want to remember which data you've seen 5 time units ago, all you need to do is look back 5 places in your array.
There's a catch, though: when you begin, you only have a fixed-size buffer, but your input can go on for a long time. The input may be so long, that you don't even want to keep a buffer large enough to capture all of it. Maybe your input is 100,000 time units long, but you only care about remembering the past 20 time steps. A solution is to use a circular buffer. The buffer fills up, but when it gets to the end, it starts back at the beginning and starts overwriting what's already there. Basically, the buffer has a moving head and a moving tail.
So if our buffer is length 20, at the beginning, our head is at position 0, our tail is at position 1, and the data for the previous time step is at position 19, and five time steps ago is at position 15. We read in a bit of data, and it gets stored at position 0. Now, the head is at position 1, the tail is at position 2, the previous time step (the one you've just read in) is at position 0, and five time steps ago is at position 16. Etc...
For this class you implement a circular buffer which will store input values as you read them from the source, after which you forward them out as-is through the filter. You will implement the following:
public BufferedFilter(DataSource src, int bufSize)
initializes the filter with the given data source and a buffer of length bufSize
. Hint: it's expected that your array will be all zeros at the start, but if you declare a list of Double
values, their initial value would be null
instead of zero.
public Double getLast(int t)
retrieve the memory of the input from t
time units ago. If t
is zero, then it refers to the most recently read input. Assume that t
is within the range of the buffer size, otherwise the result is undefined.
public Double inputNext()
this retrieves the next input value from the input DataSource
, stores it in the circular buffer (updating anything you need to update in the process), and returns the value you've just read in.
@Override public Double next()
uses inputNext()
to send on the next input from the source as-is.
public class EchoFilter extends BufferedFilter
This filter uses the memory of previous inputs to produce an echo effect. At every step, it will output a value which is the sum of the current input and an input from some n
steps ago. To do this, the following needs to be implemented:
public EchoFilter(DataSource src, int echo)
initializes the filter with the given data source and an echo delay of echo
. To produce an echo which is delayed by a certain number of steps, Think about how big of a buffer we need: if we want a delay of 1 step, for example, then our memory would need to store both the current step and the previous step, so two steps. What about if we want an echo delay of 5 steps? We'd need the previous five steps plus the current step, so a 6 step memory. So if we wanted a delay of length echo
, how big of a buffer do we need to ask for?
@Override public Double next()
produces an output which is the result of adding the current input to the input from echo
steps ago.
display()
method to produce a visual output to see what they look like.
Grading:
The implementation of each class carries the weight mentioned in each part above (10% each except for BufferedFilter
, which is 20%). Half of that score comes from automatic testing, and half comes from manual inspection. 10% will
be granted for style (i.e. commenting code, not writing unreadable code), including properly distinguishing which fields and methods should be private.
Partial credit is possible, but hard-coding
to avoid test cases will receive an automatic zero on the manual inspection points.
Programs which do not compile without modification will receive a zero in most cases.
Submission:
Submission instructions are as follows.
xxx_yyyyyyyy_P3/
ID.txt
in the format shown below, containing your name, userid, G#, lecture section
and lab section, and add it to the directory. Your directory should contain nine files
total.
Full Name: Donald Knuth