ボロノイ図を作成する(ASCIIバリアント)


24

空白のセルの長方形配列に散在するいくつかの明確な大文字が与えられたとします。配列内の各セルは、それに最も近い文字に属し、最小数の水平および/または垂直ステップで到達可能な文字として定義され、斜めのステップはありません。(セルが2つ以上の最も近い文字から等距離にある場合、それらはアルファベット順で最初の文字に属します。大文字のセルはその文字に属します。)境界-セルは水平または垂直のセルです。自身が属する文字に属さない1つ以上のセルに隣接している。

次の動作を持つプロシージャサブプログラムを作成して、一種のボロノイ図を作成します...

入力:ドット、大文字、改行のみで構成される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; 面白い; しかし、サンプルの入力と出力のセルには、各文字の間に1つのスペースがあることがわかりました。それは要件ですか?
ドアノブ

@DoorknobofSnow-おっと、私の間違い-意図しないものでした。それらを削除するために編集します。
解像度

明確にするために、これはユークリッドではなく、マンハッタンの計量図ですか?ボロノイ図は、非ユークリッドメトリック空間で非常にクールです(こちらを参照するか、コピーがある場合はBlenderを起動してください。興味深いメトリックが組み込まれています)。
wchargin

@WChargin-基本的にははい。ここで、2つのセル間の「距離」は、一方のセルから他方のセルに移動するのに必要な最小ステップ数であり、途中で水平方向または垂直方向に隣接するセル間でのみステップします。(常に負ではない整数です。)これは、道路が幅ゼロでブロックが単位正方形である都市のセルを道路の交差点と想定した場合のタクシー指標です。
解像度

回答:


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、ルーチンが結果を出力する必要があるため、aを使用する必要がありました。

コードの説明

外側のブロックは、基本的に、入力長方形のサイズに従ってすべての位置(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

次に、基本的に、a .があるかどうか、つまり(おそらく)文字を置き換える必要があるかどうかをチェックします。

.'.'={              # 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]

最近の内部コードスニペットは、2つの座標が与えられると、最も近いポイントの(小文字の)文字を単に返します。

{                   # 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でいくつかのセルを誤って実行したときは驚きました。最初の3行のそれぞれで、「。ui」を「ui」に置きます。あるべきであり、4行目には、「s」を「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"))

3つの部分があり、それぞれがPythonの構文のために独自の行にある必要があります。

  1. 最初の行は変数を設定します。wボードの行の幅です(最後の改行を含み、パディング列としてリサイクルされます)。rは、のrangeすべての文字にインデックスを付けるオブジェクトですsn文字の隣を取得するためのインデックスオフセットのタプルです(したがって、文字が斜めに広がるようにしたい場合は-w-1,-w+1,w-1,w+1、タプルに追加するだけです)。メソッドのx短縮名でありstr.replace、後のコードで何度か使用されます(ただし、x(s,*"xy")従来の文字ではなく文字を保存するために使用するため、呼び出しは奇妙に見えますs.replace("x", "y"))。sそれとパラメータ文字列は、同様にこの時点でわずかに修正され.た文字や改行が置き換えられます~文字(すべての文字の後にソートされるため)。行のパディング~文字の価値も最後に追加されます。t後での「古い」バージョンへの参照として使用されますsが、それは等しくない何かに初期化する必要がありますs開始時に、ゼロが一つだけの文字(よりPython的値は次のようになりかかるNoneが、それは3つの余分な文字だが) 。
  2. 2行目にはs、リスト内包表記を使用して繰り返し更新するループがあります。内包表記がのインデックスで繰り返されるとs~文字はmin隣の文字に置き換えられます。~キャラクターが他のに完全に囲まれている場合~、これは何もしません。1つ以上の文字の隣にある場合は、それらの文字のうち最小のものになります(などが優先さ"a"れます"b")。~文字に変換された改行は、モジュラス演算子でインデックスを検出することにより保持されます。末尾のパディング行はリスト内包表記では更新されません(インデックスの範囲、rが追加される前に計算されたためs)。代わりに、の新しい行~理解が完了した後に文字が追加されます。sループの最初のパスの後、文字列ではなく文字のリストになることに注意してください(ただし、Pythonは型に柔軟性があるため、同じ方法で文字を取得するためにインデックスを作成できます)。
  3. 最後の行は、ダイアグラムの内部をくぼみ、文字を再構築して印刷する文字列にします。最初に、それ自体の他のコピー(または~パディングの文字)だけで囲まれている文字はすべてに置き換えられ.ます。次に、文字はすべて1つの文字列に連結されます。最後に、パディング~文字が改行に戻され、文字列が印刷されます。

おそらく、r=range呼び出し可能なプロシージャの一部と見なされる関数本体内にあるべきですが、を書くことで文字を保存できますr=range;s=[l.replace。また、書き込みにより多くの文字を絞り出すことができますif"~"==s[y][x]elseif"~"==s[y][x]else、422(Pythonの2.7と私のためにところで、このRAN)の合計
resは

@res:それらの提案をありがとう。r=range関数の最初の行(他の変数を設定する場所)の最後に配置し、以前見逃していたスペースをいくつか削除しました。あなたが同じことを二度言ったように見えるので、あなたが言及しているものの両方を得たかどうかはわかりません。また、Python 2.7では、後の括弧は必要ないため、さらに2文字短くすることができますprint(通常は1文字しか保存されませんが、print"\n".join(...)機能します)。
Blckknght

おっと、その2つ目を誤って貼り付けました。s[y][x]for(スペースを削除する)はずでしたが、とにかくそれを見つけたようです。
解像度

うん、それは私が得たもう一つです。もっと大きな変更をしようと決めて、2Dではなく1Dのリストに移動しました。
Blckknght

3

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

結果を計算するために塗りつぶしを行います。末尾for/ zipコンボは、xセルとその4つの隣接セルの値を含む各セルの配列を生成します。次に、Blkckknghtのトリックとmin各セルの可能性の束を使用します。これらは、元のセル値、セルがまだアクセスされていない場合はすべての隣接セル、または.アクセスされており、すべての隣接.セルがセル自体と等しいか等しい場合です。


サブプログラムが印刷を行うことになっているので、あなただけ変更することができますreturn sprint s。また、y!=bに変更することはできませんy>bか?226文字になると思います。
解像度

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
ピーターテイラーはあなたが正しいです。私は逃しました。このサイトには「プログラミングパズル」が多く必要であり、「コードゴルフ」は必要ありません。
フィリップスコットギブンズ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.