到達可能な蛇の方向の数


11

この課題は、ゲームSnakeについてのものではありません。

長さの水平線を引くことによって形成された2Dのヘビを想像してくださいn。体に沿った整数点で、この蛇は体を90度回転させることができます。最初にヘビの前部を左端に定義すると、ヘビの後ろの部分が回転によって移動し、前の部分はそのままになります。繰り返し回転することにより、さまざまな蛇の形を作ることができます。

ルール

  1. ヘビの体のある部分が別の部分と重なることはできません。
  2. ヘビの体のどの部分も間に重なることなく、最終的な方向に到達できる必要があります。この問題では、接触する2つのポイントが重複しているとカウントされます。
  3. 私はヘビとその逆を同じ形だと考えています。

仕事

回転、平行移動、鏡面対称まで、作成可能なさまざまな蛇の形の総数はいくつですか?

ヘビの体の一部の回転の例。想像n=10してみると、ヘビは直線の開始方向にあります。ここで、4反時計回りに90度回転します。私たちはヘビ410(ヘビ の尾)から垂直に、ヘビをから水平04横たえます。ヘビの体には直角が1つあります。

以下は、MartinBüttnerのおかげです。

水平のヘビから始めます。

ここに画像の説明を入力してください

次に、位置4から回転します。

ここに画像の説明を入力してください

この向きで回転した後、私たちは終わります。

ここに画像の説明を入力してください

次に、異なるヘビのこの方向を考えてみましょう。

ここに画像の説明を入力してください

ローテーション中にオーバーラップが発生する違法な動きを見ることができます。

衝突の例

スコア

あなたのスコアは、nあなたのコードが私のコンピューター上で1分以内に問題を解決できる最大のものです。

回転が発生すると、蛇の半分が一緒に移動します。回転しているこの部分のいずれかが、回転中にヘビの一部と重なるかどうかを心配する必要があります。簡単にするために、蛇の幅はゼロであると想定できます。ヘビの特定の点でのみ、時計回りに反時計回りに最大90度回転できます。なぜなら、同じ方向に同じポイントで2つの回転が必要になるため、ヘビを2つに完全に折り畳むことはできません。

作れない形

作ることのできない形の簡単な例は首都Tです。より洗練されたバージョンは

ここに画像の説明を入力してください

(この例についてはHarald Hanche-Olsenに感謝します)

この例では、すべての隣接する水平線は垂直線と同様に1離れています。したがって、この位置からの法的な動きはなく、問題は可逆的であるため、開始位置からそこに到達する方法はありません。

言語とライブラリ

自由に利用できるコンパイラ/インタープリター/などを備えた任意の言語を使用できます。LinuxおよびLinuxでも自由に利用できるライブラリ用。

私のマシン タイミングは私のマシンで実行されます。これは、AMD FX-8350 8コアプロセッサへの標準のUbuntuインストールです。これは、コードを実行できる必要があることも意味します。結果として、簡単に入手できる無料のソフトウェアのみを使用し、コードをコンパイルして実行する方法の完全な指示を含めてください。


1
@TApicella質問をありがとう。「回転が起こると、ヘビの半分が一緒に移動します」と言うとき、私は50%を意味しませんでした。回転点の前の部分とその後の部分を参照していました。ヘビに沿って0から回転すると、全体が回転します!

2
@TApicella 2番目の質問について。ポイントは、私が与えた位置からの法的な回転がないということです。到達可能である場合、一連の回転(最後の方向に到達するために必要な回転の逆)によって水平方向の開始方向に戻ることが可能でなければなりません。この位置から?明確にするために、ヘビは成長しません。全体を通して常に同じ長さです。

3
@TApicellaヘビの成長を期待しているようですね。ただし、サイズは固定されています。1つの長い蛇から始めて、その一部を90度折ります。現在の位置からは、ヘビの前の段階につながるフォールドをまったく適用できません。
マーティンエンダー

1
1つのポイントで複数回(前後)フォールドできますか?できればそれはかなり複雑になります。
randomra

1
@randomra真っ直ぐに90度を超えない限り、本当にできます。

回答:


5

Python 3-暫定スコア:n = 11(PyPy *ではn = 13)

最初の週には答えがなかったので、競争を促すPythonの例を以下に示します。私は、非効率性を簡単に特定して、他の答えのアイデアを提供できるように、合理的に読みやすくしようとしました。

アプローチ

  • まっすぐな蛇から始めて、1回の動きで合法的に到達できるすべての位置を見つけます。
  • まだ特定されていない、これらのポジションから合法的に到達できるすべてのポジションを見つけます。
  • これ以上見つからなくなるまで繰り返し、見つかった位置の数をすべて返します。

コード

(現在、いくつかのdoctestsと私の間違った最初の試みの後にアサートします)

'''
Snake combinations

A snake is represented by a tuple giving the relative orientation at each joint.
A length n snake has n-1 joints.
Each relative orientation is one of the following:

0: Clockwise 90 degrees
1: Straight
2: Anticlockwise 90 degrees

So a straight snake of length 4 has 3 joints all set to 1:

(1, 1, 1)

x increases to the right
y increases upwards

'''


import turtle


def all_coords(state):
    '''Return list of coords starting from (0,0) heading right.'''
    current = (1, 0)
    heading = 0
    coords = [(0,0), (1,0)]
    for item in state:
        heading += item + 3
        heading %= 4
        offset = ((1,0), (0,1), (-1,0), (0,-1))[heading]
        current = tuple(current[i]+offset[i] for i in (0,1))
        coords.append(current)
    return coords


def line_segments(coords, pivot):
    '''Return list of line segments joining consecutive coords up to pivot-1.'''
    return [(coords[i], coords[i+1]) for i in range(pivot+1)]


def rotation_direction(coords, pivot, coords_in_final_after_pivot):
    '''Return -1 if turning clockwise, 1 if turning anticlockwise.'''
    pivot_coord = coords[pivot + 1]
    initial_coord = coords[pivot + 2]
    final_coord = coords_in_final_after_pivot[0]
    initial_direction = tuple(initial_coord[i] - pivot_coord[i] for i in (0,1))
    final_direction = tuple(final_coord[i] - pivot_coord[i] for i in (0,1))
    return (initial_direction[0] * final_direction[1] -
            initial_direction[1] * final_direction[0]
            )


def intersects(arc, line):
    '''Return True if the arc intersects the line segment.'''
    if line_segment_cuts_circle(arc, line):
        cut_points = points_cutting_circle(arc, line)
        if cut_points and cut_point_is_on_arc(arc, cut_points):
            return True


def line_segment_cuts_circle(arc, line):
    '''Return True if the line endpoints are not both inside or outside.'''
    centre, point, direction = arc
    start, finish = line
    point_distance_squared = distance_squared(centre, point)
    start_distance_squared = distance_squared(centre, start)
    finish_distance_squared = distance_squared(centre, finish)
    start_sign = start_distance_squared - point_distance_squared
    finish_sign = finish_distance_squared - point_distance_squared
    if start_sign * finish_sign <= 0:
        return True


def distance_squared(centre, point):
    '''Return the square of the distance between centre and point.'''
    return sum((point[i] - centre[i]) ** 2 for i in (0,1))


def cut_point_is_on_arc(arc, cut_points):
    '''Return True if any intersection point with circle is on arc.'''
    centre, arc_start, direction = arc
    relative_start = tuple(arc_start[i] - centre[i] for i in (0,1))
    relative_midpoint = ((relative_start[0] - direction*relative_start[1])/2,
                         (relative_start[1] + direction*relative_start[0])/2
                         )
    span_squared = distance_squared(relative_start, relative_midpoint)
    for cut_point in cut_points:
        relative_cut_point = tuple(cut_point[i] - centre[i] for i in (0,1))
        spacing_squared = distance_squared(relative_cut_point,
                                           relative_midpoint
                                           )
        if spacing_squared <= span_squared:
            return True


def points_cutting_circle(arc, line):
    '''Return list of points where line segment cuts circle.'''
    points = []
    start, finish = line
    centre, arc_start, direction = arc
    radius_squared = distance_squared(centre, arc_start)
    length_squared = distance_squared(start, finish)
    relative_start = tuple(start[i] - centre[i] for i in (0,1))
    relative_finish = tuple(finish[i] - centre[i] for i in (0,1))
    relative_midpoint = tuple((relative_start[i] +
                               relative_finish[i]
                               )*0.5 for i in (0,1))
    determinant = (relative_start[0]*relative_finish[1] -
                   relative_finish[0]*relative_start[1]
                   )
    determinant_squared = determinant ** 2
    discriminant = radius_squared * length_squared - determinant_squared
    offset = tuple(finish[i] - start[i] for i in (0,1))
    sgn = (1, -1)[offset[1] < 0]
    root_discriminant = discriminant ** 0.5
    one_over_length_squared = 1 / length_squared
    for sign in (-1, 1):
        x = (determinant * offset[1] +
             sign * sgn * offset[0] * root_discriminant
             ) * one_over_length_squared
        y = (-determinant * offset[0] +
             sign * abs(offset[1]) * root_discriminant
             ) * one_over_length_squared
        check = distance_squared(relative_midpoint, (x,y))
        if check <= length_squared * 0.25:
            points.append((centre[0] + x, centre[1] + y))
    return points


