要Vectory!–矢量赛车大奖赛


39

CarpetPython用户发布了对此问题的新观点,由于搜索空间的增加,该问题更加关注启发式解决方案。我个人认为挑战比我的挑战要好得多,因此请考虑尝试一下!

矢量赛车是一种令人上瘾的游戏,可以用钢笔和一张正方形的纸来玩。您可以在纸上绘制任意的赛道,定义起点和终点,然后以转弯为基础操纵点型汽车。尽可能快地到达终点,但请注意不要陷入困境!

轨道

  • 该地图是一个二维网格,其中每个单元格都有整数坐标。
  • 您在网格单元上移动。
  • 每个网格单元要么是轨道的一部分,要么是一堵墙。
  • 恰好一个跟踪单元是起始坐标。
  • 至少一个跟踪单元被指定为目标。登陆其中任何一个都可以完成比赛。多个目标单元不一定连接。

驾驶汽车

您的汽车以给定的坐标和速度矢量开始(0, 0)。在每个转弯中,您都可以调整速度的每个分量,也可以保持不变±1。然后,将得到的速度矢量添加到您的汽车位置。

图片可能会有所帮助!红色圆圈是您上一转弯的位置。蓝色圆圈是您当前的位置。您的速度是从红色到蓝色圆圈的向量。在这回合中,取决于您如何调整速度,您可以移动到任何绿色圆圈。

                                    在此处输入图片说明

如果您降落在墙壁上,则会立即损失。

你的任务

您猜对了:编写一个程序,在输入跑道的情况下,将汽车尽可能少地转向到一个目标单元。您的解决方案应该能够很好地处理任意轨道,并且不会针对所提供的测试案例进行专门优化。

输入值

调用程序时,请从stdin中读取:

target
n m
[ASCII representation of an n x m racetrack]
time

target是您可能会完成此轨道的最大匝数,并且time是该轨道的总时间预算(以秒为单位)(不一定是整数)。有关计时的详细信息,请参见下文。

对于以换行符分隔的轨道,使用以下字符:

  • # –墙
  • S- 开始
  • *一个目标
  • . –所有其他跟踪单元(即道路)

n x m提供的网格外部的所有单元格均隐含为墙。

坐标原点在左上角。

这是一个简单的示例:

8
4.0
9 6
###...***
###...***
###...***
......###
S.....###
......###

使用基于0的索引,起始坐标为(0,4)

之后的一举一动,你会得到进一步的输入:

x y
u v
time

其中xyuv都是基于0的整数。(x,y)是您当前的位置,(u,v)是您当前的速度。请注意,x+u和/或y+v可能超出范围。

time是您剩余的时间预算,以秒为单位。随意忽略这一点。这仅适用于确实希望将实施时间限制到时间限制的参与者。

游戏结束时(因为您已经降落在墙壁上,越界,超出了target转弯,没有时间或达到了目标),您将得到一个空白行。

输出量

对于每转,写入stdout

Δu Δv

其中ΔuΔv各为一个-101。这将被添加以(u,v)确定您的新职位。为了澄清,说明如下

(-1,-1) ( 0,-1) ( 1,-1)
(-1, 0) ( 0, 0) ( 1, 0)
(-1, 1) ( 0, 1) ( 1, 1)

上述示例的最佳解决方案是

1 0
1 -1
1 0

请注意,控制器不会将自身附加到stderr,因此您可以自由地将其用于开发机器人时可能需要的任何调试输出。不过,请在提交的代码中删除/注释掉任何此类输出。

您的机器人可能需要半秒钟才能在每个回合中做出响应。对于需要较长时间的转弯,您将有每秒钟的时间预算(每条轨道)target/2。每当转弯时间超过半秒时,额外的时间将从您的时间预算中扣除。当您的时间预算达到零时,当前比赛将被终止。

新增内容:出于实际原因,我必须设置内存限制(因为合理的磁道大小,内存似乎比时间更受限制)。因此,我将不得不中止所有赛车运行的测试,在该测试中,赛车使用了超过1GB的内存(由Process Explorer测量为Private Bytes)

计分

有20条曲目的基准。对于每个轨道:

  • 如果您完成了跟踪,则得分就是达到目标单元格所需的移动次数除以target
  • 如果您没时间/ 没有记忆,或者target转弯之前没有达到目标,或者您在任何时候进入墙/界外,您的得分都是2
  • 如果您的程序不是确定性的,则您的分数是该轨道上10次运行的平均值(请在答案中注明)。

您的总体分数是各个曲目分数的总和。最低分获胜!

此外,每个参与者都可以(并强烈建议)提供一个附加的基准跟踪,它将被添加到官方列表中。先前的答案将被重新评估,包括此新曲目。这是为了确保没有一个解决方案太适合现有的测试用例,并考虑到了我可能错过的有趣的边缘用例(但是您可能会发现)。

打破领带

现在已经有了一个最佳的解决方案,这可能是参与者得分的主要因素。

