C ++ 11-6.66109
또 다른 광범위한 첫 번째 검색 구현은 최적화되었습니다.
-s 옵션 으로 실행해야합니다 .
입력이 전혀 위생 처리되지 않으므로 잘못된 트랙으로 인해 호박이 될 수 있습니다.
Microsoft Visual C ++ 2013, 기본 / O2 플래그를 사용하여 릴리스 빌드 (속도 최적화)로 테스트했습니다.
g ++ 및 Microsoft IDE를 사용하여 올바르게 빌드합니다.
내 베어 본 메모리 할당자는 쓰레기이므로 다른 STL 구현에서 작동하지 않을 것입니다 unordered_set
!
#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
공연
그 엉뚱한 C ++ 언어는 성냥개비를 움직이기 위해 농구대를 뛰어 넘을 수있는 요령을 가지고 있습니다. 그러나 비교적 빠르고 메모리 효율적인 코드를 생성 할 수 있습니다.
해싱
여기서 핵심은 노드에 좋은 해시 테이블을 제공하는 것입니다. 이것이 실행 속도의 주요 요소입니다. (GNU와 Microsoft)
두 가지 구현으로 unordered_set
30 %의 실행 속도 차이가 나왔습니다 (GNU, yay!).
그 차이는 실제로 놀랍지 않습니다 unordered_set
. 코드의 트럭로드가 숨겨져있는 것 입니다.
호기심으로 해시 테이블의 최종 상태에 대한 통계를 작성했습니다.
두 알고리즘 모두 거의 동일한 버킷 / 요소 비율로
끝나지만 재 파티셔닝은 다양합니다. 290x290 타이 브레이커의 경우 GNU는 비어 있지 않은 버킷 당 평균 1.5 개의 요소를 가져오고 Microsoft는 5.8 (!)입니다.
내 해싱 함수가 Microsoft에서 잘 무작위 화하지 않은 것 같습니다 ... 레드몬드의 사람들이 실제로 STL을 벤치마킹했는지 또는 내 유스 케이스가 GNU 구현을 선호하는지 궁금합니다 ...
물론, 내 해싱 함수는 최적의 위치에 없습니다. 다중 시프트 / mul을 기반으로 일반적인 정수 믹싱을 사용할 수 있었지만 해시 효율적인 함수를 계산하는 데 시간이 걸립니다.
해시 테이블 쿼리 수가 삽입 수에 비해 매우 높은 것으로 보입니다. 예를 들어 290x290 타이 브레이커에는 2270 만 건의 쿼리에 약 360 만 건의 삽입이 있습니다.
이와 관련하여 차선책이지만 빠른 해싱은 더 나은 성능을 제공합니다.
메모리 할당
효율적인 메모리 할당자를 제공하는 것이 두 번째입니다. 성능이 약 30 % 향상되었습니다. 추가 된 쓰레기 코드의 가치가 있는지 여부는 논쟁의 여지가 있습니다 :).
현재 버전은 노드 당 40-55 바이트를 사용합니다.
기능 데이터에는 노드에 24 바이트가 필요합니다 (4 개의 좌표 및 2 개의 포인터).
미친 100.000 줄 테스트 사례로 인해 좌표는 4 바이트 단어로 저장해야합니다. 그렇지 않으면 short (최대 좌표 값 32767)를 사용하여 8 바이트를 얻을 수 있습니다. 나머지 바이트는 대부분 순서가없는 세트의 해시 테이블에서 사용됩니다. 이는 데이터 처리가 실제로 "유용한"페이로드보다 약간 더 많이 소비 함을 의미합니다.
그리고 승자는...
내 PC에서 Win7에서는 타이 브레이커 (케이스 23, 290x290)가 약 2.2 초 만에 최악의 버전 (예 : Microsoft 컴파일)으로 약 185Mb의 메모리 소비로 해결됩니다.
비교를 위해 현재 리더 (user2357112의 Python 코드)는 30 초 이상 걸리며 약 780Mb를 소비합니다.
컨트롤러 문제
생명을 구하기 위해 루비로 코드를 작성할 수 있을지 잘 모르겠습니다.
그러나 컨트롤러 코드에서 두 가지 문제를 발견하고 해킹했습니다.
1)지도 읽기 track.rb
루비 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 초 소요, 600Mb 피크)!
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";
?>