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だけ増やすことを許可する理由です。