Skip to content

Commit

Permalink
Reimplementing algorithm using State pattern. Much better now :)
Browse files Browse the repository at this point in the history
  • Loading branch information
luiz committed Mar 8, 2012
1 parent 2505c57 commit 9d09a68
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 75 deletions.
253 changes: 188 additions & 65 deletions src/main/java/br/ime/usp/aztec/AZTEC.java
Expand Up @@ -16,7 +16,6 @@
package br.ime.usp.aztec;

import java.io.IOException;
import java.util.Iterator;

/**
* The algorithm implementation
Expand All @@ -26,6 +25,7 @@
public final class AZTEC {

private final AlgorithmParameters params;
private State state;

public AZTEC(AlgorithmParameters params) {
this.params = params;
Expand All @@ -44,121 +44,244 @@ public AZTEC(AlgorithmParameters params) {
* @see EncodingOutput
*/
public void encode(Iterable<Double> signal) throws IOException {
Iterator<Double> signalPoints = signal.iterator();
Line line = new Line(signalPoints.next());
Slope slope = null;
while (signalPoints.hasNext()) {
double current = signalPoints.next();
if (!line.canContain(current) || line.isTooLong()) {
if (line.isTooShort()) {
if (slope == null) {
slope = new Slope();
}
slope.update(line);
} else {
line.end();
this.state = new ShortLine();
for (Double sample : signal) {
this.state.process(sample);
}
this.state.finish();
}

private interface State {
void process(double sample) throws IOException;

void finish() throws IOException;
}

private class ShortLine implements State {
private final Line line = new Line();

@Override
public void process(double sample) throws IOException {
if (this.line.canContain(sample)) {
this.line.update(sample);
if (!this.line.isTooShort()) {
AZTEC.this.state = new NormalLine(this.line);
}
} else {
AZTEC.this.state = new PossibleSlope(this.line);
AZTEC.this.state.process(sample);
}
}

@Override
public void finish() throws IOException {
this.line.end();
}
}

private class NormalLine implements State {
private final Line line;

NormalLine(Line line) {
this.line = line;
}

@Override
public void process(double sample) throws IOException {
if (this.line.canContain(sample)) {
this.line.update(sample);
if (this.line.isTooLong()) {
this.finish();
AZTEC.this.state = new ShortLine();
}
line = new Line(current);
} else {
line.update(current);
this.finish();
AZTEC.this.state = new ShortLine();
AZTEC.this.state.process(sample);
}
if (!line.isTooShort() && slope != null) {
slope.end();
slope = null;
}

@Override
public void finish() throws IOException {
this.line.end();
}
}

private class PossibleSlope implements State {
private final Line previousLine;
private final Line currentLine = new Line();

PossibleSlope(Line previousLine) {
this.previousLine = previousLine;
}

@Override
public void process(double sample) throws IOException {
if (this.currentLine.canContain(sample)) {
this.currentLine.update(sample);
if (!this.currentLine.isTooShort()) {
this.previousLine.end();
AZTEC.this.state = new NormalLine(this.currentLine);
}
} else {
this.detectSlopeType();
AZTEC.this.state.process(sample);
}
}

private void detectSlopeType() {
int duration = this.previousLine.length + this.currentLine.length;
if (this.currentLine.average() > this.previousLine.average()) {
Slope slope = new Slope(this.previousLine.min,
this.currentLine.max, 1.0, duration);
AZTEC.this.state = new AscendingSlope(slope);
} else {
Slope slope = new Slope(this.currentLine.min,
this.previousLine.max, -1.0, duration);
AZTEC.this.state = new DescendingSlope(slope);
}
}
if (slope != null) {
if (line.isTooShort()) {
slope.update(line);
slope.end();

@Override
public void finish() throws IOException {
this.detectSlopeType();
AZTEC.this.state.finish();
}
}

private abstract class SlopeState implements State {
protected final Slope slope;
protected Line line;

SlopeState(Slope slope) {
this.slope = slope;
this.line = new Line();
}

@Override
public void process(double sample) throws IOException {
if (this.line.canContain(sample)) {
this.line.update(sample);
if (!this.line.isTooShort()) {
this.slope.end();
AZTEC.this.state = new NormalLine(this.line);
}
} else {
slope.end();
line.end();
if (this.changedSignal()) {
this.slope.end();
AZTEC.this.state = new PossibleSlope(this.line);
AZTEC.this.state.process(sample);
} else {
this.slope.update(this.line);
this.line = new Line();
this.line.update(sample);
}
}
} else {
line.end();
}

@Override
public void finish() throws IOException {
this.slope.update(this.line);
this.slope.end();
}

protected abstract boolean changedSignal();
}

private class Line {
private double min;
private double max;
private double length;
private class AscendingSlope extends SlopeState {

Line(double initialValue) {
this.min = initialValue;
this.max = initialValue;
this.length = 1;
AscendingSlope(Slope slope) {
super(slope);
}

@Override
protected boolean changedSignal() {
return this.line.average() < this.slope.max;
}
}

private class DescendingSlope extends SlopeState {
DescendingSlope(Slope slope) {
super(slope);
}

@Override
protected boolean changedSignal() {
return this.line.average() > this.slope.min;
}
}

private class Line {
private double min = Double.POSITIVE_INFINITY;
private double max = Double.NEGATIVE_INFINITY;
private int length = 0;

void update(double current) {
if (current > max) {
max = current;
} else if (current < min) {
min = current;
}
length++;
this.min = Math.min(current, this.min);
this.max = Math.max(current, this.max);
this.length++;
}

boolean canContain(double value) {
return value <= min + params.getK() && params.getK() + value >= max;
return value <= this.min + AZTEC.this.params.getK()
&& AZTEC.this.params.getK() + value >= this.max;
}

boolean isTooLong() {
return this.length >= params.getN();
return this.length >= AZTEC.this.params.getN();
}

boolean isTooShort() {
return this.length < params.getT();
return this.length < AZTEC.this.params.getT();
}

void end() throws IOException {
EncodingOutput out = params.getOutput();
out.put(length);
out.put((max + min) / 2);
EncodingOutput out = AZTEC.this.params.getOutput();
out.put(this.length);
out.put(this.average());
}

double average() {
return (this.max + this.min) * 0.5;
}

@Override
public String toString() {
return "Line [min=" + min + ", max=" + max + ", length=" + length
+ "]";
return "Line [min=" + this.min + ", max=" + this.max + ", length="
+ this.length + "]";
}
}

private class Slope {
private double min;
private double max;
private double duration;
private double signal;
private final double signal;

Slope() {
this.min = Double.POSITIVE_INFINITY;
this.max = Double.NEGATIVE_INFINITY;
this.duration = 0;
this.signal = 0;
Slope(double min, double max, double signal, double duration) {
this.min = min;
this.max = max;
this.signal = signal;
this.duration = duration;
}

void end() throws IOException {
EncodingOutput out = params.getOutput();
out.put(-duration);
out.put(signal * (max - min));
EncodingOutput out = AZTEC.this.params.getOutput();
out.put(-this.duration);
out.put(this.signal * (this.max - this.min));
}

void update(Line line) {
if (line.max >= this.max) {
this.signal = 1.0;
} else {
this.signal = -1.0;
}
this.min = Math.min(line.min, this.min);
this.max = Math.max(line.max, this.max);
this.duration += line.length;
}

@Override
public String toString() {
return "Slope [min=" + min + ", max=" + max + ", duration="
+ duration + ", signal=" + signal + "]";
return "Slope [min=" + this.min + ", max=" + this.max
+ ", duration=" + this.duration + ", signal=" + this.signal
+ "]";
}
}

Expand Down
32 changes: 22 additions & 10 deletions src/test/java/br/ime/usp/aztec/AZTECTest.java
Expand Up @@ -20,7 +20,6 @@
import static org.junit.Assert.assertThat;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import br.ime.usp.aztec.test.MockEncodingOutput;
Expand All @@ -35,7 +34,7 @@ public final class AZTECTest {
@Before
public void setUp() throws Exception {
this.output = new MockEncodingOutput();
AlgorithmParameters params = createDefaultParameters();
AlgorithmParameters params = this.createDefaultParameters();
this.aztec = new AZTEC(params);
}

Expand Down Expand Up @@ -86,13 +85,19 @@ public void encodesASingleNegativeSlope() throws Exception {
}

@Test
public void encodesASmallSlopeBetweenTwoLines() throws Exception {
public void encodesASmallLineBetweenTwoLines() throws Exception {
Iterable<Double> signal = asList(1.0, 1.0, 1.1, 1.1, 1.2, 1.3, 1.4,
1.5, 1.5, 1.5);
this.aztec.encode(signal);
double subtractionError = 9e-17;
assertThat(this.output,
contains(4.0, 1.05, -2.0, 0.1 + subtractionError, 4.0, 1.45));
assertThat(this.output, contains(4.0, 1.05, 2.0, 1.25, 4.0, 1.45));
}

@Test
public void encodesASmallSlopeBetweenTwoLines() throws Exception {
Iterable<Double> signal = asList(1.0, 1.05, 1.0, 1.05, 1.15, 1.3, 1.4,
1.45, 1.5, 1.5, 1.5);
this.aztec.encode(signal);
assertThat(this.output, contains(4.0, 1.025, -3.0, 0.25, 4.0, 1.475));
}

@Test
Expand All @@ -104,12 +109,19 @@ public void encodesABigNegativeSlopeBetweenTwoLines() throws Exception {
}

@Test
@Ignore
public void encodesAPositiveSlopeFollowedByANegativeSlope()
public void encodesAPositiveSlopeFollowedByANegativeSlopeFollowedByAPositiveSlope()
throws Exception {
Iterable<Double> signal = asList(1.0, 2.0, 3.0, 4.0, 3.0, 2.0, 1.0, 0.0);
Iterable<Double> signal = asList(1.0, 2.0, 3.0, 4.0, 3.0, 2.0, 1.0,
0.0, 0.5, 1.0, 1.5, 2.0);
this.aztec.encode(signal);
assertThat(this.output, contains(-4.0, 3.0, -4.0, -3.0, -4.0, 1.5));
}

@Test
public void encodesASmallSlopeAtTheEndOfTheSignal() throws Exception {
Iterable<Double> signal = asList(0.0, 0.0, 0.0, 0.0, 1.0, 2.0);
this.aztec.encode(signal);
assertThat(this.output, contains(-4.0, 3.0, -4.0, -3.0));
assertThat(this.output, contains(4.0, 0.0, -2.0, 1.0));
}

private AlgorithmParameters createDefaultParameters() {
Expand Down

0 comments on commit 9d09a68

Please sign in to comment.