Sides of a polygon

Given an ASCII-art shape made of the characters /\|_, your challenge is to return the number of sides it has.

A side is a straight line of one of those, for example:

\
\
\

/
/

|
|
|
|

_____


Are all sides.

For example, this shape:

  ___
/   \
/_____\


Has four sides. You can assume the input will be a single valid, closed shape - that is, things like this:

/
_
/ \

\
/

/_\
\_/
_   _
/_\ /_\


Will not occur, and the shape will never touch itself, so this is invalid:

  /\/\
/ /\ \
/ /__\ \
|______|


Testcases:

 /\
/__\ -> 3

________
/        \
|_________\  -> 5

___
|_| -> 4

/\/\/\/\/\
|________| -> 13

_   _
| |_| |
|  _  | -> 12
|_| |_|

/\
/  \
/\  /    \    /\
/  \/      \  /  \
/            \/    \
/____________________\ -> 7

_
/ |
_| |
/___/ -> 8

• 10/10 testcases, especially the last one
– okie
Jan 18 at 3:00

J, 49 bytes

-1 thanks to Jonah

[:+/@,i.@4(1==-{&(0 1,1,.i:1)@[|.!.0=)"{'_/|\'&i.


Try it online!

• '_/|\'&i. Map walls to 0…4.
• i.@4 For each possible wall …
• |.!.0= shift the corresponding bitmap …
• {&(0 1,1,.i:1)@[ into the direction the wall points to.
• 1==- Subtract this from the original bitmap and keep the 1s.
• [:+/@, Count the 1s.

Example for the _-lines in

____
|__|   original
____
|__|  shifted by 1 into a direction _ points to

X....  spots where _ is in the original image,
.X...  but not in the shifted one.


Two Xs, thus two _-lines.

• Very clever solution! Might want to add a picture of a shape before and after expansion, to show off the key central idea visually. Jan 18 at 1:43
• @Jonah thanks & done!
– xash
Jan 18 at 1:53
• 0>. -> 1= seems to work for -1 Jan 18 at 3:02

Retina 0.8.2, 94 bytes

(?<=(.)*)(\|(?=.*¶(?<-1>.)*\|)|\$$?=.*¶(?<-1>.)*.\$$|/(?=.*¶(?<-1>.)*(?<-1>/)))(?(1)^)

_+|\S


Try it online! Link includes test suite that takes double-spaced test cases. Explanation:

(?<=(.)*)(\|(?=.*¶(?<-1>.)*\|)|\$$?=.*¶(?<-1>.)*.\$$|/(?=.*¶(?<-1>.)*(?<-1>/)))(?(1)^)



Match any of |, \ or / with the corresponding symbol on the next line, but indented to line up with it and replace it with a space. (This is slightly golfier than using negative lookaheads.)

_+|\S


Count any remaining non-whitespace, except a run of _ only counts as one side.

Charcoal, 42 bytes

ＷＳ⊞υι≔⪫υ⸿θＰθＦθ⎇№⁺ ⊟ＫＤ²✳⊕｜⌕/|\ι⁴ιψι≔ＬＫＡθ⎚Ｉθ


Try it online! Link is to verbose version of code. Takes input as a list of newline-terminated strings. Explanation:

ＷＳ⊞υι


Input the list of strings.

≔⪫υ⸿θ


Join them with carriage returns.

Ｐθ


Print the result to the canvas without moving the cursor.

Ｆθ


Loop over each character.

⎇№⁺ ⊟ＫＤ²✳⊕｜⌕/|\ι⁴ιψι


If the character is a space or equal to the adjacent character in the appropriate direction (E, SE, S or SW as appropriate), then erase it, otherwise reprint it.

≔ＬＫＡθ


Count the number of remaining characters on the canvas.

⎚Ｉθ


Clear the canvas and output the result.

Python3, 598 bytes:

import re
def f(b,c,l,s=[]):
k=0
for u,v in{'/':[[-1,1],[1,-1]],'\\':[[1,1],[-1,-1]]}[l]:
if(x:=c[0]+u)>=0 and(y:=c[1]+v)>=0 and(x,y)not in s:
try:
if b[x][y]==l:k=1;yield from f(b,(x,y),l,s+[(x,y)])
except:1
if k==0:yield tuple(sorted(s))
def g(b):
b=[*filter(None,b.split('\n'))]
e=enumerate;S=set;R=sum;L=len
t=[((x,y),j) for x,a in e(b)for y,j in e(a)if j=='/'or j=='\\']
n={m for i in t for m in f(b,i[0],i[-1],[i[0]])}
return R(not any(S(i)&S(j)==S(i)and L(j)>L(i)for j in n)for i in n)+R(L(re.findall('_+',i))for i in b)+R(L(re.findall('\|+',''.join(i)))for i in zip(*b))


Very simple algorithm: find the number of diagonal edges (using f), and then the number of horizontal and vertical edges (using re.findall)

Try it online!