Остров Гольф № 1: Кругосветное плавание


43

Это первый из серии испытаний Island Golf. Следующая задача

Учитывая остров в ASCII-искусстве, выведите оптимальный путь для его обхода.

вход

Ваш ввод будет прямоугольной сеткой, состоящей из двух символов, представляющих землю и воду. В приведенных ниже примерах земля есть, #а вода есть ., но вы можете заменить любые два разных символа по вашему желанию.

...........
...##......
..#####....
..#######..
.#########.
...#######.
...#####.#.
....####...
...........

Там всегда будет хотя бы одна плитка земли. Плитки земли все будут смежными (то есть есть только один остров). Водяные плитки также будут смежными (т.е. там нет озер). Внешняя граница сетки будет водяной плиткой. Плитки земли не будут связаны по диагонали: то есть вы никогда не увидите ничего подобного

....
.#..
..#.
....

Выход

Ваш код должен выводить ту же сетку с нарисованной кратчайшей круговой навигацией. В приведенных ниже примерах путь кругового плавания начертан o, но вы можете заменить любого персонажа, если он отличается от ваших символов земли и воды.

Кругосветное является простой замкнутой кривой, составленный полностью на воде плитки, которая полностью окружает все наземные плитки на сетке. Диагональные соединения будут разрешены. Например, это кругосветное плавание над островом (но не самое короткое):

.ooooo.....
o..##.oo...
o.#####.o..
o.#######o.
o#########o
ooo#######o
..o#####.#o
..oo####..o
....oooooo.

Длина кругового плавания вычисляется следующим образом: для каждой пары соседних плиток на пути, если они соединены горизонтально или вертикально, добавьте 1; если они связаны по диагонали, добавьте √2. Длина указанного пути составляет 22 + 7√2 (≈ 31,9).

Самым коротким кругосветным плаванием является кругосветное плавание с наименьшей возможной длиной. Ваша программа должна выводить любой путь, который удовлетворяет этому условию. Для большинства островов будет несколько возможных решений. Вот одно решение для указанного острова длиной 10 + 13√2 (≈ 28,4):

...oo......
..o##oo....
.o#####oo..
.o#######o.
o#########o
.o.#######o
..o#####.#o
...o####.o.
....ooooo..

Детали

Ваше решение может быть полной программой или функцией . Любой из методов ввода и вывода по умолчанию является приемлемым.

Ваш ввод и вывод может быть многострочной строкой или списком строк. Если ваш язык имеет символьный тип, отличный от односимвольных строк, вы можете заменить «список символов» на «строку» в предыдущем предложении. Если вашему языку нужно ввести высоту и / или ширину сетки, вы можете сделать это. Ваш вывод может (опционально) иметь один завершающий перевод строки. Как упоминалось выше, вы можете использовать любые три различных символа вместо #.o(пожалуйста, укажите в своем представлении, какие символы вы используете).

Контрольные примеры

А. Острова с уникальными кратчайшими кругосветными плаваниями:

...
.#.
...

.o.
o#o
.o.

......
.####.
......

.oooo.
o####o
.oooo.

......
......
..##..
...#..
......
......

......
..oo..
.o##o.
..o#o.
...o..
......

.......
.#####.
...#...
...#...
.#####.
.......

.ooooo.
o#####o
o..#..o
o..#..o
o#####o
.ooooo.

.......
...#...
...#...
.#####.
...#...
...#...
.......

...o...
..o#o..
.o.#.o.
o#####o
.o.#.o.
..o#o..
...o...

.......
.#####.
.##..#.
..#..#.
.......

.ooooo.
o#####o
o##..#o
.o#..#o
..oooo.

Б. Пример острова с несколькими возможными решениями:

........
....##..
...####.
..###...
.#####..
.#####..
..##....
........

Возможные выводы:

....oo..
...o##o.
..o####o
.o###.o.
o#####o.
o#####o.
.o##oo..
..oo....

....oo..
...o##o.
..o####o
.o###.o.
o#####o.
o#####o.
.o##.o..
..ooo...

....oo..
...o##o.
..o####o
.o###..o
o#####.o
o#####o.
.o##oo..
..oo....

....oo..
...o##o.
..o####o
.o###..o
o#####.o
o#####o.
.o##.o..
..ooo...

