it is now a website
This commit is contained in:
parent
a3cf7b6a27
commit
5b1584e300
18 changed files with 939 additions and 55 deletions
5
pom.xml
5
pom.xml
|
|
@ -22,6 +22,11 @@
|
|||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
|
|
|
|||
12
src/main/java/assessment/Application.java
Normal file
12
src/main/java/assessment/Application.java
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package assessment;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package assessment;
|
||||
package assessment.algorithm;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
|
|
@ -7,6 +7,7 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
public record Grid(List<List<Integer>> grid) {
|
||||
private static final double timeValueFactor = .2;
|
||||
|
||||
public static Grid fromFile(String resource) {
|
||||
try {
|
||||
|
|
@ -27,12 +28,30 @@ public record Grid(List<List<Integer>> grid) {
|
|||
}
|
||||
}
|
||||
|
||||
public int get(int r, int c){
|
||||
return grid.get(r).get(c);
|
||||
public int getInitialValue(int x, int y){
|
||||
return grid.get(y).get(x);
|
||||
}
|
||||
|
||||
//assumes square
|
||||
public int getWidth(){
|
||||
return grid.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* de waarde van een punt x,y wordt bepaald door de beginwaarde, tenzij we er al geweest zijn
|
||||
* dan telt de tijd sinds we er geweest zijn = afstand in sindsdien afgelegd pad
|
||||
* de waarde is rechtevenredig met de afstand
|
||||
*/
|
||||
public double getCurrentValue(Path path, int x, int y) {
|
||||
int gridValue = getInitialValue(x, y);
|
||||
if (path.hasPoint(this, x, y)) {
|
||||
// been there
|
||||
int distanceInPath = path.getDistanceInPath(x, y) - 1;
|
||||
double increment = gridValue * timeValueFactor;
|
||||
|
||||
return Math.min((distanceInPath - 1) * increment, gridValue);
|
||||
} else {
|
||||
return gridValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,16 @@
|
|||
package assessment;
|
||||
package assessment.algorithm;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
//TODO make non static (CH)
|
||||
public class BestPathFinder {
|
||||
private static final double timeValueFactor = .2;
|
||||
/**
|
||||
* Finds most valuable path given distance/time covered (t) and elapsed compute time (T)
|
||||
* for a drone moving through a square grid of size N that has cells that have varying value
|
||||
* <p/>
|
||||
* outstanding questions:
|
||||
* * is a cell of (initial) value 0 equally valuable as a cell just occupied?
|
||||
* -> probably, what is the chance that you find a 'treasure' behind it, that you would have not found via an alternative path?
|
||||
*/
|
||||
public class OptimalPathFinder {
|
||||
|
||||
// paths to be considered
|
||||
private final PriorityQueue<Path> paths = new PriorityQueue<>();
|
||||
|
|
@ -20,8 +26,8 @@ public class BestPathFinder {
|
|||
* @param y startpositie Y
|
||||
* @return het meest waardevolle pad
|
||||
*/
|
||||
public Path findMaxValPath(Grid g, int N, int t, int T, int x, int y) {
|
||||
Path path = Path.newPath(g, x, y);
|
||||
public Path findOptimalPath(Grid g, int N, int t, long T, int x, int y) {
|
||||
Path path = Path.newPath(g, Point.create(g, x, y));
|
||||
paths.add(path);
|
||||
// overall best path
|
||||
Path max = path;
|
||||
|
|
@ -56,27 +62,27 @@ public class BestPathFinder {
|
|||
// find best new directions
|
||||
List<Point> newDirections = new ArrayList<>();
|
||||
if (y > 0) {
|
||||
newDirections.add(new Point(x, y - 1, getValueFromGrid(g, path, x, y - 1)));
|
||||
newDirections.add(Point.create(g, path, x, y - 1));
|
||||
if (x < N - 1) {
|
||||
newDirections.add(new Point(x + 1, y - 1, getValueFromGrid(g, path, x + 1, y - 1)));
|
||||
newDirections.add(Point.create(g, path, x + 1, y - 1));
|
||||
}
|
||||
}
|
||||
if (x > 0) {
|
||||
newDirections.add(new Point(x - 1, y, getValueFromGrid(g, path, x - 1, y)));
|
||||
newDirections.add(Point.create(g, path, x - 1, y));
|
||||
if (y > 0) {
|
||||
newDirections.add(new Point(x - 1, y - 1, getValueFromGrid(g, path, x - 1, y - 1)));
|
||||
newDirections.add(Point.create(g, path, x - 1, y - 1));
|
||||
}
|
||||
}
|
||||
if (x < N - 1) {
|
||||
newDirections.add(new Point(x + 1, y, getValueFromGrid(g, path, x + 1, y)));
|
||||
newDirections.add(Point.create(g, path, x + 1, y));
|
||||
if (y < N - 1) {
|
||||
newDirections.add(new Point(x + 1, y + 1, getValueFromGrid(g, path, x + 1, y + 1)));
|
||||
newDirections.add(Point.create(g, path, x + 1, y + 1));
|
||||
}
|
||||
}
|
||||
if (y < N - 1) {
|
||||
newDirections.add(new Point(x, y + 1, getValueFromGrid(g, path, x, y + 1)));
|
||||
newDirections.add(Point.create(g, path, x, y + 1));
|
||||
if (x > 0)
|
||||
newDirections.add(new Point(x - 1, y + 1, getValueFromGrid(g, path, x - 1, y + 1)));
|
||||
newDirections.add(Point.create(g, path, x - 1, y + 1));
|
||||
}
|
||||
|
||||
if (!newDirections.isEmpty()) {
|
||||
|
|
@ -98,7 +104,7 @@ public class BestPathFinder {
|
|||
}
|
||||
}
|
||||
if (!pointsAdded) {
|
||||
//evict
|
||||
// dead end, evict
|
||||
Path ended = paths.poll();
|
||||
if (ended != null && ended.value() > max.value()) {
|
||||
max = ended;
|
||||
|
|
@ -108,22 +114,4 @@ public class BestPathFinder {
|
|||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* de waarde van een punt x,y wordt bepaald door de beginwaarde, tenzij we er al geweest zijn
|
||||
* dan telt de tijd sinds we er geweest zijn = afstand in sindsdien afgelegd pad
|
||||
* de waarde is rechtevenredig met de afstand
|
||||
*/
|
||||
private double getValueFromGrid(Grid grid, Path path, int x, int y) {
|
||||
int gridValue = grid.get(x, y);
|
||||
if (path.hasPoint(grid, x, y)) {
|
||||
// been there
|
||||
int distanceInPath = path.getDistanceInPath(x, y);
|
||||
double increment = gridValue * timeValueFactor;
|
||||
|
||||
return Math.min((distanceInPath - 1) * increment, gridValue);
|
||||
} else {
|
||||
return gridValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package assessment;
|
||||
package assessment.algorithm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
|
@ -8,7 +8,7 @@ public class Path implements Comparable<Path> {
|
|||
// beide bevatten de al afgelegde punten
|
||||
|
||||
// points is bedoeld om door de punten te lopen
|
||||
private final ArrayList<Point> points = new ArrayList<>();
|
||||
public final ArrayList<Point> points = new ArrayList<>();
|
||||
|
||||
// trodden is bedoeld om zo snel mogelijk vast te stellen of we al op het punt geweest zijn
|
||||
private final HashSet<Integer> trodden = new HashSet<>();
|
||||
|
|
@ -17,9 +17,9 @@ public class Path implements Comparable<Path> {
|
|||
}
|
||||
|
||||
// meh row/col vs x/y
|
||||
static Path newPath(Grid g, int x, int y) {
|
||||
public static Path newPath(Grid g, Point start) {
|
||||
Path p = new Path();
|
||||
p.add(g, new Point(x, y, g.get(y, x)));
|
||||
p.add(g, start);
|
||||
return p;
|
||||
}
|
||||
|
||||
|
|
@ -28,14 +28,14 @@ public class Path implements Comparable<Path> {
|
|||
trodden.add(point.y * g.getWidth() + point.x);
|
||||
}
|
||||
|
||||
public Double value() {
|
||||
public double value() {
|
||||
return points.stream().mapToDouble(point -> point.value).sum();
|
||||
}
|
||||
|
||||
// compare descending, highest value first
|
||||
@Override
|
||||
public int compareTo(Path o) {
|
||||
return -this.value().compareTo(o.value());
|
||||
return -Double.compare(this.value(), o.value());
|
||||
}
|
||||
|
||||
public int length() {
|
||||
|
|
@ -1,17 +1,28 @@
|
|||
package assessment;
|
||||
package assessment.algorithm;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
public class Point implements Comparable<Point> {
|
||||
|
||||
final int x;
|
||||
final int y;
|
||||
final double value;
|
||||
public final int x;
|
||||
public final int y;
|
||||
public double value;
|
||||
|
||||
public Point(int x, int y, double value) {
|
||||
private Point(int x, int y, double initialValue) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.value = value;
|
||||
this.value = initialValue;
|
||||
}
|
||||
|
||||
public static Point create(Grid g, int x, int y) {
|
||||
return new Point(x, y, g.getInitialValue(x, y));
|
||||
}
|
||||
|
||||
public static Point create(Grid g, Path p, int x, int y) {
|
||||
return new Point(x, y, g.getCurrentValue(p, x, y));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -37,4 +48,5 @@ public class Point implements Comparable<Point> {
|
|||
public int hashCode() {
|
||||
return Objects.hash(x, y);
|
||||
}
|
||||
|
||||
}
|
||||
29
src/main/java/assessment/restapi/FlightPathApi.java
Normal file
29
src/main/java/assessment/restapi/FlightPathApi.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package assessment.restapi;
|
||||
|
||||
import assessment.algorithm.OptimalPathFinder;
|
||||
import assessment.algorithm.Grid;
|
||||
import assessment.algorithm.Path;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class FlightPathApi {
|
||||
|
||||
@GetMapping(path = "/api/path/{gridname}/{pathlength}/{maxMillis}/{x}/{y}")
|
||||
public PathDto getOptimalPath(@PathVariable String gridname,
|
||||
@PathVariable int pathlength,
|
||||
@PathVariable long maxMillis,
|
||||
@PathVariable int x,
|
||||
@PathVariable int y) {
|
||||
Grid grid = Grid.fromFile("grids/" + gridname + ".txt");
|
||||
Path p = new OptimalPathFinder().findOptimalPath(grid, grid.getWidth(), pathlength, maxMillis, x, y);
|
||||
return new PathDto(p);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/api/grid/{gridname}")
|
||||
public GridDto getGrid(@PathVariable String gridname) {
|
||||
Grid grid = Grid.fromFile("grids/" + gridname + ".txt");
|
||||
return new GridDto(grid);
|
||||
}
|
||||
}
|
||||
23
src/main/java/assessment/restapi/GridDto.java
Normal file
23
src/main/java/assessment/restapi/GridDto.java
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package assessment.restapi;
|
||||
|
||||
import assessment.algorithm.Grid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class GridDto {
|
||||
private List<List<Integer>> grid;
|
||||
private int size;
|
||||
|
||||
public GridDto(Grid grid) {
|
||||
this.grid = grid.grid();
|
||||
this.size = grid.getWidth();
|
||||
}
|
||||
|
||||
public List<List<Integer>> getGrid() {
|
||||
return grid;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
25
src/main/java/assessment/restapi/PathDto.java
Normal file
25
src/main/java/assessment/restapi/PathDto.java
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package assessment.restapi;
|
||||
|
||||
import assessment.algorithm.Path;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PathDto {
|
||||
private final List<PointDto> points;
|
||||
private final double value;
|
||||
|
||||
public PathDto(Path p){
|
||||
this.points = p.points.stream().map(PointDto::new).collect(Collectors.toList());
|
||||
this.value = p.value();
|
||||
}
|
||||
|
||||
public List<PointDto> getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
22
src/main/java/assessment/restapi/PointDto.java
Normal file
22
src/main/java/assessment/restapi/PointDto.java
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package assessment.restapi;
|
||||
|
||||
import assessment.algorithm.Point;
|
||||
|
||||
public class PointDto {
|
||||
private final int x;
|
||||
private final int y;
|
||||
|
||||
public PointDto(Point p) {
|
||||
this.x = p.x;
|
||||
this.y = p.y;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
}
|
||||
62
src/main/resources/static/css/style.css
Normal file
62
src/main/resources/static/css/style.css
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
body{
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#console {
|
||||
font: 13px Arial, sans-serif;
|
||||
padding: 5px;
|
||||
color: greenyellow;
|
||||
background: black;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 0;
|
||||
width: 20%;
|
||||
height: 10em;
|
||||
border: 2px solid darkgray;
|
||||
border-radius: 10px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#prompt {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
#command_input {
|
||||
background: black;
|
||||
color: greenyellow;
|
||||
outline: none;
|
||||
width: 90%;
|
||||
height: 1em;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
#command_history {
|
||||
font-size: 12px;
|
||||
color: greenyellow;
|
||||
position: absolute;
|
||||
bottom: 1.5em;
|
||||
max-height: 20em;
|
||||
width: 100%;
|
||||
overflow-y: visible;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.multiline {
|
||||
border-top: 1px slategray solid;
|
||||
border-left: 1px slategray solid;
|
||||
border-right: 1px slategray solid;
|
||||
border-bottom: none transparent;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
||||
.single_line {
|
||||
border: none transparent;
|
||||
}
|
||||
24
src/main/resources/static/index.html
Normal file
24
src/main/resources/static/index.html
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>OptimalPathFinder</title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="myCanvas" width="500px" height="500px"></canvas>
|
||||
|
||||
<div id="console">
|
||||
<div id="command_history">
|
||||
<div id="bottom"></div>
|
||||
</div>
|
||||
<label id="prompt">>
|
||||
<textarea id="command_input" class="single_line" autofocus></textarea>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<script src="js/grid.js"></script>
|
||||
<script src="js/console.js"></script>
|
||||
<script src="js/scanner.js"></script>
|
||||
<script src="js/parser.js"></script>
|
||||
|
||||
</body>
|
||||
596
src/main/resources/static/js/console.js
Normal file
596
src/main/resources/static/js/console.js
Normal file
|
|
@ -0,0 +1,596 @@
|
|||
const command_input_element = document.getElementById('command_input');
|
||||
const command_history_element = document.getElementById('command_history');
|
||||
let command_history = [''];
|
||||
let command_history_index = 0;
|
||||
let token_index = 0;
|
||||
const bindings = {};
|
||||
const state = {};
|
||||
const keywords = {
|
||||
'true': true,
|
||||
'false': false,
|
||||
'pi': Math.PI,
|
||||
'PI': Math.PI,
|
||||
'e': Math.E
|
||||
}
|
||||
|
||||
let tokens;
|
||||
|
||||
const adjust_input_element_height = function () {
|
||||
let num_lines = command_input_element.value.split(/\n/).length;
|
||||
command_input_element.setAttribute('style', 'height: ' + num_lines + 'em');
|
||||
if (num_lines > 1) {
|
||||
command_input_element.setAttribute('class', 'multiline');
|
||||
} else {
|
||||
command_input_element.setAttribute('class', 'single_line');
|
||||
}
|
||||
}
|
||||
|
||||
// command_input_element.onkeypress = function handle_key_input(event) {
|
||||
// if (event.key === 'Enter') {
|
||||
// event.preventDefault();
|
||||
// }
|
||||
// }
|
||||
|
||||
command_input_element.onkeyup = function handle_key_input(event) {
|
||||
adjust_input_element_height();
|
||||
if (event.key === 'c' && event.ctrlKey) {
|
||||
command_input_element.value = '';
|
||||
}
|
||||
if (event.key === 'ArrowUp' && !event.shiftKey) {
|
||||
if (command_history_index > -1) {
|
||||
command_input_element.value = command_history[command_history_index];
|
||||
if (command_history_index > 0) {
|
||||
command_history_index -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event.key === 'ArrowDown' && !event.shiftKey) {
|
||||
if (command_history_index < command_history.length - 1) {
|
||||
command_history_index += 1;
|
||||
command_input_element.value = command_history[command_history_index];
|
||||
} else {
|
||||
command_input_element.value = '';
|
||||
}
|
||||
}
|
||||
if (event.key === 'Enter') {
|
||||
handle_enter();
|
||||
}
|
||||
};
|
||||
|
||||
const handle_enter = function () {
|
||||
let command = command_input_element.value.trim();
|
||||
command_input_element.value = '';
|
||||
adjust_input_element_height();
|
||||
|
||||
if (command.length > 0) {
|
||||
command_history_element.innerText += command + "\n";
|
||||
command_input_element.value = '';
|
||||
command_history_index = command_history.length;
|
||||
|
||||
scan(command);
|
||||
let statement = parse();
|
||||
let value = evaluate(statement);
|
||||
|
||||
if (value !== undefined) {
|
||||
let binding;
|
||||
if (value.is_binding) { // if it's declaration work with the initializer
|
||||
binding = value.name; // but we also need the name of the bound variable
|
||||
value = state[value.name]; // lookup the value for the binding
|
||||
}
|
||||
|
||||
// if (value.is_visual) {
|
||||
//
|
||||
// } else {
|
||||
// if (binding && bindings[binding].previous && bindings[binding].previous.is_visual) {
|
||||
// label(bindings[binding].previous, '@' + bindings[binding].previous.id);
|
||||
// }
|
||||
// }
|
||||
// if (value.description) {
|
||||
// value = value.description;
|
||||
// }
|
||||
command_history_element.innerText += value.toString() + "\n";
|
||||
command_history.push(command);
|
||||
command_history_element.scrollTo(0, command_history_element.scrollHeight);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const evaluate = function (expr) {
|
||||
switch (expr.type) {
|
||||
case 'declaration': {
|
||||
let value = evaluate(expr.initializer);
|
||||
let binding_name = expr.var_name.value;
|
||||
bindings[binding_name] = {
|
||||
is_binding: true,
|
||||
name: binding_name,
|
||||
};
|
||||
state[binding_name] = value; // assign new value to binding
|
||||
|
||||
return bindings[binding_name]; // don't return the value itself, but the binding_object
|
||||
} // with which you can lookup the value
|
||||
|
||||
case 'group': // expression within parentheses
|
||||
return evaluate(expr.expression);
|
||||
case 'unary': {
|
||||
let right_operand = evaluate(expr.right);
|
||||
if (expr.operator === token_types.MINUS) {
|
||||
return -right_operand; //TODO create negate function (because now it only works for numbers)
|
||||
} else if (expr.operator === token_types.NOT) {
|
||||
return !right_operand;
|
||||
} else {
|
||||
throw {message: 'illegal unary operator'};
|
||||
}
|
||||
}
|
||||
case 'binary': {
|
||||
switch (expr.operator) {
|
||||
case token_types.MINUS:
|
||||
return subtraction(evaluate(expr.left), evaluate(expr.right));
|
||||
case token_types.PLUS:
|
||||
return add(evaluate(expr.left), evaluate(expr.right));
|
||||
case token_types.STAR:
|
||||
return multiply(evaluate(expr.left), evaluate(expr.right));
|
||||
case token_types.SLASH:
|
||||
return division(evaluate(expr.left), evaluate(expr.right));
|
||||
case token_types.EQUALS_EQUALS:
|
||||
return test_equal(evaluate(expr.left), evaluate(expr.right));
|
||||
case token_types.AND:
|
||||
return logical_and(evaluate(expr.left), evaluate(expr.right));
|
||||
case token_types.OR:
|
||||
return logical_or(evaluate(expr.left), evaluate(expr.right));
|
||||
}
|
||||
throw {message: 'illegal binary operator'};
|
||||
}
|
||||
case 'identifier': {
|
||||
if (expr.name in keywords) {
|
||||
return keywords[expr.name];
|
||||
} else {
|
||||
if (state[expr.name]) {
|
||||
return state[expr.name];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'literal': {
|
||||
return expr.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parse() {
|
||||
token_index = 0;
|
||||
if (check(token_types.IDENTIFIER, token_index) && check(token_types.EQUALS, token_index + 1)) {
|
||||
let var_name = current_token();
|
||||
advance();
|
||||
advance();
|
||||
return {type: 'declaration', var_name: var_name, initializer: expression()};
|
||||
} else {
|
||||
return expression();
|
||||
}
|
||||
}
|
||||
|
||||
function expression() {
|
||||
return equality();
|
||||
}
|
||||
|
||||
function equality() {
|
||||
let expr = comparison()
|
||||
|
||||
while (match([token_types.EQUALS_EQUALS, token_types.NOT_EQUALS])) {
|
||||
let operator = previous_token();
|
||||
let right = unary();
|
||||
expr = {type: 'binary', left: expr, operator: operator, right: right};
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
function comparison() {
|
||||
let expr = add_sub();
|
||||
|
||||
while (match([token_types.LESS, token_types.LESS_OR_EQUAL, token_types.GREATER, token_types.GREATER_OR_EQUAL])) {
|
||||
let operator = previous_token();
|
||||
let right = add_sub();
|
||||
expr = {type: 'binary', left: expr, operator: operator, right: right};
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
function add_sub() {
|
||||
let expr = mult_div();
|
||||
|
||||
while (match([token_types.OR, token_types.MINUS, token_types.PLUS])) {
|
||||
let operator = previous_token();
|
||||
let right = mult_div();
|
||||
expr = {type: 'binary', left: expr, operator: operator, right: right};
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
function mult_div() {
|
||||
let expr = unary();
|
||||
|
||||
while (match([token_types.AND, token_types.SLASH, token_types.STAR, token_types.DOT])) {
|
||||
let operator = previous_token();
|
||||
let right = unary();
|
||||
expr = {type: 'binary', left: expr, operator: operator, right: right};
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
function unary() {
|
||||
if (match([token_types.NOT, token_types.MINUS])) {
|
||||
let operator = previous_token();
|
||||
let right = unary();
|
||||
return {type: 'unary', operator: operator, right: right};
|
||||
} else {
|
||||
return primary();
|
||||
}
|
||||
}
|
||||
|
||||
function primary() {
|
||||
if (match([token_types.NUMERIC, token_types.STRING])) {
|
||||
return {type: 'literal', value: previous_token().value, value_type: previous_token().type};
|
||||
} else if (match([token_types.LEFT_PAREN])) {
|
||||
let expr = expression();
|
||||
if (expr && match([token_types.RIGHT_PAREN])) {
|
||||
return {
|
||||
type: 'group',
|
||||
expression: expr
|
||||
};
|
||||
} else {
|
||||
throw {message: 'expected expression or )'};
|
||||
}
|
||||
} else if (check(token_types.IDENTIFIER, token_index)) {
|
||||
let identifier = {
|
||||
type: 'identifier',
|
||||
name: current_token().value
|
||||
};
|
||||
advance();
|
||||
return identifier;
|
||||
} else if (match([token_types.LEFT_BRACKET])) {
|
||||
let array = [];
|
||||
if (!check(token_types.RIGHT_BRACKET, token_index)) {
|
||||
let result;
|
||||
do {
|
||||
result = expression();
|
||||
if (result) {
|
||||
array.push(result);
|
||||
} else {
|
||||
throw {message: "Expect ']' after array elements."};
|
||||
}
|
||||
match([token_types.COMMA]);
|
||||
} while (!match([token_types.RIGHT_BRACKET]));
|
||||
}
|
||||
|
||||
return {type: 'array', elements: array};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* matches token against array of tokens to check for equality (matching type)
|
||||
* @param tokens_to_match array of tokens
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function match(tokens_to_match) {
|
||||
for (let i = 0; i < tokens_to_match.length; i++) {
|
||||
if (are_same(tokens_to_match[i], current_token())) {
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if token at position index matches the given
|
||||
* @param token_to_check expected token type
|
||||
* @param index of token to check
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function check(token_to_check, index) {
|
||||
let token = tokens[index];
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
return are_same(token_to_check, token);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if 2 tokens have same type
|
||||
* @param token_1
|
||||
* @param token_2
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function are_same(token_1, token_2) {
|
||||
if (is_at_end()) {
|
||||
return false;
|
||||
} else {
|
||||
return token_1.type === token_2.type;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function is_at_end() {
|
||||
return token_index >= tokens.length;
|
||||
}
|
||||
|
||||
function advance() {
|
||||
token_index += 1;
|
||||
}
|
||||
|
||||
function previous_token() {
|
||||
return tokens[token_index - 1];
|
||||
}
|
||||
|
||||
function current_token() {
|
||||
return tokens[token_index];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an array of tokens from a line of input.
|
||||
*
|
||||
* @returns {token_types[]}
|
||||
* @param command
|
||||
*/
|
||||
const scan = function (command) {
|
||||
tokens = [];
|
||||
let current_index = 0, // current index of char to look at in the command string
|
||||
word_start_index = 0; // marker for start of a literal or identifier
|
||||
|
||||
while (!is_at_end()) {
|
||||
word_start_index = current_index;
|
||||
let token = scan_token();
|
||||
if (token) { // undefined mostly means whitespace
|
||||
tokens.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
function scan_token() {
|
||||
let next_char = advance();
|
||||
switch (next_char) {
|
||||
case '(':
|
||||
return token_types.LEFT_PAREN;
|
||||
case ')':
|
||||
return token_types.RIGHT_PAREN;
|
||||
case '[':
|
||||
return token_types.LEFT_BRACKET;
|
||||
case ']':
|
||||
return token_types.RIGHT_BRACKET;
|
||||
case ',':
|
||||
return token_types.COMMA;
|
||||
case '.':
|
||||
return token_types.DOT;
|
||||
case '-':
|
||||
return token_types.MINUS;
|
||||
case '+':
|
||||
return token_types.PLUS;
|
||||
case '*':
|
||||
return token_types.STAR;
|
||||
case '/':
|
||||
return token_types.SLASH;
|
||||
case '>':
|
||||
if (expect('=')) {
|
||||
return token_types.GREATER_OR_EQUAL;
|
||||
} else {
|
||||
return token_types.GREATER;
|
||||
}
|
||||
case '<':
|
||||
if (expect('=')) {
|
||||
return token_types.LESS_OR_EQUAL;
|
||||
} else {
|
||||
return token_types.LESS;
|
||||
}
|
||||
case '!':
|
||||
if (expect('=')) {
|
||||
return token_types.NOT_EQUALS;
|
||||
} else {
|
||||
return token_types.NOT;
|
||||
}
|
||||
case '=':
|
||||
if (expect('=')) {
|
||||
return token_types.EQUALS_EQUALS;
|
||||
} else {
|
||||
return token_types.EQUALS;
|
||||
}
|
||||
case '&':
|
||||
return token_types.AND;
|
||||
case '|':
|
||||
return token_types.OR;
|
||||
case '\'':
|
||||
return string();
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
advance();
|
||||
break;
|
||||
}
|
||||
if (is_digit(next_char)) {
|
||||
let token = Object.assign({}, token_types.NUMERIC);
|
||||
token.value = parse_number();
|
||||
return token;
|
||||
} else {
|
||||
if (is_alpha_or_underscore(next_char)) {
|
||||
let token = Object.assign({}, token_types.IDENTIFIER);
|
||||
token.value = parse_identifier();
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function expect(expected_char) {
|
||||
if (is_at_end()) {
|
||||
return false;
|
||||
}
|
||||
if (current_char() === expected_char) {
|
||||
advance();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function advance() {
|
||||
if (current_index < command.length) {
|
||||
current_index += 1;
|
||||
}
|
||||
return command[current_index - 1];
|
||||
}
|
||||
|
||||
function is_at_end() {
|
||||
return current_index >= command.length;
|
||||
}
|
||||
|
||||
function current_char() {
|
||||
return command[current_index];
|
||||
}
|
||||
|
||||
function is_digit(char) {
|
||||
return char >= '0' && char <= '9';
|
||||
}
|
||||
|
||||
function is_part_of_number(char) {
|
||||
return is_digit(char) || char === '.'; // no scientific notation for now
|
||||
}
|
||||
|
||||
function parse_reference() {
|
||||
while (current_char() === '@' || is_digit(current_char())) {
|
||||
advance();
|
||||
}
|
||||
return command.substring(word_start_index, current_index);
|
||||
}
|
||||
|
||||
function parse_number() {
|
||||
while (is_part_of_number(current_char())) {
|
||||
advance();
|
||||
}
|
||||
let number_string = command.substring(word_start_index, current_index);
|
||||
return Number.parseFloat(number_string);
|
||||
}
|
||||
|
||||
function is_alpha_or_underscore(char) {
|
||||
return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || char === '_';
|
||||
}
|
||||
|
||||
function is_alphanumeric_or_underscore(char) {
|
||||
return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || is_digit(char) || char === '_';
|
||||
}
|
||||
|
||||
function parse_identifier() {
|
||||
while (is_alphanumeric_or_underscore(current_char())) {
|
||||
advance();
|
||||
}
|
||||
return command.substring(word_start_index, current_index);
|
||||
}
|
||||
|
||||
function string() { // as of yet strings may not unclude escaped quotes that are also the start/end quote
|
||||
while (current_char() !== '\'' && !is_at_end()) {
|
||||
advance();
|
||||
}
|
||||
if (is_at_end() && current_char() !== '\'') {
|
||||
throw {message: 'unterminated string'}
|
||||
} else {
|
||||
let string_token = Object.assign({}, token_types.STRING);
|
||||
string_token.value = command.substring(word_start_index + 1, current_index);
|
||||
advance();
|
||||
return string_token;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const token_types = {
|
||||
LEFT_PAREN: {type: 'left_paren'},
|
||||
RIGHT_PAREN: {type: 'right_paren'},
|
||||
LEFT_BRACKET: {type: 'left_bracket'},
|
||||
RIGHT_BRACKET: {type: 'right_bracket'},
|
||||
COMMA: {type: 'comma'},
|
||||
DOT: {type: 'dot'},
|
||||
MINUS: {type: 'minus'},
|
||||
PLUS: {type: 'plus'},
|
||||
STAR: {type: 'star'},
|
||||
SLASH: {type: 'slash'},
|
||||
EQUALS: {type: 'equals'},
|
||||
EQUALS_EQUALS: {type: 'equals_equals'},
|
||||
NOT_EQUALS: {type: 'not_equals'},
|
||||
NOT: {type: 'not'},
|
||||
GREATER: {type: 'greater'},
|
||||
GREATER_OR_EQUAL: {type: 'greater_or_equal'},
|
||||
LESS: {type: 'less'},
|
||||
LESS_OR_EQUAL: {type: 'less_or_equal'},
|
||||
NUMERIC: {type: 'number', value: undefined},
|
||||
IDENTIFIER: {type: 'identifier', value: undefined},
|
||||
STRING: {type: 'string', value: undefined},
|
||||
AND: {type: 'logical_and'},
|
||||
OR: {type: 'logical_or'}
|
||||
};
|
||||
|
||||
const multiply = function (left, right) {
|
||||
return left * right;
|
||||
}
|
||||
|
||||
const division = function (left, right) {
|
||||
return left / right;
|
||||
}
|
||||
|
||||
const add = function (left, right) {
|
||||
return left + right;
|
||||
}
|
||||
|
||||
const subtraction = function (left, right) {
|
||||
return left - right;
|
||||
}
|
||||
|
||||
const test_equal = function (left, right) {
|
||||
if (left.is_vector && right.is_vector) {
|
||||
return left.equals(right);
|
||||
} else {
|
||||
return left === right;
|
||||
}
|
||||
}
|
||||
|
||||
const logical_and = function (left, right) {
|
||||
return left && right;
|
||||
}
|
||||
|
||||
const logical_or = function (left, right) {
|
||||
return left || right;
|
||||
}
|
||||
|
||||
const create_2d_id_matrix = function () {
|
||||
return {
|
||||
data: [[1, 0], [0, 1]],
|
||||
id: index_sequence++,
|
||||
is_visual: true,
|
||||
is_vector: false, // for type comparison
|
||||
is_matrix: true,
|
||||
type: 'matrix', // for showing type to user
|
||||
is_new: true, // to determine view action
|
||||
visible: true,
|
||||
toString: function () {
|
||||
return `matrix@${this.id}`;
|
||||
},
|
||||
hide: function () {
|
||||
return hide(this);
|
||||
},
|
||||
label: function (text) {
|
||||
return label(this, text);
|
||||
},
|
||||
show: function () {
|
||||
return show(this);
|
||||
},
|
||||
equals: function (other) {
|
||||
return (this.id === other.id || (this.type === other.type && this.data === other.data)); // TODO
|
||||
},
|
||||
row: function (index) {
|
||||
return this.data[index];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
32
src/main/resources/static/js/grid.js
Normal file
32
src/main/resources/static/js/grid.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
let canvas = document.getElementById('myCanvas');
|
||||
let ctx;
|
||||
if (canvas.getContext) {
|
||||
ctx = canvas.getContext('2d');
|
||||
|
||||
// draw here
|
||||
ctx.fillStyle = 'green';
|
||||
ctx.fillRect(0, 0, 500, 500);
|
||||
} else {
|
||||
console.log("Canvas not supported");
|
||||
}
|
||||
|
||||
fetch('/api/grid/100', {
|
||||
method: 'GET' // or 'POST'
|
||||
}).then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json(); // Here we're using JSON but you can use other formats such as blob, text etc.
|
||||
} else {
|
||||
throw new Error('Server response wasn\'t OK');
|
||||
}
|
||||
}).then((grid) => {
|
||||
const cell_factor = 500 / grid.size;
|
||||
ctx.font = `5px Arial`;
|
||||
ctx.fillStyle = "grey"
|
||||
for (let r = 0; r < grid.size; r++) {
|
||||
for (let c = 0; c < grid.size; c++) {
|
||||
ctx.fillText("" + grid.grid[r][c], 5 + c * cell_factor, r * cell_factor);
|
||||
}
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log('Fetching failed', err);
|
||||
});
|
||||
0
src/main/resources/static/js/parser.js
Normal file
0
src/main/resources/static/js/parser.js
Normal file
34
src/test/java/assessment/algorithm/CircleTest.java
Normal file
34
src/test/java/assessment/algorithm/CircleTest.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package assessment.algorithm;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class CircleTest {
|
||||
|
||||
@Test
|
||||
void test_return_to_previous_is_not_worth_it() {
|
||||
Grid grid = Grid.fromFile("grids/20.txt");
|
||||
Path path = Path.newPath(grid, Point.create(grid, 0, 0)); // 0.0
|
||||
path.add(grid, Point.create(grid, path, 0, 1)); // 1.0
|
||||
path.add(grid, Point.create(grid, path, 0, 2)); // 3.0
|
||||
path.add(grid, Point.create(grid, path, 0, 1)); // 3.0
|
||||
Assertions.assertEquals(3.0, path.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_return_to_previous_is_worth_something() {
|
||||
Grid grid = Grid.fromFile("grids/20.txt");
|
||||
Path path = Path.newPath(grid, Point.create(grid, 0, 0)); // 0.0
|
||||
path.add(grid, Point.create(grid, path, 0, 1)); // 1.0
|
||||
path.add(grid, Point.create(grid, path, 0, 2)); // 3.0
|
||||
path.add(grid, Point.create(grid, path, 1, 1)); // 3.0
|
||||
path.add(grid, Point.create(grid, path, 0, 1)); // 3.2 distance 1, factor .2
|
||||
Assertions.assertEquals(3.2, path.value());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import assessment.Grid;
|
||||
package assessment.algorithm;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import assessment.Grid;
|
||||
import assessment.Path;
|
||||
import assessment.BestPathFinder;
|
||||
package assessment.algorithm;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class PathFinderTest {
|
||||
|
|
@ -8,21 +7,21 @@ public class PathFinderTest {
|
|||
@Test
|
||||
public void testBestPath20() {
|
||||
Grid grid = Grid.fromFile("grids/20.txt");
|
||||
Path path = new BestPathFinder().findMaxValPath(grid, 20, 8, 1000, 9, 9);
|
||||
Path path = new OptimalPathFinder().findOptimalPath(grid, 20, 8, 1000, 9, 9);
|
||||
System.out.println(path);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBestPath100() {
|
||||
Grid grid = Grid.fromFile("grids/100.txt");
|
||||
Path path = new BestPathFinder().findMaxValPath(grid, 100, 8, 10000, 50, 50);
|
||||
Path path = new OptimalPathFinder().findOptimalPath(grid, 100, 8, 10000, 50, 50);
|
||||
System.out.println(path);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBestPath1000() {
|
||||
Grid grid = Grid.fromFile("grids/1000.txt");
|
||||
Path path = new BestPathFinder().findMaxValPath(grid, 1000, 18, 100000, 500, 500);
|
||||
Path path = new OptimalPathFinder().findOptimalPath(grid, 1000, 18, 10000, 500, 500);
|
||||
System.out.println(path);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue