सी ++ 11 - लगभग काम :)
इस लेख को पढ़ने के बाद , मैंने उस आदमी से ज्ञान के बिट्स एकत्र किए, जिन्होंने स्पष्ट रूप से एक वर्ग जाली पर आत्म-परहेज पथों की गिनती की कम जटिल समस्या पर 25 साल तक काम किया।
#include <cassert>
#include <ctime>
#include <sstream>
#include <vector>
#include <algorithm> // sort
using namespace std;
// theroretical max snake lenght (the code would need a few decades to process that value)
#define MAX_LENGTH ((int)(1+8*sizeof(unsigned)))
#ifndef _MSC_VER
#ifndef QT_DEBUG // using Qt IDE for g++ builds
#define NDEBUG
#endif
#endif
#ifdef NDEBUG
inline void tprintf(const char *, ...){}
#else
#define tprintf printf
#endif
void panic(const char * msg)
{
printf("PANIC: %s\n", msg);
exit(-1);
}
// ============================================================================
// fast bit reversal
// ============================================================================
unsigned bit_reverse(register unsigned x, unsigned len)
{
x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
return((x >> 16) | (x << 16)) >> (32-len);
}
// ============================================================================
// 2D geometry (restricted to integer coordinates and right angle rotations)
// ============================================================================
// points using integer- or float-valued coordinates
template<typename T>struct tTypedPoint;
typedef int tCoord;
typedef double tFloatCoord;
typedef tTypedPoint<tCoord> tPoint;
typedef tTypedPoint<tFloatCoord> tFloatPoint;
template <typename T>
struct tTypedPoint {
T x, y;
template<typename U> tTypedPoint(const tTypedPoint<U>& from) : x((T)from.x), y((T)from.y) {} // conversion constructor
tTypedPoint() {}
tTypedPoint(T x, T y) : x(x), y(y) {}
tTypedPoint(const tTypedPoint& p) { *this = p; }
tTypedPoint operator+ (const tTypedPoint & p) const { return{ x + p.x, y + p.y }; }
tTypedPoint operator- (const tTypedPoint & p) const { return{ x - p.x, y - p.y }; }
tTypedPoint operator* (T scalar) const { return{ x * scalar, y * scalar }; }
tTypedPoint operator/ (T scalar) const { return{ x / scalar, y / scalar }; }
bool operator== (const tTypedPoint & p) const { return x == p.x && y == p.y; }
bool operator!= (const tTypedPoint & p) const { return !operator==(p); }
T dot(const tTypedPoint &p) const { return x*p.x + y * p.y; } // dot product
int cross(const tTypedPoint &p) const { return x*p.y - y * p.x; } // z component of cross product
T norm2(void) const { return dot(*this); }
// works only with direction = 1 (90° right) or -1 (90° left)
tTypedPoint rotate(int direction) const { return{ direction * y, -direction * x }; }
tTypedPoint rotate(int direction, const tTypedPoint & center) const { return (*this - center).rotate(direction) + center; }
// used to compute length of a ragdoll snake segment
unsigned manhattan_distance(const tPoint & p) const { return abs(x-p.x) + abs(y-p.y); }
};
struct tArc {
tPoint c; // circle center
tFloatPoint middle_vector; // vector splitting the arc in half
tCoord middle_vector_norm2; // precomputed for speed
tFloatCoord dp_limit;
tArc() {}
tArc(tPoint c, tPoint p, int direction) : c(c)
{
tPoint r = p - c;
tPoint end = r.rotate(direction);
middle_vector = ((tFloatPoint)(r+end)) / sqrt(2); // works only for +-90° rotations. The vector should be normalized to circle radius in the general case
middle_vector_norm2 = r.norm2();
dp_limit = ((tFloatPoint)r).dot(middle_vector);
assert (middle_vector == tPoint(0, 0) || dp_limit != 0);
}
bool contains(tFloatPoint p) // p must be a point on the circle
{
if ((p-c).dot(middle_vector) >= dp_limit)
{
return true;
}
else return false;
}
};
// returns the point of line (p1 p2) that is closest to c
// handles degenerate case p1 = p2
tPoint line_closest_point(tPoint p1, tPoint p2, tPoint c)
{
if (p1 == p2) return{ p1.x, p1.y };
tPoint p1p2 = p2 - p1;
tPoint p1c = c - p1;
tPoint disp = (p1p2 * p1c.dot(p1p2)) / p1p2.norm2();
return p1 + disp;
}
// variant of closest point computation that checks if the projection falls within the segment
bool closest_point_within(tPoint p1, tPoint p2, tPoint c, tPoint & res)
{
tPoint p1p2 = p2 - p1;
tPoint p1c = c - p1;
tCoord nk = p1c.dot(p1p2);
if (nk <= 0) return false;
tCoord n = p1p2.norm2();
if (nk >= n) return false;
res = p1 + p1p2 * (nk / n);
return true;
}
// tests intersection of line (p1 p2) with an arc
bool inter_seg_arc(tPoint p1, tPoint p2, tArc arc)
{
tPoint m = line_closest_point(p1, p2, arc.c);
tCoord r2 = arc.middle_vector_norm2;
tPoint cm = m - arc.c;
tCoord h2 = cm.norm2();
if (r2 < h2) return false; // no circle intersection
tPoint p1p2 = p2 - p1;
tCoord n2p1p2 = p1p2.norm2();
// works because by construction p is on (p1 p2)
auto in_segment = [&](const tFloatPoint & p) -> bool
{
tFloatCoord nk = p1p2.dot(p - p1);
return nk >= 0 && nk <= n2p1p2;
};
if (r2 == h2) return arc.contains(m) && in_segment(m); // tangent intersection
//if (p1 == p2) return false; // degenerate segment located inside circle
assert(p1 != p2);
tFloatPoint u = (tFloatPoint)p1p2 * sqrt((r2-h2)/n2p1p2); // displacement on (p1 p2) from m to one intersection point
tFloatPoint i1 = m + u;
if (arc.contains(i1) && in_segment(i1)) return true;
tFloatPoint i2 = m - u;
return arc.contains(i2) && in_segment(i2);
}
// ============================================================================
// compact storage of a configuration (64 bits)
// ============================================================================
struct sConfiguration {
unsigned partition;
unsigned folding;
explicit sConfiguration() {}
sConfiguration(unsigned partition, unsigned folding) : partition(partition), folding(folding) {}
// add a bend
sConfiguration bend(unsigned joint, int rotation) const
{
sConfiguration res;
unsigned joint_mask = 1 << joint;
res.partition = partition | joint_mask;
res.folding = folding;
if (rotation == -1) res.folding |= joint_mask;
return res;
}
// textual representation
string text(unsigned length) const
{
ostringstream res;
unsigned f = folding;
unsigned p = partition;
int segment_len = 1;
int direction = 1;
for (size_t i = 1; i != length; i++)
{
if (p & 1)
{
res << segment_len * direction << ',';
direction = (f & 1) ? -1 : 1;
segment_len = 1;
}
else segment_len++;
p >>= 1;
f >>= 1;
}
res << segment_len * direction;
return res.str();
}
// for final sorting
bool operator< (const sConfiguration& c) const
{
return (partition == c.partition) ? folding < c.folding : partition < c.partition;
}
};
// ============================================================================
// static snake geometry checking grid
// ============================================================================
typedef unsigned tConfId;
class tGrid {
vector<tConfId>point;
tConfId current;
size_t snake_len;
int min_x, max_x, min_y, max_y;
size_t x_size, y_size;
size_t raw_index(const tPoint& p) { bound_check(p); return (p.x - min_x) + (p.y - min_y) * x_size; }
void bound_check(const tPoint& p) const { assert(p.x >= min_x && p.x <= max_x && p.y >= min_y && p.y <= max_y); }
void set(const tPoint& p)
{
point[raw_index(p)] = current;
}
bool check(const tPoint& p)
{
if (point[raw_index(p)] == current) return false;
set(p);
return true;
}
public:
tGrid(int len) : current(-1), snake_len(len)
{
min_x = -max(len - 3, 0);
max_x = max(len - 0, 0);
min_y = -max(len - 1, 0);
max_y = max(len - 4, 0);
x_size = max_x - min_x + 1;
y_size = max_y - min_y + 1;
point.assign(x_size * y_size, current);
}
bool check(sConfiguration c)
{
current++;
tPoint d(1, 0);
tPoint p(0, 0);
set(p);
for (size_t i = 1; i != snake_len; i++)
{
p = p + d;
if (!check(p)) return false;
if (c.partition & 1) d = d.rotate((c.folding & 1) ? -1 : 1);
c.folding >>= 1;
c.partition >>= 1;
}
return check(p + d);
}
};
// ============================================================================
// snake ragdoll
// ============================================================================
class tSnakeDoll {
vector<tPoint>point; // snake geometry. Head at (0,0) pointing right
// allows to check for collision with the area swept by a rotating segment
struct rotatedSegment {
struct segment { tPoint a, b; };
tPoint org;
segment end;
tArc arc[3];
bool extra_arc; // see if third arc is needed
// empty constructor to avoid wasting time in vector initializations
rotatedSegment(){}
// copy constructor is mandatory for vectors *but* shall never be used, since we carefully pre-allocate vector memory
rotatedSegment(const rotatedSegment &){ assert(!"rotatedSegment should never have been copy-constructed"); }
// rotate a segment
rotatedSegment(tPoint pivot, int rotation, tPoint o1, tPoint o2)
{
arc[0] = tArc(pivot, o1, rotation);
arc[1] = tArc(pivot, o2, rotation);
tPoint middle;
extra_arc = closest_point_within(o1, o2, pivot, middle);
if (extra_arc) arc[2] = tArc(pivot, middle, rotation);
org = o1;
end = { o1.rotate(rotation, pivot), o2.rotate(rotation, pivot) };
}
// check if a segment intersects the area swept during rotation
bool intersects(tPoint p1, tPoint p2) const
{
auto print_arc = [&](int a) { tprintf("(%d,%d)(%d,%d) -> %d (%d,%d)[%f,%f]", p1.x, p1.y, p2.x, p2.y, a, arc[a].c.x, arc[a].c.y, arc[a].middle_vector.x, arc[a].middle_vector.y); };
if (p1 == org) return false; // pivot is the only point allowed to intersect
if (inter_seg_arc(p1, p2, arc[0]))
{
print_arc(0);
return true;
}
if (inter_seg_arc(p1, p2, arc[1]))
{
print_arc(1);
return true;
}
if (extra_arc && inter_seg_arc(p1, p2, arc[2]))
{
print_arc(2);
return true;
}
return false;
}
};
public:
sConfiguration configuration;
bool valid;
// holds results of a folding attempt
class snakeFolding {
friend class tSnakeDoll;
vector<rotatedSegment>segment; // rotated segments
unsigned joint;
int direction;
size_t i_rotate;
// pre-allocate rotated segments
void reserve(size_t length)
{
segment.clear(); // this supposedly does not release vector storage memory
segment.reserve(length);
}
// handle one segment rotation
void rotate(tPoint pivot, int rotation, tPoint o1, tPoint o2)
{
segment.emplace_back(pivot, rotation, o1, o2);
}
public:
// nothing done during construction
snakeFolding(unsigned size)
{
segment.reserve (size);
}
};
// empty default constructor to avoid wasting time in array/vector inits
tSnakeDoll() {}
// constructs ragdoll from compressed configuration
tSnakeDoll(unsigned size, unsigned generator, unsigned folding) : point(size), configuration(generator,folding)
{
tPoint direction(1, 0);
tPoint current = { 0, 0 };
size_t p = 0;
point[p++] = current;
for (size_t i = 1; i != size; i++)
{
current = current + direction;
if (generator & 1)
{
direction.rotate((folding & 1) ? -1 : 1);
point[p++] = current;
}
folding >>= 1;
generator >>= 1;
}
point[p++] = current;
point.resize(p);
}
// constructs the initial flat snake
tSnakeDoll(int size) : point(2), configuration(0,0), valid(true)
{
point[0] = { 0, 0 };
point[1] = { size, 0 };
}
// constructs a new folding with one added rotation
tSnakeDoll(const tSnakeDoll & parent, unsigned joint, int rotation, tGrid& grid)
{
// update configuration
configuration = parent.configuration.bend(joint, rotation);
// locate folding point
unsigned p_joint = joint+1;
tPoint pivot;
size_t i_rotate = 0;
for (size_t i = 1; i != parent.point.size(); i++)
{
unsigned len = parent.point[i].manhattan_distance(parent.point[i - 1]);
if (len > p_joint)
{
pivot = parent.point[i - 1] + ((parent.point[i] - parent.point[i - 1]) / len) * p_joint;
i_rotate = i;
break;
}
else p_joint -= len;
}
// rotate around joint
snakeFolding fold (parent.point.size() - i_rotate);
fold.rotate(pivot, rotation, pivot, parent.point[i_rotate]);
for (size_t i = i_rotate + 1; i != parent.point.size(); i++) fold.rotate(pivot, rotation, parent.point[i - 1], parent.point[i]);
// copy unmoved points
point.resize(parent.point.size()+1);
size_t i;
for (i = 0; i != i_rotate; i++) point[i] = parent.point[i];
// copy rotated points
for (; i != parent.point.size(); i++) point[i] = fold.segment[i - i_rotate].end.a;
point[i] = fold.segment[i - 1 - i_rotate].end.b;
// static configuration check
valid = grid.check (configuration);
// check collisions with swept arcs
if (valid && parent.valid) // ;!; parent.valid test is temporary
{
for (const rotatedSegment & s : fold.segment)
for (size_t i = 0; i != i_rotate; i++)
{
if (s.intersects(point[i+1], point[i]))
{
//printf("! %s => %s\n", parent.trace().c_str(), trace().c_str());//;!;
valid = false;
break;
}
}
}
}
// trace
string trace(void) const
{
size_t len = 0;
for (size_t i = 1; i != point.size(); i++) len += point[i - 1].manhattan_distance(point[i]);
return configuration.text(len);
}
};
// ============================================================================
// snake twisting engine
// ============================================================================
class cSnakeFolder {
int length;
unsigned num_joints;
tGrid grid;
// filter redundant configurations
bool is_unique (sConfiguration c)
{
unsigned reverse_p = bit_reverse(c.partition, num_joints);
if (reverse_p < c.partition)
{
tprintf("P cut %s\n", c.text(length).c_str());
return false;
}
else if (reverse_p == c.partition) // filter redundant foldings
{
unsigned first_joint_mask = c.partition & (-c.partition); // insulates leftmost bit
unsigned reverse_f = bit_reverse(c.folding, num_joints);
if (reverse_f & first_joint_mask) reverse_f = ~reverse_f & c.partition;
if (reverse_f > c.folding)
{
tprintf("F cut %s\n", c.text(length).c_str());
return false;
}
}
return true;
}
// recursive folding
void fold(tSnakeDoll snake, unsigned first_joint)
{
// count unique configurations
if (snake.valid && is_unique(snake.configuration)) num_configurations++;
// try to bend remaining joints
for (size_t joint = first_joint; joint != num_joints; joint++)
{
// right bend
tprintf("%s -> %s\n", snake.configuration.text(length).c_str(), snake.configuration.bend(joint,1).text(length).c_str());
fold(tSnakeDoll(snake, joint, 1, grid), joint + 1);
// left bend, except for the first joint
if (snake.configuration.partition != 0)
{
tprintf("%s -> %s\n", snake.configuration.text(length).c_str(), snake.configuration.bend(joint, -1).text(length).c_str());
fold(tSnakeDoll(snake, joint, -1, grid), joint + 1);
}
}
}
public:
// count of found configurations
unsigned num_configurations;
// constructor does all the work :)
cSnakeFolder(int n) : length(n), grid(n), num_configurations(0)
{
num_joints = length - 1;
// launch recursive folding
fold(tSnakeDoll(length), 0);
}
};
// ============================================================================
// here we go
// ============================================================================
int main(int argc, char * argv[])
{
#ifdef NDEBUG
if (argc != 2) panic("give me a snake length or else");
int length = atoi(argv[1]);
#else
(void)argc; (void)argv;
int length = 12;
#endif // NDEBUG
if (length <= 0 || length >= MAX_LENGTH) panic("a snake of that length is hardly foldable");
time_t start = time(NULL);
cSnakeFolder snakes(length);
time_t duration = time(NULL) - start;
printf ("Found %d configuration%c of length %d in %lds\n", snakes.num_configurations, (snakes.num_configurations == 1) ? '\0' : 's', length, duration);
return 0;
}
निष्पादन योग्य निर्माण
"लिनक्स" बिल्ड के लिए g ++ 4.8 के साथ Win7 के तहत मैं मिनगॉव का उपयोग करता हूं, संकलन करता हूं, इसलिए पोर्टेबिलिटी 100% गारंटी नहीं है।g++ -O3 -std=c++11
यह एक मानक MSVC2013 परियोजना के साथ (सॉर्ट) भी काम करता है
अपरिभाषित करके NDEBUG
, आप एल्गोरिथ्म निष्पादन के निशान और पाया विन्यास का सारांश प्राप्त करते हैं।
प्रदर्शन
हैश टेबल के साथ या उसके बिना, Microsoft संकलक बुरी तरह से प्रदर्शन करता है: g ++ बिल्ड 3 गुना तेज है ।
एल्गोरिथ्म व्यावहारिक रूप से स्मृति का उपयोग नहीं करता है।
चूँकि टकराव की जाँच लगभग O (n) में होती है, परिकलन का समय O (nk n ) में होना चाहिए , k की तुलना में थोड़ा कम 3.
मेरे i3-2100@3.1GHz पर, n = 17 में लगभग 1:30 (लगभग 2 मिलियन) लगते हैं सांप / मिनट)।
मैं अनुकूलन नहीं कर रहा हूं, लेकिन मैं x3 से अधिक लाभ की उम्मीद नहीं करूंगा, इसलिए मूल रूप से मैं एक घंटे के तहत n = 20 या एक दिन के तहत n = 24 तक पहुंचने की उम्मीद कर सकता हूं।
पहले ज्ञात असंबद्ध आकार (n = 31) तक पहुँचने में कुछ साल और एक दशक के बीच का समय लगेगा, कोई भी बिजली की कमी नहीं होगी।
आकृतियों की गिनती
एक एन आकार साँप है N-1 जोड़ों।
प्रत्येक जोड़ को बाएं या दाएं (3 संभावनाओं) से सीधा या मुड़ा जा सकता है।
संभावित तह की संख्या इस प्रकार 3 एन -1 है ।
टकराव उस संख्या को कुछ हद तक कम कर देगा, इसलिए वास्तविक संख्या 2.7 एन -1 के करीब है
हालांकि, ऐसे कई तह समान आकार के होते हैं।
दो आकृतियाँ एक जैसी होती हैं यदि एक घूर्णन या एक समरूपता होती है जो एक को दूसरे में बदल सकती है।
आइए खंड को शरीर के किसी भी सीधे हिस्से के रूप में परिभाषित करें ।
उदाहरण के लिए, 5 जोड़ पर मुड़े 5 सांप के 2 खंड होंगे (एक 2 इकाई लंबा और दूसरा 3 इकाई लंबा)।
पहले खंड का नाम प्रमुख होगा , और अंतिम पूंछ ।
अधिवेशन द्वारा हम सांपों के सिर को क्षैतिज रूप से शरीर की ओर इंगित करते हुए दाईं ओर रखते हैं (जैसे ओपी प्रथम आकृति में)।
हम दिए गए खंड को हस्ताक्षरित खंड लंबाई की सूची के साथ नामित करते हैं, सकारात्मक लंबाई के साथ एक सही तह और नकारात्मक एक बाईं तह को दर्शाता है।
प्रारंभिक अधिवेशन सम्मेलन द्वारा सकारात्मक है।
अलग सेगमेंट और झुकता है
यदि हम केवल अलग-अलग तरीकों पर विचार करते हैं, तो लंबाई एन का एक साँप खंडों में विभाजित हो सकता है, हम एन की रचनाओं के समान एक पुनरावृत्ति के साथ समाप्त होते हैं।
विकी पेज में दिखाए गए एल्गोरिथ्म का उपयोग करके, साँप के सभी 2 एन -1 संभावित विभाजन उत्पन्न करना आसान है ।
प्रत्येक विभाजन बारी-बारी से बाएं या दाएं झुककर अपने सभी जोड़ों को लागू करके सभी संभव तह उत्पन्न करेगा। इस तरह के एक तह को कॉन्फ़िगरेशन कहा जाएगा ।
सभी संभावित विभाजनों को एन -1 बिट्स के पूर्णांक द्वारा दर्शाया जा सकता है, जहां प्रत्येक बिट एक संयुक्त की उपस्थिति का प्रतिनिधित्व करता है। हम इस पूर्णांक को एक जनरेटर कहेंगे ।
विभाजन विभाजन
यह देखते हुए कि सिर के नीचे से दिए गए विभाजन को झुकाना पूंछ के ऊपर से सममित विभाजन को झुकने के बराबर है, हम सभी जोड़ों के सममित विभाजन को खोज सकते हैं और दो में से एक को समाप्त कर सकते हैं।
एक सममित विभाजन का जनरेटर विभाजन का जनरेटर है जो रिवर्स बिट ऑर्डर में लिखा गया है, जो कि पता लगाने के लिए तुच्छ रूप से आसान और सस्ता है।
यह संभावित विभाजनों के लगभग आधे को समाप्त कर देगा, अपवाद "पैलिंड्रोमिक" जनरेटर के साथ विभाजन हैं जो बिट रिवर्सल (उदाहरण के लिए 00100100) द्वारा अपरिवर्तित छोड़ दिए जाते हैं।
क्षैतिज समरूपता का ख्याल रखना
हमारे सम्मेलनों के साथ (एक साँप दाईं ओर इंगित करना शुरू करता है), दाईं ओर लगाया गया पहला मोड़ मोड़ के एक परिवार का उत्पादन करेगा जो कि पहले मोड़ से भिन्न होने वाले लोगों से क्षैतिज समरूपता होगी।
यदि हम तय करते हैं कि पहला मोड़ हमेशा दाईं ओर होगा, तो हम एक बड़े झपट्टे में सभी क्षैतिज समरूपता को समाप्त कर देते हैं।
पलिंडों को खंगालना
ये दो कटौती कुशल हैं, लेकिन इन pesky palindromes की देखभाल करने के लिए पर्याप्त नहीं है।
सामान्य मामले में सबसे गहन जाँच इस प्रकार है:
एक palindromic विभाजन के साथ एक विन्यास सी पर विचार करें।
- यदि हम C में हर मोड़ को उल्टा करते हैं , तो हम C के क्षैतिज सममित के साथ समाप्त होते हैं।
- अगर हम C को उल्टा करते हैं (पूंछ से ऊपर की तरफ झुकते हुए), तो हमें वही फिगर घुमाया जाता है
- यदि हम C को उल्टा और उल्टा दोनों करते हैं, तो हमें एक ही आकृति बाएं घूमती है।
हम 3 अन्य लोगों के खिलाफ हर नए कॉन्फ़िगरेशन की जांच कर सकते हैं। हालाँकि, चूंकि हम पहले से ही एक सही मोड़ के साथ शुरू होने वाले केवल कॉन्फ़िगरेशन उत्पन्न करते हैं, हमारे पास केवल एक संभावित समरूपता की जांच करना है:
- उलटा सी एक बाएं मोड़ के साथ शुरू होगा, जो कि नकल से असंभव निर्माण से है
- उल्टे और उल्टे-उलटे विन्यासों में से केवल एक सही मोड़ के साथ शुरू होगा।
यह केवल एक विन्यास है जिसकी हम संभवतः नकल कर सकते हैं।
बिना किसी स्टोरेज के डुप्लिकेट को खत्म करना
मेरा प्रारंभिक दृष्टिकोण एक विशाल हैश तालिका में सभी कॉन्फ़िगरेशन को संग्रहीत करना था, पहले से गणना की गई सममित कॉन्फ़िगरेशन की उपस्थिति की जांच करके डुप्लिकेट को समाप्त करना।
उपर्युक्त लेख के लिए धन्यवाद, यह स्पष्ट हो गया कि, चूंकि विभाजन और तह बिटफ़िल्ड के रूप में संग्रहीत किए जाते हैं, इसलिए उनकी तुलना किसी संख्यात्मक मान की तरह की जा सकती है।
इसलिए एक सममित जोड़ी के एक सदस्य को खत्म करने के लिए, आप बस दोनों तत्वों की तुलना कर सकते हैं और व्यवस्थित रूप से सबसे छोटी एक (या सबसे बड़ी एक, जैसा आप चाहते हैं) रख सकते हैं।
इस प्रकार, सहानुभूति विभाजन की गणना के लिए दोहराव मात्रा के लिए एक कॉन्फ़िगरेशन का परीक्षण करना, और यदि दोनों समान हैं, तह। किसी भी मेमोरी की आवश्यकता नहीं है।
पीढ़ी का आदेश
स्पष्ट रूप से टक्कर की जांच सबसे अधिक समय लेने वाला हिस्सा होगा, इसलिए इन गणनाओं को कम करना एक प्रमुख समय बचाने वाला है।
एक संभावित समाधान एक "रैगडोल सांप" है जो एक फ्लैट कॉन्फ़िगरेशन में शुरू होगा और धीरे-धीरे तुला होगा, प्रत्येक संभव कॉन्फ़िगरेशन के लिए पूरे साँप ज्यामिति को फिर से विभाजित करने से बचने के लिए।
उस क्रम को चुनकर जिसमें कॉन्फ़िगरेशन का परीक्षण किया जाता है, ताकि प्रत्येक जोड़ों की कुल संख्या के लिए एक रैगडोल संग्रहीत हो, हम इंस्टेंस की संख्या को एन -1 तक सीमित कर सकते हैं।
मैं पूंछ से खातिर एक पुनरावर्ती स्कैन का उपयोग करता हूं, प्रत्येक स्तर पर एक संयुक्त जोड़ रहा हूं। इस प्रकार एक नया ragdoll उदाहरण अभिभावक विन्यास के शीर्ष पर बनाया गया है, जिसमें एकल एडिक्शन बेंड है।
इसका मतलब यह है कि झुकता क्रमिक क्रम में लगाया जाता है, जो लगभग सभी मामलों में आत्म-टकराव से बचने के लिए पर्याप्त लगता है।
जब आत्म-टकराव का पता लगाया जाता है, तो झुकने वाले कदम को सभी संभावित आदेशों में लागू किया जाता है जब तक कि कानूनी तह नहीं मिलती है या सभी संयोजन समाप्त हो जाते हैं।
स्थैतिक जाँच
चलती भागों के बारे में सोचने से पहले, मुझे स्व-चौराहों के लिए सांप के स्थिर अंतिम आकार का परीक्षण करना अधिक कुशल लगा।
यह एक ग्रिड पर सांप को खींचकर किया जाता है। प्रत्येक संभावित बिंदु को नीचे से नीचे की ओर रखा जाता है। यदि कोई आत्म-चौराहा है, तो कम से कम एक जोड़ी एक ही स्थान पर गिर जाएगी। इसके लिए निरंतर O (N) समय के लिए किसी भी सांप विन्यास के लिए बिल्कुल एन प्लॉट की आवश्यकता होती है।
इस दृष्टिकोण का मुख्य लाभ यह है कि अकेले स्थैतिक परीक्षण एक वर्ग जाली पर मान्य स्व-परहेज पथों का चयन करेगा, जो गतिशील टकराव का पता लगाने में बाधा डालकर पूरे एल्गोरिथ्म का परीक्षण करने की अनुमति देता है और यह सुनिश्चित करता है कि हम ऐसे रास्तों की सही गिनती पाएं।
गतिशील जाँच
जब एक सांप एक जोड़ के चारों ओर मोड़ता है, तो प्रत्येक घूमता हुआ क्षेत्र एक ऐसे क्षेत्र को घुमाएगा, जिसकी आकृति कुछ भी है लेकिन तुच्छ है।
स्पष्ट रूप से आप व्यक्तिगत रूप से ऐसे सभी बह क्षेत्रों में समावेश का परीक्षण करके टकराव की जाँच कर सकते हैं। एक वैश्विक जाँच अधिक कुशल होगी, लेकिन जिन क्षेत्रों की जटिलता के बारे में मैं नहीं सोच सकता, उन्हें देखते हुए (शायद सभी क्षेत्रों को आकर्षित करने के लिए GPU का उपयोग करके और वैश्विक हिट की जाँच करें)।
चूंकि स्थैतिक परीक्षण प्रत्येक सेगमेंट के शुरुआती और समाप्त होने वाले पदों का ध्यान रखता है, हमें सिर्फ अपने घूर्णन खंडों द्वारा बहने वाले आर्क्स के साथ चौराहों की जांच करने की आवश्यकता है ।
ट्राइकोप्लाक्स के साथ एक दिलचस्प चर्चा और मेरे बीयरिंग प्राप्त करने के लिए जावास्क्रिप्ट का थोड़ा सा , मैं इस पद्धति के साथ आया:
यदि आप कॉल करते हैं, तो इसे कुछ शब्दों में रखने की कोशिश करें
- C रोटेशन का केंद्र,
- S , मनमाने ढंग से लंबन और दिशा का एक घूमने वाला खंड जिसमें C शामिल नहीं है ,
- L , S को लम्बी करने वाली रेखा
- H , C से होकर गुजरने वाली L से ऑर्थोगोनल रेखा ,
- मैं एल और एच का चौराहा ,
(स्रोत: free.fr )
किसी भी सेगमेंट में जिसमें I नहीं है , स्वेप्ट एरिया 2 आर्क्स (और 2 सेक्सेस पहले से ही स्टैटिक चेक द्वारा ध्यान रखा गया है) से बंधा है।
यदि मैं खंड के भीतर आता हूं , तो मेरे द्वारा दिए गए चाप को भी ध्यान में रखा जाना चाहिए।
इसका मतलब है कि हम प्रत्येक घूमने वाले खंड के खिलाफ 2 या 3 सेगमेंट-आर्क चौराहों के साथ प्रत्येक अनमोविंग सेगमेंट की जांच कर सकते हैं
मैंने त्रिकोणमितीय कार्यों से पूरी तरह बचने के लिए वेक्टर ज्यामिति का उपयोग किया।
वेक्टर ऑपरेशन कॉम्पैक्ट और (अपेक्षाकृत) पठनीय कोड का उत्पादन करते हैं।
सेगमेंट-टू-आर्क चौराहे के लिए एक अस्थायी बिंदु वेक्टर की आवश्यकता होती है, लेकिन तर्क गोलाई त्रुटियों के लिए प्रतिरक्षा होना चाहिए।
मुझे एक अस्पष्ट फोरम पोस्ट में यह सुरुचिपूर्ण और कुशल समाधान मिला। मुझे आश्चर्य है कि यह अधिक व्यापक रूप से प्रचारित क्यों नहीं है।
क्या यह काम करता है?
डायनेमिक टक्कर का पता लगाने में बाधा डालने से एन = 19 तक सही स्व-टालिंग पथों का निर्माण होता है, इसलिए मुझे वैश्विक लेआउट कार्यों पर पूरा भरोसा है।
डायनेमिक टक्कर का पता लगाने से लगातार परिणाम मिलते हैं, हालांकि अलग-अलग क्रम में मोड़ की जांच गायब है (अभी के लिए)।
नतीजतन, कार्यक्रम सांपों को गिनता है जो सिर से नीचे की ओर झुका जा सकता है (यानी सिर से दूरी बढ़ाने के क्रम में मुड़े हुए जोड़ों के साथ)।