如果有平局(由于有多个答案可以最佳地解决所有问题,否则,我将添加其他(较大)测试用例以打破平局。为了避免在创建这些决胜局时出现任何人为偏见,这些问题将以固定方式生成

  • 我将增加边长n10比以这种方式产生的最后一首曲目。(如果他们不打破常规,我可能会跳过尺寸。)
  • 基础是此矢量图形
  • 将使用此Mathematica代码片段以所需的分辨率对其进行栅格化。
  • 起点在左上角。特别是,它将是该轨道末端最顶层的最左侧的单元格。
  • 目标在右下角。特别是,它将是轨道那端最底行的最右边的单元格。
  • target意志4*n

初始基准测试的最终轨道已经像这样生成了n = 50

控制器

测试提交的程序是用Ruby编写的,可以在GitHub上找到我将使用的基准文件。那里还有一个示例漫游器randomracer.rb,它只是随机选择。您可以使用其基本结构作为机器人的起点,以查看通信的工作原理。

您可以针对自己选择的跟踪文件运行自己的机器人,如下所示:

ruby controller.rb track_file_name command to run your racer

例如

ruby controller.rb benchmark.txt ruby randomracer.rb

该存储库还包含Point2D和的两个类Track。如果您的提交是用Ruby编写的,请随时方便地重用它们。

命令行开关

您可以添加命令行开关-v-s-t基准的文件名之前。如果要使用多个开关,也可以执行-vs。他们是这样做的:

-v (详细):使用它从控制器产生更多的调试输出。

-s (静音):如果您希望自己跟踪自己的位置和速度并且不关心时间预算,可以使用此标志关闭每转(发送到提交)的那三行输出。

-t(曲目):让您选择要测试的单个曲目。例如-t "1,2,5..8,15",仅测试轨道1、2、5、6、7、8 和15。非常感谢Ventero的这项功能和选项解析器。

您所提交

总之,请在回答中包括以下内容:

  • 你的分数。
  • 如果您使用的是随机性,请说明这一点,这样我可以在多次运行中平均您的得分。
  • 您提交的代码。
  • 在Windows 8机器上运行的所选语言的免费编译器或解释器的位置。
  • 必要时提供编译说明。
  • Windows命令行字符串,用于运行提交。
  • 您的提交是否需要-s标记。
  • (可选)一条新的可解决的曲目,它将添加到基准中。我将target手动确定一个合理的轨道。将曲目添加到基准后,我将根据您的答案对其进行编辑。我保留要求您选择其他曲目的权利(以防万一您添加了不成比例的大型曲目,在曲目中包含淫秽的ASCII艺术作品等)。当我将测试用例添加到基准测试集中时,我将用指向基准测试文件中轨道的链接替换您答案中的轨道,以减少本文中的混乱情况。

如您所见,我将在Windows 8计算机上测试所有提交。如果绝对无法在Windows上运行您的提交,我也可以在Ubuntu VM上尝试。但是,这将相当慢,因此,如果您要延长时间限制,请确保程序在Windows上运行。

愿最好的司机成为矢量!

但是想玩!

如果您想亲自尝试游戏以获得更好的体验,可以使用此实现。我认为,这里使用的规则稍微复杂一些,但是足够有用。

排行榜

上次更新时间: 2014年1 月9日,21 29 UTC
基准测试成绩 25
条决胜局规格: 290,440

  1. 6.86688 – Kuroi Neko
  2. 8.73108 – user2357112-第二次提交
  3. 9.86627 – 尼奥尼奥
  4. 10.66109 – user2357112-第一次提交
  5. 12.49643 –
  6. 40.0759 – 假名 117 (概率)

详细的测试结果。(概率提交的分数已分别确定。)

Answers:


5

C ++ 11-6.66109

另一个广度优先搜索实现,仅进行了优化。

它必须与-s选项一起运行。
它的输入完全没有经过消毒,因此错误的跟踪可能会将其变成南瓜。

我使用Microsoft Visual C ++ 2013测试了它,并使用默认的/ O2标志发布了版本(针对速度进行了优化)。
可以使用g ++和Microsoft IDE生成OK。
我的准系统内存分配器是一堆废话,所以不要指望它可与unordered_set!的其他STL实现一起使用。

#include <cstdint>
#include <iostream>
#include <fstream>
#include <sstream>
#include <queue>
#include <unordered_set>

#define MAP_START 'S'
#define MAP_WALL  '#'
#define MAP_GOAL  '*'

#define NODE_CHUNK_SIZE   100 // increasing this will not improve performances
#define VISIT_CHUNK_SIZE 1024 // increasing this will slightly reduce mem consumption at the (slight) cost of speed

#define HASH_POS_BITS 8 // number of bits for one coordinate
#define HASH_SPD_BITS (sizeof(size_t)*8/2-HASH_POS_BITS)

typedef int32_t tCoord; // 32 bits required to overcome the 100.000 cells (insanely) long challenge

// basic vector arithmetics
struct tPoint {
    tCoord x, y;
    tPoint(tCoord x = 0, tCoord y = 0) : x(x), y(y) {}
    tPoint operator+ (const tPoint & p) { return tPoint(x + p.x, y + p.y); }
    tPoint operator- (const tPoint & p) { return tPoint(x - p.x, y - p.y); }
    bool operator== (const tPoint & p) const { return p.x == x && p.y == y;  }
};

// a barebone block allocator. Improves speed by about 30%
template <class T, size_t SIZE> class tAllocator
{
    T * chunk;
    size_t i_alloc;
    size_t m_alloc;
public:
    typedef T                 value_type;
    typedef value_type*       pointer;
    typedef const value_type* const_pointer;
    typedef std::size_t       size_type;
    typedef value_type&       reference;
    typedef const value_type& const_reference;
    tAllocator()                                              { m_alloc = i_alloc = SIZE; }
    template <class U> tAllocator(const tAllocator<U, SIZE>&) { m_alloc = i_alloc = SIZE; }
    template <class U> struct rebind { typedef tAllocator<U, SIZE> other; };
    pointer allocate(size_type n, const_pointer = 0)
    {
        if (n > m_alloc) { i_alloc = m_alloc = n; }      // grow max size if request exceeds capacity
        if ((i_alloc + n) > m_alloc) i_alloc = m_alloc;  // dump current chunk if not enough room available
        if (i_alloc == m_alloc) { chunk = new T[m_alloc]; i_alloc = 0; } // allocate new chunk when needed
        T * mem = &chunk[i_alloc];
        i_alloc += n;
        return mem;
    }
    void deallocate(pointer, size_type) { /* memory is NOT released until process exits */ }
    void construct(pointer p, const value_type& x) { new(p)value_type(x); }
    void destroy(pointer p) { p->~value_type(); }
};

// a node in our search graph
class tNode {
    static tAllocator<tNode, NODE_CHUNK_SIZE> mem; // about 10% speed gain over a basic allocation
    tNode * parent;
public:
    tPoint pos;
    tPoint speed;
    static tNode * alloc (tPoint pos, tPoint speed, tNode * parent) { return new (mem.allocate(1)) tNode(pos, speed, parent); }
    tNode (tPoint pos = tPoint(), tPoint speed = tPoint(), tNode * parent = nullptr) : parent(parent), pos(pos), speed(speed) {}
    bool operator== (const tNode& n) const { return n.pos == pos && n.speed == speed; }
    void output(void)
    {
        std::string output;
        tPoint v = this->speed;
        for (tNode * n = this->parent ; n != nullptr ; n = n->parent)
        {
            tPoint a = v - n->speed;
            v = n->speed;
            std::ostringstream ss;  // a bit of shitty c++ text I/O to print elements in reverse order
            ss << a.x << ' ' << a.y << '\n';
            output = ss.str() + output;
        }
        std::cout << output;
    }
};
tAllocator<tNode, NODE_CHUNK_SIZE> tNode::mem;

// node queueing and storing
static int num_nodes = 0;
class tNodeJanitor {
    // set of already visited nodes. Block allocator improves speed by about 20%
    struct Hasher { size_t operator() (tNode * const n) const 
    {
        int64_t hash = // efficient hashing is the key of performances
            ((int64_t)n->pos.x   << (0 * HASH_POS_BITS))
          ^ ((int64_t)n->pos.y   << (1 * HASH_POS_BITS))
          ^ ((int64_t)n->speed.x << (2 * HASH_POS_BITS + 0 * HASH_SPD_BITS))
          ^ ((int64_t)n->speed.y << (2 * HASH_POS_BITS + 1 * HASH_SPD_BITS));
        return (size_t)((hash >> 32) ^ hash);
        //return (size_t)(hash);
    }
    };
    struct Equalizer { bool operator() (tNode * const n1, tNode * const n2) const
        { return *n1 == *n2; }};
    std::unordered_set<tNode *, Hasher, Equalizer, tAllocator<tNode *, VISIT_CHUNK_SIZE>> visited;
    std::queue<tNode *> queue; // currently explored nodes queue
public:
    bool empty(void) { return queue.empty();  }
    tNode * dequeue() { tNode * n = queue.front(); queue.pop(); return n; }
    tNode * enqueue_if_new (tPoint pos, tPoint speed = tPoint(0,0), tNode * parent = nullptr)
    {
        tNode signature (pos, speed);
        tNode * n = nullptr;
        if (visited.find (&signature) == visited.end()) // the classy way to check if an element is in a set
        {
            n = tNode::alloc(pos, speed, parent);
            queue.push(n);
            visited.insert (n);
num_nodes++;
        }
        return n;
    }
};

// map representation
class tMap {
    std::vector<char> cell;
    tPoint dim; // dimensions
public:
    void set_size(tCoord x, tCoord y) { dim = tPoint(x, y); cell.resize(x*y); }
    void set(tCoord x, tCoord y, char c) { cell[y*dim.x + x] = c; }
    char get(tPoint pos)
    {
        if (pos.x < 0 || pos.x >= dim.x || pos.y < 0 || pos.y >= dim.y) return MAP_WALL;
        return cell[pos.y*dim.x + pos.x];
    }
    void dump(void)
    {
        for (int y = 0; y != dim.y; y++)
        {
            for (int x = 0; x != dim.x; x++) fprintf(stderr, "%c", cell[y*dim.x + x]);
            fprintf(stderr, "\n");
        }
    }
};

// race manager
class tRace {
    tPoint start;
    tNodeJanitor border;
    static tPoint acceleration[9];
public:
    tMap map;
    tRace ()
    {
        int target;
        tCoord sx, sy;
        std::cin >> target >> sx >> sy;
        std::cin.ignore();
        map.set_size (sx, sy);
        std::string row;
        for (int y = 0; y != sy; y++)
        {
            std::getline(std::cin, row);
            for (int x = 0; x != sx; x++)
            {
                char c = row[x];
                if (c == MAP_START) start = tPoint(x, y);
                map.set(x, y, c);
            }
        }
    }

    // all the C++ crap above makes for a nice and compact solver
    tNode * solve(void)
    {
        tNode * initial = border.enqueue_if_new (start);
        while (!border.empty())
        {
            tNode * node = border.dequeue();
            tPoint p = node->pos;
            tPoint v = node->speed;
            for (tPoint a : acceleration)
            {
                tPoint nv = v + a;
                tPoint np = p + nv;
                char c = map.get(np);
                if (c == MAP_WALL) continue;
                if (c == MAP_GOAL) return new tNode (np, nv, node);
                border.enqueue_if_new (np, nv, node);
            }
        }
        return initial; // no solution found, will output nothing
    }
};
tPoint tRace::acceleration[] = {
    tPoint(-1,-1), tPoint(-1, 0), tPoint(-1, 1),
    tPoint( 0,-1), tPoint( 0, 0), tPoint( 0, 1),
    tPoint( 1,-1), tPoint( 1, 0), tPoint( 1, 1)};

#include <ctime>
int main(void)
{
    tRace race;
    clock_t start = clock();
    tNode * solution = race.solve();
    std::cerr << "time: " << (clock()-start)/(CLOCKS_PER_SEC/1000) << "ms nodes: " << num_nodes << std::endl;
    solution->output();
    return 0;
}

结果

 No.       Size     Target   Score     Details
-------------------------------------------------------------------------------------
  1       37 x 1        36   0.22222   Racer reached goal at ( 36, 0) in 8 turns.
  2       38 x 1        37   0.24324   Racer reached goal at ( 37, 0) in 9 turns.
  3       33 x 1        32   0.25000   Racer reached goal at ( 32, 0) in 8 turns.
  4       10 x 10       10   0.40000   Racer reached goal at ( 7, 7) in 4 turns.
  5        9 x 6         8   0.37500   Racer reached goal at ( 6, 0) in 3 turns.
  6       15 x 7        16   0.37500   Racer reached goal at ( 12, 4) in 6 turns.
  7       17 x 8        16   0.31250   Racer reached goal at ( 14, 0) in 5 turns.
  8       19 x 13       18   0.27778   Racer reached goal at ( 0, 11) in 5 turns.
  9       60 x 10      107   0.14953   Racer reached goal at ( 0, 6) in 16 turns.
 10       31 x 31      106   0.23585   Racer reached goal at ( 27, 0) in 25 turns.
 11       31 x 31      106   0.24528   Racer reached goal at ( 15, 15) in 26 turns.
 12       50 x 20       50   0.24000   Racer reached goal at ( 49, 10) in 12 turns.
 13      100 x 100    2600   0.01385   Racer reached goal at ( 50, 0) in 36 turns.
 14       79 x 63      242   0.24380   Racer reached goal at ( 3, 42) in 59 turns.
 15       26 x 1        25   0.32000   Racer reached goal at ( 25, 0) in 8 turns.
 16       17 x 1        19   0.52632   Racer reached goal at ( 16, 0) in 10 turns.
 17       50 x 1        55   0.34545   Racer reached goal at ( 23, 0) in 19 turns.
 18       10 x 7        23   0.34783   Racer reached goal at ( 1, 3) in 8 turns.
 19       55 x 55       45   0.17778   Racer reached goal at ( 50, 26) in 8 turns.
 20      101 x 100     100   0.14000   Racer reached goal at ( 99, 99) in 14 turns.
 21   100000 x 1         1   1.00000   Racer reached goal at ( 0, 0) in 1 turns.
 22       50 x 50      200   0.05500   Racer reached goal at ( 47, 46) in 11 turns.
 23      290 x 290    1160   0.16466   Racer reached goal at ( 269, 265) in 191 turns.
-------------------------------------------------------------------------------------
TOTAL SCORE:                 6.66109

表演节目

那种cr脚的C ++语言有一个诀窍,可让您跳圈,仅移动火柴。但是,您可以鞭打它以生成相对快速且内存有效的代码。

散列

这里的关键是为节点提供一个良好的哈希表。到目前为止,这是执行速度的主要因素。GNU和Microsoft的
两个实现unordered_set产生了30%的执行速度差异(赞成GNU,是的!)。

区别并不令人惊讶,隐藏在后面的大量代码又如何呢unordered_set

出于好奇,我对哈希表的最终状态做了一些统计。
两种算法最终得到的存储桶/元素比率几乎相同,但是分区有所不同:
对于290x290的平局决胜游戏,GNU每个非空存储桶平均获得1.5个元素,而Microsoft的平均得分为5.8(!)。

看起来我的哈希函数不是很好地被Microsoft随机分配...我想知道Redmond的家伙是否真的对他们的STL进行了基准测试,或者我的用例仅偏爱GNU实现...

当然,我的哈希函数远非最佳。我本可以使用基于多个移位/倍数的常规整数混合,但是哈希有效的函数需要花费一些时间来计算。

与插入的数量相比,哈希表查询的数量似乎非常高。例如,在290x290的平局决胜游戏中,您有大约360万次插入操作,可查询2270万次查询。
在这种情况下,次优但快速的散列会产生更好的性能。

内存分配

第二,提供有效的内存分配器。它使性能提高了约30%。值得添加的废话代码是否值得争论:)。

