Складіть діаграму Вороного (варіант ASCII)


24

Припустимо, вам надано кілька великих літер, розкиданих у прямокутному масиві інакше порожніх комірок. Кожна комірка масиву належить до найближчої до неї літери , визначеної як буква, доступна на найменшій кількості горизонтальних та / або вертикальних кроків - без діагональних кроків. (Якщо комірка рівновіддалена від двох або більше найближчих літер, вона належить до того, хто з цих букв спочатку в алфавітному порядку. Клітина з великою літерою в ній належить цій букві.) Межові клітини - це ті, що розташовані горизонтально або вертикально сусідні з однією або кількома клітинками, які не належать до букви, до якої вони самі належать.

Напишіть підпрограму процедури з такою поведінкою, створивши своєрідну діаграму Вороного ...

Введення : Будь-який рядок ASCII, що складається лише з крапок, великих літер та нових рядків, таким чином, що при друкуванні він відображає прямокутний масив, описаний вище, з крапками, що виконують функції пробілів.

Вихідні дані : роздруківка вхідного рядка з кожною порожньою осередковою коробкою, замінена на малу версію букви, до якої вона належить. (Підпрограма робить друк.)

Приклад 1

Вхід:

......B..
.........
...A.....
.........
.......D.
.........
.C.......
.....E...
.........

Вихід:

...ab.B..
....ab.bb
...A.abdd
aa...ad..
cca.ad.D.
..caeed..
.C.ce.edd
..ce.E.ee
..ce.....

Ескіз, що виділяє межі:

Ескіз, що підкреслює межі

Приклад 2

Вхід:

............................U...........
......T.................................
........................................
.....................G..................
..R.......S..........F.D.E............I.
.........................H..............
.....YW.Z...............................
......X.................................
........................................
........................................
......MN...........V....................
......PQ................................
........................................
.............L...............J..........
........................................
........................................
....C...........K.......................
........................................
..................................A.....
...........B............................

Вихід:

..rt.....ts...sg......gduu..U.....ui....
..rt..T..ts...sg......gddeu......ui.....
...rt...ts....sg......gddeeu....ui......
....rttts.....sggggggGgdde.euuuui.......
..R.rywss.S....sfffffFdDdEeeeeeei.....I.
...ryywwzs.....sf....fddhHhhhhhhhi......
..ryyYWwZzs..sssffff.fddh.......hi......
..rxxxXxzzs.sllvvvvvffddh....hhhhi......
rrrxxxxnzzssl.lv....vfddh...hjjjjii.....
mmmmmmmnnnnnl.lv.....vvdh..hj....jai....
mmmmmmMNnnnnl.lv...V...vvhhj.....jaai...
ppppppPQqqql...lv.......vhj......ja.ai..
ppppp.pq.ql....lkv.....vjj.......ja..aii
cccccppqql...L.lkkv...vj.....J...ja...aa
.....cpqqlll..lk..kvvvvj........ja......
......cccbbbllk....kkkkj.......ja.......
....C...cb..bk..K......kj.....ja........
.......cb....bk........kjjjjjja.........
......cb......bk.......kaaaaaa....A.....
.....cb....B...bk......ka...............

Поліпшення кольору:

посилення кольору


1
+1; цікавий; але я помітив, що комірки у вхідному та вихідному зразках мають один пробіл між символами. Це вимога?
Дверна ручка

@DoorknobofSnow - На жаль, моя помилка - це було ненавмисно. Я редагую, щоб видалити їх.
res

Отже, щоб було зрозуміло, це манхеттенська метрична діаграма, а не евклідова? Діаграми Вороного можуть бути досить крутими в неевклідових метричних просторах (див. Тут , або запустити Блендер, якщо у вас є копія; в неї вбудовано кілька цікавих показників).
wchargin

@WChargin - По суті, так. Тут "відстань" між двома клітинками - це лише найменша кількість кроків, необхідних для проходження від однієї клітини до іншої, переходячи лише між горизонтально- або вертикально-сусідніми клітинами по дорозі. (Це завжди невід’ємне ціле число.) Це показник такси, якщо ми уявляємо комірки як перехрестя вулиць у місті, вулиці якого мають нульову ширину та блоки якого є одиничними квадратами.
res

Відповіді:


5

GolfScript, 138 144 137 символів

