Cutpoint in un labirinto


13

Un labirinto è dato come una matrice di 0 (muri) e 1 (spazio percorribile) in qualsiasi formato conveniente. Ogni cellula è considerata connessa ai suoi 4 (o meno) vicini ortogonali. Un componente collegato è un insieme di celle calpestabili tutte collegate in modo transitivo tra loro. Il tuo compito è quello di identificare i punti di taglio - celle calpestabili che, se trasformate in muri, cambieranno il numero di componenti collegati. Emette una matrice booleana con 1-s solo in quelle posizioni. L'obiettivo è farlo nel minor numero di byte di codice.

La matrice di input sarà composta da almeno 3 righe e 3 colonne. Almeno una delle sue celle sarà un muro e almeno una sarà percorribile. La tua funzione o programma deve essere in grado di elaborare uno qualsiasi degli esempi seguenti in meno di un minuto su TIO (o sul tuo computer, se la lingua non è supportata da TIO).

in:
11101001
11011101
00000001
11101111
11110101
00011111
10110001
11111111
out:
01000000
00001001
00000001
00000101
00110000
00010000
00000000
11100000

in:
1111111111111111
1000000000000001
1111111111111101
0000000000000101
1111111111110101
1000000000010101
1011111111010101
1010000001010101
1010111101010101
1010101111010101
1010100000010101
1010111111110101
1010000000000101
1011111111111101
1000000000000001
1111111111111111
out:
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000

in:
1011010001111010
1111111011101101
1110010101001011
1111001110010010
1111010000101001
0111101001000101
0011100111110010
1001110011111110
0101000011100011
1110110101001110
0010100111000110
1000110111011010
0100101000100101
0001010101100011
1001010000111101
1000111011000010
out:
0000000000111010
1011110001001000
0000000000000011
0000000100010000
0000010000101000
0000001000000100
0000000011000000
1001100000011110
0000000001000010
0110100001000110
0000100101000010
1000100000000000
0100001000000100
0000000100100001
0000010000111000
0000010000000010

quindi, trova tutti i ponti in tutti i sottografi
HyperNeutrino

1
Penso che la sfida trarrebbe beneficio da un esempio dettagliato per una matrice più piccola.
Mr. Xcoder

1
@HyperNeutrino un ponte è qualcosa di diverso - è un bordo (non un vertice) la cui rimozione aumenta il numero di componenti collegati
ngn

1
Anche @HyperNeutrino, un sottografo non è esattamente lo stesso di un componente collegato
ngn

1
@Notatree Hai ragione. Ho fatto un errore. È troppo tardi per risolverlo ora, ma spero che non rovinerà il divertimento.
ngn

Risposte:


3

Stax , 40 byte

