Make the Stretchy Snakes Kiss
Retina, 209 107 99 97 92 bytes
.(?=(.+)(?<=(?=<((\|+|(?<-5>\|(?=\1())?)+)[^|]+)+$(?(5)!)).+( )+\S+))\4
/ \
+` (.*~|~.*)
$1
For counting purposes, each line goes in a separate file, but you can run the code from a single file with the -s
flag.
Bringing the best features of .NET regex and Retina together: balancing groups, arbitrary length lookbehinds and repeated regex substitution.
Essentially, the long regex encodes a valid solution and the regex engine's backtracker finds one of the optimal ones for me.
Explanation
First, let's consider how we can find a valid solution (not necessarily producing the correct output) with a regex. We can use .NET's balancing groups to help us count the stretchy parts. Consider the following simpler regex:
\S+( )+.+(?<=(?(1)!)^([^|]+(\|+|(?<-1>\|)*))+>)
We can disect that.
\S+( )+.+
This matches the entire string, pushing one capture onto the group 1
stack for each space in the input. We will use that stack to ensure that the stretchy parts exactly fill the space captured into those groups.
Next is a lookbehind. The catch is that lookbehinds are matched from right to left in .NET (so that's how you should read them). This gives us the opportunity to traverse the string a second time, figuring out whether there's a subset of stretchy part that sums to the number of matched spaces. Going through the lookbehind from right-to-left:
>
This is just ensure that we're actually starting from the end of the string (the snake's tail).
(
[^|]+
(
\|+
|
(?<-1>\|)+
)
)+
For each stretchy part, this either just matches the entire part without doing anything (\|+
), or it matches the entire part while popping captures off stack 1
((?<-1>\|)*
). Having this alternation ensures that we can only fully extend a stretchy part or leave it unchanged, and not get stuff like |/\|
. Then we move on to the next stretchy part with [^|]+
.
(?(1)!)^
Finally, we ensure that we've traverse the entire string (both snakes), and that stack 1
is completely empty. I.e. that we've found a subset of stretchy part that exactly sums to the number of spaces we've captured earlier.
The backtracker will go back and forth through the string trying all combinations of unchanged and extended parts until the subset sum problem is solved. If such a subset doesn't exist, the lookbehind will fail. This will cause the backtracker to go back to the \S+( )+.+
part and try capturing one space less with ( )+
(which will just be cover by .+
instead). Due to the greediness of +
we therefore try filling as many spaces as possible.
You can check the validity of this approach with this slightly modified substitution:
\S+(( )+).+(?<=(?(2)!)^([^|]+(\|+|(?<-2>\|)*))+>)
=$1=
Which will give you a string =spaces=
with exactly the number of spaces that can be filled with the given snakes.
I've had to add some more trickery to actually expand the correct |
s. Basically, I want to replace all |
s which were matched using the (?<-1>\|)+
branch. The idea is to match an individual character, put the solver in a lookaround and set a flag if the match happens to be inside that branch. If that flag wasn't set we invalidate the match at the end to avoid replacement of other characters.
To do this, we use a bunch of nested lookarounds. Again, .NET's variable-length lookbehinds are matched from right to left, so if we nest lookaheads and lookbehinds, we can let the regex engine traverse the string several times. For golfing reasons, the solver is reversed in my actual solution (starting at the end, picking up the spaces from right to left, and then solving the subset sum from left to right), but otherwise the structure of the solver is exactly the same. Let's dissect the full regex:
.(?=(.+)...)
We match a single character, then capture the remainder of the string and move the cursor to the end of the string. We will use this group 1
later to check in the solver whether we're at the position of the match.
(?<=....+( )+\S+)
This is like the first part of the simple solver above, except that we pick up the spaces from right to left. The backtracking of the number of spaces works exactly the same as before, except that we're using group 5
now.
(?=<((\|+|(?<-5>\|(?=\1())?)+)[^|]+)+$(?(5)!))
This is the same as before, except we're going from left to right, and whenever we match a |
in the expanding branch, we check if it's the one being matched right with
(?=\1())?
This is an optional lookahead. It tries to match group 1
again (which, here, is only possible if we're right after the character being matched), and if we do, we capture an empty string into group 4
, which indicates that we did find the current character in one of the expanded bits. If \1
doesn't match, 4
won't capture anything, and the ?
ensures that the failing lookahead won't affect the solver at all.
Finally, after all the solving is done, we just check with \4
whether this lookahead was used. If so, we want to replace the current character with /\
.
One difficulty remains: removing the right amount of spaces. The shortest way to do this I've found so far is to insert a space along with the /\
and then get rid of one space between the tongues for each of those marker spaces in a separate step:
+` (.*~|~.*)
$1
CJam, 87 71 70 68 bytes
l:L"|"f&Qa%_,Y\m*\f{.{_,"/\\"*?}L'|%.\s" /"1$fe=:-\a+}${0a>}=~S%\S**
Try it online in the CJam interpreter.
How it works
l:L e# Read a line from STDIN and save it in L.
"|"f& e# Intersect each character with the string "|".
e# This pushes either "|" or "".
Qa% e# Split the resulting array at runs of "".
_, e# Compute the length of the resulting array (A).
e# This yield K, the number of stretchy parts.
Y\m* e# Push the array of all vectores in {0,1}^K.
\f{ e# For each vector V in {0,1}^K, push V and A; then:
.{ e# For each C in V and the corresponding P in A:
_, e# Compute the length of the stretchy part P.
"/\\"* e# Repeat "/\" that many times.
? e# If C, select P; else, select "/\"*length(P).
} e# This modifies A.
L'|% e# Split L at runs of vertical lines.
.\s e# Interleave the chunks of L and the modified A. Sringify.
e# In each iteration, this constructs a different modification of L,
e# with some stretched out stretchy parts.
" /"1$ e# Push " /" and a copy of the modified L.
fe= e# Calculate the number of spaces and slashes in the modifed L.
:- e# Subtract the number of occurrences.
\a+ e# Construct the array [difference modified-L].
} e#
$ e# Sort the array (by final number of spaces).
{0a>}= e# Find the first element greater than [0].
e# This skips over too far stretched snakes, where the number of
e# slashes is less than the number of spaces.
~ e# Dump the difference (D) and modified L on the stack.
S% e# Split L at runs of spaces.
\S* e# Construct a string of D spaces.
* e# Join the split L, delimiting by D spaces.
Ruby 191 187 186 170 162
->t{s=r=t.size
i=m=t[o=/ +/].size
(0...2**t.scan(y=/\|+/).size).map{|n|q=-1
x=t.gsub(y){|r|n[q+=1]<1?r:'\/'*r.size}
d=i+s-x.size
d<0||d<m&&r=x.gsub(o,' '*m=d)}
r}
This is a function that takes a string as a parameter and returns a string.
Online tests: http://ideone.com/uhdfXt
Here's the readable version:
# enumerates the possible states for any string containing snakes
COMBINATIONS =-> snake {
expandable_fragments = snake.scan /(\|+)/
(0...2**(expandable_fragments.size)).map{ |i|
x=-1
snake.gsub(/\|+/){|r| i[x+=1]>0 ? '\/'*r.size : r}
}
}
# finds the configuration in which snakes are closest to each other
KISS=
-> input {
result = input
s = input.size
initial_distance = min_distance = input[/ +/].size
COMBINATIONS[input].map{|c|
distance = initial_distance + s - c.size
if distance > -1 && distance < min_distance
min_distance = distance
result = c.gsub(/ +/,' '*distance)
end
}
result
}
In the golfed version, the main function is the equivalent of the KISS
function above, and the COMBINATIONS
function has been inlined.