# Shapeshifting boxes

I define four box styles:

+-----+   ooooooo    ^^^^^   *******
|  1  |   o  2  o   <  3  >  *  4  *
|     |   o     o   <     >  *     *
+-----+   ooooooo    vvvvv   *******


Write a program or function that given an integer and a string will find one of the above boxes inside the string and change its style into the box style requested by the user. For example:

1
This is a test document.
It ********* has
no *purpose* other than
dem*onstrat*ion.
*********


Becomes:

This is a test document.
It +-------+ has
no |purpose| other than
dem|onstrat|ion.
+-------+


You may assume that the input contains exactly one valid box that's at least 3x3. Your code must ignore incomplete/incorrect boxes:

ooooooooooooooooooooooooooooooooo
o This is not a box. o This is. o
ooooooooooo ooooo ooooooooooooooo

^^^^     ######
<NOPE>    #NOPE#
vVVv     ######


Shortest code in bytes wins.

• Just to be clear, in the last example, the box with the 2 capital Vs is invalid, but if those were to be made lowercase v's, would the box still be valid? Also, if there are multiple boxes, we only need to change 1, correct? – Kade Aug 25 '15 at 19:55
• @Vioz- Correct, the box with the capital Vs is only invalid because of the mismatched capitalization. You may assume that there will never be two or more valid boxes - there will always be exactly one valid box in the input. – orlp Aug 25 '15 at 19:58
• Does style 3 require spaces in the corners? – PurkkaKoodari Aug 26 '15 at 4:35
• @Pietu1998 Yes. – orlp Aug 26 '15 at 5:48
• It took a moment to figure out why I was getting an odd output from my "find the box" code... your example of what is/isn't a box at the end has two valid boxes in it: o This is. o, and o This is no (with lines of o above and below, of course). – Glen O Aug 26 '15 at 13:15

# Julia, 995818713 613 bytes