Çóê↓â.Φ}╞│*w<(♦◙¼ñ£º█¢,D`ì♥W4·☺╛gÇÜ♠╗4D┬

Eseguire ed eseguire il debug di casi di test

Questo programma accetta l'input come stringa separata da spazi contenente righe. L'output è nello stesso formato. Ecco la rappresentazione ascii scompattata.

{2%{_xi48&GxG=-}_?m}{'1'2|e{"12|21".22RjMJguHgu%

L'operazione fondamentale per contare un'isola funziona in questo modo.

  1. Sostituisci il primo '1'con a '2'.
  2. Regex sostituisce '12|21'con '22'.
  3. Dividi sugli spazi.
  4. Trasporre matrice.
  5. Ripetere da 2. fino a quando non viene ripetuta una stringa.
  6. Ripeti da 1. fino a quando non c'è più a '1'nella stringa. Il numero di ripetizioni è il numero di isole.

.

{               start map block over input string, composed of [ 01]
  2%            mod by 2. space and 0 yield 0. 1 yields 1. (a)
  {             start conditional block for the 1s.
    _           original char from string (b)
    xi48&       make copy of input with current character replaced with 0
    G           jump to unbalanced }, then return; counts islands (c)
    xG          counts islands in original input (d)
    =           are (c) and (d) equal? 0 or 1 (e)
    -           b - e; this is 1 iff this character is a bridge
  }             end conditional block
  _?            execute block if (a) is 1, otherwise use original char from string
m               close block and perform map over input
}               goto target - count islands and return
{               start generator block
  '1'2|e        replace the first 1 with a 2
  {             start generator block
    "12|21".22R replace "12" and "21" with "22"
    jMJ         split into rows, transpose, and rejoin with spaces
  gu            generate values until any duplicate is encountered
  H             keep the last value
gu              generate values until any duplicate is encountered
%               count number of iterations it took

Programma bonus da 44 byte - questa versione input e output usando il formato griglia.


elabora il secondo esempio in meno di un minuto sul tuo computer?
ngn

@ngn: fa tutti e tre gli esempi in 41s su questo laptop di fascia media in Chrome. Inoltre, ho appena corretto il collegamento principale. L'ho lasciato accidentalmente impostato su una versione precedente non funzionante.
ricorsivo

3

MATL , 26 byte

n:"GG0@(,w4&1ZIuz]=~]vGZye

L'input è una matrice numerica, che utilizza ;come separatore di riga.

Provalo online! Oppure verifica tutti i casi di test .

Spiegazione

n           % Implicit input: matrix. Push number of elements, N
:           % Range: gives [1 2 ... N]
"           % For each k in [1 2 ... N]
  GG        %   Push input matrix twice
  0@(       %   Write 0 at position k (in column-major order: down, then across).
            %   The stack now contains the original matrix and a modified matrix
            %   with 0 at position k
  ,         %   Do twice
    w       %     Swap
    4       %     Push 4. This specifies 4-element neighbourhood
    &1ZI    %     Label each connected component, using the specified
            %     neighbourhood. This replaces each 1 in the matrix by a
            %     positive integer according to the connected component it
            %     belongs to
    u       %     Unique: gives a vector of deduplicate elements
    z       %     Number of nonzeros. This is the number of connected components
  ]         %   End
  =~        %   Are they different? Gives true of false
]           % End
v           % Concatenate stack into a column vector
GZye        % Reshape (in column-major order) according to size of input matrix.
            % Implicit display

2

Perl 5 , -p0 105 101 96 93 90 89 byte

Utilizza banziché 1nell'input.

Assicurarsi che la matrice su STDIN sia terminata con una nuova riga

#!/usr/bin/perl -p0
s%b%$_="$`z$'";s:|.:/
/>s#(\pL)(.{@{-}}|)(?!\1)(\pL)#$&|a.$2.a#se&&y/{c/z />0:seg&/\B/%eg

Provalo online!

Utilizza 3 livelli di sostituzione!

Questa versione da 87 byte è sia in formato input che output più semplice da interpretare, ma non è in competizione poiché utilizza 3 caratteri diversi nell'output:

#!/usr/bin/perl -0p
s%b%$_="$`z$'";s:|.:/
/>s#(\w)(.{@{-}}|)(?!\1)(\w)#$&|a.$2.a#se&&y/{c/z />0:seg&/\B/%eg

Provalo online!

È facile salvare un altro byte (il smodificatore regex ) in entrambe le versioni utilizzando alcuni caratteri diversi (non alfanumerici) come terminatore di riga (anziché newline), ma ciò rende di nuovo l'input piuttosto illeggibile.

Come funziona

Considera la sostituzione

s#(\w)(.{columns}|)(?!1)(\w)#c$2c#s

Questo troverà due lettere diverse e una accanto all'altra in senso orizzontale o verticale e sostituirle con c. In un labirinto i cui percorsi sono costituiti interamente dalla lettera bnon accadrà nulla poiché le lettere sono uguali, ma non appena una delle lettere viene sostituita da un'altra (ad es. z) Quella lettera e un vicino verranno sostituiti ce l'applicazione ripetuta è un riempimento di piena del componente collegato con cda seme z.

In questo caso, tuttavia, non voglio un riempimento completo. Voglio riempire solo una delle braccia vicine z, quindi dopo il primo passo voglio zandare. Funziona già con la c$2csostituzione, ma in seguito voglio ricominciare un riempimento inondazione lungo un altro braccio a partire dallo stesso punto e non so più quale delle cs fosse originariamente z. Quindi invece lo uso

s#(\w)(.{columns}|)(?!\1)(\w)#$&|a.$2.a#se

b | aè c, b | cè cez | a è {. Quindi in un labirinto con percorsi costituiti be un seme znel primo passaggio bverrà sostituito da ce zverrà sostituito da {quale non è una lettera e non corrisponde \we quindi non causerà ulteriori riempimenti. Il ctuttavia manterrà un ulteriore diluvio-fill in corso e un braccio vicino del seme si riempie. Ad esempio a partire da

  b                      c
  b                      c
bbzbb       becomes    bb{bb
  b                      b
  b                      b

Posso quindi sostituire tutte le lettere c con alcune non lettere (ad es. -) E sostituirle {di znuovo per riavviare il riempimento:

  -                      -
  -                      -
bbzbb       becomes    cc{bb
  b                      b
  b                      b

e ripetere questo processo fino a quando tutti i vicini del seme non sono stati convertiti. Se dunque io ancora una volta sostituire {da ze inondazioni riempimento:

  -                      -
  -                      -
--z--       stays      --z--
  -                      -
  -                      -

Alla fine zresta dietro perché non c'è un vicino con cui fare una trasformazione. Ciò chiarisce cosa succede nel seguente frammento di codice:

/\n/ >                                    

Trova la prima newline. L'offset iniziale è ora in@-

s#(\w)(.{@{-}}|)(?!\1)(\w)#$&|a.$2.a#se

La regex discussa sopra con @{-}il numero di colonne (poiché plain @-confonde il parser perl e non sostituisce correttamente)

&&

Il /\n/ riesce sempre e la sostituzione è vero finché possiamo ancora alluvione-fill. Quindi la parte successiva &&viene eseguita se viene eseguito il riempimento di un braccio. In caso contrario, il lato sinistro restituisce una stringa vuota

y/{c/z / > 0

Riavviare il riempimento dell'inondazione e restituire 1 se il riempimento precedente ha fatto qualcosa. Altrimenti restituisce la stringa vuota. L'intero pezzo di codice è racchiuso all'interno

s:|.: code :seg

Quindi, se questo viene eseguito su una stringa iniziale $_con un znella posizione seed, il pezzo di codice all'interno verrà eseguito molte volte principalmente restituendo nient'altro che un1 ogni volta che un braccio vicino viene riempito. $_Viene efficacemente distrutto e sostituito da tanti 1s quanti sono i componenti collegati collegati z. Si noti che il ciclo deve essere eseguito fino alla somma delle dimensioni dei componenti + numero di tempi delle armi, ma va bene dato che sarà "numero di caratteri comprese le nuove righe * 2 + 1" volte.

Il labirinto viene disconnesso se non ci sono 1(stringa vuota, un vertice isolato) o se ci sono più di 1 braccio (più di 2 1s). Questo può essere verificato usando il regex /\B/(questo 0invece che 1nelle versioni precedenti di perl. È discutibile quale sia sbagliato). Sfortunatamente se non corrisponde questo darà una stringa vuota invece di 0. Tuttavia, è s:|.: code :segstato progettato per restituire sempre un numero dispari, quindi facendo un &con/\B/ questo darà 0o 1.

Tutto ciò che rimane è camminare sull'intero array di input e in ogni posizione di walkable seed con z e contare i bracci collegati. Questo è facile da fare con:

s%b%$_="$`z$'"; code %eg

L'unico problema è che nelle posizioni non percorribili viene mantenuto il vecchio valore. Dal momento che abbiamo bisogno di 0s lì, ciò significa che l'array di input originale deve avere 0posizioni non calpestabili e 0corrispondenze \wnella sostituzione originale e innescherebbe riempimenti. Ecco perché uso \pLinvece (solo lettere di corrispondenza).


2

Java 8, 503 489 459 455 byte

int R,C,v[][];m->{int c[][]=new int[R=m.length][C=m[0].length],r[][]=new int[R][C],i=R*C,t,u;for(;i-->0;)c[t=i/C][u=i%C]=m[t][u];for(;++i<R*C;r[t][u]=i(c)!=i(m)?1:0,c[t][u]=m[t][u])c[t=i/C][u=i%C]=0;return r;}int i(int[][]m){int r=0,i=0,t,u;for(v=new int[R][C];i<R*C;)if(m[t=i/C][u=i++%C]>v[t][u]){d(m,t,u);r++;}return r;}void d(int[][]m,int r,int c){v[r][c]=1;for(int k=-3,t,u;k<4;k+=2)if((t=r+k/2)>=0&t<R&(u=c+k%2-k/2)>=0&u<C&&m[t][u]>v[t][u])d(m,t,u);}

-18 byte grazie a @ceilingcat .

Spiegazione:

Provalo online.

int R,C,                    // Amount of rows/columns on class-level
    v[][];                  // Visited-matrix on class-level

m->{                        // Method with int-matrix as both parameter and return-type
  int c[][]=new int[R=m.length][C=m[0].length],
                            //  Create a copy-matrix, and set `R` and `C`
      r[][]=new int[R][C],  //  Create the result-matrix
      i=R*C,                //  Index-integer
      t,u;                  //  Temp integers
  for(;i-->0;)              //  Loop `i` over each cell:
    c[t=i/C][u=i%C]=m[t][u];//   And copy the values of the input to the copy-matrix
  for(;++i<R*C              //  Loop over the cells again:
      ;                     //    After every iteration:
       r[t][u]=i(c)!=i(m)?  //     If the amount of islands in `c` and `m` are different
        1                   //      Set the current cell in the result-matrix to 1
       :                    //     Else:
        0,                  //      Set it to 0
       c[t][u]=m[t][u])     //     And set the copy-value back again
    c[t=i/C][u=i%C]=0;      //   Change the current value in the copy-matrix to 0
  return r;}                //  Return the result-matrix

// Separated method to determine the amount of islands in a matrix
int i(int[][]m){
  int r=0,                  //  Result-count, starting at 0
      i=0,                  //  Index integer
      t,u;                  //  Temp integers
  for(v=new int[R][C];      //  Reset the visited array
      i<R*C;)               //  Loop over the cells
    if(m[t=i/C][t=i++%C]    //   If the current cell is a 1,
       >v[t][u]){           //   and we haven't visited it yet:
      d(m,i,j);             //    Check every direction around this cell
      r++;}                 //    And raise the result-counter by 1
   return r;}               //  Return the result-counter

// Separated method to check each direction around a cell
void d(int[][]m,int r,int c){
  v[r][c]=1;                //  Flag this cell as visited
  for(int k=-3,u,t;k<4;k+=2)//  Loop over the four directions:
    if((t=r+k/2)>=0&t<R&(u=c+k%2-k/2)>=0&u<C
                            //   If the cell in the direction is within bounds,
       &&m[t][u]            //   and it's a path we can walk,
         >v[t][u])          //   and we haven't visited it yet:
      d(m,i,j);}            //    Do a recursive call for this cell

1

Python 2 , 290 byte

lambda m:[[b([[C and(I,J)!=(i,j)for J,C in e(R)]for I,R in e(m)])!=b(eval(`m`))for j,c in e(r)]for i,r in e(m)]
def F(m,i,j):
	if len(m)>i>=0<=j<len(m[i])>0<m[i][j]:m[i][j]=0;F(m,i,j+1);F(m,i,j-1);F(m,i+1,j);F(m,i-1,j)
b=lambda m:sum(F(m,i,j)or c for i,r in e(m)for j,c in e(r))
e=enumerate

Provalo online!

-11 byte grazie a Rod
-11 byte grazie a Lynn


1
È più breve da utilizzare F(m,i,j)per ogni elemento, risparmiando 11 byte
Rod

for q in((i,j+1),(i,j-1),(i+1,j),(i-1,j)):-> for q in(i,j+1),(i,j-1),(i+1,j),(i-1,j):- rm parens esterno
ngn

Poiché Frestituisce implicitamente None, è possibile utilizzare F(m,i,j)or cinvece di [F(m,i,j)]and c.
Lynn

Inoltre, and m[i][j]può essere >0<m[i][j]e [q[:]for q in m]può essere eval(`m`).
Lynn

@Lynn intendi eval ('m')? non restituirebbe la stessa istanza dell'elenco?
ngn


1

Javascript 122 byte

Input / output come stringa multilinea.

m=>m.replace(/./g,(v,p,m,n=[...m],f=p=>n[p]==1&&(n[p]=0,v=f(p-1)+f(p+1)+f(p-w)+f(p+w)-1?1:0,1))=>(f(p),v),w=~m.search`\n`)

Per ogni cella percorribile, metti un blocco e prova a riempire le 4 celle vicine. Se la cella corrente non è un punto di taglio, a partire da qualsiasi vicino aperto li riempirà tutti. Altrimenti, avrò bisogno di più di un'operazione di riempimento per raggiungere tutte le celle vicine.

Meno golf

m=>{
  w = m.search('\n') + 1; // offset to the next row
  result = [...m].map( // for each cell
     ( v, // current value
       p  // current position
     ) => {
     n = [...m]; // work on a copy of the input
     // recursive fill function from position p
     // returns 1 if managed to fill at least 1 cell
     fill = (p) => {
        if (n[p] == 1)
        {
           n[p] = 0;
           // flag will be > 1 if the fill from the current point found disjointed areas
           // flag will be 0 if no area could be filled (isolated cell)
           var flag = fill(p+1) + fill(p-1) + fill(p+w) + fill(p-w);
           // v is modified repeatedly, during recursion
           // but I need the value at top level, when fill returns to original caller
           v = flag != 1 ? 1 : 0;
           return 1; // at least 1 cell filled
        }
        else
           return 0; // no fill
     }
     fill(p)
     return v // orginal value or modified by fill function
  }) 
}

Test

var F=
m=>m.replace(/./g,(v,p,m,n=[...m],f=p=>n[p]==1&&(n[p]=0,v=f(p-1)+f(p+1)+f(p-w)+f(p+w)-1?1:0,1))=>(f(p),v),w=~m.search`\n`)

var test=`in:
11101001
11011101
00000001
11101111
11110101
00011111
10110001
11111111
out:
01000000
00001001
00000001
00000101
00110000
00010000
00000000
11100000

in:
1111111111111111
1000000000000001
1111111111111101
0000000000000101
1111111111110101
1000000000010101
1011111111010101
1010000001010101
1010111101010101
1010101111010101
1010100000010101
1010111111110101
1010000000000101
1011111111111101
1000000000000001
1111111111111111
out:
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000

in:
1011010001111010
1111111011101101
1110010101001011
1111001110010010
1111010000101001
0111101001000101
0011100111110010
1001110011111110
0101000011100011
1110110101001110
0010100111000110
1000110111011010
0100101000100101
0001010101100011
1001010000111101
1000111011000010
out:
0000000000111010
1011110001001000
0000000000000011
0000000100010000
0000010000101000
0000001000000100
0000000011000000
1001100000011110
0000000001000010
0110100001000110
0000100101000010
1000100000000000
0100001000000100
0000000100100001
0000010000111000
0000010000000010
`.match(/\d[10\n]+\d/g);
for(i = 0; test[2*i]; ++i)
{
   input = test[2*i]
   check = test[2*i+1]
   result = F(input)
   ok = check == result
   console.log('Test '+ i + ' ' + (ok?'OK':'FAIL'),
   '\n'+input, '\n'+result)
}

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.