制作Voronoi图(ASCII变体)


24

假设您得到了一些分散在空白单元格矩形阵列中的不同大写字母。数组中的每个单元格都属于最接近它的字母,定义为以最小水平和/或垂直步长即可到达的字母-没有对角线步长。(如果一个像元与两个或更多个最近的字母等距,则它属于那些字母中第一个字母的字母顺序。其中一个大写字母的像元属于该字母。)边界像元是水平或垂直的像元与一个或多个不属于其自身字母的单元格相邻。

编写一个具有以下行为的过程子程序,生成一种Voronoi图 ...

输入:仅由点,大写字母和换行符组成的任何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-糟糕,我的错误-这是意外的。我将删除它们。
2013年

因此要清楚一点,这是曼哈顿度量图,不是欧几里德?在非欧式度量空间中,Voronoi图可能非常酷(请参阅此处,如果有副本,请启动Blender;它内置了一些有趣的度量)。
wchargin

@WChargin-本质上是。在这里,两个单元之间的“距离”只是从一个单元到另一个单元所需的最少步数,仅沿水平或垂直相邻单元之间步距。(它始终是一个非负整数。)如果我们将单元格想象成街道为零宽度且块为单位正方形的城市中的街道交叉口,则这是出租车的度量
res

Answers:


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

然后基本上,我们检查是否有一个.,即是否(可能)必须替换字符。

.'.'={              # 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是木板的一行的宽度(包括末尾的换行符,它将作为填充列回收)。rrange索引中所有字符的对象sn是一个索引偏移量的元组,可以到达一个字符的邻居(因此,如果您想让字母对角分布,则只需将其添加-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

@res:感谢您的建议。我已经r=range在函数的第一行的末尾(我设置了其他变量),并删除了之前错过的几个空格。我不确定您是否同时提到了两个,因为您似乎两次提到同一件事。而且,在Python 2.7中,它可以再短两个字符,因为您不需要在后面加上括号print(通常只保存1个字符,但是print"\n".join(...)可以使用)。
Blckknght

糟糕,我误贴了第二个。本来应该是s[y][x]for(删除空间),但是您似乎还是找到了它。
res

是的,那是我得到的另一个。我只是决定尝试更大的更改,然后转到1d而不是2d列表,结果节省了很多字符!
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为每个包含该单元格及其四个邻居的值的单元格生成一个数组。然后,我们使用Blckknght的技巧和min每个单元的大量可能性。这些是原始单元格值,如果尚未访问该单元格,则为任何邻居,如果已访问该单元格,.并且所有邻居均.等于或等于该单元格本身,则为a 。


由于应该由子程序进行打印,因此您可以更改return sprint 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
PeterTaylor,您是对的。我错过了。该站点需要更多的“编程难题”,而较少的“代码高尔夫”。
菲利普·斯科特·纪梵斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.