11
\$\begingroup\$

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 , 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
\$\endgroup\$
1
  • \$\begingroup\$ Nice bidiurnal challenge. \$\endgroup\$
    – Riker
    Commented Jan 29, 2016 at 17:50

9 Answers 9

7
\$\begingroup\$

Retina, 102 100 93 88 bytes

Byte count assumes ISO 8859-1 encoding.

$
!¶$`
m`(?<=^(\w+ )*)(?=_.*¶(?<-1>\w+ )*(\w+))
$2
(([a-z])+)(?<-2>_)*(_*)\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`(?<=^(\w+ )*)(?=_.*¶(?<-1>\w+ )*(\w+))
$2

This prepends the correct word to each gap. We start by counting the current word position with the lookbehind (?<=^(\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 (?<-1>\w+ )* to get to the right position, and then matches the word found there with (\w+) into group 2.

(([a-z])+)(?<-2>_)*(_*)\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.
\$\endgroup\$
5
\$\begingroup\$

Pyth, 30

jL;Cmm|*}J\_k.[lkhx#JdJkdCcR;Q

Takes input and outputs as a list of two strings. Uses a pretty basic split–zip–double map–centre–zip–join approach.

Try it here

Expanded:

jL;Cmm|*}J\_k.[lkhx#JdJkdCcR;Q   ##
                          cR;Q   ##  split
                         C       ##  zip
    mm                           ##  double map
      |*}J\_k.[lkhx#JdJkd        ##  centre
   C                             ##  zip
jL;                              ##  join

I'll explain more once I'm really satisfied that I can't golf this any more, although it should be pretty clear, given the ubiquity of the split–zip–double map–centre–zip–join approach and all.

\$\endgroup\$
3
  • 8
    \$\begingroup\$ Ahhh, the classic split-zip-double map-centre-zip-join approach. I fondly remember it being used as an introductory example in my Algorithms 101 lecture. \$\endgroup\$ Commented Jan 29, 2016 at 14:48
  • 3
    \$\begingroup\$ @MartinBüttner Yeah, I have some bad memories of it because I slept through that class, had to solve problems on the exam using the duplicate-append-lookback-match-add-centre approach instead. \$\endgroup\$ Commented Jan 29, 2016 at 14:54
  • 4
    \$\begingroup\$ I'll go apply some cold water to the burn. \$\endgroup\$ Commented Jan 29, 2016 at 15:11
4
\$\begingroup\$

Python 2, 109

def f(a,b):exec"print' '.join([x,y][x<'`'].center(len(x),'_')for x,y in zip(a.split(),b.split()));a,b=b,a;"*2

The function takes two strings as arguments and prints the output as in the examples. It uses a boring approach, with str.center(width, fillchar) doing most of the work.

Try it online.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ I don't think you need z, unless I'm missing something you can just do the swap after the print and inline z. \$\endgroup\$ Commented Jan 29, 2016 at 15:54
  • \$\begingroup\$ @FryAmTheEggman yeah, you're right. Thanks :) \$\endgroup\$
    – grc
    Commented Jan 29, 2016 at 22:50
2
\$\begingroup\$

Ruby, 111 109 characters

->l{l.map(&:split).transpose.map{|c|c[m=c[0]<c[1]?0:1]=c[1-m].center c[m].size,?_
c}.transpose.map{|s|s*' '}}

Input: array of 2 strings; output: array of 2 strings.

Sample run:

2.1.5 :001 > puts ->l{l.map(&:split).transpose.map{|c|c[m=c[0]<c[1]?0:1]=c[1-m].center c[m].size,?_;c}.transpose.map{|s|s*' '}}[[
2.1.5 :002 >       'programming _____________ and code golf',
2.1.5 :003 >       'programming puzzles ______ code ____',
2.1.5 :004 >       ]]
programming ___puzzles___ and code golf
programming puzzles _and__ code golf
\$\endgroup\$
1
\$\begingroup\$

JavaScript, 194 185 bytes

