4
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ 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? \$\endgroup\$ – Martin Ender Aug 7 '15 at 7:20
  • 2
    \$\begingroup\$ 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... \$\endgroup\$ – Sp3000 Aug 7 '15 at 12:29
  • 2
    \$\begingroup\$ 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. \$\endgroup\$ – Peter Taylor Aug 7 '15 at 17:28
  • 1
    \$\begingroup\$ 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! \$\endgroup\$ – ETHproductions Aug 7 '15 at 18:02
  • 1
    \$\begingroup\$ 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? \$\endgroup\$ – xnor Aug 7 '15 at 22:04
3
\$\begingroup\$

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:]
\$\endgroup\$
2
\$\begingroup\$

Ruby, 53 49 bytes

Work in progress; I don't have much time atm, will fix later:

f=->s,t{s+"_"+t=~/(.*?)(.+)(.*?)_.*?\2/;$1+$2+$'}

Doesn't give the expected result for camel + leopard:

f["django","angular"] -> "djangular"
f["camel","leopard"] -> "card"
f["croissant","donut"] -> "cronut"
f["spoon","fork"] -> "spork"
f["ham","hammer"] -> "hammer"
\$\endgroup\$
2
\$\begingroup\$

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`)
\$\endgroup\$
2
\$\begingroup\$

Haskell, 61 bytes

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++).(`drop`b))$last a`lookup`zip b[1..]

Then "Django"!"Angular" is "Djangular".

\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – Leif Willerts Aug 20 '15 at 18:19
1
\$\begingroup\$

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;

    return ucfirst($resultingString);
}

Usage:

echo portmanteau('Croissant', 'Donut');

Tested and works with the examples in the question.

\$\endgroup\$
  • 1
    \$\begingroup\$ What will it output ? Coissonut ? \$\endgroup\$ – The random guy Aug 7 '15 at 11:20
  • \$\begingroup\$ No, "Cronut". It iterates/loops the characters in the first word first, then the second word. \$\endgroup\$ – Dzhuneyt Aug 8 '15 at 18:36
1
\$\begingroup\$

Haskell, 148 bytes

C/case isn't restored though. Without case this was 99...

import Data.Char
o a b=|a==""=b|0<1=a
p""b=""
p a""=""
p a b|last a==head b=a++tail b|0<1=o(p(init a)b)(p a(tail b))
z a b=p(t a)(t b)
t=map toLower
\$\endgroup\$
  • \$\begingroup\$ You can make o, p, and z infix operators (try !, #, %) to save some bytes everywhere (as a!b=... is shorter than o a b=...). \$\endgroup\$ – Lynn Aug 20 '15 at 16:39
1
\$\begingroup\$

Perl, 138

I'm sure someone can optimize this further
($a,$b,$l,$r)=(@ARGV,0);for$A(split//,$a){$r.=$A;for$B(split//,$b){$r.=$B if$l;if($A=~/$B/i){$l=1;next;}}last if$l;}$r.=$b if!$l;

138 w/ optimizations and output
($a,$b,$l,$r)=(@ARGV,0);for$A($a=~/./g){$r.=$A;for$B($b=~/./g){$r.=$B if$l;if($A=~/$B/i){$l=1;next;}}last if$l;}$r.=$b if!$l;print"$r\n";

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

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.

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

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.

\$\endgroup\$
  • 2
    \$\begingroup\$ 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. \$\endgroup\$ – NoOneIsHere Jul 28 '17 at 18:01
  • \$\begingroup\$ P.S. You can comment on your own posts. \$\endgroup\$ – NoOneIsHere Jul 29 '17 at 6:39
  • \$\begingroup\$ xD They thought it all through! Thanks for the comment :D \$\endgroup\$ – Cyril Lemaire Jul 29 '17 at 8:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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