Is my prison secure?

58

8

Your challenge is given an input of a prison layout to work out whether any of the prisoners can escape.

Input

Input may be in any reasonable format such as a string, array, array of arrays etc. The input will consist of three characters, in this case #, P and space. The input will not necessarily contain all three characters.

  • #: A wall
  • P: A prisoner
  • space: An empty space

An example input will look like:

#####
#   #
# P #
#   #
#####

Output

A truthy/falsey value of whether or not the prison is secure. The prison is only secure if it can hold all of the prisoners. If any prisoner can escape it is not secure.

A prisoner can escape if they are not fully enclosed by a wall. A diagonal joining is fully enclosed.

Test cases

############# Truthy
# P #  P#   #
#   #   # P #
#############

############# Truthy
# P    P    #
#   #   # P #
#############

############# Falsey
# P #  P#   #
#   #   # P #
########## ##

####          Truthy
#   #
 #   #
  # P ####
  ####

P             Falsey

###           Falsey
# #
# #
### P

TheLethalCoder

Posted 2017-06-14T12:41:41.823

Reputation: 6 930

8I have a feeling this is a duplicate or at least a similar challenge. Good challenge anyways. – John Dvorak – 2017-06-14T12:53:45.543

May we require that the input be padded to a rectangle? – John Dvorak – 2017-06-14T12:54:29.297

1@JanDvorak You can have leading and trailing whitespace yes. – TheLethalCoder – 2017-06-14T12:55:21.470

2@JanDvorak It might be but with my limited Google Fu I could not find a duplicate. – TheLethalCoder – 2017-06-14T12:55:54.260

@JanDvorak It also looks very familiar to me. I think I've seen a similar challenge in the Sandbox before, iirc. – Kevin Cruijssen – 2017-06-14T13:26:33.977

Could you add a test case where we have a closed box, but with a prisoner outside it? I.e. ####\n# #\n# #\n#### P – Kevin Cruijssen – 2017-06-14T13:31:14.603

@KevinCruijssen Added – TheLethalCoder – 2017-06-14T13:32:48.357

@JanDvorak Aren't you thinking about the challenge where you had to plan the layout of the prison? Can't find it anymore but I remember it well. – LiefdeWen – 2017-06-14T14:10:49.177

I would personally move the prisoner in the third test case one place to their right so that they no longer have a direct vertical path to freedom. – Jonathan Allan – 2017-06-14T14:15:26.307

1

Somehow related

– officialaimm – 2017-06-14T16:09:14.220

2related (Flood-fill a 2D grid) – Esolanging Fruit – 2017-06-14T16:32:23.103

3It would be good to have Falsey examples where both horizontal and vertical movement are required to escape. – xnor – 2017-06-14T20:38:44.943

1I wonder if there's any convenient way to interpret this input as a bitmap and use an image editor's flood fill tool on it. – user2357112 supports Monica – 2017-06-15T23:00:40.540

Possible duplicate of Thinking outside the box - Am I doing it right?

– FantaC – 2018-03-02T00:19:14.243

2@tfbninja Not really a duplicate. That one asks to try to have the program extrapolate from given data to determine if the word is in the box. This one is BFS floodfill to see if there are unenclosed spaces holding marked values. – HyperNeutrino – 2018-03-02T02:57:23.253

1

@tfbninja Certainly not a duplicate. The other question also restrict the wrapper to be in the shape of a box, and only give partial information. Still, useful as related.

– user202729 – 2018-03-02T06:07:56.843

@HyperNeutrino and user202729 I see your point, I will remove my close vote – FantaC – 2018-03-02T16:13:06.413

Answers

54

Snails, 13 bytes