g=(s,n)->(w=map;f=t->(t[z=end];for i=1:z-2,j=eachmatch(r"([*o]|(\+)|(\ ))(?(2)-|(?(3)\^|\1))+\1",t[i],1>0),k=i+2:z N=j.match;N[M=end];p=N[1];J=j.offset;u=w(i->i[[J:J+M-1]∩[1:end]],t);try p%3<1?for l=matchall(r"^([*o])\1+\1",u[k]),q=3:endof(l) w(r->r[[1,q]],u[i:k])⊆["$p$p"]&&return(i,k,J,J+q-1)end:u[k]==replace(N,"^","v")&&w(r->r[[1,M]],u[i+1:k-1])⊆[p<33?"<>":"||"]&&return(i,k,J,M+J-1)end;end);o=ones(5)';T=split(s,'\n');(i,j,k,l)=f(T);u=w(collect,T);(a,u[i][r=k+1:l-1],u[j][r],b,c)=41+[2 4 4 83 83;70o;-9 53 77 19 21;o][n,:];u[i][I]=u[j][I=[k,l]]=a;w(e->(e[k]=b;e[l]=c),u[i+1:j-1]);join(w(join,u),'\n'));


Ungolfed with explanation:

function g(s,n)
# First, we define function f(t), which finds the box
function f(t)
# determine the number of rows of text
z=length(t)
# Get an iterator of all of the matches to iterate over
# Regex handles all four box styles
temp=i->eachmatch(r"([*o]|(\+)|(\ ))(?(2)-|(?(3)\^|\1))+\1",t[i],1>0)
# Iterate over rows up to third-last one (i)...
# and over any possible box-tops on each of those rows (j)...
# and all possible box-bottom rows for each possible box-top (k)
for i=1:z-2,j=temp(i),k=i+2:z
# N holds the matched box-top
N=j.match
# M stores the length of the match
M=length(N)
# p holds the first letter of the match, the corner character.
p=N[1]
# J holds the position of the first character of the match in row i
J=j.offset
# The intersection here allows truncation of each row to only those
# parts that lie within the valid range of the box-top
u=map(i->i[[J:J+M-1]∩[1:end]],t)
# A try block is being used to skip if a BoundsError is encountered
# this BoundsError will occur if a box cannot be formed due to
# a row not being long enough to form both sides or to form bottom
try
# This distinguishes between simple boxes (types 2 and 4)
# from fancy boxes (types 1 and 3), as code differs between them
if p%3<1 # "then" for simple boxes
# loop over l either doesn't run (if bottom won't form a match
# from position 1 within u) or holds the unique match
# then loop over q looks at all possible bottom-lengths
for l=matchall(r"^([*o])\1+\1",u[k]),q=3:endof(l)
# If box sides are found to match top and bottom...
if map(r->r[[1,q]],u[i:k])⊆["$p$p"]
# return the coordinates of the box
return(i,k,J,J+q-1)
end
end
else # "else" for fancy boxes
# If the bottom matches the top (replace fixes for type 3)...
if u[k]==replace(N,"^","v")
# ... and the edges are also there...
if map(r->r[[1,M]],u[i+1:k-1])⊆[p<33?"<>":"||"]
# return the coordinates
return(i,k,J,M+J-1)
end
end
end
end
end
end
# That defines function f(t), now for the replacement part of the code
# Input s is a single string with newlines, split into separate strings
T=split(s,'\n')
# Find the coordinates of the box using f(T)
(i,j,k,l)=f(T)
# u holds the same strings, but stored as char arrays
u=map(collect,T)
# Here, we have the appropriate replacement characters for each type
# with n determining which character from each array is taken
# Variable names are used here to make it clearer
corners =  ['+';'o';' ';'*'][n]
topedge =  ['-';'o';'^';'*'][n]
bottomedge=['-';'o';'v';'*'][n]
leftedge = ['|';'o';'<';'*'][n]
rightedge= ['|';'o';'>';'*'][n]
# Assign the appropriate characters in the appropriate places
u[i][[k,l]]=corners
u[j][[k,l]]=corners
u[i][k+1:l-1]=topedge
u[j][k+1:l-1]=bottomedge
# Iteration is required here because it's an array of arrays
for e=i+1:j-1
u[e][k]=leftedge
u[e][l]=rightedge
end
# All that's left to do is recombine to form a single string again
# we join each internal char array into single-line strings...
# then join the strings together with a newline delimiter, and return
return join(map(join,u),'\n')
end


Contrary to how I first approached it, this code will only work correctly for "valid" type numbers - 1, 2, 3, or 4. It is split into two parts - a box-finder, and a box-replacer. The box-finder code, function f(t), uses regex to locate tops and, for the simpler boxes (types 2 and 4), bottoms.

The first regex is the simplest way I could find to find the box-tops. Here's the logic of it:

r"([*o]|(\+)|(\ ))(?(2)-|(?(3)\^|\1))+\1"
([*o]|(\+)|(\ ))                        < This finds the first corner
( 2) ( 3)                         . if a + or space, conditionals
(    1         )                        . kick in, so they're captured
. separately
(?(2)-|           )     < If a +, top edge must be
. at least one -
(?(3)\^|\1)      < Otherwise, if a space, top
. edge must be at least one ^,
. otherwise, repeat the corner
+    < Allows more than one top-edge
. character
\1  < finish with the same corner
. char found at the start


The code is used like this:

julia> s="""This is a test document.
It************* has
no *purpose* other than
dem*onstrat*ion.
************o""";

julia> print(s)
This is a test document.
It************* has
no *purpose* other than
dem*onstrat*ion.
************o
julia> print(g(s,1))
This is a test document.
It*+-------+*** has
no |purpose| other than
dem|onstrat|ion.
**+-------+*o


Printing is not included in the function, as the output format isn't specified - I just return the string, you can print it afterwards as seen above.