32
\$\begingroup\$

A portmanteau word is a combination of two words that takes part of each word and makes them into a single new word. For example, lion + tiger => liger.

Let's write a program to generate portmanteaus from a pair of input words. Computers aren't the best at English, so we'll need to establish some rules to ensure that the output portmanteaus are pleasant to the eye and ear.

(Examples here are shown with a separator between the prefix and suffix for clarity: li|ger. However, the program's actual outputs should not have a separator: liger.)

  • Each portmanteau will consist of a nonempty prefix of the first word concatenated to a nonempty suffix of the second word: yes to li|ger, no to |iger.
  • If the prefix ends with a vowel, the suffix must start with a consonant, and vice versa: yes to lio|ger or l|er, no to lio|iger or l|ger. You may decide whether to count y as a vowel or a consonant. Your solution must pick one option and stick with it, however.
  • The resulting word must not contain either of the original words in full: yes to lio|ger, no to lion|iger or li|tiger.
    • This rule holds even if the portion in question is formed from parts of both words: with input of two + words, output tw|ords is still illegal because it contains the substring words. (The only valid output for this pair would be t|ords.)

Your program or function must take two words and output/return a list of all pleasant portmanteaus that can be formed from those words in that order.

Details

  • Standard input and output methods apply. Standard loopholes are forbidden.
  • Words will consist only of lowercase letters (or, if you prefer, only of uppercase letters).
  • You may take the two input words as a list, tuple, two separate inputs, a single string with a non-letter delimiter, etc.
  • Output format is similarly flexible; if you return or output a string, it should be delimited such that it's clear where one portmanteau word ends and the next one begins.
  • There should be no delimiters inside a portmanteau word.
  • It's okay if your output list includes duplicate results; it's also okay to remove duplicates.

Test cases

> lion, tiger
< liger, ler, liger, lir, lioger, lior

> tiger, lion
< tion, ton, tin, tigion, tigon, tigen

> spoon, fork
< sork, spork, spork, spok, spoork, spook

> smoke, fog
< sog, smog, smog, smokog

> gallop, triumph
< giumph, gumph, gariumph, gamph, gaph, gah, galiumph, galumph, galliumph, gallumph, galloriumph, gallomph, galloh

> breakfast, lunch
< bunch, brunch, brench, brech, breh, breanch, breach, breah, breakunch, breakfunch, breakfanch, breakfach, breakfah, breakfasunch

> two, words
< tords

> harry, ginny (if y is treated as a consonant)
< hinny, hanny, hany, hay, harinny, harrinny

> harry, ginny (if y is treated as a vowel)
> hinny, hy, hanny, hany, harinny, hary, harrinny

Reference solution

Here's a reference solution in Pip (treats y as a consonant).


This is : the shortest answer in each language wins!

\$\endgroup\$
  • \$\begingroup\$ Related. More distantly related. \$\endgroup\$ – DLosc Jul 3 '18 at 4:01
  • \$\begingroup\$ must the delimiter be constant or can i put a bunch of spaces between the words? \$\endgroup\$ – Asone Tuhid Jul 11 '18 at 13:54
  • \$\begingroup\$ @AsoneTuhid Sure, variable amounts of whitespace would be an acceptable delimiter. The only requirement is that "it's clear where one portmanteau word ends and the next one begins." \$\endgroup\$ – DLosc Jul 12 '18 at 16:14

12 Answers 12

5
\$\begingroup\$

05AB1E, 28 bytes

y is a vowel (same byte count as consonant though).

нη¨sθ.s¨âʒ`нsθ‚žOsåË_}Jʒs¢Z_

Try it online! or as a slightly modified Test Suite

\$\endgroup\$
  • 2
    \$\begingroup\$ Nice answer! Funny how there are quite a few options for the last filter, but unfortunately all the same byte-count.. ʒs¢Z_; ʒsåO_; ʒsм__; etc. \$\endgroup\$ – Kevin Cruijssen Jul 3 '18 at 7:14
4
\$\begingroup\$

Retina, 72 bytes

L$w`(?<=[aeiou]()|.())((.+),(.+))\B(?!\4)(?<!\5\3)(?([aeiou])\2|\1)
$`$'

Try it online!

\$\endgroup\$
  • \$\begingroup\$ Bah, I had got as far as Lw$`(?<=[aeiou])(.+),(.+)(?<!^\2\1,\2)(?!\1)(?=[^aeiou])|(?<=[^aeiou])(.+),(.+)(?<!^\4\3,\4)(?!\3)(?=[aeiou]) but I couldn't concentrate on golfing it because of a headache. \$\endgroup\$ – Neil Jul 3 '18 at 15:09
  • \$\begingroup\$ My first attempt was quite similar, although I avoided repeating the centre part by checking the vowel/consonant thing at the end with something like (?=.(?<=[aeiou]\1[^aeiou]|[^aeiou]\1[aeiou])) and then needed probably at least six iterations to get it down to where it is now. \$\endgroup\$ – Martin Ender Jul 3 '18 at 15:11
  • \$\begingroup\$ (The ^s in my previous comment are erroneous) Indeed, I would never have thought of that ()|.() trick, I would have probably stopped at Lw$`(?<=([aeiou])|.)((.+),(.+))(?<!\4\2)(?!\3)(?=(?(1)[^aeiou]|[aeiou])). \$\endgroup\$ – Neil Jul 3 '18 at 15:18
3
\$\begingroup\$

Pyth, 38 bytes

f!s}RTQm+hd_edfxFm}ed"aeiou"T*._hQ.__e

Input is a list of the two words, and y isn't treated as a consonant.

Try it online here, or verify all test cases at once here.

f!s}RTQm+hd_edfxFm}ed"aeiou"T*._hQ.__e   Implicit: Q=eval(input())
                                hQ       First input word
                              ._         All prefixes of the above
                                     e   Second input word (Q inferred)
                                  .__    Reverse, take all prefixes
                             *           Cartesian product of the above
              f                          Filter the above using:
                 m          T              Map d in the current element using:
                   ed                        The last letter of the word part
                  }  "aeiou"                 Is it contained in the vowel list?
               xF                          Take the XOR of the list
                                         (This ensures that the word parts meet at one consonant)
       m                                 Map d in the filtered set using:
        +hd_ed                             Add the first part to the reversed second part
f                                        Filter the above using:
  s}RTQ                                    Does the portmanteau contain either of the input words?
 !                                         Logical NOT (remove from list if the above is true)
\$\endgroup\$
3
\$\begingroup\$

Java 8, 228 225 215 bytes

v->w->{String r="",t,p=" aeiou";for(int i=w.length(),j;--i>0;)for(j=1;j<v.length();)r+=(t=v.substring(0,j)+w.substring(i)).matches(v+".*|.*"+w)|p.indexOf(t.charAt(j-1))*p.indexOf(t.charAt(j++))>0?"":t+" ";return r;}

Takes two Strings in currying syntax and returns a String. Treats y as a consonant. Try it online here.

Thanks to DLosc for golfing 2 bytes.

Ungolfed:

v -> w -> { // lambda taking two String parameters in currying syntax
    String r = "", // result
    t, // temporary variable used for storing
       // the portmanteau candidate currently being evaluated
    p = " aeiou"; // vowels for the purposes of this function;
                  // the leading space is so that they all have a positive index
    for(int i = w.length(), j; --i > 0; ) // loop over all proper suffixes
                                          // of the second word
        for(j = 1; j < v.length(); )      // loop over all proper prefixes
                                          // of the first word
            r += // construct the portmanteau candidate
                 (t = v.substring(0, j) + w.substring(i))
                 // if it contains one of the input words ...
                 .matches(v + ".*|.*" + w)
                 // ... or the boundary is consonant-consonant 
                 // or vowel-vowel (here we make use of the facts
                 // that all the vowels have a positive index, and
                 // indexOf() returns -1 in case of no match) ...
                 | p.indexOf(t.charAt(j-1)) * p.indexOf(t.charAt(j++)) > 0
                 ? "" // ... reject it ...
                 : t + " "; // ... else add it to the result
    return r; // return the result
}
\$\endgroup\$
3
+50
\$\begingroup\$

Japt, 32 bytes

å+ ïVw å+)f_xè"%v$" uÃmrÈ+YwÃkøN

Japt Interpreter

Saved 10 bytes thanks to Shaggy's clearer understanding of Japt's syntax.

Saved 8 bytes due to a new language feature

Saved 2 byte thanks to some suggestions from ETHproductions

The newest version of Japt introduced the Cartesian Product function, which saved quite a few bytes and allowed me to restore the ordering of inputs (so "lion" "tiger" outputs "liger" and such). "y" is still treated as a consonant.

Explanation:

   ï     )       Cartesian product of...
å+                prefixes of first input
    Vw å+         and suffixes of second input.

f_         Ã     Remove the ones where...
  xè"%v$"         the number of vowels at the joining point
          u       is not 1.

m     Ã          Replace each pair with...
 rÈ+Yw            the prefix and suffix joined together
       køN       then remove the ones that contain either input
\$\endgroup\$
  • \$\begingroup\$ Welcome to Japt (again!). I can definitely see some potential for more golfing here; I'll have a proper look at it when I get back to a computer. \$\endgroup\$ – Shaggy Jul 4 '18 at 11:11
  • 1
    \$\begingroup\$ A few quick savings for you from my phone. \$\endgroup\$ – Shaggy Jul 4 '18 at 12:47
3
\$\begingroup\$

Python 3, 156 150 bytes

I've considered y as a consonant.

lambda a,b:{a[:i]+b[j:]for i in range(1,len(a))for j in range(1,len(b))if((a[i-1]in'aeiou')^(b[j]in'aeiou'))*0**(a in a[:i]+b[j:]or b in a[:i]+b[j:])}

-6 bytes thanks to Jonathan Frech

Try it online!

\$\endgroup\$
  • \$\begingroup\$ Possible 150 bytes. \$\endgroup\$ – Jonathan Frech Jul 8 '18 at 1:35
  • \$\begingroup\$ @JonathanFrech thanks for spotting it \$\endgroup\$ – PieCot Jul 8 '18 at 13:45
  • \$\begingroup\$ You can use default arguments in lambda x=0 to get it down to save... 0 characters, annoyingly. lambda a,b,v='aeiou',r=range:{a[:i]+b[j:]for i in r(1,len(a))for j in r(1,len(b))if((a[i-1]in v)^(b[j]in v))*0**(a in a[:i]+b[j:]or b in a[:i]+b[j:])} (Still 150) \$\endgroup\$ – The Matt Jul 11 '18 at 21:28
2
\$\begingroup\$

JavaScript (ES6), 124 bytes

Takes the 2 words in currying syntax (a)(b) and prints the results with alert(). Assumes y is a consonant.

a=>b=>[...a].map(c=>[...b].map((C,j)=>!(w=s+b.slice(j)).match(a+'|'+b)&v.test(c)-v.test(C)&&alert(w),s+=c),s='',v=/[aeiou]/)

Try it online!

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

Jelly, 27 bytes

¹Ƥp¹ÐƤ}Ø.ị"e€Øc⁻/ƲƇẎ€wÐḟƒ@,

Try it online!

Yy is a consonant. Both cases supported. Returns duplicates.

Output has been prettified over TIO. Remove +/€ from the footer to see the actual output.

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

C++ 11, 217 202 bytes

[](auto v,auto w){auto r=v,t=v,p=v;r="",p="aeiou";for(int i=w.size(),j;--i;)for(j=v.size();j;)(t=v.substr(0,j)+w.substr(i)).find(v)+1|t.find(w)+1|p.find(t[j-1])<5==p.find(t[j--])<5?v:r+=t+" ";return r;}

Makes heavy use of std::string#find. Treats y as a consonant. Try it online here.

Ungolfed:

// lambda; relies on auto to keep declarations short
[] (auto v, auto w) {
    // let's declare some strings. To keep it terse, we're using auto and the type of the arguments.
    auto r = v, // result string
    t = v,      // temporary string for storing the portmanteau candidate
    p = v;      // vowels string
    // now assign them their values
    r = "",    // result starts empty
    p = "aeiou"; // vowels don't include 'y'
    for(int i = w.size(), j; --i; ) // suffixes of the second word
        for(j = v.size(); j; ) // prefixes of the first word
            // create the portmanteau candidate
            (t = v.substr(0, j) + w.substr(i))
            // if it includes one of the input words ...
            .find(v) + 1 | t.find(w) + 1
            // ... or the boundary is consonant-consonant or vowel-vowel ...
            | p.find(t[j - 1]) < 5 == p.find(t[j--]) < 5
            ? v // ... discard it ...
            : r += t + " "; // ... else add it to the result.
    return r; // return the result
}
\$\endgroup\$
1
\$\begingroup\$

Python 2, 179 176 166 162 bytes

lambda s,t:[w for w in g(s,t)if(s in w)<1>(t in w)]
g=lambda s,t:s[:-1]and[s[:-1]+t[j:]for j in range(1,len(t))if(s[-2]in'aeiou')^(t[j]in'aeiou')]+g(s[:-1],t)or[]

Try it online!

3 bytes from Jonathan Frech. And 10 bytes thx to The Matt.

In my world, y is not a vowel. (It's a yowl!)

\$\endgroup\$
  • \$\begingroup\$ There are stray spaces in t) if and t) or []. \$\endgroup\$ – Jonathan Frech Jul 7 '18 at 13:30
  • \$\begingroup\$ @Jonathon Frech: Thanks! Got a bit lazy there... \$\endgroup\$ – Chas Brown Jul 7 '18 at 19:44
  • \$\begingroup\$ I see ... I presume you also got a bit lazy whilst typing my name :P \$\endgroup\$ – Jonathan Frech Jul 7 '18 at 21:09
  • \$\begingroup\$ *JonathAn: D'oh! Well, at least I was consistent! :) \$\endgroup\$ – Chas Brown Jul 7 '18 at 23:10
  • 1
    \$\begingroup\$ @The Matt: Thanks! Actually, I squeezed an additional 2 bytes out via (s in w)<1>(t in w). \$\endgroup\$ – Chas Brown Jul 12 '18 at 1:24
0
\$\begingroup\$

Ruby, 113 112 109 104 bytes

y is a consonant

This outputs the same duplicates as the examples in the question, I must be using the same loop

->a,b,i=j=1{r=a[0,i]+b[j..-1];g=:aeiou;!g[a[i-1]]^g[b[j]]|r[a]|r[b]||z=[*z,r];b[j+=1]||a[i+=j=1]?redo:z}

Try it online!

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

Emacs Lisp, 306 + 13 = 319 bytes

+13 for (require'seq)

(require'seq)(lambda(a b)(dotimes(i(1-(length b)))(dotimes(j(1-(length a)))(progn(setq w(substring a 0(1+ j))x(substring b(1+ i))c(concat w x))(defun V(c)(seq-contains"aeiou"(elt c 0)'char-equal))(if(not(or(string-prefix-p a c)(string-suffix-p b c)))(if(V(substring w -1))(if(not(V x))(print c))(if(V x)(print c))))))))

Try It Online!

Defines an anonymous lambda function. Outputs a sequence of newline-separated portmanteaus with each one surrounded by quotes. Golfing tips are welcome. The letter y is considered a consonant.

Ungolfed

(require 'seq)                                                                                                                                                           
(defun Portmanteus(word1 word2)
  "Find all valid portmanteus of the two given words"
  (dotimes (i (1- (length word2)))
    (dotimes (j (1- (length word1)))
      (progn
        (setq w (substring word1 0 (1+ j)) w2 (substring word2 (1+ i)) comb (concat w w2))
        (defun isVowel (c) (seq-contains "aeiou" (elt c 0) 'char-equal))
        (if (not (or (string-prefix-p word1 comb) (string-suffix-p word2 comb)))
          (if (isVowel (substring w -1))
            (if (not (isVowel w2))
              (princ (format "%s\n" comb))
            )
            (if (isVowel w2)
              (princ (format "%s\n" comb))
            )
          )
        )
      )
    )
  )
)
\$\endgroup\$

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.