!(t\P(o\ ),o~

Try it online!

Prints 0 for insecure prisons and the size of the input's bounding box for secure prisons.

The idea is to ensure that we can't find a path from a P to an out of bounds cell (~) moving only orthogonally (o) through spaces. The t is a teleport so that regardless where we attempt the match it tries all possible starting positions to find a P.

Martin Ender

Posted 2017-06-14T12:41:41.823

Reputation: 184 808

23The right tool. – Jonathan Allan – 2017-06-14T14:10:59.187

16

C# (.NET Core), 485 480 474 470 421 408 bytes

The absolutely wrong tool and approach, but nonetheless...

  • 7 bytes (and more) saved with the useful tips from TheLethalCoder.
  • 4 bytes saved by returning an integer.
  • 4 more bytes saved thanks (once again) to TheLethalCoder by replacing ' ' with 32 in the comparisons.
  • LOTS of bytes saved by refactoring the code.
  • 13 more bytes thanks to (guess who?) TheLethalCoder. :) I keep forgetting his tips and he keeps reminding me them.
m=>{var p='P';int a=m.Length,b=m[0].Length,i=0,j,x,y;var c=new System.Collections.Stack();for(;i<a;i++)for(j=0;j<b;j++)if(m[i][j]==p)c.Push(new[]{i,j});while(c.Count>0){var t=(int[])c.Pop();x=t[0];y=t[1];if(x<1|x>a-2|y<1|y>b-2)return 0;foreach(var v in new[]{-1,1}){var z=x>0&x<a-1&y>0&y<b-1;if(z&m[x+v][y]==32){m[x][y]=p;c.Push(new[]{x+v,y});}if(z&m[x][y+v]==32){m[x][y]=p;c.Push(new[]{x,y+v});}}}return 1;}

Try it online!

Basically I expand the positions of the P's whenever a white space is around until it reaches (or not) the border of the layout.

Some licenses:

  • I use a char[][] as the input for the layout.
  • Returns 0 as insecure and 1 as secure.

Charlie

Posted 2017-06-14T12:41:41.823

Reputation: 11 448

You can't take extra input to the function so you can assume the dimensions... Unless you can find a meta post to persuade me otherwise. – TheLethalCoder – 2017-06-14T14:41:43.313

1>0 and 1<0 are shorter than true and false. – TheLethalCoder – 2017-06-14T14:44:41.607

@TheLethalCoder more and more amazed with your tips! I was afraid that the extra parameters were too much, so I changed the answer. – Charlie – 2017-06-14T14:49:03.950

1Can ==0 become <1? You have at least 1 byte of irrelevant whitespace. Can you remove the new[]s? (Doesn't always work but sometimes does like in int[] n = {1,2,3};). – TheLethalCoder – 2017-06-14T14:51:39.217

Inputting a multidimensional array instead if a jagged one may save you some bytes i.e. char[,] and seeing as you are padding the input to a rectangle anyway it shouldn't be a problem. – TheLethalCoder – 2017-06-14T14:55:40.600

@TheLethalCoder if I change char[][] with char[,] then m.Length becomes m.GetLength(0) and m[0].Length becomes m.GetLength(1) which are larger, but maybe the rest of the code gets shorter enough. As for the irrelevant whitespace, I always slip some of them, no matter how much I try... :) – Charlie – 2017-06-14T15:04:48.813

@TheLethalCoder funny, the code remains the same length after the change, so I'll leave it as is. :) – Charlie – 2017-06-14T15:06:42.590

Me too, I've been meaning to create a small tool for removing the whitespace in C# but I've never gotten around to it. And haha unfortunate. – TheLethalCoder – 2017-06-14T15:08:24.810