def potential_neighbours(candidate):
    '''Return list of states one turn away from candidate.'''
    states = []
    for i in range(len(candidate)):
        for orientation in range(3):
            if abs(candidate[i] - orientation) == 1:
                state = list(candidate)
                state[i] = orientation
                states.append(tuple(state))
    return states


def reachable(initial, final):
    '''
    Return True if final state can be reached in one legal move.

    >>> reachable((1,0,0), (0,0,0))
    False

    >>> reachable((0,1,0), (0,0,0))
    False

    >>> reachable((0,0,1), (0,0,0))
    False

    >>> reachable((1,2,2), (2,2,2))
    False

    >>> reachable((2,1,2), (2,2,2))
    False

    >>> reachable((2,2,1), (2,2,2))
    False

    >>> reachable((1,2,1,2,1,1,2,2,1), (1,2,1,2,1,1,2,1,1))
    False

    '''
    pivot = -1
    for i in range(len(initial)):
        if initial[i] != final[i]:
            pivot = i
            break

    assert pivot > -1, '''
        No pivot between {} and {}'''.format(initial, final)
    assert initial[pivot + 1:] == final[pivot + 1:], '''
        More than one pivot between {} and {}'''.format(initial, final)

    coords_in_initial = all_coords(initial)
    coords_in_final_after_pivot = all_coords(final)[pivot+2:]
    coords_in_initial_after_pivot = coords_in_initial[pivot+2:]
    line_segments_up_to_pivot = line_segments(coords_in_initial, pivot)

    direction = rotation_direction(coords_in_initial,
                                   pivot,
                                   coords_in_final_after_pivot
                                   )

    pivot_point = coords_in_initial[pivot + 1]

    for point in coords_in_initial_after_pivot:
        arc = (pivot_point, point, direction)
        if any(intersects(arc, line) for line in line_segments_up_to_pivot):
            return False
    return True


def display(snake):
    '''Display a line diagram of the snake.

    Accepts a snake as either a tuple:

    (1, 1, 2, 0)

    or a string:

    "1120"

    '''
    snake = tuple(int(s) for s in snake)
    coords = all_coords(snake)

    turtle.clearscreen()
    t = turtle.Turtle()
    t.hideturtle()
    s = t.screen
    s.tracer(0)

    width, height = s.window_width(), s.window_height()

    x_min = min(coord[0] for coord in coords)
    x_max = max(coord[0] for coord in coords)
    y_min = min(coord[1] for coord in coords)
    y_max = max(coord[1] for coord in coords)
    unit_length = min(width // (x_max - x_min + 1),
                      height // (y_max - y_min + 1)
                      )

    origin_x = -(x_min + x_max) * unit_length // 2
    origin_y = -(y_min + y_max) * unit_length // 2

    pen_width = max(1, unit_length // 20)
    t.pensize(pen_width)
    dot_size = max(4, pen_width * 3)

    t.penup()
    t.setpos(origin_x, origin_y)
    t.pendown()

    t.forward(unit_length)
    for joint in snake:
        t.dot(dot_size)
        t.left((joint - 1) * 90)
        t.forward(unit_length)
    s.update()


def neighbours(origin, excluded=()):
    '''Return list of states reachable in one step.'''
    states = []
    for candidate in potential_neighbours(origin):
        if candidate not in excluded and reachable(origin, candidate):
            states.append(candidate)
    return states


def mirrored_or_backwards(candidates):
    '''Return set of states that are equivalent to a state in candidates.'''
    states = set()
    for candidate in candidates:
        mirrored = tuple(2 - joint for joint in candidate)
        backwards = candidate[::-1]
        mirrored_backwards = mirrored[::-1]
        states |= set((mirrored, backwards, mirrored_backwards))
    return states


def possible_snakes(snake):
    '''
    Return the set of possible arrangements of the given snake.

    >>> possible_snakes((1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1))
    {(1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1)}

    '''
    reached = set()
    candidates = set((snake,))

    while candidates:
        candidate = candidates.pop()
        reached.add(candidate)
        new_candidates = neighbours(candidate, reached)
        reached |= mirrored_or_backwards(new_candidates)
        set_of_new_candidates = set(new_candidates)
        reached |= set_of_new_candidates
        candidates |= set_of_new_candidates

    excluded = set()
    final_answers = set()
    while reached:
        candidate = reached.pop()
        if candidate not in excluded:
            final_answers.add(candidate)
            excluded |= mirrored_or_backwards([candidate])

    return final_answers


def straight_derived_snakes(length):
    '''Return the set of possible arrangements of a snake of this length.'''
    straight_line = (1,) * max(length-1, 0)
    return possible_snakes(straight_line)


if __name__ == '__main__':
    import doctest
    doctest.testmod()
    import sys
    arguments = sys.argv[1:]
    if arguments:
        length = int(arguments[0])
    else:
        length = int(input('Enter the length of the snake:'))
    print(len(straight_derived_snakes(length)))

結果

私のマシンでは、1分以内に計算できる最長の蛇は長さ11(またはPyPy *の場合は長さ13)です。これは明らかに、公式スコアがLembikのマシンからのものであることがわかるまで、単なる暫定スコアです。

比較のため、nの最初のいくつかの値の結果を以下に示します。

 n | reachable orientations
-----------------------------
 0 | 1
 1 | 1
 2 | 2
 3 | 4
 4 | 9
 5 | 22
 6 | 56
 7 | 147
 8 | 388
 9 | 1047
10 | 2806
11 | 7600
12 | 20437
13 | 55313
14 | 148752
15 | 401629
16 | 1078746
17 | MemoryError (on my machine)

これらのいずれかが間違っていることが判明した場合はお知らせください。

展開できないはずのアレンジメントの例がある場合、この機能neighbours(snake)を使用して、コードのテストとして、1ステップで到達可能なアレンジメントを見つけることができます。snakeジョイント方向のタプルです(時計回りの場合は0、直線の場合は1、反時計回りの場合は2)。たとえば、(1,1,1)は、長さが4の(3つのジョイントを持つ)直線の蛇です。

可視化

考えているヘビ、またはによって返されたヘビを視覚化するにneighboursは、関数を使用できますdisplay(snake)。これは他の関数と同様にタプルを受け入れますが、メインプログラムでは使用されず、したがって高速である必要がないため、便宜上、文字列も受け入れます。

display((1,1,2,0)) に等しい display("1120")

Lembikがコメントで言及しているように、私の結果はOEIS A037245と同一であり、ローテーション中に交差を考慮していません。これは、nの最初のいくつかの値に違いがないためです-自己交差しないすべての形状は、直線の蛇を折り畳むことで到達できます。コードの正確性は、neighbours()交差せずに展開できないヘビを呼び出してテストできます。ネイバーがないため、OEISシーケンスのみに寄与し、このシーケンスには寄与しません。私が知っている最小の例は、デビッドKのおかげで、レンビクが言及したこの長さ31のヘビです。

(1,1,1,1,2,1,2,1,1,1,1,1,1,2,1,1,1,2,1,1,2,2,1,0,1,0,1,1,1,1)

display('111121211111121112112210101111') 次の出力が得られます。

隣人のいない最短のヘビの画像

ヒント:表示ウィンドウのサイズを変更してから再度displayを呼び出すと、ヘビは新しいウィンドウサイズに合わせられます。

隣人のいない短い例がある人から聞いてみたいです。このような最短の例では、2つのシーケンスが異なる最小のnがマークされると思います。


* nの各増分には約3倍の時間がかかるため、n = 11からn = 13に増やすには、ほぼ10倍の時間が必要です。これが、PyPyが標準のPythonインタープリターよりもかなり高速に実行されるにもかかわらず、nを2だけ増やすことを許可する理由です。


6
このコメントが5つの賛成票を獲得した場合、分析に役立つ場合に備えて、可能な取り決めの視覚化を含めるオプションの追加を検討します。
trichoplax


私は@Geobits だと思う私は右のこの時間はそれを持っている...
センモウヒラムシ


1
@Jakubeこれは多くの方法で開くことができます。例えば、ジョイント#1#3#2#4#5#6の順です。
-randomra

1

C ++ 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;
}

実行可能ファイルの構築

