# Simple portmanteau with longest overlap

I've been using a library called Djangular recently, which integrates Django and Angular.

After a while I thought 'Wouldn't it be fun to programmatically generate portmanteau?'

Here are some examples (e.g. testcases):

Django + Angular -> Djangular
Camel + Leopard -> Cameleopard
Spoon + Fork -> Spoork
Ham + Hammer -> Hammer

The general rule is to find the first common letter working backwards on first word, and working forwards on the last word. Not all portmanteau can be found this way though...

I've deliberately picked 'simple' ones to generate (for instance we're ignoring things like Zebra + Donkey = Zedonk, which is too arbitrary to generate programmatically - the correct behaviour).

Ties can be resolved eitherway - suppose you have cart + ante - both carte, (since t is the last common letter in the first word) and cante, (since a is the first common letter of the last word) are value. Unfun loopholes are not allowed (including pulling 'solved' examples off wikipedia)

This is code golf, so shortest answer wins. I'll pick the winner a month from posting. Please post the output for all your code.

• Could you elaborate on the rules for double letters? It seems that they are omitted if they appear in the first word but are kept if they appear in the second? Or is it that double vowels are omitted but double consonants are kept? Aug 7 '15 at 7:20
• Read literally, "latest in first word, earliest in last word" would mean Spoon + Fork -> Spoork if we take the second o to be the latest in the first word. Similarly, the common t would mean Croissant + Donut -> Croissant. Another example, with earliest in last word this time, would be mock + cocktail = mococktail. I'm not sure if I'm just missing something major or... Aug 7 '15 at 12:29
• This question is still very confusing. The title asks for the longest overlap, and I'm not sure what the body of the question is asking for, but whatever it is seems to favour shorter overlaps. Aug 7 '15 at 17:28
• Perhaps we could use a system like this for choosing which overlap is best: the final 'score' of each option is calculated with (final word length + letters overlapped) * (1 - abs(amount of first word used - amount of second word used)). For example, django + angular = djangular uses 5/6 of django and all of angular, a difference of 1/6. Subtract that from (the final length of 9 + the overlap of 3), and you get a total score of 10, by far the highest possible. I'd be glad to clarify any questions about this system! Aug 7 '15 at 18:02
• I still don't understand this part: The general rule is to find the first common letter working backwards on first word, and working forwards on the last word. Does one of them take priority?
– xnor
Aug 7 '15 at 22:04

# Python 3, 128 79 bytes

Let me know if I got anything wrong and critiques are always welcome.

def f(a,b):
c=len(a)-1-a[::-1].find(b[1])
if a[-1]==b[0]:return a[:-1]+b
if a[c]==a[c-1]:c-=1
return a[:c]+b[1:]


Called with f(word 1,word 2) and returns the portmanteau. Will throw an error if the inputs aren't compatible. Works for all test cases.

Edit:

If I've read the edits to this question correctly, this updated code should work

f=lambda a,b:a[:-1]+b if a[-1]==b[0] else a[:len(a)-1-a[::-1].find(b[1])]+b[1:]


## CoffeeScript, 111 bytes/characters

Source:

l="toLowerCase";f=(a,b)->return a.substr(0,i)+b.substr(j)[l]()for c,j in b when(i=a[l]().lastIndexOf(c[l]()))>0


Try it online!

To use:

alert("
#{f("Django","Angular")}
#{f("Camel","Leopard")}
#{f("Spoon","Fork")}
#{f("Ham","Hammer")}
")


I hoped it will be shorter, but.... ok, still better than PHP :D

The examples result in (using the alert statement above):

Djangular Cameleopard Spoork Hammer


Explanation (Might not be valid coffescript):

l="toLowerCase"; # Keeps code shorter (and DRY :D )
f=(a,b)->
return a.substr(0,i)+b.substr(j)[l]() # Return the result
for c,j in b # Postfix for loop
when(i=a[l]().lastIndexOf(c[l]()))>0
# Run the return statement only when the
# character c is found (also stores its last position in i)


Doesn't handle case, but that doesn't seem to be a big deal? Tell me if it is.

a!b=maybe(init a!b)((a++).(dropb))$last alookupzip b[1..]  Then "Django"!"Angular" is "Djangular". • Oh my, I am horrible at this. Agree that case makes this challenge "ugly", so 61 vs my 99... I learn to use lookup now, and infixes. And I just confused myself cause infix operator notation in Haskell ^= code in markdown. Aug 20 '15 at 18:19 # PHP (270 bytes) function portmanteau($s1, $s2){$r = ''; $y = false; foreach(str_split($s1) as $c1){$r .= $c1; foreach(str_split($s2) as $c2){ if($y){ $r.=$c2; } if(strtolower($c1)==strtolower($c2)){ $y = true; continue; } } if($y){ break; } } if(!$y)$r .= $s2; return ucfirst($r); }


Ungolfed:

function portmanteau($str1,$str2){
$resultingString = '';$letterMatched = false;
foreach(str_split($str1) as$char){
$resultingString .=$char;

foreach(str_split($str2) as$char2){
if($letterMatched){$resultingString.= $char2; } if(strtolower($char)==strtolower($char2)){$letterMatched = true;
continue;
}
}
if($letterMatched){ break; } } if(!$letterMatched) $resultingString .=$str2;

# rs, 24 bytes

(.*)(.).* .*?(\2.*)/\1\3


To copy the initial post from @refi64 here's the live demo and all test cases. The only improvement is that you can spare the \2 back reference by including it in the third group.

I didn't want to pollute with an answer that's actually a comment, but some person on Stack Exchange decided that you can't comment when you're a newcomer and that edits should be refused, because that should obviously be a comment. Thus, sorry for that misplaced content.

• Hello, and welcome to PPCG! I'll comment for you if you want, and when you get 50 rep, you can comment too. However, Stack Exchange has a Be Nice policy, and unfortunately calling someone retarded is against the policy. Jul 28 '17 at 18:01
• P.S. You can comment on your own posts. Jul 29 '17 at 6:39
• xD They thought it all through! Thanks for the comment :D Jul 29 '17 at 8:35

# rs, 26 bytes

(.*)(.).* .*?\2(.*)/\1\2\3


Live demo and all test cases.

So far this is the shortest answer!

The underlying logic is pretty simple. It just matches greedily on the first word (to get the last common character) and non-greedily on the second (to get the first common character). It then replaces the string with everything but the text in between the two characters.

• @CyrilLemaire posted this: (.*)(.).* .*?(\2.*)/\1\3 (but does not have enough rep to comment) Jul 28 '17 at 18:02