Mutually fill in the blanks Given an input of two strings with sequences of underscores representing
corresponding words, output the sentences with the "blanks" filled in.

The best way to describe this challenge is by example. Here is an example
input:

programming _____________ and code golf
programming puzzles ______ code ____


And here is the corresponding output:

programming ___puzzles___ and code golf
programming puzzles _and__ code golf


For the purposes of this challenge, a "word" is defined as a sequence of one or
more lowercase letters, and a "blank" is defined as one or more underscores
(the input will always contain only lowercase letters, spaces, and
underscores). Words and blanks in the input strings are separated by single
spaces, and the sum of the number of words and blanks in the sentences will
always be equal.

The objective of the challenge is to fill all of the blanks with the correct
words, which are the words that occupy the same index in the other string
when split by spaces.


The word must be centered in the blank, as shown with the word "puzzles" in
the example above—an equal number of underscores remain on either side.
If the word cannot be exactly centered, the extra underscore may go either on
the left or the right (ex. the word "and" in the example above).
There will always be enough underscores for the word to fit, but there may be
exactly as many as the length of the word (ex. the word "golf" in the example
above).
There will never be a blank in the same position in both strings.


Input/output may be any of the following (input/output do not necessarily have
to be via the same method):


single string separated by any character
that is not alphabetic, a space, or an underscore (ex. newline or comma
separated string)
an array/list/etc. of two strings
two function/command line arguments (input only)


Since this is code-golf, the shortest code in bytes will win.

The example above can be used as a test case. Here is a larger test case
(second string in output may vary slightly due to different centering behavior):

lorem _____ dolor _____ amet _______________ adipiscing elit mauris dapibus tincidunt _____________________________ accumsan fringilla proin vulputate viverra lorem fermentum dictum
lorem ipsum ______ sit _______ consectetur _______________ elit mauris dapibus tincidunt metus accumsan fringilla proin vulputate viverra lorem ____________________________ dictum

lorem ipsum dolor _sit_ amet __consectetur__ adipiscing elit mauris dapibus tincidunt ____________metus____________ accumsan fringilla proin vulputate viverra lorem fermentum dictum
lorem ipsum dolor_ sit _amet__ consectetur __adipiscing___ elit mauris dapibus tincidunt metus accumsan fringilla proin vulputate viverra lorem _________fermentum__________ dictum
Retina, 102 100 93 88 bytes

Byte count assumes ISO 8859-1 encoding.

$
!¶$`
m`(?&lt;=^(\w+ )*)(?=_.*¶(?&lt;-1&gt;\w+ )*(\w+))
$2
(([a-z])+)(?&lt;-2&gt;_)*(_*)\3|!\D+
$3$1$3


The strings will are separated by a linefeed. If there is an odd number of underscores left, the extraneous one will be after the word.

Try it online!

Explanation

I guess this is the "duplicate-append-lookback-match-add-centre approach", or something close...

$
!¶$`


We start by duplicating the input (separated with a ! and a linefeed). The purpose of this is that we can then process both lines by fetching words from the next line (instead of having to treat the second line separately).

m`(?&lt;=^(\w+ )*)(?=_.*¶(?&lt;-1&gt;\w+ )*(\w+))
$2


This prepends the correct word to each gap. We start by counting the current word position with the lookbehind (?&lt;=^(\w+ )*) (the position is stored as the depth of group 1). Then the lookahead a) ensures that we're at the beginning of a gap by matching _, then skips to the next line with .*¶, matches the correct number of words with (?&lt;-1&gt;\w+ )* to get to the right position, and then matches the word found there with (\w+) into group 2.

(([a-z])+)(?&lt;-2&gt;_)*(_*)\3|!\D+
$3$1$3


This stage does three things:


It removes the underscores corresponding to each word length. This is done by counting the word length into group 2 with ([a-z])+ and then matching that many underscores (which are never written back).
It shifts the word to the centre of the gap by capturing half of the remaining underscores with (_*)\3 and writing $3$1$3 back.
It removes the duplicated input by matching !\D+ and replacing it with nothing.