보로 노이 다이어그램 만들기 (ASCII 변형)


24

그렇지 않으면 빈 셀의 직사각형 배열에 흩어져있는 구별되는 대문자가 있다고 가정하십시오. 배열의 각 셀은 가장 가까운 문자에 속하며 대각선 단계없이 가장 적은 수의 가로 및 / 또는 세로 단계에서 도달 할 수있는 문자로 정의됩니다. 셀이 두 개 이상의 가장 가까운 문자와 같은 거리에 있으면 알파벳 순서로 첫 번째 문자에 속합니다. 대문자가있는 셀은 해당 문자에 속합니다. 경계- 셀은 가로 또는 세로로 셀입니다. 자신이 속한 문자에 속하지 않는 하나 이상의 셀에 인접합니다.

다음과 같은 동작 으로 프로 시저 서브 프로그램 을 작성하여 일종의 보로 노이 다이어그램을 생성하십시오 .

입력 : 점, 대문자 및 줄 바꿈으로 만 구성된 ASCII 문자열로, 인쇄시 점이 공백으로 작동하는 위에서 설명한 종류의 사각형 배열을 표시합니다.

Output : 각 빈 경계 셀이 소문자 버전의 문자로 대체 된 입력 문자열의 출력. (서브 프로그램이 인쇄를 수행합니다.)

실시 예 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; 흥미로운; 그러나 샘플 입력 및 출력의 셀에는 각 문자 사이에 하나의 패딩 공간이 있음을 알았습니다. 그게 필수입니까?
Doorknob

@DoorknobofSnow-죄송합니다. 실수가 아닙니다. 그것들을 제거하기 위해 편집하겠습니다.
res

분명히 이것은 유클리드가 아닌 맨해튼 메트릭 다이어그램입니까? Voronoi 다이어그램은 비 유클리드 메트릭스 공간에서 매우 멋질 수 있습니다 ( 여기 참조 또는 사본이있는 경우 Blender를 실행하십시오. 흥미로운 메트릭이 내장되어 있습니다).
wchargin

@WChargin-기본적으로 그렇습니다. 여기서 두 셀 사이의 "거리"는 한 셀에서 다른 셀로 걸어가는 데 필요한 최소한의 단계 일 뿐이며, 가로 또는 세로로 인접한 셀들 사이에서만 나옵니다. (항상 음수가 아닌 정수입니다.) 이것은 거리가 폭이 0이고 블록이 단위 제곱 인 도시에서 거리 교차로로 셀을 상상할 경우 택시 메트릭 입니다.
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)에 가장 가까운 5 개의 문자에서 모두 첫 번째 문자를 취합니다. 같으면 그렇지 않으면을 가져옵니다 ..

.                   # 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"라고 입력합니다. 네 번째 줄에서 "s"를 "s"에 넣습니다. "ui"를 "i"에 넣습니다. 기타 등등
res

@res "같은 순서로-알파벳 순서로 첫 번째"부분을 놓쳤다. 불행히도 정렬 작업이 안정적이지 않습니다. 그 문제를 해결하기 위해 몇 가지 문자를 추가했습니다.
Howard

7

파이썬 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"))