コンパイル Win7で「linux」ビルド用のg ++​​ 4.8でMinGWを使用するため、移植性は100%保証されません。g++ -O3 -std=c++11

また、標準のMSVC2013プロジェクトでも機能します。

undefiningによりNDEBUG、アルゴリズム実行のトレースと検出された構成の要約を取得します。

公演

ハッシュテーブルの有無にかかわらず、Microsoftコンパイラはひどく動作します。g++ビルドは3倍高速です。

このアルゴリズムは、実際にはメモリを使用しません。

衝突チェックはおおよそO(n)であるため、計算時間はO(nk n)である必要があり、kは3よりわずかに低いです。
私のi3-2100@3.1GHzでは、n = 17は約1:30(約200万)ヘビ/分)。

最適化は完了していませんが、3倍以上のゲインは期待できないため、基本的には1時間でn = 20、1日でn = 24に到達することを期待できます。

停電が発生しないと仮定すると、最初の既知の曲げられない形状(n = 31)に達するには、数年から10年かかります。

形状を数える

NのサイズのヘビはありN-1関節を。
各ジョイントは、直線のままにするか、左または右に曲げることができます(3つの可能性)。
したがって、可能な折りたたみの数は3 N-1です。
衝突はその数をいくらか減らすので、実際の数は2.7 N-1に近い

ただし、このような折り畳みの多くは、同じ形状になります。

一方を他方に変換できる回転または対称性がある場合、2つの形状は同一です。

折り畳まれたボディの直線部分としてセグメントを定義しましょう。
たとえば、2番目のジョイントで折りたたまれたサイズ5の蛇には、2つのセグメントがあります(1つは2ユニット、2番目は3ユニット)。
最初のセグメントにはheadという名前が付けられ、最後のセグメントには最後の名前が付けられます

慣例により、ヘビの頭を水平に向け、身体が右を向くようにします(OPの最初の図のように)。

符号付きセグメントの長さのリストで特定の図を指定します。正の長さは右折を示し、負の長さは左折を示します。
慣例により、初期の長さは正です。

セグメントとベンドの分離

長さNのスネークをセグメントに分割できるさまざまな方法のみを考慮すると、N の構成と同一の再パーティションになります。

wikiページに示されているのと同じアルゴリズムを使用すると、2つのN-1個の可能なヘビのパーティションをすべて簡単に生成できます。

各パーティションは、そのすべてのジョイントに左または右のベンドを適用することにより、可能なすべての折りたたみを生成します。そのような折りたたみの1つを構成と呼びます

すべての可能なパーティションは、N-1ビットの整数で表すことができます。各ビットはジョイントの存在を表します。この整数をジェネレーターと呼びます。

プルーニングパーティション

特定のパーティションを頭から下に曲げることは、対称パーティションを尾から上に曲げることと同等であることに注目すると、対称パーティションのすべてのカップルを見つけて、2つのうち1つを排除できます。
対称パーティションのジェネレータは、逆ビット順で記述されたパーティションのジェネレータであり、簡単に検出でき、安価です。

これにより、可能なパーティションのほぼ半分が削除されます。例外は、ビット反転によって変更されない「パリンドローム」ジェネレーターを持つパーティションです(たとえば、00100100)。

水平対称の世話をする

慣例(ヘビが右を指すようになります)では、右に適用される最初のベンドは、最初のベンドだけが異なるものとは水平対称な折り畳みのファミリを生成します。

最初のベンドが常に右にあると判断した場合、すべての水平対称性を1回の大規模な変化で排除します。

パリンドロームの掃討

これらの2つのカットは効率的ですが、これらの厄介な回文を処理するには不十分です。
一般的な場合の最も徹底的なチェックは次のとおりです。

回文パーティションを持つ構成Cを検討してください。

  • Cのすべてのベンドを反転させると、Cの水平対称になります。
  • C を逆にすると(ベンドをテールから上に適用)、同じ図形が右に回転します
  • Cを反転および反転させると、同じ図形が左に回転します。

他の3つの構成に対してすべての新しい構成を確認できます。ただし、右折から始まる構成のみがすでに生成されているため、確認できる対称性は1つだけです。

  • 逆向きのCは左折で始まりますが、これは構造上複製することは不可能です
  • 反転および反転反転構成のうち、1つだけが右折で開始されます。
    これが、おそらく複製できる唯一の構成です。

ストレージなしで重複を排除

