Crea un diagramma Voronoi (variante ASCII)


24

Supponi di avere alcune lettere maiuscole distinte sparse in una matrice rettangolare di celle altrimenti vuote. Ogni cella dell'array appartiene alla lettera più vicina ad essa, definita come la lettera raggiungibile nel minor numero di passi orizzontali e / o verticali - nessun passo diagonale. (Se una cella è equidistante da due o più lettere più vicine, appartiene a qualunque di quelle lettere sia la prima in ordine alfabetico. Una cella con una lettera maiuscola appartiene a quella lettera.) Le celle di confine sono quelle che sono orizzontali o verticali adiacente a una o più celle che non appartengono alla lettera alla quale appartengono.

Scrivi un sottoprogramma di procedura con il seguente comportamento, producendo una sorta di diagramma Voronoi ...

Input : qualsiasi stringa ASCII composta solo da punti, lettere maiuscole e nuove righe, in modo tale che una volta stampata mostri una matrice rettangolare del tipo sopra descritto, con punti che agiscono come spazi vuoti.

Output : una stampa della stringa di input con ciascuna cella limite vuota sostituita dalla versione minuscola della lettera a cui appartiene. (Il sottoprogramma esegue la stampa.)

Esempio 1

Ingresso:

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

Produzione:

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

Uno schizzo che evidenzia i confini:

Uno schizzo che evidenzia i confini

Esempio 2

Ingresso:

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

Produzione:

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

Miglioramento del colore:

miglioramento del colore


1
+1; interessante; ma ho notato che le celle nell'input e nell'output del campione hanno uno spazio di riempimento tra ogni carattere. È un requisito?
Maniglia della porta

@DoorknobofSnow - Oops, errore mio - non era intenzionale. Modificherò per rimuoverli.
ris

Quindi, per essere chiari, questo è un diagramma metrico di Manhattan, non euclideo? I diagrammi di Voronoi possono essere piuttosto interessanti in spazi metrici non euclidei (vedi qui , o accendi Blender se ne hai una copia; ha alcune metriche interessanti incorporate).
mercoledì

@WChargin - In sostanza, sì. Qui la "distanza" tra due celle è solo il numero minimo di passaggi necessari per camminare da una cella all'altra, facendo un passo solo tra celle adiacenti orizzontalmente o verticalmente lungo la strada. (È sempre un numero intero non negativo.) Questa è la metrica del taxi se immaginiamo le celle come intersezioni di strade in una città le cui strade sono a larghezza zero e i cui blocchi sono quadrati di unità.
res

Risposte:


5

GolfScript, 138 144 137 caratteri

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

L'input viene dato al sottoprogramma come singola stringa nello stack. Sfortunatamente ho dovuto usare un a putscausa del requisito che la routine deve stampare il risultato.

Spiegazione del codice

Il blocco esterno scorre essenzialmente su tutta la posizione (x, y) in base alla dimensione dei rettangoli di input. All'interno del ciclo le coordinate xey vengono lasciate in pila ogni volta. Al termine di ogni riga, il risultato viene stampato sulla console.

:^              # 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

Il codice eseguito all'interno del loop assume prima il carattere corrispondente dell'input.

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

Quindi fondamentalmente controlliamo, se abbiamo un ., cioè se (eventualmente) dobbiamo sostituire il personaggio.

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

Ancora una volta, il codice interno inizia con un ciclo, ora su tutte le coordinate (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]

Il frammento di codice interno recente restituisce semplicemente la lettera (minuscola) del punto più vicino, date le due coordinate.

{                   # 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

Quindi dalle cinque lettere più vicine per le coordinate (x, y) (x, y + 1) (x + 1, y) (x, y-1) (x-1, y) prendi la prima, se non tutte sono uguale, altrimenti prendi a ..

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

Il tuo codice ha funzionato bene con l'esempio 1, quindi sono rimasto sorpreso quando ha fatto alcune celle in modo errato nell'esempio 2: in ciascuna delle prime tre righe, inserisce ".ui" dove "ui". dovrebbe essere, e nella quarta riga, mette "zs" dove "s". dovrebbe essere e inserisce "ui" dove "io". dovrebbe essere, ecc. ecc.
ris

@res Ha perso la parte "equidistante - prima in ordine alfabetico". Sfortunatamente, l'operazione di ordinamento non è stabile. Aggiunti alcuni caratteri per risolverlo.
Howard,

7

Python 3-424 422 417 332 295 caratteri:

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

Ci sono tre parti, ognuna delle quali deve essere sulla propria linea a causa della sintassi di Python:

  1. La prima riga imposta le variabili. wè la larghezza di una riga del tabellone (inclusa la nuova riga alla fine, che verrà riciclata come una colonna di riempimento). rè un rangeoggetto che indicizza tutti i caratteri in s. nè una tupla di offset dell'indice per raggiungere i vicini di un personaggio (quindi se vuoi consentire alle lettere di espandersi in diagonale, devi solo aggiungere -w-1,-w+1,w-1,w+1alla tupla). xè un nome breve per il str.replacemetodo, che viene utilizzato più volte nel codice successivo (le chiamate sembreranno comunque strane, dal momento che uso x(s,*"xy")per salvare i caratteri, piuttosto che il convenzionale s.replace("x", "y")). Anche sa questo punto la stringa di parametri viene modificata leggermente, .sostituendo i suoi caratteri e le nuove righe~personaggi (perché ordinano dopo tutte le lettere). Alla ~fine vengono aggiunti anche i caratteri di riempimento di una riga . tverrà successivamente utilizzato come riferimento alla "vecchia" versione di s, ma deve essere inizializzato su qualcosa di non uguale sall'inizio, e zero accetta solo un carattere (sarebbe un valore più Pythonic None, ma sono tre caratteri extra) .
  2. La seconda riga ha un ciclo che si aggiorna ripetutamente susando una comprensione dell'elenco. Mentre la comprensione scorre sugli indici di s, i ~personaggi vengono sostituiti da quelli mindei loro vicini. Se un ~personaggio è stato completamente circondato da altri ~, questo non farà nulla. Se era accanto a uno o più lettere, diventerà il più piccolo di loro (favorendo "a"sopra "b", etc.). Le nuove righe che sono state trasformate in ~caratteri vengono preservate rilevando i loro indici con l'operatore del modulo. La riga di riempimento alla fine non viene aggiornata nella comprensione dell'elenco (poiché l'intervallo di indici è rstato calcolato prima di essere aggiunto a s). Invece, una nuova fila di~i caratteri vengono aggiunti al termine della comprensione. Nota che sdiventa un elenco di caratteri anziché una stringa dopo il primo passaggio del ciclo (ma poiché Python è flessibile sui tipi, possiamo ancora indicizzarli per ottenere i caratteri allo stesso modo).
  3. L'ultima riga svuota le parti interne del diagramma e ricostruisce i caratteri in una stringa da stampare. In primo luogo, ogni lettera che è circondata solo da altre copie di se stessa (o ~personaggi dall'imbottitura) viene sostituita da .. Successivamente, i personaggi vengono tutti concatenati insieme in un'unica stringa. Infine, i ~caratteri di riempimento vengono riconvertiti in nuove righe e la stringa viene stampata.