当前版本每个节点使用40到55个字节。
对于一个节点,功能数据需要24个字节(4个坐标和2个指针)。
由于疯狂的100.000行测试用例,必须将坐标存储在4个字节的字中,否则可以通过使用短裤(最大坐标值为32767)获得8个字节。其余字节主要由无序集的哈希表消耗。这意味着数据处理实际上比“有用的”有效负载消耗更多。

最终获胜者是...

在Win7下的PC上,最坏的版本(即Microsoft编译)在大约2.2秒内解决了决胜局(案例23,290x290),并消耗了约185 Mb的内存。
为了进行比较,当前的领导者(user2357112的python代码)花费了30多秒钟的时间,消耗了大约780 Mb。

控制器问题

我不太确定我是否可以使用Ruby进行编码以挽救生命。
但是,我发现并破解了控制器代码中的两个问题:

1)读图 track.rb

安装了ruby 1.9.3之后,磁道阅读器会发出嘶哑的声音,表示shift.to_i无法使用string.lines
经过漫长的遍历在线Ruby文档之后,我放弃了字符串,而是使用了一个中间数组,就像这样(就在文件开头):

def initialize(string)
    @track = Array.new;
    string.lines.each do |line|
        @track.push (line.chomp)
    end

2)杀死幽灵 controller.rb

正如其他张贴者已经指出的那样,控制器有时会试图杀死已经退出的进程。为了避免这些令人讨厌的错误输出,我只是将异常处理掉了,就像这样(在第134行):

if silent
    begin # ;!;
        Process.kill('KILL', racer.pid)
    rescue Exception => e
    end

测试用例

为了击败BFS求解器的强力方法,最糟糕的轨道是100.000单元图的反面:一个完全自由的区域,目标离起点尽可能远。

在此示例中,一张100x400的地图,目标在左上角,起点在右下角。

这张地图有28个转弯的解决方案,但是BFS求解器将探索数百万个州来查找它(我的地雷访问了10.022.658个州,耗时约12秒,并达到600 Mb!)。

由于290x290平顶断路器的表面不到一半,因此需要更多的节点访问3倍。另一方面,基于启发式/ A *的求解器应该很容易克服它。

30
100 400
*...................................................................................................
....................................................................................................
                          < 400 lines in all >
....................................................................................................
....................................................................................................
...................................................................................................S

奖励:等效(但效率稍差)的PHP版本

这是我开始的,在固有的语言迟缓说服我使用C ++之前。
PHP内部哈希表似乎不如Python高效,至少在这种情况下:)。

<?php

class Trace {
    static $file;
    public static $state_member;
    public static $state_target;
    static function msg ($msg)
    {
        fputs (self::$file, "$msg\n");
    }

    static function dump ($var, $msg=null)
    {
        ob_start();
        if ($msg) echo "$msg ";
        var_dump($var);
        $dump=ob_get_contents();
        ob_end_clean();
        fputs (self::$file, "$dump\n");
    }

    function init ($fname)
    {
        self::$file = fopen ($fname, "w");
    }
}
Trace::init ("racer.txt");

class Point {
    public $x;
    public $y;

    function __construct ($x=0, $y=0)
    {
        $this->x = (float)$x;
        $this->y = (float)$y;
    }

    function __toString ()
    {
        return "[$this->x $this->y]";
    }

    function add ($v)
    {
        return new Point ($this->x + $v->x, $this->y + $v->y);
    }

    function vector_to ($goal)
    {
        return new Point ($goal->x - $this->x, $goal->y - $this->y);
    }
}

class Node {
    public $posx  , $posy  ;
    public $speedx, $speedy;
    private $parent;

    public function __construct ($posx, $posy, $speedx, $speedy, $parent)
    {
        $this->posx = $posx;
        $this->posy = $posy;
        $this->speedx = $speedx;
        $this->speedy = $speedy;
        $this->parent = $parent;
    }