私の最初のアプローチは、すべての構成を巨大なハッシュテーブルに保存し、以前に計算された対称構成の存在を確認して重複を排除することでした。

前述の記事のおかげで、パーティションとフォールディングはビットフィールドとして保存されるため、数値のように比較できることが明らかになりました。
そのため、対称ペアの1つのメンバーを削除するには、両方の要素を単純に比較し、最小のもの(または必要に応じて最大のもの)を体系的に保持します。

したがって、構成の重複をテストすることは、対称パーティションを計算することになり、両方が同一である場合、折りたたみを計算することになります。メモリはまったく必要ありません。

世代の順序

明らかに衝突チェックは最も時間のかかる部分になるので、これらの計算を減らすことは大きな時間の節約になります。

可能な解決策は、「ラグドールスネーク」を平らな構成から始めて徐々に曲げることで、各構成ごとにスネークのジオメトリ全体を再計算しないようにすることです。

構成のテスト順序を選択することで、ジョイントの総数ごとに最大1つのラグドールが保存されるように、インスタンスの数をN-1に制限できます。

私は酒の再帰スキャンを最後から最後まで使用し、各レベルに単一のジョイントを追加します。したがって、新しいラグドールインスタンスは、単一の追加ベンドを使用して、親構成の上に構築されます。

これは、曲げが順番に適用されることを意味します。これは、ほとんどすべての場合に自己衝突を回避するのに十分であると思われます。

自己衝突が検出されると、問題のある移動につながる曲げは、正当な折りたたみが見つかるか、すべての組み合わせがなくなるまで、可能なすべての順序で適用されます。

静的チェック

可動部品について考える前に、ヘビの静的最終形状を自己交差でテストする方が効率的であることがわかりました。

これは、グリッド上にヘビを描くことによって行われます。可能な各ポイントは、頭から下にプロットされます。自己交差がある場合、少なくとも1組のポイントが同じ場所に落ちます。これには、一定のO(N)時間で、すべてのスネーク構成に対して正確にN個のプロットが必要です。

このアプローチの主な利点は、静的テストのみが正方格子上の有効な自己回避パスを選択するだけであり、動的衝突検出を禁止し、そのようなパスの正しい数を確認することでアルゴリズム全体をテストできることです。

動的チェック

ヘビが1つのジョイントの周りに折り畳まれると、回転した各セグメントは、形状が些細なもの以外の領域をスイープします。
明らかに、そのようなすべての掃引領域内の包含を個別にテストすることにより、衝突をチェックできます。グローバルチェックの方が効率的ですが、エリアの複雑さを考えると、私には何も考えられません(おそらく、GPUを使用してすべてのエリアを描画し、グローバルヒットチェックを実行することを除く)。

静的テストは各セグメントの開始位置と終了位置を処理するため、各回転セグメントによってスイープされたアークとの交差をチェックするだけです。

trichoplaxとベアリングを取得するためのJavaScript少し使って興味深い議論をした後、この方法を思いつきました。

あなたが電話した場合、いくつかの言葉でそれをしようとする

  • C回転の中心、
  • S含有しない任意な長さと方向の回転セグメントCは
  • L延長線S
  • H Cを通るLに直交する線、
  • LHの交差点、

数学
(ソース:free.fr

Iを含まないセグメントの場合、スイープ領域は2つのアークにバインドされます(2つのセグメントはすでに静的チェックで処理されています)。

セグメント内収まる場合、が掃引したアークも考慮する必要があります。

これは、2つまたは3つの円弧付きセグメントが交差する各回転セグメントに対して、各不動セグメントをチェックできることを意味します

ベクトルジオメトリを使用して、三角関数を完全に回避しました。
ベクトル演算は、コンパクトで(比較的)読み取り可能なコードを生成します。

セグメントとアークの交差には浮動小数点ベクトルが必要ですが、ロジックは丸め誤差の影響を受けません。
このエレガントで効率的なソリューションは、あいまいなフォーラムの投稿で見つけました。なぜもっと広く公表されないのだろうか。

動作しますか?

動的衝突検出を禁止すると、正しい自己回避パスがn = 19までカウントされるため、グローバルレイアウトが機能すると確信しています。

動的衝突検出では一貫した結果が得られますが、異なる順序での曲げのチェックはありません(今のところ)。
結果として、プログラムは頭から下に曲がることができるヘビをカウントします(つまり、頭からの距離が増加する順に関節を折りたたむ)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.