在笛卡尔坐标中找到具有运动约束的另一个点的最近点?

时间:2017-10-05 20:25:05

标签: java algorithm

我一直在练习算法,我找到了一个花了我几个星期但仍然无法解决的算法。我无法想出一个完整的算法,但我一直在研究一个想法,到目前为止我写的代码是:

  

注意:我之所以分享全面问题的原因不是提问,而是我可能首先误解了问题的主要观点。

问题

PropBot只能进行两次不同的动作。它可以向前移动10 cm ,或者向右转 45度。每个单独的动作都需要一秒钟

输入

您的模块有两个输入:PropBot希望尽可能接近的平面上的点的笛卡尔坐标,以及可用于执行此操作的最大秒数。在导航开始时,机器人位于原点,指向+ x方向。秒数将是0到24之间的整数,包括0和24。所需目标点的x和y坐标都是介于-100和100之间的实数。输入文件中的第一个条目将是测试次数 情况,t(0

输出

您的程序必须返回目标点与机器人在给定时间内可以到达的最近点之间的距离。您的结果应包括小数点左侧的至少一位数字,以及小数点右侧的正好六位数字。为了消除影响结果的舍入误差的可能性,我们构造了测试数据,因此真实结果的小数点右边的第七个数字从不是4或5。

示例输入

2

24 5.0 5.0

9 7.0 17.0

示例输出

0.502525< - 如何?

0.100505好的

JAVA CODE

枚举方向

public enum Direction {

EAST(1), N_EAST(2), NORTH(3), N_WEST(4), WEST(5), S_WEST(6), SOUTH(7), S_EAST(8);

private int direction;
private int index;

Direction(){
    direction = 1;
    index = 0;
}

Direction(int dir){
    direction = dir;
}

int getDirection(){
    return direction;
}

public int incrementDir(){
    if(direction > 1 && direction <= 8){
        direction = 8 - index++;
        // Rotate towards right side

    }
    else if(direction == 1){
        direction = 8;
        index = 1;
    }
    return direction;
}
}

摘要 - Calculation.java

import java.awt.Point;

public abstract class Calculation {

public static Direction getDir(Point p){
    int line = getCloseLine(p);
    switch (line) {
        case 1:
            return Direction.EAST;
        case 2:
            return Direction.N_EAST;

            // 2nd Quadrant
        case 3:
            return Direction.NORTH;
        case 4:
            return Direction.N_WEST;

            // 3rd Quadrant
        case 5:
            return Direction.WEST;
        case 6:
            return Direction.S_WEST;

            // 4th Quadrant
        case 7:
            return Direction.SOUTH;
        case 8:
            return Direction.S_EAST;

    default:
        return Direction.EAST;
    }
}

public static int getSelectedLine(Point p){ 
    int a = getCloseLine(p);
    return a;
}

public static int getQuadrant(Point target) {
    double x = target.getX();
    double y = target.getY();
    if (x > 0 && y > 0)
        return 1;
    else if (x < 0 && y > 0)
        return 2;
    else if (x < 0 && y < 0)
        return 3;
    else if (x > 0 && y < 0)
        return 4;
    // Means point lies on an Axis not in any Quadrant
    return -1;
}

public static int getAxis(Point target) {
    double x = target.getX();
    double y = target.getY();
    if (x > 0 && y == 0)
        return 1;
    else if (x == 0 && y > 0)
        return 2;
    else if (x < 0 && y == 0)
        return 3;
    else if (x == 0 && y < 0)
        return 4;
    else if( x == 0 && y == 0)
        return 0;

    return -1;
}

public static double getAngle(Point v2) {
    double d = v2.getY() / v2.getX();
    double ang = Math.toDegrees(Math.atan(d));
    return ang;
}

public static int getSector(Point point) {
    double angle = getAngle(point);
    int quad = getQuadrant(point);

    if(quad == -1)
        return -1;          

    switch (quad) {
        case 1:
            if (angle < 45.0)
                return 1;
            else
                return 2;

        case 2:
            if (angle < -45.0)
                return 3;
            else
                return 4;
        case 3:
            if (angle < 45.0)
                return 5;
            else
                return 6;
        case 4:
            if (angle < -45.0)
                return 7;
            else
                return 8;
    }

    return -1;
}

public static int getCloseLine(Point p) {
    int sec = getSector(p);
    double angle = getAngle(p);
    System.out.println("ANGLE : " + angle);

    if(sec == -1){
        int axis = getAxis(p);
        switch(axis){
        case 1:
            return 1;
        case 2:
            return 3;
        case 3:
            return 5;
        case 4:
            return 7;
        case 0:
            return 0;
        }
    }

    switch (sec) {

    case 1:
        if (angle < 22.5)
            return 1;
        else
            return 2;
    case 2:
        if (angle < 67.5)
            return 2;
        else
            return 3;

        // 2nd Quadrant
    case 3:
        if (angle < -67.5)
            return 3;
        else
            return 4;
    case 4:
        if (angle < -22.5)
            return 4;
        else
            return 5;

        // 3rd Quadrant
    case 5:
        if (angle < 22.5)
            return 5;
        else
            return 6;
    case 6:
        if (angle < 67.5)
            return 6;
        else
            return 7;

        // 4th Quadrant
    case 7:
        if (angle < -67.5)
            return 7;
        else
            return 8;
    case 8:
        if (angle < -22.5)
            return 8;
        else
            return 1;

    }
    return -1;
}
}

Main.java

import java.awt.Point;
import java.util.Scanner;


public class Main {

public static void main(String[] args) 
{

    Scanner s =  new Scanner(System.in);

    int time = 0;

    Point p = new Point(0, 0);
    System.out.println("Enter time: ");
    time = s.nextInt();
    System.out.println( " X: ");
    p.x = s.nextInt();
    System.out.println( " Y: " );
    p.y = s.nextInt();

    if(!(time > 0 && time <= 24)){
        s.close();
        System.err.println("INPUT ERROR!");
        return;
    }
    // Initialize bot facing +x direction
    Bot b = new Bot(p, Direction.EAST);

    int line = Calculation.getCloseLine(p);

    while(time != 0 && b.getDirectionInt() != line){
        // Rotate the face towards the point
        b.rotate();
        time--;
    }

    s.close();
}

}

Bot.java

import java.awt.Point;

public class Bot {

private Point location;
private Direction direction;

public Bot(){
    location = new Point(0, 0);
    direction = Direction.EAST;
}

public Bot(Point loc, Direction dir){
    location = loc;
    direction = dir;
}

public Point move(){

    return location;
}

public int rotate(){
    direction.incrementDir();
    return direction.getDirection();
}

public int getDirectionInt(){
    return direction.getDirection();
}
}

我的方法是将笛卡尔平面划分为扇区并获得输入点的壁橱线并旋转机器人然后向前移动。

第一期:我了解了第二个案例输出的评估方式,但我对第一个案例输出没有任何了解。

Graph of the points

线分布如下:

Line distribution

第二个问题:如果机器人沿对角线(45度)移动然后水平或垂直移动,那么看起来似乎整个笛卡尔平面已经移动并且我写的代码无效了。

我的方法是否正确?如果是,那么我如何进一步改进呢? 如果我的方法有误?请提出更好的选择。

3 个答案:

答案 0 :(得分:1)

我认为你可以为每个可能的机器人停止及其方向创建一个图形。这将是一个三元组(x,y,方向)和方向是0,1,..,7之一。对于每个方向,向前移动由向量orientation_vec [i]

给出
import networkx as nx


def one_second(G):
    # In each step you can either go forward or turn. Create one edge
    # for each node and these two possible moves.
    orientation_vec = [(10000000, 0), (7071067, -7071067), (0, -10000000),
                       (-7071067, -7071067), (-10000000, 0), (-7071067, 7071067),
                       (0, 10000000), (7071067, 7071067)]

    for node in G.nodes():
        # edge to next orientation
        G.add_edge(node, (node[0], node[1], (node[2] + 1)%8))
        # edge going forward
        G.add_edge(node, (node[0] + orientation_vec[node[2]][0],
                          node[1] + orientation_vec[node[2]][1],
                          node[2]))


def create_graph(max_time):
    G = nx.Graph()
    G.add_node(n=(0, 0, 0))
    for t in range(max_time):
       one_second(G)
    print(len(G.nodes()))

我们现在可以遍历所有节点并找到最接近目标的节点。 创建图形后,我们可以使用dijkstra或A *找到最短路径。我使用了networkx软件包。

import math

def find_closest_path(paths, end):
    min_dist = end[0]**2 + end[1]**2
    best_path = None
    for key in paths.keys():
        end_path = paths[key][-1]
        path_to_end = (end_path[0] - end[0])**2 + (end_path[1]-end[1])**2
        if path_to_end < min_dist:
            min_dist = path_to_end
            best_path = paths[key]
    min_dist = math.sqrt(min_dist)/10**6
    x = [p[0]/10**6 for p in best_path]
    y = [p[1]/10**6 for p in best_path]
    return min_dist, x, y 


def robot_path(end, max_time):
    create_graph(max_time)
    paths = nx.single_source_shortest_path(G, (0, 0, 0), cutoff=max_time)
    return find_closest_path(paths, end)

我在某处从stackoverflow复制粘贴的绘图功能。

from pylab import *
import matplotlib.pyplot as plt

def plot_robot_path(x,y, end):
    assert len(x) == len(y)
    color=['b']*len(x) + ['r']

    fig = plt.figure()
    ax = fig.add_subplot(111)

    scatter(x+[end[0]], y+[end[1]], s=100 ,marker='o', c=color)

    [plot([x[i], x[i+1]], [y[i], y[i+1]], '-', linewidth=3 ) for i in range(len(x)-1)] 

    left,right = ax.get_xlim()
    low,high = ax.get_ylim()
    arrow(left, 0, right -left, 0, length_includes_head = True, head_width = 0.15 )
    arrow(0, low, 0, high-low, length_includes_head = True, head_width = 0.15 ) 

    grid()

    show()

如果我们运行它,我会得到以下结果:

end1=(5*10**6, 5*10**6)
max_time = 24
min_dist1, x1, y1 = robot_path(end1, max_time)
print(min_dist1)
plot_robot_path(x1, y1, (5, 5))

max_time=9
end2=(7*10**6, 17*10**6)
min_dist2, x2, y2 = robot_path(end2, max_time)
print(min_dist2)
plot_robot_path(x2, y2, (7, 17))

enter image description here

enter image description here

答案 1 :(得分:1)

这里紧凑的回溯解决方案:

import math

min_d = 1e+10
c = 0.70710678
xt = 5.0
yt = 5.0

def solve(remaining_t, x, y, dir_x, dir_y):
    global min_d
    d = math.sqrt((x - xt)**2 + (y - yt)**2)
    if d < min_d:
        min_d = d
    if remaining_t == 0 or d - remaining_t * 10 >= min_d:
        return 
    solve(remaining_t - 1, x + dir_x, y + dir_y, dir_x, dir_y)
    solve(remaining_t - 1, x, y, c * (dir_x - dir_y), c * (dir_x + dir_y))

solve(24, 0.0, 0.0, 10.0, 0.0)
print(min_d)

需要约5秒。在我的电脑上。

一点解释:

min_d - 是当前的最小距离,我们用较大的值初始化它(应该大于任何可能的距离)

solve函数采用以下参数:

remaining_t - 剩余时间(秒),每一步减少1

xy - 机器人的当前坐标

dir_xdir_y - 机器人当前方向向量的坐标。这个向量的长度为10,我们从这个向量指向x轴开始:(10,0)

在每一步,solve函数都会考虑到目标点的当前距离,并在需要时更新min_d。如果没有剩余剩余时间或我们离目标点太远,我们会立即停止前进。否则我们尝试1)前进2)转45度。

  1. 当僵尸程序正在进行时(考虑当前方向)x变为x+dir_xy变为y+dir_y。方向向量保持不变。

  2. 当机器人转动时,其坐标保持不变,但方向矢量会发生变化。见https://en.m.wikipedia.org/wiki/Rotation_matrix(我们的常数c = sin 45 = cos 45)

答案 2 :(得分:0)

以下是Algrid的答案的Java代码:

public class Main {

static double min_d = 1e+10;

// diagonal distance factor cos(45), needs to multiply with hypotenuse 
static double c = 0.707110678; 

static double xt = 7.0;
static double yt = 17.0;

public static void solve(int time, double x, double y, double dirX, double dirY) {

    double d = Math.sqrt(Math.pow((x - xt), 2) + Math.pow((y - yt), 2));
    if( d < min_d )
        min_d = d;

    if( time == 0 || (d-time * 10) >= min_d ){
        return;
    }

    solve(time - 1, x + dirX, y + dirY, dirX, dirY);
    solve(time - 1, x, y, c * (dirX - dirY), c * (dirX + dirY));
}

public static void main(String[] args) 
{

    solve(9, 0.0, 0.0, 10.0, 0.0);
}

}// Class END