    public function path ()
    {
        $res = array();
        $v = new Point ($this->speedx, $this->speedy);
        for ($node = $this->parent ; $node != null ; $node = $node->parent)
        {
            $nv = new Point ($node->speedx, $node->speedy);
            $a = $nv->vector_to ($v);
            $v = new Point ($node->speedx, $node->speedy);
            array_unshift ($res, $a);
        }
        return $res;
    }
}

class Map {

    private static $target;       // maximal number of turns
    private static $time;         // time available to solve
    private static $sx, $sy;      // map dimensions
    private static $cell;         // cells of the map
    private static $start;        // starting point
    private static $acceleration; // possible acceleration values

    public static function init ()
    {
        // read map definition
        self::$target = trim(fgets(STDIN));
        list (self::$sx, self::$sy) = explode (" ", trim(fgets(STDIN)));
        self::$cell = array();
        for ($y = 0 ; $y != self::$sy ; $y++) self::$cell[] = str_split (trim(fgets(STDIN)));
        self::$time = trim(fgets(STDIN));

        // get starting point
        foreach (self::$cell as $y=>$row)
        {
            $x = array_search ("S", $row);
            if ($x !== false)
            {
                self::$start = new Point ($x, $y);
Trace::msg ("start ".self::$start);
                break;
            }
        }

        // compute possible acceleration values
        self::$acceleration = array();
        for ($x = -1 ; $x <= 1 ; $x++)
        for ($y = -1 ; $y <= 1 ; $y++)
        {
            self::$acceleration[] = new Point ($x, $y);
        }
    }

    public static function solve ()
    {
        $now = microtime(true);
        $res = array();
        $border = array (new Node (self::$start->x, self::$start->y, 0, 0, null));
        $present = array (self::$start->x." ".self::$start->y." 0 0" => 1);
        while (count ($border))
        {
if ((microtime(true) - $now) > 1)
{
Trace::msg (count($present)." nodes, ".round(memory_get_usage(true)/1024)."K");
$now = microtime(true);
}
            $node = array_shift ($border);
//Trace::msg ("node $node->pos $node->speed");
            $px = $node->posx;
            $py = $node->posy;
            $vx = $node->speedx;
            $vy = $node->speedy;
            foreach (self::$acceleration as $a)
            {
                $nvx = $vx + $a->x;
                $nvy = $vy + $a->y;
                $npx = $px + $nvx;
                $npy = $py + $nvy;
                if ($npx < 0 || $npx >= self::$sx || $npy < 0 || $npy >= self::$sy || self::$cell[$npy][$npx] == "#")
                {
//Trace::msg ("invalid position $px,$py $vx,$vy -> $npx,$npy");
                    continue;
                }
                if (self::$cell[$npy][$npx] == "*")
                {
Trace::msg ("winning position $px,$py $vx,$vy -> $npx,$npy");
                    $end = new Node ($npx, $npy, $nvx, $nvy, $node);
                    $res = $end->path ();
                    break 2;
                }
//Trace::msg ("checking $np $nv");
                $signature = "$npx $npy $nvx $nvy";
                if (isset ($present[$signature])) continue;
//Trace::msg ("*** adding $np $nv");
                $border[] = new Node ($npx, $npy, $nvx, $nvy, $node);
                $present[$signature] = 1;
            }
        }
        return $res;
    }
}

ini_set("memory_limit","1000M");
Map::init ();
$res = Map::solve();
//Trace::dump ($res);
foreach ($res as $a) echo "$a->x $a->y\n";
?>

erf ...我的准系统分配器太准系统了。然后,我将添加必要的废话使其与g ++一起使用。对于那个很抱歉。

好,那是固定的。实际上,g ++版本的运行速度大约快30%。现在,它在stderr上输出一些统计信息。随意将其注释掉(从源代码的最后几行开始)。再次为您的失误感到抱歉。

好了,现在可以了,我转载了您的分数。该死的快!:)我将把您的测试用例添加到基准中,但是我将目标更改为400,因为这与我确定所有其他目标(决胜局除外)的方式一致。重新整理所有其他提交内容后,我将更新主要帖子。
马丁·恩德

更新了结果。无需打破常规,因为所有其他提交都超过了测试轨道上的内存限制。恭喜!:)
Martin Ender

谢谢。实际上,这一挑战使我有机会深入研究了这些STL哈希表。尽管我讨厌C ++胆量,但我还是被好奇心杀死了。喵!:)。

10

C ++ 5.4(确定性,最佳)

动态编程解决方案。可能是最佳的。非常快:只需0.2秒即可解决所有20个测试用例。在64位计算机上应该特别快。假设木板在每个方向上的位置少于32,000个(希望如此)。

这个赛车手有点不寻常。它在起始行计算最佳路径,然后立即执行计算出的路径。它忽略了时间控制,并假定它可以按时完成优化步骤(对于任何合理的现代硬件都应如此)。在过大的地图上,赛车手存在段错误的可能性很小。如果您可以说服它进行段错误,则会得到一个布朗尼点,我将对其进行修复以使用显式循环。

用编译g++ -O3。可能需要C ++ 11(用于<unordered_map>)。要运行,只需运行已编译的可执行文件(不支持标志或选项;所有输入均在stdin上获取)。

#include <unordered_map>
#include <iostream>
#include <string>
#include <vector>
#include <sstream>

#include <cstdint>

#define MOVES_INF (1<<30)

union state {
    struct {
        short px, py, vx, vy;
    };
    uint64_t val;
};

struct result {
    int nmoves;
    short dvx, dvy;
};

typedef std::unordered_map<uint64_t, result> cache_t;
int target, n, m;
std::vector<std::string> track;
cache_t cache;

static int solve(uint64_t val) {
    cache_t::iterator it = cache.find(val);
    if(it != cache.end())
        return it->second.nmoves;

    // prevent recursion
    result res;
    res.nmoves = MOVES_INF;
    cache[val] = res;

    state cur;
    cur.val = val;
    for(int dvx = -1; dvx <= 1; dvx++) for(int dvy = -1; dvy <= 1; dvy++) {
        state next;
        next.vx = cur.vx + dvx;
        next.vy = cur.vy + dvy;
        next.px = cur.px + next.vx;
        next.py = cur.py + next.vy;
        if(next.px < 0 || next.px >= n || next.py < 0 || next.py >= m)
            continue;
        char c = track[next.py][next.px];
        if(c == '*') {
            res.nmoves = 1;
            res.dvx = dvx;
            res.dvy = dvy;
            break;
        } else if(c == '#') {
            continue;
        } else {
            int score = solve(next.val) + 1;
            if(score < res.nmoves) {
                res.nmoves = score;
                res.dvx = dvx;
                res.dvy = dvy;
            }
        }
    }

    cache[val] = res;
    return res.nmoves;
}

bool solve_one() {
    std::string line;
    float time;

    std::cin >> target;
    // std::cin >> time; // uncomment to use "time" control
    std::cin >> n >> m;
    if(!std::cin)
        return false;
    std::cin.ignore(); // skip newline at end of "n m" line

    track.clear();
    track.reserve(m);

    for(int i=0; i<m; i++) {
        std::getline(std::cin, line);
        track.push_back(line);
    }

    cache.clear();

    state cur;
    cur.vx = cur.vy = 0;
    for(int y=0; y<m; y++) for(int x=0; x<n; x++) {
        if(track[y][x] == 'S') {
            cur.px = x;
            cur.py = y;
            break;
        }
    }

    solve(cur.val);

    int sol_len = 0;
    while(track[cur.py][cur.px] != '*') {
        cache_t::iterator it = cache.find(cur.val);
        if(it == cache.end() || it->second.nmoves >= MOVES_INF) {
            std::cerr << "Failed to solve at p=" << cur.px << "," << cur.py << " v=" << cur.vx << "," << cur.vy << std::endl;
            return true;
        }

        int dvx = it->second.dvx;
        int dvy = it->second.dvy;
        cur.vx += dvx;
        cur.vy += dvy;
        cur.px += cur.vx;
        cur.py += cur.vy;
        std::cout << dvx << " " << dvy << std::endl;
        sol_len++;
    }

    //std::cerr << "Score: " << ((float)sol_len) / target << std::endl;

    return true;
}

int main() {
    /* benchmarking: */
    //while(solve_one())
    //    ;

    /* regular running */
    solve_one();
    std::string line;
    while(std::cin) std::getline(std::cin, line);

    return 0;
}