Probabilmente r=rangedovrebbe essere all'interno del corpo della funzione per essere considerato parte di una procedura richiamabile, ma è possibile salvare i caratteri scrivendo r=range;s=[l.replace. Puoi anche spremere più personaggi scrivendo if"~"==s[y][x]elsee if"~"==s[y][x]else, per un totale di 422. (A proposito, questo ha funzionato per me con Python 2.7)
res

@res: grazie per questi suggerimenti. Ho messo r=rangealla fine della prima riga della funzione (dove ho impostato altre variabili) e mi sono rasato un paio di spazi che avevo perso prima. Non sono sicuro di aver ottenuto entrambi quelli a cui ti riferivi, poiché sembra che tu abbia menzionato la stessa cosa due volte. E, in Python 2.7 può essere più breve di altri due caratteri, dal momento che non hai bisogno delle parentesi dopo print(di solito questo salva solo 1 carattere, ma print"\n".join(...)funziona).
Blckknght,

Oops, ho incollato male quella seconda. Doveva essere s[y][x]for(rimozione di uno spazio), ma sembra che tu l'abbia trovato comunque.
res

Sì, è l'altro che ho. Ho appena deciso di provare un cambiamento più grande e sono andato a un elenco 1d anziché 2d, che si è rivelato per salvare un sacco di personaggi!
Blckknght,

3

Python, 229 226 caratteri

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

Riempie un'inondazione per calcolare il risultato. Il trailing for/ zipcombo genera un array xper ogni cella contenente il valore in quella cella e i suoi quattro vicini. Quindi usiamo il trucco di Blckknght e minun sacco di possibilità per ogni cella. Questi sono il valore della cella originale, tutti i vicini se la cella non è stata ancora visitata o un .se è stata visitata e tutti i vicini sono .o uguali alla cella stessa.


Poiché si suppone che il sottoprogramma esegua la stampa, è possibile passare return sa print s. Inoltre, non può y!=bessere modificato in y>b? Ciò renderebbe 226 personaggi, credo.
res

3

Ecco qui. Questo è il mio primo programma F #. Se ho perso una caratteristica della lingua, avvisami mentre sto ancora imparando.

Ecco il mio input di esempio

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

Ecco l'output

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

Ecco il codice Godere.

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

Ora abbiamo bisogno di convertire quei dati in un array a doppia dimensione in modo che possiamo accedervi attraverso gli indicizzatori.

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

Creiamo una matrice che rappresenti la proprietà di ogni cella

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

Cerchiamo di avere un metodo di utilità per vedere cosa è successo.

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

Creiamo un record per rappresentare dove risiede una particolare lettera maiuscola.

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

Ora vogliamo trovare tutte le lettere maiuscole.

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)

Mentre ci muoviamo, abbiamo bisogno di un concetto di direzione.

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. 

Mentre ci muoviamo, avremo bisogno di conoscere le dimensioni. Questo ci aiuterà a monitorare se stiamo andando oltre i limiti.

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

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

Pattern attivo: corrisponde ai criteri di una determinata cella.

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)

Ora stiamo arrivando alla tassa sull'ottone. Questo afferma la cellula!

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

Utilizzando il modello attivo, rivendica questa cella se non rivendicata e restituisci le coordinate delle celle adiacenti.

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

Stiamo iniziando a creare elenchi di questo data bag, creiamo un tipo per rendere le cose più chiare.

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

Dato un elenco di criteri per rivendicare le celle, passeremo in rassegna l'elenco restituendo le celle successive per rivendicare e ricorrere in tale elenco.

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)

Per ogni capitale, creare un criterio di rivendicazione in ciascuna direzione e quindi rivendicare in modo ricorsivo quelle celle.

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

Ogni programma ha bisogno di un principale.

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

Ben fatto per avere una soluzione funzionante in una lingua che non conosci. Tuttavia, hai perso il passaggio finale: questo è code-golf , il che significa che l'obiettivo è quello di scrivere il programma più breve possibile: identificatori a carattere singolo, solo lo spazio bianco che è strettamente necessario per compilare, ecc.
Peter Taylor

3
PeterTaylor hai ragione. Ho perso questo. Questo sito ha bisogno di più "Puzzle di programmazione" e meno "Code Golf".
Phillip Scott Givens,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.