Composing fill in the blanks

18

1

Let us say that we have a particular set of functions on strings. These functions are kind of like fill in the blanks or madlibs, except that they only take one input and use that to fill in all of their blanks. For example we might have a function that looks like

I went to the ____ store and bought ____ today.

If we applied this function to the string cheese the result would be:

I went to the cheese store and bought cheese today.

We can represent these functions as a non-empty list of strings, where the blanks are simply the gaps in between strings. For example our function above would be:

["I went to the ", " store and bought ", " today."]

With this representation there is only one representation for every function of this sort and only one function for each representation.

A really neat thing is that the set of such functions is closed under composition. That is to say composition of two of our functions is always another one of these functions. For example if I compose our function above with

["blue ", ""]

(the function that prepends blue to the input) We get the function:

["I went to the blue ", " store and bought blue ", " today."]

These can get a little more complex though. For example if we compose the first function with

["big ", " and ", ""]

The result is

["I went to the big ", " and ", " store and bought big ", "and", " today."]

Task

Your task is to take two functions as described as non-empty lists of strings and output their composition as a non-empty list of strings.

For the purpose of this challenge a list can be any ordered container that permits duplicates and a string may be a native string type, a list of characters or a list of integers.

This is answers will be scored in bytes with fewer bytes being better.

Test cases

["","xy"] ["ab",""] -> ["ab","xy"]
["x","y","z"] ["a","b"] -> ["xa","bya","bz"]
["xy"] ["ab"] -> ["xy"]
["","",""] ["a",""] -> ["a","a",""]
["x",""] ["","",""] -> ["x","",""]
["x","y","z"] ["a","b","c"] -> ["xa","b","cya","b","cz"]
["x","x","x"] ["a"] -> ["xaxax"]
["w","x","y","z"] ["ab","cd","e"] -> ["wab","cd","exab","cd","eyab","cd","ez"]

Post Rock Garf Hunter

Posted 2019-07-29T13:30:42.180

Reputation: 55 382

1All 3 existing answers currently fail if some non-printable ASCII character is used in the input (SOH, TAB or LF, depending on the answer). So I think you should really decide if the input is restricted to printable ASCII or not. – Arnauld – 2019-07-29T15:51:20.187

@Arnauld Ok well as of now it is unrestricted and I have not seen a reason to change that so it will remain. – Post Rock Garf Hunter – 2019-07-29T15:57:50.760

@SriotchilismO'Zaic In that case all six current answers are invalid. – Kevin Cruijssen – 2019-07-29T16:54:27.740

2@KevinCruijssen mine's valid since zero is not a character. Lucky language feature helping out. – Jonathan Allan – 2019-07-29T16:56:07.020

@JonathanAllan Ah, I thought Jelly had mixed types so "0" and 0 would be the same, my bad. The other five are invalid, though. – Kevin Cruijssen – 2019-07-29T16:58:11.737

@KevinCruijssen I do not know C#, 05AB1E, perl 5, or Javascript well enough to verify the answers are incorrect or what they are doing but I have informed flawr that the Haskell answer has this issue. – Post Rock Garf Hunter – 2019-07-29T17:00:59.590

1@SriotchilismO'Zaic My 05AB1E was joining/splitting by newlines. The JavaScript and Haskell answers are joining/splitting by tabs, the C# answer by the unprintable character `` (SOH), so those are all invalid as well. I don't know Perl 5 well enough either, though. So that one might be valid. – Kevin Cruijssen – 2019-07-29T17:03:52.023

Does "unrestricted" refer to the ASCII range 1..127 or to the byte range 1..255? In other words, can we use non-ASCII characters in the range 128..255 as separators that will be guaranteed never to occur in the input strings? – Roman – 2019-07-30T11:54:31.333

3@Roman You cannot assume that any character will not appear in the input so that you may use it as a separator. You must actually solve the challenge. – Post Rock Garf Hunter – 2019-07-30T13:07:24.037

Answers

11

Jelly, 6 bytes

j0j@ṣ0

A dyadic Link accepting the first function representation on the right and the second function representation on the left which yields the resulting function representation. Each function representation is a list of lists of characters (Jelly has no other strings).

Try it online! (the full-program arguments are given in Python notation; strings become lists. The footer shows a Python representation of the Link's output.)

Here is a test-suite which reformats the Link's output like the inputs.

How?

Takes advantage of Jelly's mixed type lists to allow the entire domain of representations (any list of lists of characters) by using the integer zero as a place-holder:

j0j@ṣ0 - Link: b, a        e.g.    b = [['a','b'],['c','d'],['e']]
       -                   ...and  a = [['w'],['x'],['y'],['z']]
                             (i.e. test-case ["w","x","y","z"] ["ab","cd","e"])
j0     - join b with zeros         ['a','b',0,'c','d',0,'e']    
  j@   - join a with that          ['w','a','b',0,'c','d',0,'e','x','a','b',0,'c','d',0,'e','y','a','b',0,'c','d',0,'e','z']
    ṣ0 - split at zeros            [['w','a','b'],['c','d'],['e','x','a','b'],['c','d'],['e','y','a','b'],['c','d'],['e','z']
                             (i.e.: ["wab","cd","exab","cd","eyab","cd","ez"])

If we needed to deal with any of Jelly's mixed lists (including those of any depth or shape) we could use this eight byter: j,©⁹jœṣ® which uses the paired arguments as the place-holder.

Jonathan Allan

Posted 2019-07-29T13:30:42.180

Reputation: 67 804

5

Python 3.8 (pre-release),  60  58 bytes

lambda a,b:(v:='&'.join(a+b)+'$').join(b).join(a).split(v)

An unnamed function accepting two lists of strings, a and b, which returns a list of strings.

Try it online! Or see the test-suite.

How?

First forms a separator string, v, which cannot be found inside a or b. Then forms a string by joining up the strings in b with copies of v. Then forms a string by joining up the strings in a with copies of that. Finally splits that string at instances of v to give a list of strings.

While ensuring v is not in a or b we must also ensure that v wont make us split early in the case where all the strings in a and b are equal. To do so we form v by joining all the strings in both lists with instances of a string (here '&') and add an extra, different character (here '$'). Note that doing either in isolation is not enough as all strings in the inputs could equal the chosen character.

Jonathan Allan

Posted 2019-07-29T13:30:42.180

Reputation: 67 804

Could you give an example input where & is required? and using ''.join(a+b)+'$' is not enough? – Post Rock Garf Hunter – 2019-07-31T14:26:30.677

It took me a while but ['$','$'] ['$','$'] would be one. – Post Rock Garf Hunter – 2019-07-31T14:32:11.827

Yeah if all strings equal the chosen '$' character and the result will be more than one string we need a different character in there to avoid splitting early. – Jonathan Allan – 2019-07-31T15:35:08.853

5

Haskell, 78 bytes

(a:b:r)#t@(x:s)|s>[]=(a++x):init s++((last s++b):r)#t|z<-a++x++b=(z:r)#t
x#_=x

Try it online!

Laikoni

Posted 2019-07-29T13:30:42.180

Reputation: 23 676

2

05AB1E, 4 15 19 9 11 bytes

«TýR©ý¹sý®¡

Unlike the Jelly answer, 05AB1E's string "0", integer 0, and float 0.0 are all (somewhat) equal, so I can't split/join by an integer. This is why we had the +15 bytes as workarounds, although I've golfed it back to 9 bytes now. Thanks to @JonathanAllan for finding 2 bugs.

Try it online or verify all test cases.

Explanation:

«            # Merge the two (implicit) input-lists together
 Tý          # Then using a "10" delimiter join all strings together
   R         # Reverse this string
    ©        # Store this string in variable `®` (without popping)
     ý       # Use this string as delimiter to join the second (implicit) input-list
      ¹sý    # Then join the first input-list by this entire string
         ®¡  # And split it back on variable `®` so it's the expected list of strings
             # (after which this result is output implicitly)

Kevin Cruijssen

Posted 2019-07-29T13:30:42.180

Reputation: 67 575

2This fails if the input has newlines (OP has said that the input is currently unrestricted). – Erik the Outgolfer – 2019-07-29T14:36:22.633

@EriktheOutgolfer Every other answer has the same issue btw. – Kevin Cruijssen – 2019-07-29T16:53:32.703

@EriktheOutgolfer Can definitely be golfed some more, but made a quick and dirty fix for now. – Kevin Cruijssen – 2019-07-29T17:18:58.120

Cool. Could you use the idea of my Jelly alternative j,©⁹jœṣ® perhaps (uses the pair of arguments itself as a separator), or something similar? – Jonathan Allan – 2019-07-29T18:34:58.483

@JonathanAllan I was actually just about to post something like that when I realized that. :) – Kevin Cruijssen – 2019-07-29T18:36:45.333

...Nicely done :) – Jonathan Allan – 2019-07-29T18:42:12.337

@JonathanAllan Thanks. I should have thought a bit more before posting that 19-byter though.. Had a frustrating day at work and it's currently almost 9 o'clock in the evening, so I lack a bit of concentration. Anyway, I'm content with the current 9-byter version. :) – Kevin Cruijssen – 2019-07-29T18:45:36.627

1Uh, sorry about this ...it wont work if the input lists contain strings only containing newlines :( (it'll split early) – Jonathan Allan – 2019-07-29T18:55:38.090

1@JonathanAllan Lol.. Ah well, I guess I can only thank you for finding these bugs.. I hope it's fixed now and you won't find anything else.. Although I have the feeling you might.. – Kevin Cruijssen – 2019-07-29T20:37:48.727

2

Japt, 8 bytes

Adapts Jonathan's approach.

qVqN²)qN

Try it

qVqN²)qN     :Implicit input of arrays U & V (N=[U,V])
q            :Join U with
 Vq          :  V joined with
   N²        :    Push 2 to N (modifying the original), which gets coerced to a string
             >     e.g., N=[["a","b"],["c","d"]] -> N=[["a","b"],["c","d"],2] -> "a,b,c,d,2"
     )       :End join
      qN     :Split on the modified N, which, again, gets coerced to a string
             > e.g., N=[["a","b"],["c","d"],2] -> "a,b,c,d,2"

Shaggy

Posted 2019-07-29T13:30:42.180

Reputation: 24 623

What is N in this case? If I understand it correctly (using the search functionality of the TryIt-link), it repeats N two times (). It then uses that to join the second input V (VqN²), and then uses that entire string to join the first (implicit) input U (q...)). And finally splits the resulting string on N (qN). But what is N in this case? – Kevin Cruijssen – 2019-07-30T08:41:56.563

Ah wait, I think I've looked at the wrong p(...) method in the search. It appends the 2 to both inputs paired together doesn't it. Only results in [["w","x","y","z"],["ab","cd","e"],2], and it uses that entire list to join. Why does the final qN not leave the 2 to the result-list in that case? Or does modify the original N? – Kevin Cruijssen – 2019-07-30T08:43:45.500

1@KevinCruijssen, added an explanation but you pretty much have it figured out. And, yes, pushing elements to an array in JS modifies the original array. – Shaggy – 2019-07-30T09:05:15.170

2

Wolfram Language (Mathematica), 62 61 bytes

""<>#&/@Flatten[#~(R=Riffle)~I/.I->#2~R~I]~SequenceSplit~{I}&

Try it online!

-1 thanks to Roman


Though it's not a valid output, this returns a function that actually does the job.. (34 bytes)

(g=a""<>a~Riffle~#&)[#]@*g[#2]&

Try it online!

attinat

Posted 2019-07-29T13:30:42.180

Reputation: 3 495

161 bytes by recycling Riffle. – Roman – 2019-07-30T18:46:47.993

1

J, 44 43 42 29 bytes

_<;._1@,(;@}:@,@,.(,_&,)&.>/)