f=(m,n)=>(m=m.split` `,n=n.split` `,G=(x,i,a)=>x[0]!='_'?x:(b=(a?n:m)[i],s=x.length-b.length,(k='_'.repeat(s/2))+b+k+(s%2?'_':'')),H=(e,y)=>e.map((x,i)=>G(x,i,y)).join` `,[H(m,1),H(n)])

Takes two strings as parameters and outputs two strings as array/list

f=(m,n)=>(
    m=m.split` `,
    n=n.split` `,
    G=(x,i,a)=>
        x[0]!='_'?x:
        (
            b=(a?n:m)[i],
            s=x.length-b.length,
            (k='_'.repeat(s/2))+b+k+(s%2?'_':'')
        ),
    H=(e,y)=>e.map((x,i)=>G(x,i,y)).join` `,
    [H(m,1),H(n)]
)

document.body.innerHTML = '<pre>' +
    "f('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')<br>" +
    f('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') + "<br><br>" +
    "f('programming _____________ and code golf', 'programming puzzles ______ code ____')<br>" +
    f('programming _____________ and code golf', 'programming puzzles ______ code ____')
'</pre>'

\$\endgroup\$
1
\$\begingroup\$

Mathematica 223

There must be a shorter way to do this.

k=StringLength;m=StringSplit;
g=Partition[Riffle[m@#,m@#2],2]/.{{a_,a_}:> a<>" ",{a_,b_/; StringTake[b,1]=="_"}:> a<>" ",
{a_,b_}:>Table["_",Ceiling[z=(k@a-k@b)/2]]<>b<>""<>Table["_",Floor@z]<>" "}&;
s_~h~t_:={""<>g[s,t],""<>g[t,s]}

Sample run

h["programming _____________ and code golf", "programming puzzles ______ code ____"]

enter image description here

\$\endgroup\$
0
0
\$\begingroup\$

Gema, 208 203 characters

\B=@set{i;0}
<I>=@push{${v;f};$0}@incr{i}
\n=@set{v;s}@set{i;0}
 =
\E=@repeat{$i;@cmps{$f;$s;@set{f;@fill-center{$f;$s}};;@set{s;@fill-center{$s;$f}}}@set{F;$f ${F;}}@set{S;$s ${S;}}@pop{f}@pop{s}}$F\n$S

Just because Gema has the perfect function for this task: @fill-center{background;value}.

Input: 2 newline separated lines (no final newline); output: 2 newline separated lines (with trailing spaces – not seem to be forbidden).

Sample run:

bash-4.3$ echo -ne 'programming _____________ and code golf\nprogramming puzzles ______ code ____' |
> gema '\B=@set{i;0};<I>=@push{${v;f};$0}@incr{i};\n=@set{v;s}@set{i;0}; =;\E=@repeat{$i;@cmps{$f;$s;@set{f;@fill-center{$f;$s}};;@set{s;@fill-center{$s;$f}}}@set{F;$f ${F;}}@set{S;$s ${S;}}@pop{f}@pop{s}}$F\n$S'
programming ___puzzles___ and code golf 
programming puzzles _and__ code golf 
\$\endgroup\$
0
\$\begingroup\$

C, 197 bytes

#define c(w,y)l=strspn(w,"_"),r=strcspn(y," "),memcpy(w+(l-r)/2,y,r),w+=l,y+=r;
main(l,v,w,y,r)char**v,*w,*y;{for(w=v[1],y=v[2];*w;w++,y++)if(*w^*y)if(*w^95)c(y,w)else c(w,y)puts(v[1]);puts(v[2]);}

Output

$ ./a.out "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
\$\endgroup\$
0
\$\begingroup\$

ES6, 122 bytes

a=>a.map(s=>s.split` `).map((s,n,a)=>s.map((w,i)=>w<'a'?(l=w.length,t=w+a[n^1][i]+w,t.substr(t.length-l>>1,l)):w).join` `)

Takes an array of two strings as a single parameter and returns another array of two strings.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.