C. Большой контрольный пример


Это : выигрывает самый короткий код на каждом языке.


1
Самая короткая кругосветка для 3-го контрольного примера - это «буханка» в игре Конвея о жизни!
Товарищ SparklePony

Ответы:


18

Mathematica (версия 9), 165 байт

Хорошая, короткая ConvexHullMeshфункция, которую использовал Грег Мартин, была представлена ​​только в Mathematica версии 10, поэтому я решил сделать попытку без нее, используя мою древнюю версию Mathematica 9. Хотя мне удалось немного ее сократить! Это функция , которая принимает и возвращает список строк (с ., #и , oкак и символы).

""<>#&/@("o"MorphologicalTransform[MorphologicalComponents[#,Method->"ConvexHull"],Max[#(1-#[[2,2]])CrossMatrix@1]&]+"#"#/.{0->"."})&[Characters@#/.{"."->0,"#"->1}]&

Объяснение:

  • Во-первых, Characters@# /. {"."->0, "#"->1}превращает вход в матрицу 0s и 1s.
  • "o"MorphologicalTransform[MorphologicalComponents[#,Method->"ConvexHull"],Max[#(1-#[[2,2]])CrossMatrix@1]&]+"#"#затем использует мощные (но чрезвычайно байтно…) возможности обработки изображений Mathematica, чтобы сначала заполнить выпуклый корпус острова (именно такую ​​форму вы получите, если протянете кусок веревки вокруг него), а затем взять его границу. Затем мы умножаем эту матрицу на строку, "o"чтобы получить матрицу 0s и "o"s (благодаря впечатляющей адаптируемости Mathematica в отношении типов), и добавляем это к "#"временам матрицы острова.
  • Наконец, ""<>#& /@ (... /. {0->"."})превращает эту матрицу "o"s, "#"s и 0s в матрицу "o"s, "#"s и "."s и объединяет каждую строку в строку.

Когда мы проверяем это на примере B , мы получаем вывод

{"....oo..",
 "...o##o.",
 "..o####o",
 ".o###..o",
 "o#####o.",
 "o#####o.",
 ".o##oo..",
 "..oo...."}

[Редактировать, спасибо Грегу Мартину:] Если нам разрешено использовать массивы символов вместо списков строк, мы можем сократить это до 144 байт:

"o"MorphologicalTransform[MorphologicalComponents[#,Method->"ConvexHull"],Max[#(1-#[[2,2]])CrossMatrix@1]&]+"#"#/.{0->"."}&[#/.{"."->0,"#"->1}]&

1
Красиво сделано! Я никогда не знал об этом MorphologicalComponents[#, Method->"ConvexHull"] :) Вы можете сохранить еще больше байтов, предполагая, что ввод уже разделен на символы, а также возвращая двумерный массив символов.
Грег Мартин,

@GregMartin, я не знал об этом использовании MorphologicalComponentsдо сегодняшнего дня!
Не дерево

Новичок Mathematica здесь: как мне вызвать эту функцию? Я попытался f[{"...",".#.","..."}]и получил некоторые ошибки.
DLosc

@DLosc, функция это целое, а не только f. (Ну, строго говоря, это вещи после точки с запятой.) Чтобы вызвать функцию, введите всю эту вещь в окно Mathematica, а затем [свой ввод и ], таким образом, он должен выглядеть примерно так f@m_:=m(1-m[[2,2]]) . . . #/.{"."->0,"#"->1}]&[{"...", ".#.", "..."}](сокращенно для пробела).
Не дерево

@DLosc Ну, это потому, что код не работает. Я думаю, что я исправил это сейчас, хотя! (Я понятия не имею, что там произошло; извините ...)
Не дерево

11

(Но идите на голосование против Нотри , лучше!)

Mathematica, 168 байт

(x_~n~y_:=Min[Norm[x-#]&/@y];i=#;p=i~Position~#&;t=p["#"|"."]~Select~#&;(i~Part~##="o")&@@@t[#~n~t[ConvexHullMesh[Join[s=p@"#",#+{.1,.1}&/@s]]~RegionMember~#&]==1&];i)&

Чистая функция, принимающая двумерный массив символов в качестве входных данных и возвращающая двумерный массив символов. Более удобная для чтения версия:

1  (x_~n~y_ := Min[Norm[x - #] & /@ y];
2  i = #; p = i~Position~# &; 
3  t = p["#" | "."]~Select~# &;
4  (i~Part~## = "o") & @@@ 
5    t[#~n~
6      t[ConvexHullMesh[
7        Join[s = p@"#", # + {.1, .1} & /@ s]]
8      ~RegionMember~# &] == 1 &];
9  i) &

Линия 1 определяет функцию, nкоторая создает (наименьшее) расстояние между точкой xна плоскости и набором yдругих точек. Строка 2 инициализирует переменную iдля ввода, чтобы как позже разрешить неоднозначность каррирования, так и иметь возможность изменить ее для получения конечного результата; Строка 2 также определяет функцию, pкоторая возвращает координаты всех вхождений своего ввода в i.

В строке 3 p["#" | "."]представляет каждую отдельную координату из входной карты (поскольку все ее символы либо либо, "#"либо "."), так tже как и функция, которая выбирает только те координаты, которые удовлетворяют пока неопределенному свойству. В строке 4 i~Part~## = "o"будет изменено несколько записей iсимвола "o"; эти символы будут выбраны из набора возможных координат в соответствии с материалом в строках 5-8. И строка 9 просто возвращает ответ, как только он вычислен.

Хорошо, инфраструктура сделана, теперь к фактическим вычислениям. ConvexHullMeshявляется встроенным в Mathematica для вычисления выпуклой оболочки набора точек (самый маленький выпуклый многоугольник, содержащий эти точки). С моральной точки зрения это должно «заполнить» бухты и фьорды острова (то есть s = p@"#"), чтобы исключить их из нашего кругосветного плавания. Есть небольшая проблема, ConvexHullMeshкогда этот набор точек находится в одной строке (спасибо, тестовый пример № 2), которую мы решаем, добавляя слегка смещенную версию sк себе в строке 7. Этот вывод является многоугольником, поэтому строки 7 -9 (t[...~RegionMember~# &]) создает список точек с целочисленными координатами в этом многоугольнике. Наконец, строка 5 и конец строки 9 вычисляют все точки, которые находятся на расстоянии ровно 1 (следовательно, не 0) от этого набора целых точек; этот набор становится кругосветным путем.

Ниже приведен вывод для большого контрольного примера по ссылке ОП. Обратите внимание, что в левом верхнем углу необычный выбор того, когда идти на запад, а не на юго-запад, намекает на то, что он затеняет невидимую линию наклона -2/3 между двумя полуостровами (указанный отрезок является частью границы выпуклой оболочки).

........................
.............o..........
...........oo#ooooooo...
..........o#.#.##...#o..
........oo.#.#.###.##o..
.......o..########.##o..
.....oo...############o.
...oo#....############o.
..o#.###.##############o
.o##.##################o
.o####################o.
.o.##################.o.
.o##################..o.
.o..################..o.
o###################..o.
o#####################o.
o.##################.o..
o####################o..
o#...##############.o...
o##...#############o....
o#.....###....#oooo.....
.oooooo#ooooooo.........
.......o................

Mathematica обычно представляет строки как одномерные массивы символов? Если нет, то вам нужно вместо этого взять / вернуть одномерный массив строк. (Кроме того, с нетерпением жду объяснения! Я не думаю, что смогу запустить это без Mathematica, верно?)
DLosc

Mathematica имеет строковый тип данных, но кажется, что массив символов также подходит для целей этого сайта (то есть я узнал об этом, когда начал работать с PPCG, но я забыл, почему). Да, к сожалению, Mathematica не является бесплатной и поэтому недоступна для многих :(
Грег Мартин

1
@GregMartin Я всегда попробовать решения Mathematica в sandbox.open.wolframcloud.com
овс

Текущее согласие говорит, что списки односимвольных строк не могут использоваться вместо строки. Насколько я могу судить, "символы" в Mathematica - это просто односимвольные строки, как в Python. Ситуация отличается в языке как Java, у которого есть отдельный charтип; в этом случае charвместо строки можно использовать массив.
DLosc

1
Вот как я это прочитал: Основной ответ с поправкой был опубликован в 2014 году. Ответ, на который я ссылался, был опубликован в 2016 году, как попытка прояснить двусмысленность в предыдущем ответе. Поэтому я прочитал отрицательный балл на новом ответе, когда люди говорили: «Нет, мы не хотим, чтобы старый ответ означал, что списки строк из одного символа в порядке». Но независимо от мета, я запрещаю списки строк из одного символа в этом вопросе (и я уточнил формулировку, чтобы отразить это).
DLosc

10

Python 3, 779 байт (отступ с вкладками)

Это целая программа. Он читает входные данные из стандартного ввода и выводит их на стандартный вывод. Stdin должен заканчиваться EOF. Пример запуска с большим вводом: https://ideone.com/XIfYY0

import itertools,sys
L=list
C=itertools.count
d=L(map(L,filter(None,sys.stdin.read().split('\n'))))
X=len(d[0])
Y=len(d)
R=range
def P(r):return all(d[y][x]=='.'for x,y in r)
def S(f):
    for n in C(0):
        if P(f(n)):l=n
        else:break
    for n in C(l+1):
        if P(f(n)):return l,n
def f(V,a,*b):return L(eval('lambda '+a+':('+i+')',V)for i in b)
V=locals()
def D(n):
    y=min(n,Y-1);x=n-y
    while y>=0and x<X:yield(x,y);x+=1;y-=1
def E(n):
    x=max(0,n-Y);y=x+Y-n
    while y<Y and x<X:yield(x,y);x+=1;y+=1
F=f(V,'p','(p,y)for y in R(0,Y)','(x,p)for x in R(0,X)')+[D,E]
r=f(V,'x,y','x','y','x+y','x-y+Y')
B=L(map(S,F))
for x in R(0,X):
    for y in R(0,Y):
        z=L(zip(r,B))
        if all(g(x,y)in R(a,b+1)for g,(a,b)in z)and any(g(x,y)in e for g,e in z):d[y][x]='o'
print('\n'.join(''.join(x)for x in d))

Идея проста: он вычисляет наименьшие восьмиугольные границы и рисует ячейки, которые находятся внутри всех вычисленных границ и пересекают хотя бы одно из ребер.


1
Вам не нужно использовать в sys.stdinкачестве входных данных. input()получение многострочного
Мертвый Опоссум

2
Может быть в состоянии заменить R(0,x)сR(x)
ceilingcat

+1 за неиспользование встроенного.
Роберт Фрейзер,

1
Ницца! Еще несколько советов по игре в гольф: сэкономьте 5 байтов каждый, используя лямбды для определения Pи f; L(generator expression)=> [generator expression]; F, rи, Bкажется, используются только один раз за штуку и, таким образом, могут быть встроены.
DLosc

8

JavaScript (ES6), 369 343 байта

f=s=>(a=s.split`
`.map(s=>[...s]),m=Array(8),a.map((b,i)=>b.map((c,j)=>c>'#'||[i-j,i,j+i,j,j-i,-i,-i-j,-j].map((d,k)=>d>m[k]||(m[k]=d-1)))),[o,p,q,r,t,u,v,w]=m,g=(i,j,k,l,...p)=>i-k|j-l?a[i][j]=g(i+(k>i)-(k<i),j+(l>j)-(l<j),k,l,...p):1/p[0]?g(k,l,...p):'o',g(p,p-o,p,q-p,q-r,r,r-t,r,-u,t-u,-u,u-v,w-v,-w,o-w,-w,p,p-o),a.map(b=>b.join``).join`
`)

Объяснение: Строка разбита на массив символов (мне неясно, допустим ли ввод массива символов). Массив затем повторяется и позиции всех площадей земли. Ограничивающая линия , заданные уравнения x - y = o, x = p, x + y = q, y = r, y - x = t, -x = u, -x - y = v, -y = wопределяется таким образом, чтобы максимально возможный параметр выбран , где все земли находятся за пределами линии. Это имеет эффект включения острова в восьмиугольник. Координаты углов восьмиугольника легко вычисляются из параметров, и ячейки на его краю заполняются. Массив затем соединяется обратно в строку. Причина, по которой достаточно восьмиугольника, заключается в следующем:

   /o#     /o#     /o#
 |/o #   |/o #   |/ o#
 *o###   * o #   *  o#
/|o #   /|o #   /| o#
 |o#     |o#     |o#

Рассмотрим угол восьмиугольника. В некоторый момент вдоль двух краев путь будет ограничен землей, потому что мы построили восьмиугольник, чтобы коснуться земли как можно ближе. Если в самом углу нет земли, путь может проходить по альтернативным маршрутам, как показано справа, но это все равно то же количество ортогональных и диагональных шагов, поэтому расстояние не меняется.


Что делает ´ ... p´?
Роберт Фрейзер

@RobertFraser Техническое имя - деструктуризация массива. В этом случае, однако, он просто действует как rest of argumentsпараметр.
Нил

@Neil На самом деле, техническое имя является параметром отдыха . Тот же синтаксис используется для оператора распространения . (Вы используете оба, как ...pв разных местах.) Разрушение это что-то другое (хотя оператор распространения может быть использован при разложении).
Брайан МакКатчон

@BrianMcCutchon Вы правы, я имел в виду оператор распространения, но деструктуризация работает в списках аргументов в любом случае.
Нил

6

Python 3,5, 224, 263, 234 218 байт

Отбросьте еще 16 байтов, избавившись от вложенной функции и сделав ее однострочной.

def h(s,k=0,i=0):w=s.find('\n')+1;x=s.find('X')-w;k=k or x;d=[1,w+1,w,w-1,-1,-w-1,-w,-w+1]*2;u=s[:k]+'o'+s[k+1:];return['X'>s[k]and i<8and(h(u,k+d[i+2],i+2)or h(u,k+d[i+1],i+1)or h(u,k+d[i],i))or'',s][s[k]>'X'and k==x]

Гольф от 29 байтов:

def f(s):
 w=s.find('\n')+1;x=s.find('X')-w;d=[1,w+1,w,w-1,-1,-w-1,-w,-w+1]*2
 def h(s,k,i):u=s[:k]+'o'+s[k+1:];return['X'>s[k]and i<8and(h(u,k+d[i+2],i+2)or h(u,k+d[i+1],i+1)or h(u,k+d[i],i))or'',s][s[k]>'X'and k==x]
 return h(s,x,0)

Ввод - это одна строка, использующая «~» для океана, «X» для земли и «o» для границы. (Использование «X» сохраняет байт для «>» вместо «==»)

Менее гольф-версия с комментариями:

def f(s):
    w=s.find('\n')+1                         # width of one row
    x=s.find('X')-w                          # starting point
    d=[1,w+1,w,w-1,-1,-w-1,-w,-w+1]*2        # delta to add to current index to move in 
                                             # the 8 directions: E, SE, S, SW, W, NW, 
                                             # N, NE. Make it long to avoid
                                             # lots of modulo operations in 
                                             #    the recursive calls

    def h(s,k,i):                            # s is the island string, k the current
                                             # position, i the direction index
        if s[k]>'X'and k==x:                 # if back at the begining,
            return s                         #   return the map

        elif 'X'>s[k] and i<8:               # if there is water here, and haven't
                                             #  looped around,
            u=s[:k]+'o'+s[k+1:]              #  make a new map with an 'o' in the 
                                             #  current spot

            r = h(u,k+d[i+2],i+2)            # try a 90 degree right turn
            if r: return r

            r = h(u,k+d[i+1],i+1)            # try a 45 degree turn
            if r: return r

            r= h(u,k+d[i],i)                 # try straight ahead
            if r: return r

        return ''                            # this path failed

    return h(s,x,0)

@DLosc исправлено. (я должен удалить старый ответ?)
RootTwo

Ницца! (Да, вы должны удалить старый ответ - если кто-то хочет его увидеть, он может посмотреть историю изменений в сообщении.)
DLosc

5

C # 7 - 414 369 327 байт

Редактировать : переключился на 1D зацикливание, вычисления iи jна лету

Редактировать : изменил метод ввода, свернул таблицу поиска и переключился на четко определенные начальные границы ... и удалил бессмысленное пространство в последнем внешнем цикле for

using C=System.Console;class P{static void Main(){var D=C.In.ReadToEnd().Replace("\r","");int W=D.IndexOf('\n')+1,H=D.Length,z=H,k,q,c;int P()=>z%W*(k%3-1)+z/W*(k/3-1)+H;var B=new int[9];for(;z-->0;)for(k=9;k-->0&D[z]%7<1;)if(B[k]<=P())B[k]=P()+1;for(;++z<H;C.Write(q>9?'o':D[z]))for(q=k=9;k-->0;)q*=(c=P()-B[k])>0?0:c<0?1:2;}}

Попробуйте онлайн

Полная программа, принимает входной сигнал в стандартный дюйм, печатает на стандартный выход, использование #, .и o. Для каждой ячейки он вычисляет «профиль» (который представляет собой расстояние по 8 направлениям (кажется, для удобства он вычисляет девятое 0), и записывает максимум каждого из них. Затем записывает всю карту снова, и заменяет любую ячейку, которая находится как на границе, так и вне ее, на «о». Приведенный ниже код поясняет, как все это работает.

Согласно моему ответу « Спасти гусей от вымирания» , получается самый маленький восьмиугольник (действительное кругосветное плавание с наибольшей площадью), который ограничивает остров.

Обратите внимание : что раз в жизни я использую что-то из текущего десятилетия, и этот код требует C # 7 для компиляции. Если у вас нет C # 7, есть одна строка, которую нужно заменить, которая четко обозначена в коде.

Пример использования и вывод:

type t7.txt | IslandGolf1.exe

.........ooooooooooo....
........o....#......o...
.......o...#.#.##...#o..
......o....#.#.###.##.o.
.....o....########.##..o
....o.....############.o
...o.#....############.o
..o#.###.##############o
.o##.##################o
o.####################.o
o..##################..o
o.##################...o
o...################...o
o###################...o
o#####################.o
o.##################..o.
o####################o..
o#...##############.o...
o##...#############o....
o#.....###....#...o.....
.o.....#.........o......
..ooooooooooooooo.......

Отформатированный и закомментированный код:

using C=System.Console;

class P
{
    static void Main()
    {
        // \n 10
        // # 35
        // . 46
        // o 111


        var D=C.In.ReadToEnd().Replace("\r",""); // map

        int W=D.IndexOf('\n')+1, // width
            H=D.Length, // length
            z=H, // position in map (decomposed into i and j by and for P)
            k, // bound index
            q, // bound distance, and later cell condition (0 -> outside, 8 -> inside, >8 -> on boudary)
            c; // (free), comparison store

        // 'indexes' into a profile for the point z at index k
        // effectively {i=z%W,j=z/W,-i,-j,i+j,j-i,-i-j,i-j,0}[k] (inside order is a bit different) (0 const is always treated as 'inside bounds')
        // each non-zero-const entry describes the distance in one of the 8 directions: we want to maximise these to find the 'outer bounds'
        // the non-zero-const bounds describe 8 lines, together an octogen
        int P()=>z%W*(k%3-1)+z/W*(k/3-1)+H; // new C#7 local method syntax (if you don't have C#7, you can test this code with the line below instead)
        //k=0;System.Func<int>P=()=>z%W*(k%3-1)+z/W*(k/3-1)+H; // old lambda syntax (must pre-assign k to make static checker happy)

        var B=new int[9]; // our current bounds, each is initially null (must only call P() when on a #)
        // B[k] starts off a 0, P() has a +H term, and W+(H/W)<H for W >= 3, so B[k] is assigned the first time we compare it (H-i-j always > 0)

        for(;z-->0;) // for each cell
            for(k=9;k-->0& // for each bound
                D[z]%7<1;) // if this cell is #
                if(B[k]<=P())B[k]=P()+1; // update bound if necessary (add one so that we define the bound _outside_ the hashes)
        // z=-1
        for(;++z<H; // for each cell
                C.Write(q>9?'o':D[z])) // print the cell (if q > 9, then we are on the bounds, otherwise, spit out whatever we were before)
            // check we are not 'outside' any of the bounds, and that we are 'on' atleast one of them
            for(q=k=9;k-->0;) // for each bound
                q*=(c=P()-B[k])>0?0: // outside bound (q=0)    (??0 is cheaper than (int) or .Value)
                    c<0?1: // inside (preserve q)
                    2; // on bound (if q != 0, then q becomes > 9)
    }
}

самый большой восьмиугольник? или самый маленький?
Сардж Борщ

@SargeBorsch спасибо, исправил текст.
VisualMelon
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.