结果

 No.    Size     Target   Score     Details
-------------------------------------------------------------------------------------
  1    37 x 1        36   0.22222   Racer reached goal at ( 36, 0) in 8 turns.
  2    38 x 1        37   0.24324   Racer reached goal at ( 37, 0) in 9 turns.
  3    33 x 1        32   0.25000   Racer reached goal at ( 32, 0) in 8 turns.
  4    10 x 10       10   0.40000   Racer reached goal at ( 7, 7) in 4 turns.
  5     9 x 6         8   0.37500   Racer reached goal at ( 6, 0) in 3 turns.
  6    15 x 7        16   0.37500   Racer reached goal at ( 12, 4) in 6 turns.
  7    17 x 8        16   0.31250   Racer reached goal at ( 15, 0) in 5 turns.
  8    19 x 13       18   0.27778   Racer reached goal at ( 1, 11) in 5 turns.
  9    60 x 10      107   0.14953   Racer reached goal at ( 2, 6) in 16 turns.
 10    31 x 31      106   0.25472   Racer reached goal at ( 28, 0) in 27 turns.
 11    31 x 31      106   0.24528   Racer reached goal at ( 15, 15) in 26 turns.
 12    50 x 20       50   0.24000   Racer reached goal at ( 49, 10) in 12 turns.
 13   100 x 100    2600   0.01385   Racer reached goal at ( 50, 0) in 36 turns.
 14    79 x 63      242   0.26860   Racer reached goal at ( 3, 42) in 65 turns.
 15    26 x 1        25   0.32000   Racer reached goal at ( 25, 0) in 8 turns.
 16    17 x 1        19   0.52632   Racer reached goal at ( 16, 0) in 10 turns.
 17    50 x 1        55   0.34545   Racer reached goal at ( 23, 0) in 19 turns.
 18    10 x 7        23   0.34783   Racer reached goal at ( 1, 3) in 8 turns.
 19    55 x 55       45   0.17778   Racer reached goal at ( 52, 26) in 8 turns.
 20    50 x 50      200   0.05500   Racer reached goal at ( 47, 46) in 11 turns.
-------------------------------------------------------------------------------------
TOTAL SCORE:              5.40009

新测试用例


1
好吧,这样的事情是非常令人期待的。难题中没有足够的状态来使动态编程不可行。如果输入,我将需要提交一张地图,该地图需要更复杂的搜索策略来解决。
user2357112 2014年

您的赛车手在测试用例上的表现如何?
user2357112 2014年

0.14(14步)
nneonneo 2014年

是花费时间还是移动/目标?如果是移动/目标,它在时间方面的表现如何?
user2357112

1
我认为我在周期预防代码中发现了一个错误。假设对于每个状态,搜索都从状态S到达,则最优路径不能返回到S。看来,如果最优路径确实返回到S,则状态不能在最优路径上(因为我们可以只需删除它所在的循环并获得更短的路径即可),因此我们不在乎是否对该状态获得过高的结果。但是,如果一条最佳路径按此顺序通过状态A和B,但搜索首先找到A,而B仍在堆栈中,则A的结果会因防止循环而中毒。
user2357112 2014年

6

Python 2,确定性的,最佳的

这是我的赛车手。我尚未在基准测试中对其进行过测试(仍在为要安装的Ruby版本和安装程序而感到困惑),但它应在时间限制内以最佳方式解决所有问题。运行它的命令是python whateveryoucallthefile.py。需要-s控制器标志。

# Breadth-first search.
# Future directions: bidirectional search and/or A*.

import operator
import time

acceleration_options = [(dvx, dvy) for dvx in [-1, 0, 1] for dvy in [-1, 0, 1]]

class ImpossibleRaceError(Exception): pass

def read_input(line_source=raw_input):
    # We don't use the target.
    target = int(line_source())

    width, height = map(int, line_source().split())
    input_grid = [line_source() for _ in xrange(height)]

    start = None
    for i in xrange(height):
        for j in xrange(width):
            if input_grid[i][j] == 'S':
                start = i, j
                break
        if start is not None:
            break

    walls = [[cell == '#' for cell in row] for row in input_grid]
    goals = [[cell == '*' for cell in row] for row in input_grid]

    return start, walls, goals

def default_bfs_stop_threshold(walls, goals):
    num_not_wall = sum(sum(map(operator.not_, row)) for row in walls)
    num_goals = sum(sum(row) for row in goals)
    return num_goals * num_not_wall

def bfs(start, walls, goals, stop_threshold=None):
    if stop_threshold is None:
        stop_threshold = default_bfs_stop_threshold(walls, goals)

    # State representation is (x, y, vx, vy)
    x, y = start
    initial_state = (x, y, 0, 0)
    frontier = {initial_state}
    # Visited set is tracked by a map from each state to the last move taken
    # before reaching that state.
    visited = {initial_state: None}

    while len(frontier) < stop_threshold:
        if not frontier:
            raise ImpossibleRaceError

        new_frontier = set()
        for x, y, vx, vy in frontier:
            for dvx, dvy in acceleration_options:
                new_vx, new_vy = vx+dvx, vy+dvy
                new_x, new_y = x+new_vx, y+new_vy
                new_state = (new_x, new_y, new_vx, new_vy)

                if not (0 <= new_x < len(walls) and 0 <= new_y < len(walls[0])):
                    continue
                if walls[new_x][new_y]:
                    continue
                if new_state in visited:
                    continue

                new_frontier.add(new_state)
                visited[new_state] = dvx, dvy

                if goals[new_x][new_y]:
                    return construct_path_from_bfs(new_state, visited)
        frontier = new_frontier

def construct_path_from_bfs(goal_state, best_moves):
    reversed_path = []
    current_state = goal_state
    while best_moves[current_state] is not None:
        move = best_moves[current_state]
        reversed_path.append(move)

        x, y, vx, vy = current_state
        dvx, dvy = move
        old_x, old_y = x-vx, y-vy # not old_vx or old_vy
        old_vx, old_vy = vx-dvx, vy-dvy
        current_state = (old_x, old_y, old_vx, old_vy)
    return reversed_path[::-1]

def main():
    t = time.time()

    start, walls, goals = read_input()
    path = bfs(start, walls, goals, float('inf'))
    for dvx, dvy in path:
        # I wrote the whole program with x pointing down and y pointing right.
        # Whoops. Gotta flip things for the output.
        print dvy, dvx

if __name__ == '__main__':
    main()

在检查了nneonneo的racer之后(但实际上没有测试它,因为我也没有C ++编译器),我发现它似乎对状态空间执行了几乎穷举的搜索,无论目标有多近或有多短已经找到一个路径。我还发现,时间规则意味着使用长而复杂的解决方案构建地图需要漫长而无聊的时间限制。因此,我提交的地图非常简单:

新测试用例

