Created
September 21, 2024 20:03
-
-
Save riferrei/b36463d961612b6802690f6781c57d1a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.util.*; | |
/** | |
* This file is deliberately coded to avoid all dependencies outside | |
* of the JDK, e.g. JUnit, for the sake of simplicity of use. | |
*/ | |
class BackendCodeExercise { | |
public static Point[] fix(Point[] in, int start, int end, int interval) { | |
// Fix the intervals to be multiples of the interval | |
start = roundUpToInterval(start, interval); | |
end = roundUpToInterval(end, interval); | |
// A TreeMap keep the data points ordered | |
final Map<Integer, Point> cache = new TreeMap<>(); | |
for (Point point : in) { | |
// Fix the point.ts to be multiple of the interval | |
int adjustedTs = roundUpToInterval(point.ts, interval); | |
if (adjustedTs >= start && adjustedTs < end) { | |
cache.putIfAbsent(adjustedTs, new Point(point.val, (int) adjustedTs)); | |
} | |
} | |
// Check for the missing data | |
for (int i = start; i < end; i += interval) { | |
cache.putIfAbsent(i, new Point(Double.NaN, i)); | |
} | |
return cache.values().toArray(new Point[0]); | |
} | |
private static int roundUpToInterval(int value, int interval) { | |
return ((value + interval - 1) / interval) * interval; | |
} | |
public static void main(String[] args) { | |
// this runs all test methods starting with prefix "test" as long as no argument is supplied. With arguments it uses each argument to run matching tests with that prefix. | |
if (args.length > 0) { | |
for (String a : args) { | |
executeAllTests(a); | |
} | |
} else { | |
executeAllTests("test"); | |
} | |
} | |
// test01PassThrough validates that Fix returns the input data if no modifications are required. | |
public void test01PassThrough() { | |
Point[] input = new Point[]{ | |
new Point(0, 100), | |
new Point(8.4, 110), | |
new Point(0.999, 120), | |
}; | |
Point[] got = fix(input, 100, 130, 10); | |
Point[] exp = new Point[]{ | |
new Point(0, 100), | |
new Point(8.4, 110), | |
new Point(0.999, 120), | |
}; | |
assertResult(exp, got); | |
} | |
// test02ApplyStartEnd validates that Fix only returns points that lie within the start/end range provided. | |
public void test02ApplyStartEnd() { | |
Point[] input = new Point[]{ | |
new Point(0.5, 100), | |
new Point(8.4, 110), | |
new Point(32.5, 120), | |
new Point(0.999, 130), | |
}; | |
Point[] got = fix(input, 110, 130, 10); | |
Point[] exp = new Point[]{ | |
new Point(8.4, 110), | |
new Point(32.5, 120), | |
}; | |
assertResult(exp, got); | |
} | |
// test03ApplyStartEndNaNs validates that Fix adds Double.NaN's at the front or end as needed to match the requested range. | |
public void test03ApplyStartEndNaNs() { | |
Point[] input = new Point[]{ | |
new Point(0.5, 100), | |
new Point(41.3, 110), | |
}; | |
Point[] got = fix(input, 90, 140, 10); | |
Point[] exp = new Point[]{ | |
new Point(Double.NaN, 90), | |
new Point(0.5, 100), | |
new Point(41.3, 110), | |
new Point(Double.NaN, 120), | |
new Point(Double.NaN, 130), | |
}; | |
assertResult(exp, got); | |
} | |
// test04ApplyIrregularStartEndNaNs validates that Fix honors start/end even when they are irregular (when they are not divisible by the interval). | |
public void test04ApplyIrregularStartEndNaNs() { | |
Point[] input = new Point[]{ | |
new Point(0.5, 100), | |
new Point(41.3, 110), | |
}; | |
Point[] got = fix(input, 89, 131, 10); | |
Point[] exp = new Point[]{ | |
new Point(Double.NaN, 90), | |
new Point(0.5, 100), | |
new Point(41.3, 110), | |
new Point(Double.NaN, 120), | |
new Point(Double.NaN, 130), | |
}; | |
assertResult(exp, got); | |
} | |
// test05AdjustIrregularTimestamps validates that Fix adjusts timestamps to be divisible by the interval. | |
public void test05AdjustIrregularTimestamps() { | |
Point[] input = new Point[]{ | |
new Point(0.5, 100), | |
new Point(8.4, 110), | |
new Point(32.5, 118), | |
new Point(0.999, 130), | |
new Point(41.3, 139), | |
new Point(41.9, 141), | |
}; | |
Point[] got = fix(input, 100, 160, 10); | |
Point[] exp = new Point[]{ | |
new Point(0.5, 100), | |
new Point(8.4, 110), | |
new Point(32.5, 120), | |
new Point(0.999, 130), | |
new Point(41.3, 140), | |
new Point(41.9, 150), | |
}; | |
assertResult(exp, got); | |
} | |
// test06InsertNaNs validates that Fix insert Double.NaNs for missing points. | |
public void test06InsertNaNs() { | |
Point[] input = new Point[]{ | |
new Point(0.5, 100), | |
new Point(32.5, 120), | |
new Point(0.999, 130), | |
new Point(41.3, 150), | |
}; | |
Point[] got = fix(input, 100, 160, 10); | |
Point[] exp = new Point[]{ | |
new Point(0.5, 100), | |
new Point(Double.NaN, 110), | |
new Point(32.5, 120), | |
new Point(0.999, 130), | |
new Point(Double.NaN, 140), | |
new Point(41.3, 150), | |
}; | |
assertResult(exp, got); | |
} | |
// test07FilterDuplicates validates that Fix filters out points that correspond to the same output timestamp. | |
public void test07FilterDuplicates() { | |
Point[] input = new Point[]{ | |
new Point(0.5, 100), | |
new Point(0.7, 100), | |
new Point(32.5, 110), | |
new Point(0.9, 120), | |
new Point(0.8, 125), | |
new Point(41.3, 130), | |
new Point(41.3, 140), | |
new Point(41.2, 141), | |
new Point(41.3, 142), | |
new Point(41.4, 149), | |
}; | |
Point[] got = fix(input, 100, 160, 10); | |
Point[] exp = new Point[]{ | |
new Point(0.5, 100), | |
new Point(32.5, 110), | |
new Point(0.9, 120), | |
new Point(0.8, 130), | |
new Point(41.3, 140), | |
new Point(41.2, 150), | |
}; | |
assertResult(exp, got); | |
} | |
// test08IrregularStartEndIrregularTimestamps validates that Fix correctly handles the combination | |
// of irregular start/end parameters with irregular input timestamps (irregular meaning not divisible by interval) | |
// note that the start/end filtering should be applied to the adjusted timestamps. | |
public void test08IrregularStartEndIrregularTimestamps() { | |
Point[] input = new Point[]{ | |
new Point(0.5, 105), | |
new Point(1, 120), | |
new Point(1.5, 130), | |
new Point(2, 135), | |
new Point(3, 139), | |
}; | |
Point[] got = fix(input, 108, 138, 10); | |
Point[] exp = new Point[]{ | |
new Point(0.5, 110), | |
new Point(1, 120), | |
new Point(1.5, 130), | |
}; | |
assertResult(exp, got); | |
} | |
// test09AllAtOnce validates that Fix can handle all the above conditions all at once. | |
public void test09AllAtOnce() { | |
Point[] input = new Point[]{ | |
new Point(0.5, 105), | |
new Point(41.3, 111), | |
new Point(0, 130), | |
new Point(1, 150), | |
new Point(2, 151), | |
new Point(3, 152), | |
new Point(4, 159), | |
new Point(4, 160), | |
new Point(5, 160), | |
new Point(4, 174), | |
new Point(5, 175), | |
new Point(6, 185), | |
}; | |
Point[] got = fix(input, 81, 186, 10); | |
Point[] exp = new Point[]{ | |
new Point(Double.NaN, 90), | |
new Point(Double.NaN, 100), | |
new Point(0.5, 110), | |
new Point(41.3, 120), | |
new Point(0, 130), | |
new Point(Double.NaN, 140), | |
new Point(1, 150), | |
new Point(2, 160), | |
new Point(Double.NaN, 170), | |
new Point(4, 180), | |
}; | |
assertResult(exp, got); | |
} | |
/*********************************************************************/ | |
/* Utility methods intended to avoid dependency upon a test library. */ | |
/* You are not expected to examine nor understand these methods. */ | |
/*********************************************************************/ | |
private static void executeAllTests(String prefix) { | |
Method[] methods = BackendCodeExercise.class.getDeclaredMethods(); | |
BackendCodeExercise exercise = new BackendCodeExercise(); | |
Arrays.sort(methods, Comparator.comparing(Method::getName)); | |
int passed = 0; | |
int failed = 0; | |
for (Method method : methods) { | |
if (method.getName().startsWith(prefix)) { | |
try { | |
System.out.println("========================"); | |
System.out.println("Test: " + method.getName()); | |
method.invoke(exercise, new Object[0]); | |
passed++; | |
} catch (IllegalAccessException | IllegalArgumentException e) { | |
e.printStackTrace(); | |
} catch (InvocationTargetException e) { | |
Throwable cause = e.getCause(); | |
if (cause.getMessage() != "Test failed") { | |
System.out.println("Test failed with exception:"); | |
cause.printStackTrace(); | |
} | |
failed++; | |
} | |
} | |
} | |
System.out.println("Passed: " + passed + ", Failed: " + failed); | |
} | |
private static void assertResult(Point[] exp, Point[] got) throws RuntimeException { | |
if (!equal(exp, got)) { | |
System.out.println("Output mismatch\n" + | |
"Expected:\n" + | |
fmtPoints(exp) + "\n" + | |
"Got:\n" + | |
fmtPoints(got)); | |
throw new RuntimeException("Test failed"); | |
} else { | |
System.out.println("Passed."); | |
} | |
} | |
private static boolean equal(Point[] exp, Point[] got) { | |
if (exp.length != got.length) { | |
return false; | |
} | |
for (int i = 0; i < got.length; i++) { | |
Point pgot = got[i]; | |
Point pexp = exp[i]; | |
if (Double.isNaN(pgot.val) != Double.isNaN(pexp.val)) { | |
return false; | |
} | |
if (!Double.isNaN(pgot.val) && pgot.val != pexp.val) { | |
return false; | |
} | |
if (pgot.ts != pexp.ts) { | |
return false; | |
} | |
} | |
return true; | |
} | |
public static String fmtPoints(Point[] points) { | |
String out = ""; | |
for (Point p : points) { | |
if (p == null) { | |
out += "[null],\n"; | |
continue; | |
} | |
out += String.format("[%f, %d],\n", p.val, p.ts); | |
} | |
return out; | |
} | |
} | |
class Point { | |
int ts; | |
double val; | |
public Point(double val, int ts) { | |
this.ts = ts; | |
this.val = val; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment