20
\$\begingroup\$

This challenge is based on, and contains test cases from, a programming course I took at Aalto University. The material is used with permission.

Two and a half years ago there was a challenge about spoonerisms in English. However, in Finnish spoonerisms are much more complicated.

Spoonerisms in Finnish

In Finnish, the vowels are aeiouyäö and the consonants are bcdfghjklmnpqrstvwxz. (å is technically part of Finnish, but is not considered here.)

The most basic spoonerisms only take the first vowel of each word, and any consonants preceding them, and exchange the parts:

henri kontinen -> konri hentinen
tarja halonen -> harja talonen
frakki kontti -> kokki frantti
ovi kello -> kevi ollo

Long vowels

Some words contain two of the same consecutive vowel. In those cases, the vowel pair must be swapped with the other word's first vowel, shortening or lengthening vowels to keep the length same.

haamu kontti -> koomu hantti
kisko kaappi -> kasko kiippi

In the case of two different consecutive vowels this does not apply:

hauva kontti -> kouva hantti
puoskari kontti -> kooskari puntti

Three or more of the same consecutive letter will not appear in the input.

Vowel harmony

Finnish has this lovely thing called vowel harmony. Basically, it means that the back vowels aou and front vowels äöy should not appear in the same word.

When swapping front or back vowels into a word, all vowels of the other kind in the rest of the word should be changed to match the new beginning of the word (a <-> ä, o <-> ö, u <-> y):

yhä kontti -> kouha ntti
hauva läähättää -> yvä haahattaa

e and i are neutral and may appear with all other letters; swapping them into a word must not cause changes to the rest of the word.

Special cases

Vowel harmony does not apply to some words, including many loan words and compound words. These cases are not required to be handled "correctly".

Challenge

Given two words, output the words spoonerised.

The input words will only contain the characters a-z and äö. You may choose to use uppercase or lowercase, but your choice must be consistent between both words and input/output.

I/O may be done in any convenient format. (Words should be considered strings or arrays of characters.)

This is , so the shortest solution in bytes wins.

Test cases

henri kontinen -> konri hentinen
tarja halonen -> harja talonen
frakki kontti -> kokki frantti
ovi kello -> kevi ollo
haamu kontti -> koomu hantti
hauva kontti -> kouva hantti
puoskari kontti -> kooskari puntti
köyhä kontti -> kouha köntti
hauva läähättää -> läyvä haahattaa
frakki stressi -> strekki frassi
äyskäri kontti -> kouskari äntti
hattu sfääri -> sfätty haari
ovi silmä -> sivi olma
haamu prätkä -> präämy hatka
puoskari sfääri -> sfäöskäri puuri
puoskari äyskäri -> äöskäri puuskari
uimapuku hajoa -> haimapuku ujoa
ihme baletti -> bahme iletti
uhka lima -> lihka uma
osuma makkara -> masuma okkara
makkara fakta -> fakkara makta
lapsi laiva -> lapsi laiva
firma hajoa -> harma fijoa
dimensio bitti -> bimensio ditti
flamingo joustava -> jomingo flaustava
globaali latomo -> labaali glotomo
trilogia fakta -> falogia trikta
printti maksa -> mantti priksa
riisi bitti -> biisi ritti
sini riisi -> rini siisi
aarre nyrkki -> nyyrre arkki
laavu laki -> laavu laki
kliininen parveke -> paaninen klirveke
priimus kahvi -> kaamus prihvi
spriinen lasta -> laanen sprista
kliimaksi mammona -> maamaksi klimmona
hylky hupsu -> hulku hypsy
kehys fiksu -> fihys keksu
levy huhu -> huvu lehu
tiheys guru -> guheus tiru
nyrkki heiluri -> herkki nyilyri
latomo hajoa -> hatomo lajoa
prisma lehti -> lesma prihti
viina baletti -> baana viletti

\$\endgroup\$
5
  • \$\begingroup\$ Can we choose an input/output encoding? Also, is it acceptable to require that the input use combining diacritics instead of single characters? \$\endgroup\$
    – Doorknob
    Jun 27, 2018 at 17:55
  • \$\begingroup\$ @Doorknob You may choose any encoding, but the text will be in NFC (i.e. no combining characters). An encoding may be a case of compatibility with some languages, but NFC/NFD probably won't. (Anything that can handle U+0308 COMBINING DIAERESIS should handle U+00E4 LATIN SMALL LETTER A WITH DIAERESIS just fine.) \$\endgroup\$ Jun 27, 2018 at 18:05
  • 1
    \$\begingroup\$ Since e and i are neutral, are fihus keksy, huvu lehy and lesmä prihti acceptable answers for kehys fiksu, levy huhu and prisma lehti respectively? \$\endgroup\$
    – Arnauld
    Jun 28, 2018 at 9:58
  • 1
    \$\begingroup\$ As a side comment: because of long vowels and vowel harmony, Finnish spoonerism is not an involutory function. For instance: puoskari äyskäri --> äöskäri puuskari --> puoskari ääskäri. \$\endgroup\$
    – Arnauld
    Jun 28, 2018 at 12:39
  • \$\begingroup\$ @Arnauld No. I'll update the question; neutral vowels should cause no change. \$\endgroup\$ Jun 28, 2018 at 15:01

3 Answers 3

9
+100
\$\begingroup\$

JavaScript (ES6), 196 175 bytes

Takes the words as two strings in currying syntax (a)(b). Returns an array of two arrays of characters.

a=>b=>[(e=/(.*?)([eiäaöoyu])(\2?)(.*)/,g=(a,[,c,v])=>[...c+v+(a[3]&&v)+a[4]].map(c=>(j=e.search(v),i=e.search(c))>9&j>9?e[i&~1|j&1]:c))(a=e.exec(a),b=e.exec(b),e+=e),g(b,a)]

Try it online!

How?

Each input word is passed through the regular expression e, which has 4 capturing groups:

e = /(.*?)([eiäaöoyu])(\2?)(.*)/    1: leading consonants (or empty)
     [ 1 ][     2    ][ 3 ][ 4]     2: first vowel
                                    3: doubled first vowel (or empty)
                                    4: all remaining characters

The helper function g() takes all capturing groups of the word to be updated as a[ ] and the first and second capturing groups of the other word as c and v.

We apply basic spoonerism and take care of long vowels with:

c + v + (a[3] && v) + a[4]

To apply vowel harmony, we first coerce the regular expression e to a string by adding it to itself, which gives:

e = "/(.*?)([eiäaöoyu])(\2?)(.*)//(.*?)([eiäaöoyu])(\2?)(.*)/"
     ^^^^^^^^^^^^^^^^
     0123456789ABCDEF (position as hexa)

Vowels that need to be harmonized have a position greater than 9 in the resulting string. Furthermore, the expression was arranged in such a way that front vowels äöy are located at even positions, while back vowels aou are located at odd positions, next to their counterparts.

Hence the following translation formula which is applied to each character c of the output word:

(j = e.search(v), i = e.search(c)) > 9 & j > 9 ? e[i & ~1 | j & 1] : c
\$\endgroup\$
4
\$\begingroup\$

Python 3, 235 231 225 221 217 215 bytes

import re
S=F,B='äöy','aou'
def f(a,b,C=1):
 e,r,Q,W=re.findall(fr' ?(.*?([ei{B+F}]))(\2)?(\w*)'*2,a+' '+b)[0][2:6]
 for c in zip(*S*(W in B)+(B,F)*(W in F)):r=r.replace(*c)
 return[Q+W*len(e)+r]+(C and f(b,a,[]))

Try it online!


Saved

  • -2 bytes, thanks to Lynn
  • -4 bytes, thanks to Zacharý
\$\endgroup\$
6
  • 2
    \$\begingroup\$ Save two bytes with: fr' ?(.*?([ei{B+F}]))(\2)?(\w*)' \$\endgroup\$
    – Lynn
    Jun 28, 2018 at 17:41
  • 1
    \$\begingroup\$ Even better: you can change the second line to S='äöy','aou', then on the fifth line: (F,B) => S and (B,F) => S[::-1] (This is incompatible with the suggestion @Lynn gave) \$\endgroup\$
    – Adalynn
    Jun 28, 2018 at 17:59
  • \$\begingroup\$ Also, you can change the fourth line to e,r,Q,W=re.findall(r' ?(.*?([eiaouäöy]))(\2)?(\w*)'*2,a+' '+b)[0][2:5] for a few more bytes saved. \$\endgroup\$
    – Adalynn
    Jun 28, 2018 at 18:36
  • \$\begingroup\$ What I meant to say: 2nd line to S=F,B='aöy','aou', and then on the 4th line change (F,B) to S. \$\endgroup\$
    – Adalynn
    Jun 29, 2018 at 17:17
  • \$\begingroup\$ S=F,B=... should save a few bytes if you replace (F,B) with S \$\endgroup\$
    – Adalynn
    Jul 1, 2018 at 13:45
0
\$\begingroup\$

Pyth, 84 bytes

.b++hY*W@N2JhtY2XW}JeA@DJc2"aouäöy"eNGH_Bmth:d:"^([^A*)([A)(\\2)*(.+)"\A"aeiouyäö]"4

Try it online. Test suite.

Proving that it's not that hard in golf languages. A stack-based language might do even better.

Pyth uses ISO-8859-1 by default, so äö are one byte each.

Explanation

  • Q, containing the input pair of words, is appended implicitly.
  • m: map each word d in the input to:
    • :"^([^A*)([A)(\\2)*(.+)"\A"aeiouyäö]": replace A with aeiouyäö] in the string to get the regex ^([^aeiouyäö]*)([aeiouyäö])(\2)*(.+).
    • :d: find all matches and return their capturing groups.
    • h: take the first (and only) match.
    • t: drop the first group containing the entire match.
  • _B: pair with reverse to get [[first, second], [second, first]].
  • .b: map each pair of words N, Y in that to:
    • hY: take the beginning consonants of the second word.
    • @N2: take the long first vowel of the first word, or None.
    • htY: take the first vowel of the second word.
    • J: save that in J.
    • *W2: if there was a long vowel, duplicate the second word's vowel.
    • +: append that to the consonants.
    • c2"aouäöy": split aouäöy in two to get ["aou", "äöy"].
    • @DJ: sort the pair by intersection with the first vowel of the second word. This gets the half with the second word's first vowel in the end of the pair.
    • A: save the pair to G, H.
    • e: take the second half.
    • }J: see if the first vowel of the second word is in the second half.
    • XWeNGH: if it was, map G to H in the suffix of the first word, otherwise keep the suffix as-is.
    • +: append the suffix.
\$\endgroup\$

Your Answer

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

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