세 가지 부분이 있는데, 각 부분은 파이썬의 구문 때문에 자체 라인에 있어야합니다.

  1. 첫 번째 줄은 변수를 설정합니다. w보드의 행 너비입니다 (마지막 줄 바꿈을 포함하여 패딩 열로 재활용 됨). rrange모든 문자를 색인 하는 객체입니다 s. n는 문자의 이웃에 도달하기위한 인덱스 오프셋의 튜플입니다. 따라서 문자가 대각선으로 퍼지게 -w-1,-w+1,w-1,w+1하려면 튜플 에 추가 해야합니다. xstr.replace메소드 의 짧은 이름 으로, 이후 코드에서 여러 번 사용됩니다 ( x(s,*"xy")전통적인 문자 대신 문자를 저장 하는 데 사용 하기 때문에 호출이 이상하게 보입니다 s.replace("x", "y")). s그와 매개 변수 문자열뿐만 아니라,이 시점에서 약간 수정 한 .문자와 줄 바꿈에 의해 대체되고~문자 (모든 문자를 정렬하기 때문에). 한 줄의 채우기 ~문자도 끝에 추가됩니다. t나중에 "이전"버전에 대한 참조로 사용 s되지만 s시작시 같지 않은 것으로 초기화해야 하며 0은 한 문자 만 사용합니다 (더 많은 Pythonic 값은 None이지만 세 개의 추가 문자 임) .
  2. 두 번째 줄에는 s목록 이해를 사용하여 반복적으로 업데이트되는 루프가 있습니다. 의 색인에 대한 이해가 반복 s되면서 ~문자는 min이웃 의 색인으로 대체됩니다 . ~캐릭터가 다른 것으로 완전히 둘러싸인 경우 ~아무 것도하지 않습니다. 그것은 하나 개 이상의 문자 옆에 있었다면, 그것은 (선호하는 이들의 작은 될 것 "a"이상 "b"등). 문자로 바뀐 개행 ~은 계수 연산자로 색인을 감지하여 유지됩니다. 끝에있는 패딩 행은 목록 이해에 업데이트되지 않습니다 (인덱스 범위 r가에 추가되기 전에 계산 되었기 때문입니다 s). 대신, 신선한 행~이해가 끝나면 문자가 추가됩니다. 참고 s문자 목록이 아닌 루프의 첫 번째 패스 후 문자열이된다 (하지만 파이썬은 우리가 여전히 지수는 같은 방법으로 문자에서 얻을 수있는 유형에 대한 융통성이 있기 때문에).
  3. 마지막 줄은 다이어그램 내부를 비우고 인쇄 할 문자열로 문자를 다시 작성합니다. 먼저 다른 사본 자체 (또는 ~패딩 문자 )로 둘러싸인 문자는로 바뀝니다 .. 다음으로 문자는 모두 단일 문자열로 연결됩니다. 마지막으로, 패딩 ~문자는 개행 문자로 다시 변환되고 문자열이 인쇄됩니다.

아마도 r=range호출 가능한 프로 시저의 일부로 간주 되려면 함수 본문 내에 있어야하지만을 작성하여 문자를 저장할 수 있습니다 r=range;s=[l.replace. 당신은 서면으로 더 많은 문자를 짜내 수 if"~"==s[y][x]elseif"~"==s[y][x]else(파이썬 2.7과 나를 위해 BTW,이 달아) (422)의 총,
입술

@res : 제안 해 주셔서 감사합니다. 나는 놨는데 r=range내가 전에 놓쳤다 공간의 몇 떨어져 (I 다른 변수를 설정) 함수의 첫 번째 줄의 끝에, 면도. 당신이 똑같은 것을 두 번 언급 한 것처럼 두 가지를 모두 가지고 있는지 확실하지 않습니다. 그리고 파이썬 2.7에서는 괄호가 필요하지 않기 때문에 2 자 더 짧을 수 있습니다 print(보통 1 문자 만 저장하지만 print"\n".join(...)작동합니다).
Blckknght

죄송합니다. 두 번째 부분을 잘못 붙여 넣었습니다. 그것은 s[y][x]for(공간을 제거하는) 것으로 생각 되었지만 어쨌든 그것을 발견 한 것 같습니다.
res

그렇습니다, 그것은 내가 얻은 다른 것입니다. 방금 더 큰 변화를 시도하기로 결정하고 2d 목록이 아닌 1d로갔습니다.이 문자는 많은 문자를 저장하는 것으로 나타났습니다!
Blckknght

3

파이썬, 229226

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...
.........
""")

결과를 계산하기 위해 플러드 채우기를 수행합니다. 후행 for/ zip콤보 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................."          
     "........................."
     "........................."
     "........................."
     ]

이제 인덱서를 통해 데이터에 액세스 할 수 있도록 해당 데이터를 2 차원 배열로 변환해야합니다.

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

익숙하지 않은 언어로 작동하는 솔루션을 얻는 것이 좋습니다. 그러나 마지막 단계를 놓쳤습니다. 이것은 code-golf 이며, 목표는 가능한 한 가장 짧은 프로그램 (단일 문자 식별자, 컴파일에 반드시 필요한 공백 만)을 작성하는 것입니다.
Peter Taylor

3
PeterTaylor 당신이 맞아요. 나는 그것을 놓쳤다. 이 사이트에는 더 많은 "프로그래밍 퍼즐"과 "코드 골프"가 필요합니다.
Phillip Scott Givens
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.