(GitHub无法显示长行。跟踪为*S.......[and so on].....


附加提交内容:Python 2,双向搜索

这是大约两个月前我尝试优化广度优先提交内容时写的一种方法。对于当时存在的测试用例,它没有提供任何改进,所以我没有提交它,但是对于kuroi的新地图,它似乎几乎没有受到内存限制。我仍然希望kuroi的求解器能胜过这一点,但我对它的承受方式很感兴趣。

# Bidirectional search.
# Future directions: A*.

import operator
import time

acceleration_options = [(dvx, dvy) for dvx in [-1, 0, 1] for dvy in [-1, 0, 1]]

class ImpossibleRaceError(Exception): pass

def read_input(line_source=raw_input):
    # We don't use the target.
    target = int(line_source())

    width, height = map(int, line_source().split())
    input_grid = [line_source() for _ in xrange(height)]

    start = None
    for i in xrange(height):
        for j in xrange(width):
            if input_grid[i][j] == 'S':
                start = i, j
                break
        if start is not None:
            break

    walls = [[cell == '#' for cell in row] for row in input_grid]
    goals = [[cell == '*' for cell in row] for row in input_grid]

    return start, walls, goals

def bfs_to_bidi_threshold(walls, goals):
    num_not_wall = sum(sum(map(operator.not_, row)) for row in walls)
    num_goals = sum(sum(row) for row in goals)
    return num_goals * (num_not_wall - num_goals)

class GridBasedGoalContainer(object):
    '''Supports testing whether a state is a goal state with `in`.

    Does not perform bounds checking.'''
    def __init__(self, goal_grid):
        self.goal_grid = goal_grid
    def __contains__(self, state):
        x, y, vx, vy = state
        return self.goal_grid[x][y]

def forward_step(state, acceleration):
    x, y, vx, vy = state
    dvx, dvy = acceleration

    new_vx, new_vy = vx+dvx, vy+dvy
    new_x, new_y = x+new_vx, y+new_vy

    return (new_x, new_y, new_vx, new_vy)

def backward_step(state, acceleration):
    x, y, vx, vy = state
    dvx, dvy = acceleration

    old_x, old_y = x-vx, y-vy
    old_vx, old_vy = vx-dvx, vy-dvy

    return (old_x, old_y, old_vx, old_vy)

def bfs(start, walls, goals):
    x, y = start
    initial_state = (x, y, 0, 0)
    initial_frontier = {initial_state}
    visited = {initial_state: None}

    goal_state, frontier, visited = general_bfs(
        frontier=initial_frontier,
        visited=visited,
        walls=walls,
        goalcontainer=GridBasedGoalContainer(goals),
        stop_threshold=float('inf'),
        step_function=forward_step
    )

    return construct_path_from_bfs(goal_state, visited)

def general_bfs(
        frontier,
        visited,
        walls,
        goalcontainer,
        stop_threshold,
        step_function):

    while len(frontier) <= stop_threshold:
        if not frontier:
            raise ImpossibleRaceError

        new_frontier = set()
        for state in frontier:
            for accel in acceleration_options:
                new_state = new_x, new_y, new_vx, new_vy = \
                        step_function(state, accel)

                if not (0 <= new_x < len(walls) and 0 <= new_y < len(walls[0])):
                    continue
                if walls[new_x][new_y]:
                    continue
                if new_state in visited:
                    continue

                new_frontier.add(new_state)
                visited[new_state] = accel

                if new_state in goalcontainer:
                    return new_state, frontier, visited
        frontier = new_frontier
    return None, frontier, visited

def max_velocity_component(n):
    # It takes a distance of at least 0.5*v*(v+1) to achieve a velocity of
    # v in the x or y direction. That means the map has to be at least
    # 1 + 0.5*v*(v+1) rows or columns long to accomodate such a velocity.
    # Solving for v, we get a velocity cap as follows.
    return int((2*n-1.75)**0.5 - 0.5)

def solver(
        start,
        walls,
        goals,
        mode='bidi'):

    x, y = start
    initial_state = (x, y, 0, 0)
    initial_frontier = {initial_state}
    visited = {initial_state: None}
    if mode == 'bidi':
        stop_threshold = bfs_to_bidi_threshold(walls, goals)
    elif mode == 'bfs':
        stop_threshold = float('inf')
    else:
        raise ValueError('Unsupported search mode: {}'.format(mode))

    goal_state, frontier, visited = general_bfs(
        frontier=initial_frontier,
        visited=visited,
        walls=walls,
        goalcontainer=GridBasedGoalContainer(goals),
        stop_threshold=stop_threshold,
        step_function=forward_step
    )

    if goal_state is not None:
        return construct_path_from_bfs(goal_state, visited)

    # Switching to bidirectional search.

    not_walls_or_goals = []
    goal_list = []
    for x in xrange(len(walls)):
        for y in xrange(len(walls[0])):
            if not walls[x][y] and not goals[x][y]:
                not_walls_or_goals.append((x, y))
            if goals[x][y]:
                goal_list.append((x, y))
    max_vx = max_velocity_component(len(walls))
    max_vy = max_velocity_component(len(walls[0]))
    reverse_visited = {(goal_x, goal_y, goal_x-prev_x, goal_y-prev_y): None
                        for goal_x, goal_y in goal_list
                        for prev_x, prev_y in not_walls_or_goals
                        if abs(goal_x-prev_x) <= max_vx
                        and abs(goal_y - prev_y) <= max_vy}
    reverse_frontier = set(reverse_visited)
    while goal_state is None:
        goal_state, reverse_frontier, reverse_visited = general_bfs(
            frontier=reverse_frontier,
            visited=reverse_visited,
            walls=walls,
            goalcontainer=frontier,
            stop_threshold=len(frontier),
            step_function=backward_step
        )
        if goal_state is not None:
            break
        goal_state, frontier, visited = general_bfs(
            frontier=frontier,
            visited=visited,
            walls=walls,
            goalcontainer=reverse_frontier,
            stop_threshold=len(reverse_frontier),
            step_function=forward_step
        )
    forward_path = construct_path_from_bfs(goal_state, visited)
    backward_path = construct_path_from_bfs(goal_state,
                                            reverse_visited,
                                            step_function=forward_step)
    return forward_path + backward_path[::-1]

def construct_path_from_bfs(goal_state,
                            best_moves,
                            step_function=backward_step):
    reversed_path = []
    current_state = goal_state
    while best_moves[current_state] is not None:
        move = best_moves[current_state]
        reversed_path.append(move)
        current_state = step_function(current_state, move)
    return reversed_path[::-1]

def main():
    start, walls, goals = read_input()
    t = time.time()
    path = solver(start, walls, goals)
    for dvx, dvy in path:
        # I wrote the whole program with x pointing down and y pointing right.
        # Whoops. Gotta flip things for the output.
        print dvy, dvx

if __name__ == '__main__':
    main()

这有时在情况12和13上会失败。不知道为什么,因为错误消息有些...不友好
2014年

@Ray我也收到错误消息,但是无论如何我总是得到结果。我认为这可能是我的控制器中的某些内容,因为尽管它已经完成,但看起来控制器似乎要杀死赛车程序。
Martin Ender 2014年

@ m.buettner我找到了原因,添加-s那就可以了。

@Ray哦,是的,我正在这样做。尽管结果已经存在,但是当控制器尝试终止进程时,我仍然在轨道13和14上出现错误。我想我应该调查一下,但是不影响得分,所以我还没有打扰。
Martin Ender 2014年

不幸的是,我不得不添加另一条规则。在此挑战中,内存似乎比时间更受限制,因此我不得不对内存消耗进行限制。赛车使用的内存超过1GB的任何运行都会被中止,其效果与超过时间限制相同。对于当前的曲目集,您的分数不受此更改的影响。(我认为您将在左右决胜局上达到该极限n = 400。)如果您进行了任何优化,请让我知道,以便我重新运行测试。
Martin Ender 2014年

3

Python 3:6.49643(最佳,BFS)

对于旧的20个案例基准文件,它的得分为5.35643。@nneonneo的解决方案不是最优的,因为它得到了5.4。可能有一些错误。

该解决方案使用BFS搜索图,每个搜索状态的形式为(x,y,dx,dy)。然后,我使用地图将状态映射到距离。在最坏的情况下,时间和空间复杂度为O(n ^ 2 m ^ 2)。由于速度不会太高或赛车会崩溃,因此这种情况很少发生。实际上,在我的机器上花费了3秒来完成所有22个测试用例。

from collections import namedtuple, deque
import itertools

Field = namedtuple('Map', 'n m grids')

class Grid:
    WALL = '#'
    EMPTY = '.'
    START = 'S'
    END = '*'

def read_nums():
    return list(map(int, input().split()))

def read_field():
    m, n = read_nums()
    return Field(n, m, [input() for i in range(n)])

def find_start_pos(field):
    return next((i, j)
        for i in range(field.n) for j in range(field.m)
        if field.grids[i][j] == Grid.START)

def can_go(field, i, j):
    return 0 <= i < field.n and 0 <= j < field.m and field.grids[i][j] != Grid.WALL

def trace_path(start, end, prev):
    if end == start:
        return
    end, step = prev[end]
    yield from trace_path(start, end, prev)
    yield step

def solve(max_turns, field, time):
    i0, j0 = find_start_pos(field)
    p0 = i0, j0, 0, 0
    prev = {}
    que = deque([p0])
    directions = list(itertools.product((-1, 0, 1), (-1, 0, 1)))

    while que:
        p = i, j, vi, vj = que.popleft()
        for dvi, dvj in directions:
            vi1, vj1 = vi + dvi, vj + dvj
            i1, j1 = i + vi1, j + vj1
            if not can_go(field, i1, j1):
                continue
            p1 = i1, j1, vi1, vj1
            if p1 in prev:
                continue
            que.append(p1)
            prev[p1] = p, (dvi, dvj)
            if field.grids[i1][j1] == Grid.END:
                return trace_path(p0, p1, prev)
    return []

def main():
    for dvy, dvx in solve(int(input()), read_field(), float(input())):
        print(dvx, dvy)

main()

#结果

± % time ruby controller.rb benchmark.txt python ../mybfs.py                                                                                                                                                                             !9349
["benchmark.txt", "python", "../mybfs.py"]

Running 'python ../mybfs.py' against benchmark.txt

 No.       Size     Target   Score     Details
-------------------------------------------------------------------------------------
  1       37 x 1        36   0.22222   Racer reached goal at ( 36, 0) in 8 turns.
  2       38 x 1        37   0.24324   Racer reached goal at ( 37, 0) in 9 turns.
  3       33 x 1        32   0.25000   Racer reached goal at ( 32, 0) in 8 turns.
  4       10 x 10       10   0.40000   Racer reached goal at ( 7, 7) in 4 turns.
  5        9 x 6         8   0.37500   Racer reached goal at ( 6, 0) in 3 turns.
  6       15 x 7        16   0.37500   Racer reached goal at ( 12, 4) in 6 turns.
  7       17 x 8        16   0.31250   Racer reached goal at ( 14, 0) in 5 turns.
  8       19 x 13       18   0.27778   Racer reached goal at ( 0, 11) in 5 turns.
  9       60 x 10      107   0.14953   Racer reached goal at ( 0, 6) in 16 turns.
 10       31 x 31      106   0.23585   Racer reached goal at ( 27, 0) in 25 turns.
 11       31 x 31      106   0.24528   Racer reached goal at ( 15, 15) in 26 turns.
 12       50 x 20       50   0.24000   Racer reached goal at ( 49, 10) in 12 turns.
 13      100 x 100    2600   0.01385   Racer reached goal at ( 50, 0) in 36 turns.
 14       79 x 63      242   0.24380   Racer reached goal at ( 3, 42) in 59 turns.
 15       26 x 1        25   0.32000   Racer reached goal at ( 25, 0) in 8 turns.
 16       17 x 1        19   0.52632   Racer reached goal at ( 16, 0) in 10 turns.
 17       50 x 1        55   0.34545   Racer reached goal at ( 23, 0) in 19 turns.
 18       10 x 7        23   0.34783   Racer reached goal at ( 1, 3) in 8 turns.
 19       55 x 55       45   0.17778   Racer reached goal at ( 50, 26) in 8 turns.
 20      101 x 100     100   0.14000   Racer reached goal at ( 99, 99) in 14 turns.
 21   100000 x 1         1   1.00000   Racer reached goal at ( 0, 0) in 1 turns.
 22       50 x 50      200   0.05500   Racer reached goal at ( 47, 46) in 11 turns.
-------------------------------------------------------------------------------------
TOTAL SCORE:                 6.49643

ruby controller.rb benchmark.txt python ../mybfs.py  3.06s user 0.06s system 99% cpu 3.146 total

是的,根据user2357112的评论,nneonneo的周期预防中存在一个错误。据我所知,速度受制于在方形网格上O(√n)实现的速度O(n³)(我想与其他网格相同)。我将添加一个决胜局,与今天晚些时候user2357112的提交相比,为您的提交打分。
Martin Ender 2014年

顺便说一句,您打算添加另一个测试用例吗?
Martin Ender 2014年

@ m.buettner不,我对这款游戏没有足够的理解。所以我的测试用例不会很有趣。

不幸的是,我不得不添加另一条规则。在此挑战中,内存似乎比时间更受限制,因此我不得不对内存消耗进行限制。赛车使用的内存超过1GB的任何运行都将被中止,其效果与超过时间限制相同。使用此规则,您的提交是第一个超过大小限制决胜局的限制的n=270,这就是为什么您现在落后于其他两个“最佳”提交的原因。话虽这么说,您的提交也是三者中最慢的,因此无论如何,只要有更大的决胜局,提交本来就是第三。
Martin Ender

如果您进行了任何优化,请告诉我,以便我重新运行测试。
Martin Ender

1

RandomRacer,〜40.0(平均10次)

这并不是说该机器人从未完成过曲目,但肯定比十次尝试中的一次要少得多。(大约每20到30个模拟我得到一个最差的分数)。

这主要是作为基准案例,并为赛车手演示可能的(Ruby)实现:

# Parse initial input
target = gets.to_i
size = gets.split.map(&:to_i)
track = []
size[1].times do
    track.push gets
end
time_budget = gets.to_f

# Find start position
start_y = track.find_index { |row| row['S'] }
start_x = track[start_y].index 'S'

position = [start_x, start_y]
velocity = [0, 0]

while true
    x = rand(3) - 1
    y = rand(3) - 1
    puts [x,y].join ' '
    $stdout.flush

    first_line = gets
    break if !first_line || first_line.chomp.empty?

    position = first_line.split.map(&:to_i)
    velocity = gets.split.map(&:to_i)
    time_budget = gets.to_f
end

运行它

ruby controller.rb benchmark.txt ruby randomracer.rb

1

随机赛车2.0,〜31

好吧,这不会超过发布的最佳解算器,但是对随机赛车来说是一个小改进。主要区别在于,该赛车手将只考虑随机走没有墙壁的地方,除非它耗尽有效的移动位置,并且如果可以移动到可以转弯的目标,它就会移动。除非没有其他可用的动作(不可能,但可能),否则它也不会为了保持原样而移动。

用Java实现,用java8编译,但是Java 6应该可以。没有命令行参数。有相当不错的clusterfuck层次结构,所以我认为我在做Java。

import java.util.Scanner;
import java.util.Random;
import java.util.ArrayList;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

public class VectorRacing   {
    private static Scanner in = new Scanner(System.in);
    private static Random rand = new Random();
    private static Track track;
    private static Racer racer;
    private static int target;
    private static double time;
    public static void main(String[] args)  {
        init();
        main_loop();
    }
    private static void main_loop() {
        Scanner linescan;
        String line;
        int count = 0,
            x, y, u, v;

        while(!racer.lost() && !racer.won() && count < target)  {
            Direction d = racer.think();
            racer.move(d);
            count++;
            System.out.println(d);

            line = in.nextLine();
            if(line.equals("")) {
                break;
            }
            linescan = new Scanner(line);
            x = linescan.nextInt();
            y = linescan.nextInt();
            linescan = new Scanner(in.nextLine());
            u = linescan.nextInt();
            v = linescan.nextInt();
            time = Double.parseDouble(in.nextLine());

            assert x == racer.location.x;
            assert y == racer.location.y;
            assert u == racer.direction.x;
            assert v == racer.direction.y;
        }
    }
    private static void init()  {
        target = Integer.parseInt(in.nextLine());
        int width = in.nextInt();
        int height = Integer.parseInt(in.nextLine().trim());
        String[] ascii = new String[height];
        for(int i = 0; i < height; i++) {
            ascii[i] = in.nextLine();
        }
        time = Double.parseDouble(in.nextLine());
        track = new Track(width, height, ascii);
        for(int y = 0; y < ascii.length; y++)   {
            int x = ascii[y].indexOf("S");
            if( x != -1)    {
                racer = new RandomRacer(track, new Location(x, y));
                break;
            }
        }
    }

    public static class RandomRacer extends Racer   {
        public RandomRacer(Track t, Location l) {
            super(t, l);
        }
        public Direction think()    {
            ArrayList<Pair<Location, Direction> > possible = this.getLocationsCanMoveTo();
            if(possible.size() == 0)    {
                return Direction.NONE;
            }
            Pair<Location, Direction> ret = null;
            do  {
                ret = possible.get(rand.nextInt(possible.size()));
            }   while(possible.size() != 1 && ret.a.equals(this.location));
            return ret.b;
        }
    }

    // Base things
    public enum Direction   {
        NORTH("0 -1"), SOUTH("0 1"), EAST("1 0"), WEST("-1 0"), NONE("0 0"),
        NORTH_EAST("1 -1"), NORTH_WEST("-1 -1"), SOUTH_EAST("1 1"), SOUTH_WEST("-1 1");

        private final String d;
        private Direction(String d) {this.d = d;}
        public String toString()    {return d;}
    }
    public enum Cell    {
        WALL('#'), GOAL('*'), ROAD('.'), OUT_OF_BOUNDS('?');

        private final char c;
        private Cell(char c)    {this.c = c;}
        public String toString()    {return "" + c;}
    }

    public static class Track   {
        private Cell[][] track;
        private int target;
        private double time;
        public Track(int width, int height, String[] ascii) {
            this.track = new Cell[width][height];
            for(int y = 0; y < height; y++) {
                for(int x = 0; x < width; x++)  {
                    switch(ascii[y].charAt(x))  {
                        case '#':   this.track[x][y] = Cell.WALL; break;
                        case '*':   this.track[x][y] = Cell.GOAL; break;
                        case '.':
                        case 'S':   this.track[x][y] = Cell.ROAD; break;
                        default:    System.exit(-1);
                    }
                }
            }
        }
        public Cell atLocation(Location loc)    {
            if(loc.x < 0 || loc.x >= track.length || loc.y < 0 || loc.y >= track[0].length) return Cell.OUT_OF_BOUNDS;
            return track[loc.x][loc.y];
        }

        public String toString()    {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            PrintStream ps = new PrintStream(bos);
            for(int y = 0; y < track[0].length; y++)    {
                for(int x = 0; x < track.length; x++)   {
                    ps.append(track[x][y].toString());
                }
                ps.append('\n');
            }
            String ret = bos.toString();
            ps.close();
            return ret;
        }
    }

    public static abstract class Racer  {
        protected Velocity tdir;
        protected Location tloc;
        protected Track track;
        public Velocity direction;
        public Location location;

        public Racer(Track track, Location start)   {
            this.track = track;
            direction = new Velocity(0, 0);
            location = start;
        }
        public boolean canMove() throws GoHereDammitException {return canMove(Direction.NONE);}
        public boolean canMove(Direction d) throws GoHereDammitException    {
            tdir = new Velocity(direction);
            tloc = new Location(location);
            tdir.add(d);
            tloc.move(tdir);
            Cell at = track.atLocation(tloc);
            if(at == Cell.GOAL) {
                throw new GoHereDammitException();
            }
            return at == Cell.ROAD;
        }
        public ArrayList<Pair<Location, Direction> > getLocationsCanMoveTo()    {
            ArrayList<Pair<Location, Direction> > ret = new ArrayList<Pair<Location, Direction> >(9);
            for(Direction d: Direction.values())    {
                try {
                    if(this.canMove(d)) {
                        ret.add(new Pair<Location, Direction>(tloc, d));
                    }
                }   catch(GoHereDammitException e)  {
                    ret.clear();
                    ret.add(new Pair<Location, Direction>(tloc, d));
                    return ret;
                }
            }
            return ret;
        }
        public void move()  {move(Direction.NONE);}
        public void move(Direction d)   {
            direction.add(d);
            location.move(direction);
        }
        public boolean won()    {
            return track.atLocation(location) == Cell.GOAL;
        }
        public boolean lost()   {
            return track.atLocation(location) == Cell.WALL || track.atLocation(location) == Cell.OUT_OF_BOUNDS;
        }
        public String toString()    {
            return location + ", " + direction;
        }
        public abstract Direction think();

        public class GoHereDammitException extends Exception    {
            public GoHereDammitException()  {}
        }
    }

    public static class Location extends Point  {
        public Location(int x, int y)   {
            super(x, y);
        }
        public Location(Location l) {
            super(l);
        }
        public void move(Velocity d)    {
            this.x += d.x;
            this.y += d.y;
        }
    }

    public static class Velocity extends Point  {
        public Velocity(int x, int y)   {
            super(x, y);
        }
        public Velocity(Velocity v) {
            super(v);
        }
        public void add(Direction d)    {
            if(d == Direction.NONE) return;
            if(d == Direction.NORTH || d == Direction.NORTH_EAST || d == Direction.NORTH_WEST)  this.y--;
            if(d == Direction.SOUTH || d == Direction.SOUTH_EAST || d == Direction.SOUTH_WEST)  this.y++;
            if(d == Direction.EAST || d == Direction.NORTH_EAST || d == Direction.SOUTH_EAST)   this.x++;
            if(d == Direction.WEST || d == Direction.NORTH_WEST || d == Direction.SOUTH_WEST)   this.x--;
        }
    }

    public static class Point   {
        protected int x, y;
        protected Point(int x, int y)   {
            this.x = x;
            this.y = y;
        }
        protected Point(Point c)    {
            this.x = c.x;
            this.y = c.y;
        }
        public int getX()   {return x;}
        public int getY()   {return y;}
        public String toString()    {return "(" + x + ", " + y + ")";}
        public boolean equals(Point p)  {
            return this.x == p.x && this.y == p.y;
        }
    }

    public static class Pair<T, U>  {
        public T a;
        public U b;
        public Pair(T t, U u)   {
            a=t;b=u;
        }
    }
}

结果(我见过的最好的情况)

Running 'java VectorRacing' against ruby-runner/benchmark.txt

 No.    Size     Target   Score     Details
-------------------------------------------------------------------------------------
  1    37 x 1        36   0.38889   Racer reached goal at ( 36, 0) in 14 turns.
  2    38 x 1        37   0.54054   Racer reached goal at ( 37, 0) in 20 turns.
  3    33 x 1        32   0.62500   Racer reached goal at ( 32, 0) in 20 turns.
  4    10 x 10       10   0.40000   Racer reached goal at ( 9, 8) in 4 turns.
  5     9 x 6         8   0.75000   Racer reached goal at ( 6, 2) in 6 turns.
  6    15 x 7        16   2.00000   Racer did not reach the goal within 16 turns.
  7    17 x 8        16   2.00000   Racer hit a wall at position ( 8, 2).
  8    19 x 13       18   0.44444   Racer reached goal at ( 16, 2) in 8 turns.
  9    60 x 10      107   0.65421   Racer reached goal at ( 0, 6) in 70 turns.
 10    31 x 31      106   2.00000   Racer hit a wall at position ( 25, 9).
 11    31 x 31      106   2.00000   Racer hit a wall at position ( 8, 1).
 12    50 x 20       50   2.00000   Racer hit a wall at position ( 27, 14).
 13   100 x 100    2600   2.00000   Racer went out of bounds at position ( 105, 99).
 14    79 x 63      242   2.00000   Racer went out of bounds at position (-2, 26).
 15    26 x 1        25   0.32000   Racer reached goal at ( 25, 0) in 8 turns.
 16    17 x 1        19   2.00000   Racer went out of bounds at position (-2, 0).
 17    50 x 1        55   2.00000   Racer went out of bounds at position ( 53, 0).
 18    10 x 7        23   2.00000   Racer went out of bounds at position ( 10, 2).
 19    55 x 55       45   0.33333   Racer reached goal at ( 4, 49) in 15 turns.
 20    50 x 50      200   2.00000   Racer hit a wall at position ( 14, 7).
-------------------------------------------------------------------------------------
TOTAL SCORE:             26.45641

是的,我让它运行了,尽管.class由于某种原因我不得不从文件所在的目录(而不是控制器所在的目录)中运行它。如果您决定添加一个测试用例,请给我Ping(带有注释),以便将其添加到基准测试中。您的10次跑步成绩约为33分(请参见排行榜),但是随着基准测试中添加的每条新测试成绩,这种情况可能会发生变化。
Martin Ender 2014年

啊,它也可以从另一个目录运行。对于那些不熟悉Java命令行的人:java -cp path/to/class/file VectorRacing
Martin Ender 2014年

嗯,是的,我做了很多课(准确地说是13门课)。我总是从我的classes目录运行您的脚本,所以我实际上没有对此进行测试。我可能会做一个测试用例,但我想我会尝试制作一个并非随机的赛车手。
pseudonym117 2014年

当然。如果这样做,请将其添加为单独的答案。(并且随时为每个测试例添加一个测试用例。)
Martin Ender 2014年

不幸的是,我不得不添加另一条规则。在此挑战中,内存似乎比时间更受限制,因此我不得不对内存消耗进行限制。赛车使用的内存超过1GB的任何运行都会被中止,其效果与超过时间限制相同。对于当前的曲目集,您的乐谱不受此更改的影响(而且可能永远不会)。
Martin Ender 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.