1{m[x][y]= p; c.Push(new[] -> {m[x][y]=p;c.Push(new[] – TheLethalCoder – 2017-06-14T15:13:46.270

1You can compare chars to ints so I believe you can replace the ==' ' to ==32 to save bytes. You should be able to do this on similar comparisons as well. – TheLethalCoder – 2017-06-15T15:54:20.630

408: m=>{var p='P';int a=m.Length,b=m[0].Length,i=0,j,x,y;var c=new System.Collections.Stack();for(;i<a;i++)for(j=0;j<b;j++)if(m[i][j]==p)c.Push(new[]{i,j});while(c.Count>0){var t=(int[])c.Pop();x=t[0];y=t[1];if(x<1|x>a-2|y<1|y>b-2)return 0;foreach(var v in new[]{-1,1}){var z=x>0&x<a-1&y>0&y<b-1;if(z&m[x+v][y]==32){m[x][y]=p;c.Push(new[]{x+v,y});}if(z&m[x][y+v]==32){m[x][y]=p;c.Push(new[]{x,y+v});}}}return 1;} – TheLethalCoder – 2017-06-16T08:50:25.813

Sorry for all the small improvements but I'm not the best at spotting lots at once... – TheLethalCoder – 2017-06-16T08:51:21.127

@TheLethalCoder at first I used the new int[]{-1,1} in two foreach loops, but I reduced them to one and then I forgot to move the array into the declaration of the loop. And once again I forgot to move all the declarations of ints into the same place. – Charlie – 2017-06-16T09:05:05.840

Moving x and y into the main declaration didn't save us any bytes but it's could practice for golfing :) – TheLethalCoder – 2017-06-16T09:09:55.113

15

Perl 5, 69 bytes

-10 bytes thanks to @Grimy.

-2 bytes thanks to @Neil.

77 byte of code + -p0 flags.

/
/;$_=s/(P| )(.{@{-}})?(?!\1)(?1)/P$2P/s?redo:!/\A.*P|P.*\Z|^P|P$/m

Try it online!

Some short explanations:
The idea is to put a P everywhere the prisoners can go. If any P is on the first/last line, or the first/last column, then the prisoners can go there and therefor escape, which means the prison isn't secure.
s/(P| )(.{@{-}})?(?!\1)(?1)/P$2P/s replaces a space on the right of or bellow a P with a P, or a space on the left or on top of a P.
Finally, /\A.*P|P.*\Z|^P|P$/m checks if a line starts or ends with a P, or if there is a P on the first or the last line.

Dada

Posted 2017-06-14T12:41:41.823

Reputation: 8 279

cool approach using regexps! (but probably VERY expensive when the space grows) – Olivier Dulac – 2017-06-14T14:30:03.983

Actually, it's not that inefficient. In particular, it doesn't require a lot of backtracking, there are not * or +, the longest match it can do is the size of a line... Now of course if you compare with an approach more manual, based on arrays for instance, then yes it's quite inefficient! – Dada – 2017-06-14T14:47:33.797

Can you use \A and \Z to avoid having to have a separate regex with the /m modifier? – Neil – 2017-06-14T16:37:27.077

@Neil Right, thanks! I tend to forget about those assertions because ^ and $ are usually shorter, but here it's useful! :) – Dada – 2017-06-14T16:42:35.323

-1 byte by using /\n/ instead of /.*/, and @{-} instead of @{+} (where \n means a literal newline). – Grimmy – 2017-06-16T11:02:22.703

1-6 bytes by merging the two substitutions: s/P(.{@{-}})? | (.{@{-}})?P/P$1$2P/s. – Grimmy – 2017-06-16T11:17:07.150

1-2 bytes by golfing the merged substitution: s/(P| )(.{@{-}})?(?!\1)(?1)/P$2P/s. – Grimmy – 2017-06-16T11:22:46.110

2@Grimy very nice golfing of the regex! Thanks :) – Dada – 2017-06-17T15:29:41.943

7

JavaScript (ES6), 134 133 bytes

Takes input as an array of arrays of characters. Returns 0 (insecure) or 1 (secure).

f=a=>a.map((r,y)=>r.map((c,x)=>c>'O'&&[-1,1,0,0].map((X,i)=>(R=a[y+1-'1102'[i]])&&R[X+=x]?R[X]<'!'?R[o=2,X]=c:0:o=0)),o=1)|o&2?f(a):o

Test cases

f=a=>a.map((r,y)=>r.map((c,x)=>c>'O'&&[-1,1,0,0].map((X,i)=>(R=a[y+1-'1102'[i]])&&R[X+=x]?R[X]<'!'?R[o=2,X]=c:0:o=0)),o=1)|o&2?f(a):o

console.log(f([
  [...'#############'],
  [...'# P #  P#   #'],
  [...'#   #   # P #'],
  [...'#############']
]))

console.log(f([
  [...'#############'],
  [...'# P    P    #'],
  [...'#   #   # P #'],
  [...'#############']
]))

console.log(f([
  [...'#############'],
  [...'# P #  P#   #'],
  [...'#   #   # P #'],
  [...'########## ##']
]))

console.log(f([
  [...'####'],
  [...'#   #'],
  [...' #   #'],
  [...'  # P ####'],
  [...'  ####']
]))

console.log(f([
  [...'P']
]))

console.log(f([
  [...'###'],
  [...'# #'],
  [...'# #'],
  [...'### P']
]))

Arnauld

Posted 2017-06-14T12:41:41.823

Reputation: 111 334

Can the &&s just be &? – TheLethalCoder – 2017-06-14T15:36:38.083

@TheLethalCoder Not the first one, but the second one can be replaced by |. Thanks! – Arnauld – 2017-06-14T15:40:02.540

Didn't know the spread operator worked on strings. Cool! – aebabis – 2017-06-15T22:01:15.967

6

JavaScript (ES6), 121 bytes

f=s=>s==(s=s.replace(eval('/( |P)([^]{'+s.search`
`+'})?(?!\\1)[ P]/'),'P$2P'))?!/^.*P|P.*$/.test(s)&!/^P|P$/m.test(s):f(s)

Takes input as a newline-delimited rectangular string. Returns 0 for insecure and 1 for secure. Based on my answer to Detect Failing Castles, although it would be more efficient to test for an escaped prisoner at each step, rather than once they'd finished exploring the prison.

Neil

Posted 2017-06-14T12:41:41.823

Reputation: 95 035

2

Octave, 64 55 bytes

@(a,z=padarray(a,[1 1]))~nnz(bwfill(z==35,1,1,4)&z>35);

Try it online!

or

Verify all test cases!

Explanation:

z=padarray(a,[1 1])       %add a boundary(of 0s) around the scene
F = bwfill(z==35,1,1,4)   %flood fill the prison starting from the boundary
~nnz(F&z>35);             %if position of at least a prisoner  is filled then the prison is not secure 

rahnema1

Posted 2017-06-14T12:41:41.823

Reputation: 5 435

2

APL (Dyalog Classic), 40 bytes

{⊃2≠(××{1⊃⌈/⍵,⍉⍵}⌺3 3)⍣≡(⌽1,⍉)⍣4⊢'# '⍳⍵}

Try it online!

'# '⍳⍵ encode '#', ' ', 'P' as 0 1 2

(⌽1,⍉)⍣4 surround with 1s

(××{1⊃⌈/⍵,⍉⍵}⌺3 3)⍣≡ max-of-neighbours flood fill of non-zero cells

⊃2≠ do we not have a 2 at the top left?

ngn

Posted 2017-06-14T12:41:41.823

Reputation: 11 449

1

Stax, 35 bytesCP437

ä¬my■╡╤▲l@┤êr*⌠\¶ƒläå─▄¶√¿ [Uy⌠Só4↔

Try it online!

Surely golfing language without an internal to handle path-finding can do this as well!

Explanation

Uses the unpacked format to explain.

zLz]Y+Mys+y+{{{" P|P ""PP"Rm}Y!My!Mgphh' =
zLz]Y+Mys+y+                                  Surround the original matrix with four borders of whitespaces
            {                      gp         Iterate until a fixed point is found, return the single fixed point
             {              }Y!               Store the block in register Y and execute it
              {" P|P ""PP"Rm                  For each row, flood every space adjacent to a P with P.
                               My!            Transpose the matrix and do the flooding again
                                     hh' =    The final matrix has a space on the upper left corner that has not been flooded by P 

Weijun Zhou

Posted 2017-06-14T12:41:41.823

Reputation: 3 396

1

SmileBASIC, 154 146 bytes

I was hoping an answer using flood fill would be shorter than this.

DEF S P
FOR J=0TO 1X=1Y=1FOR I=0TO LEN(P)-1GPSET X,Y,-(P[I]=="#")GPAINT X,Y,-1,-J*(P[I]>@A)X=X*(P[I]>"31")+1INC Y,X<2NEXT
NEXT?!GSPOIT(0,0)GCLS
END

Replace 31 with the corresponding ASCII character.

12Me21

Posted 2017-06-14T12:41:41.823

Reputation: 6 110