:^n%,,{{^n/1$=2$>1<.'.'={;{@~@+@@+\{^3$?^<n/),\,@-abs@@-abs+99*+}++^'.
'-\$1<{32+}%}++[0..1.0..(.0]2/%..&,(\0='.'if}{@@;;}if}+^n?,%puts}/

Вхід подається підпрограмі як окрема рядок у стеці. На жаль, мені довелося скористатися putsчерез вимогу, щоб рутина повинна надрукувати результат.

Пояснення коду

Зовнішній блок, по суті, петляється над усім положенням (x, y) відповідно до розміру вхідних прямокутників. У циклі координати x і y щоразу залишаються на стеку. Після завершення кожного рядка результат друкується на консоль.

:^              # save input in variable ^
n%,,{{          # split along newlines, count rows, make list [0..rows-1] 
    ???             # loop code, see below
}+^n?,%puts}/       # ^n?, count columns, make list [0..cols-1], loop and print

Код, виконаний в циклі, спочатку приймає відповідний символ вводу.

^n/                 # split input into lines
1$=                 # select the corresponding row
2$>1<               # select the corresponding col

Тоді в основному ми перевіряємо, чи є у нас ., тобто якщо нам (можливо) доведеться замінити символ.

.'.'={              # if the character is '.'
    ;               # throw away the '.'
    ???             # perform more code (see below)
}{                  # else
    @@;;            # remove coordinates, i.e. keep the current character 
                    # (i.e. A, B, ... or \n)
}if                 # end if

Знову внутрішній код починається з циклу, тепер над усіма координатами (x, y) (x, y + 1) (x + 1, y) (x, y-1) (x-1, y)

{                   
    @~@+@@+\        # build coordinates x+dx, y+dy
    ???             # loop code
}++                 # push coordinates before executing loop code
[0..1.0..(.0]2/%    # loop over the coordinates [0 0] [0 1] [1 0] [0 -1] [-1 0]

Недавній фрагмент внутрішнього коду просто повертає (нижній регістр) літеру найближчої точки з урахуванням двох координат.

{                   # loop
    ^3$?^<          # find the current letter (A, B, ...) in the input string, 
                    # take anything before
    n/              # split at newlines
    ),              # from the last part take the length (i.e. column in which the letter is)
    \,              # count the number of lines remaining (i.e. row in which the letter is)
    @-abs@@-abs+    # calculate distance to the given coordinate x, y
    99*+            # multiply by 99 and add character value (used for sorting
                    # chars with equal distance)
}++                 # push the coordinates x, y
^'.
'-                  # remove '.' and newline
\$                  # now sort according to the code block above (i.e. by distance to letter)
1<{32+}%            # take the first one and make lowercase

Тож з п'яти найближчих літер для координат (x, y) (x, y + 1) (x + 1, y) (x, y-1) (x-1, y) беруть першу, якщо не всі рівний, інакше візьміть а ..

.                   # copy five letter string
.&,(                # are there distinct letters?
\0=                 # first letter (i.e. nearest for coordinate x,y)
'.'                 # or dot
if                  # if command

Ваш код зробив нормально з прикладом 1, тому я був здивований, коли він зробив деякі комірки неправильно в Прикладі 2: У кожному з перших трьох рядків він ставить ".ui", де "ui". повинно бути, а в четвертому рядку він ставить "zs", де "s". повинно бути, і ставить "ui", де "i". повинно бути і т. д.
res

@res пропустив частину "рівновіддаленої - спочатку в алфавітному порядку". На жаль, операція сортування не є стабільною. Додано кілька символів, щоб виправити цю.
Говард

7

Python 3 - 424 422 417 332 295 символів:

def v(s):
 w=s.find("\n")+1;n=(-1,1,-w,w);r=range(len(s));x=str.replace;s=x(x(s,*".~"),*"\n~")+"~"*w;t=0
 while s!=t:t=s;s=[min(s[i+j]for j in n).lower()if"~"==s[i]and(i+1)%w else s[i]for i in r]+["~"]*w
 print(x("".join(s[i]if any(s[i]!=s[i+j].lower()!="~"for j in n)else"."for i in r),*"~\n"))

Існує три частини, кожна з яких повинна бути в окремому рядку завдяки синтаксису Python:

  1. Перший рядок встановлює змінні. w- ширина ряду дошки (включаючи новий рядок в кінці, який буде перероблений у вигляді колонки для набивання). rце rangeоб'єкт, який індексує всі символи в s. nє кордоном зміщення покажчиків, щоб дістатись до сусідів персонажа (тому, якщо ви хочете дозволити літери поширюватися по діагоналі, вам просто потрібно буде додати -w-1,-w+1,w-1,w+1до кортежу). x- це коротке ім'я str.replaceметоду, який кілька разів використовується у пізнішому коді (дзвінки будуть виглядати дивно, оскільки я використовую x(s,*"xy")для збереження символів, а не звичайних s.replace("x", "y")). Рядок sпараметрів також незначно змінюється і в цей момент, його .символи та нові рядки замінюються на~символи (тому що вони сортують усі літери). Значення рядків, що валяються ~, також додаються до кінця. tпізніше буде використано як посилання на "стару" версію s, але її потрібно ініціалізувати на щось, що не дорівнює sна початку, а нуль бере лише один символ (більше Pythonic буде None, але це три зайвих символи) .
  2. Другий рядок має цикл, який неодноразово оновлюється sза допомогою розуміння списку. У міру того, як розуміння повторюється над індексами s, ~символи замінюються на minсусіди. Якщо ~персонаж був повністю оточений іншими ~s, це нічого не зробить. Якби це було поруч з одним або більше букв, він стане найменшим з них ( в користь "a"більш "b", і т.д.). Нові рядки, які були перетворені на ~символи, зберігаються шляхом виявлення їх індексів за допомогою оператора модуля. Рядок прокладки в кінці не оновлюється в розумінні списку (оскільки діапазон індексів r, був обчислений до їх додавання s). Натомість свіжий ряд~символи додаються після розуміння. Зауважте, що sпісля першого проходу циклу перелік символів стає скоріше списком символів (а тому, що Python є гнучким щодо типів, ми все одно можемо індексувати, щоб потрапити на символи таким же чином).
  3. Останній рядок вирівнює нутрощі діаграми і перебудовує символи в рядок, який слід надрукувати. По-перше, будь-який лист, оточений лише іншими його копіями (або ~символами з підкладки), замінюється .. Далі символи об'єднуються в один рядок. Нарешті, ~символи прокладки перетворюються назад у нові рядки та друкується рядок.

Можливо, ви r=rangeповинні знаходитись всередині функції функції, щоб вважатись частиною процедури дзвінка, але ви можете зберегти символи, написавши r=range;s=[l.replace. Ви також можете видавити більше символів в письмовій формі if"~"==s[y][x]elseі if"~"==s[y][x]else, в цілому 422. (до речі, це балотувався на мене з Python 2.7)
рес

@res: Дякую за ці пропозиції. Я поставив r=rangeв кінці першого рядка функції (де я встановив інші змінні) і відголив пару пробілів, які я пропустив раніше. Я не впевнений, чи отримав я обидва з тих, про кого ви посилалися, як ви, схоже, двічі згадували одне й те саме. І в Python 2.7 це може бути ще два символи коротшими, оскільки вам не потрібні дужки print(як правило, це зберігає лише 1 символ, але print"\n".join(...)працює).
Blckknght

На жаль, я неправильно вставив цю другу. Це повинно було бути s[y][x]for(видалення пробілу), але ви, здається, все одно знайшли його.
res

Так, це інше, що я отримав. Я просто вирішив спробувати більшу зміну і перейшов до списку 1d, а не 2d, який виявився, щоб врятувати купу символів!
Blckknght

3

Пітона, 229 226 символів

def F(s):
 e,f,b='~.\n';N=s.index(b)+1;s=s.replace(f,e)
 for i in 2*N*e:s=''.join(min([x[0]]+[[y.lower()for y in x if y>b],all(y.lower()in f+b+x[0]for y in x)*[f]][x[0]!=e])for x in zip(s,s[1:]+b,s[N:]+b*N,b+s,b*N+s))
 print s

F("""......B..
.........
...A.....
.........
.......D.
.........
.C.......
.....E...
.........
""")

Чи заповнює потоп для обчислення результату. Trailing for/ zipcombo генерує масив xдля кожної комірки, що містить значення в цій комірці та чотирьох її сусідів. Тоді ми використовуємо хитрість Blckknght та minкупу можливостей для кожної комірки. Це вихідне значення комірки, будь-які сусіди, якщо клітинку ще не відвідували, або .якщо вона була відвідана і всі сусіди є .або дорівнює самій клітині.


Оскільки підпрограма повинна робити друк, ви можете просто змінити return sна print s. Крім того, його не y!=bможна змінити y>b? Я думаю, це зробило б 226 символів.
res

3

Ось. Це моя перша програма F #. Якщо я пропустив особливість мови, будь ласка, попередити мене, коли я ще навчаюсь.

Ось мій зразок введення

 . . . . . . . . . . . . . . . . . . . . . . . . .
 . . . . . . . . . . . . B . . . . . . . . . . . .
 . . . . . . . . . . . . . . . . . . . . . . . . .
 . . . . . . . . A . . . . . . . . . . . . . . . .
 . . . . . . . . . . . . . . . . . . . . . . . . .
 . . . . . . . . . . . . . . . . C . . . . . . . .
 . . . . . . . . . . . . . . . . . . . . . . . . .
 . . . . . . . . . . . . . . . . . . . G . . . . .
 . . . . . . . D . . . . . . . . . . . . . . . . .
 . . . . . . . . F . . . . . . . . . . . . . . . .
 . . . . . . . E . . . . . . . . . . . . . . . . .
 . . . . . . . . . . . . . . . . . . . . . . . . .
 . . . . . . . . . . . . . . . . . . . . . . . . .
 . . . . . . . . . . . . . . . . . . . . . . . . .

Ось вихід

 . . . . . . . . . a b . . . . . . . b g . . . . .
 . . . . . . . . . a b . B . . . b b b g . . . . .
 . . . . . . . . . . a b . . . b c c c g . . . . .
 . . . . . . . . A . . a b . b c . . c g . . . . .
 . . . . . . . . . . . a b b c . . . c g . . . . .
 a a a a a a a a . . . a b c . . C . c g . . . . .
 d d d d d d d d a a a a b c . . . c g . . . . . .
 . . . . . . . . d d d d b c . . c g . G . . . . .
 . . . . . . . D d d d d d c . . c g . . . . . . .
 d d d d d d d d f f f f f f c . c g . . . . . . .
 e e e e e e e e e e e e e e c . c g . . . . . . .
 . . . . . . . . . . . . . e c . c g . . . . . . .
 . . . . . . . . . . . . . e c . c g . . . . . . .
 . . . . . . . . . . . . . e c . c g . . . . . . .

Ось код. Насолоджуйтесь.

// The first thing that we need is some data. 
let originalData = [
     "........................."
     "............B............" 
     "........................." 
     "........A................" 
     "........................." 
     "................C........"          
     "........................." 
     "...................G....." 
     ".......D................." 
     "........F................"           
     ".......E................."          
     "........................."
     "........................."
     "........................."
     ]

Тепер нам потрібно перетворити ці дані в масив подвійних розмірів, щоб ми могли отримати доступ до нього через індексатори.

let dataMatrix = 
    originalData
    |> List.map (fun st -> st.ToCharArray())
    |> List.toArray

// We are going to need a concept of ownership for each
// cell. 
type Owned = 
    | Unclaimed
    | Owner of char
    | Claimed of char
    | Boundary of char

Створимо матрицю, що представляє право власності на кожну клітинку

let claims =
    dataMatrix
    |> Array.map (fun row ->
        row
        |> Array.map (function
            | '.' -> Owned.Unclaimed
            | ch -> Owned.Owner(ch))
        )

Давайте нам корисний метод, щоб побачити, що сталося.

let printIt () =
    printfn ""
    claims
    |> Array.iter (fun row ->
        row |> Array.iter (function
            | Owned.Claimed(ch) -> printf " ." 
            | Owned.Owner(ch) -> printf " %c" ch
            | Owned.Boundary(ch) -> printf " %c" ch
            | _ -> printf " ." )
        printfn "")            

Давайте створимо запис, щоб представити, де знаходиться певна велика літера.

type CapitalLocation = { X:int; Y:int; Letter:char }

Тепер ми хочемо знайти всі великі літери.

let capitals = 
    dataMatrix
    |> Array.mapi (fun y row -> 
        row 
        |> Array.mapi (fun x item -> 
            match item with
            | '.' -> None
            | _ -> Some({ X=x; Y=y; Letter=item }))
        |> Array.choose id
        |> Array.toList
        )
    |> Array.fold (fun acc item -> item @ acc) List.empty<CapitalLocation>
    |> List.sortBy (fun item -> item.Letter)

По мірі руху нам потрібна концепція напрямку.

type Direction =
    | Left = 0
    | Up = 1
    | Right = 2
    | Down = 3   

// Function gets the coordinates of the adjacent cell. 
let getCoordinates (x, y) direction =
    match direction with
    | Direction.Left -> x-1, y
    | Direction.Up -> x, y-1
    | Direction.Right -> x+1, y
    | Direction.Down -> x, y+1
    | _ -> (-1,-1) // TODO: Figure out how to best throw an error here. 

Коли ми рухаємось, нам потрібно буде знати про розміри. Це допоможе нам контролювати, чи ми не виходимо за межі.

type Size = { Width:int; Height: int }    

// Get the size of the matrix. 
let size = {Width=originalData.Head.Length; Height=originalData.Length}

Активний шаблон: відповідає критеріям даної комірки.

let (|OutOfBounds|UnclaimedCell|Claimed|Boundary|) (x,y) =
    match (x,y) with 
    | _,_ when x < 0 || y < 0 -> OutOfBounds
    | _,_ when x >= size.Width || y >= size.Height -> OutOfBounds
    | _ ->                     
        match claims.[y].[x] with
        | Owned.Unclaimed -> UnclaimedCell(x,y)
        | Owned.Claimed(ch) -> Claimed(x,y,ch)
        | Owned.Boundary(ch) -> Boundary(x,y,ch)
        | Owned.Owner(ch) -> Claimed(x,y,ch)

Зараз ми переходимо до латуннього податку. На це стверджує клітина!

let claimCell letter (x, y) =         
    // Side effect: Change the value of the cell
    (claims.[y].[x] <- Owned.Claimed (System.Char.ToLower letter)) |> ignore

Використовуючи активний малюнок, заявіть про цю клітинку, якщо її не потрібно, і поверніть координати сусідніх комірок.

let claimAndReturnAdjacentCells (letter, coordinates, direction) =
    match coordinates with 
    | UnclaimedCell (x,y) ->         
        // Claim it and return the Owned object.
        claimCell letter coordinates // meaningful side effect
        // use Direction as int to allow math to be performed. 
        let directionInt = int direction;            
        Some(
            // [counter-clockwise; forward; clockwise]
            [(directionInt+3)%4; directionInt; (directionInt+1)%4]                 
            |> List.map enum<Direction>                 
            |> List.map (fun newDirection -> 
                (
                    letter, 
                    getCoordinates coordinates newDirection, 
                    newDirection
                ))
        )
    | Claimed(cx,cy,cch) when cch <> System.Char.ToLower letter-> 
        // If we find a "Claimed" element that is not our letter, we have 
        // hit a boundary. Change "Claimed" to "Boundary" and return the 
        // element that led us to evaluating this element. It is also a 
        // boundary. 
        (claims.[cy].[cx] <- Owned.Boundary (System.Char.ToLower cch)) |> ignore
        let reverseDirection = enum<Direction>(((int direction)+2)%4)
        Some[(
            cch,
            getCoordinates (cx, cy) reverseDirection,
            reverseDirection
        )]
    | _ -> None

Ми починаємо створювати списки цього пакета даних, давайте створимо тип, щоб зробити речі зрозумілішими.

type CellClaimCriteria = (char * (int * int) * Direction)

З огляду на перелік критеріїв для заявки клітинок, ми повторимо список, повертаючи наступні комірки до заявки та повторюючи їх у цьому списку.

let rec claimCells (items:CellClaimCriteria list) =
    items
    |> List.fold (fun acc item ->
        let results = claimAndReturnAdjacentCells item 
        if Option.isSome(results) 
        then (acc @ Option.get results) 
        else acc
        ) List.empty<CellClaimCriteria> 
    |> (fun l ->            
        match l with
        | [] -> []
        | _ -> claimCells l)

Для кожного капіталу створіть критерії претензії в кожному напрямку, а потім рекурсивно заявляйте ці комірки.

let claimCellsFromCapitalsOut ()=
    capitals
    |> List.fold (fun acc capital ->
        let getCoordinates = getCoordinates (capital.X, capital.Y)
        [Direction.Left; Direction.Up; Direction.Right; Direction.Down]
        |> List.map (fun direction ->                
            (
                capital.Letter, 
                getCoordinates direction, 
                direction
            ))
        |> (fun items -> acc @ items)) List.empty<CellClaimCriteria>
    |> claimCells

Кожна програма потребує основної.

[<EntryPoint>]
let main args = 
    printIt()
    claimCellsFromCapitalsOut()
    printIt()
    0

Молодці для отримання робочого рішення мовою, якою ви не знайомі. Однак ви пропустили останній крок: це код-гольф , а це означає, що метою є написання найкоротшої можливої ​​програми: однозначні ідентифікатори, лише пробіл, який потрібно строго компілювати тощо.
Пітер Тейлор

3
ПітерТейлор, ти маєш рацію. Я пропустив це. Цей сайт потребує більше "Пазлів програмування" та менше "Код гольфу".
Філіп Скотт Грінс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.