Try it online!

-13 bytes thanks to miles!

This approach uses integers and is due to miles.

original approach with strings

g=.[:}.@,,.
f=.(<@0<@;;._1@,];@g<"0@[)<@0<@g]

Try it online!

Note: I've adjusted -3 off the TIO to account for f=.

Uses Jonathan Allen's method, adapted for J.

This was surprisingly difficult to golf, since J doesn't have a built in "join" method, and I'd be curious to see if it can be improved significantly.

g is helper verb that gives us "join"

Jonah

Posted 2019-07-29T13:30:42.180

Reputation: 8 729

Working with list of integers as input, I found a 29 char solution, _<;._1@,(;@}:@,@,.(,_&,)&.>/) uses infinity _ as the sentinel value to know where to split <;._1. Joining is first done using reduce / to form one large box, then its just array shaping.

– miles – 2019-08-07T23:38:45.067

That's impressive. Thanks @miles. This one definitely felt like there was room to improve but I didn't see how. – Jonah – 2019-08-07T23:42:23.150

@miles Shouldn't the g&.:(a.&i.&.>) count toward the bytes or am I missing something? – Jonah – 2019-08-07T23:45:49.143

The OP mentioned that input could be as a list of characters or as a list of integers, so that helper function is just to convert from boxes of char arrays to boxes of int arrays for easier viewing – miles – 2019-08-07T23:48:48.387

Ah I forgot that, thanks – Jonah – 2019-08-07T23:49:43.407

1

Haskell, 62 bytes

[a]#(c:d)=(a++c):d
(a:b)#c=a:b#c
[a]%b=[a]
(a:b)%c=[a]#c#(b%c)

Try it online!

Here is my Haskell answer. It works on any type of list.

Post Rock Garf Hunter

Posted 2019-07-29T13:30:42.180

Reputation: 55 382

0

Perl 5 (-lp), 20 bytes

As @JonathanAllan commented, this is a full-program using, for IO, a tab as a list separator and a newline to separate the two lists.

chop($n=<>);s/  /$n/g

TIO

the tab and newline were chosen because more convenient to check test cases, otherwise could be changed to non-printable characters \1 and \2.

(-02l012p)

chop($n=<>);s//$n/g

TIO

How it works,

  • -02 : to set input record separator to \2
  • -l : to remove input separator from default argument $_ and to add output record separator to default output
  • -012 : to set output record separator to \012 (\n) so that output is easier to check
  • -p : to print default argument

  • $n=<>; : to read next record and to assign to $n

  • chop$n; : to remove separator from $n
  • s/\x1/$n/g : to replace all occurences of \1 with $n

Nahuel Fouilleul

Posted 2019-07-29T13:30:42.180

Reputation: 5 582

2My Perl is almost non-existent but I believe that this is a full-program using, for IO, a tab as a list separator and a newline to separate the two lists. How can it accept input with these characters? – Jonathan Allan – 2019-07-29T17:21:47.470

@JonathanAllan, you are correct, i did not have the time to add explanation when submitting i will try to do – Nahuel Fouilleul – 2019-07-30T06:28:06.987

0

JavaScript (Node.js), 85 79 bytes

([a,...b],[c,...d])=>b.map(e=>r.push(r.pop(r.push(r.pop()+c,...d))+e),r=[a])&&r

Try it online!

Neil

Posted 2019-07-29T13:30:42.180

Reputation: 95 035

0

JavaScript, 37 bytes

Also adapts Jonathan's approach.

a=>b=>a.join(b.join(a+=b+0)).split(a)

Try it online

Shaggy

Posted 2019-07-29T13:30:42.180

Reputation: 24 623

0

JavaScript (ES6),  62  59 bytes

Saved 3 bytes thanks to @Shaggy

This is a fixed version of Luis' answer (now deleted) to support all characters.

a=>b=>a.map(e=escape).join(b.map(e)).split`,`.map(unescape)

Try it online!

Arnauld

Posted 2019-07-29T13:30:42.180

Reputation: 111 334