[Question]
  [
The Mongolian language has "vowel harmony". In short the vowel harmony means that certain vowels are not allowed to be placed next to each other in a word. This means that suffixes of words have to care about the vowels present in the words they attach to and need to resolve to a vowel that matches the harmony of the word.
## Vowels
Mongolian harmony compares about 3 binary properties that vowels can have.
* A vowel can be **round**
* A vowel can be **high**
* A vowel can be **ATR** (advanced tongue root)
If a vowel has a property, we indicate that with a `+` and square brackets if it does not we indicate that with a `-` and square brackets. e.g. **[-ATR]** indicates that the vowel does not have the **ATR** property.
There are thus 8 combinations of these properties that can be made. Mongolian has exactly 1 vowel for each combination except **[+high][-ATR][-round]** which is missing a vowel.
The 7 vowels and their values can be given in the table below:
|  |  | [+ATR] | [-ATR] |
| --- | --- | --- | --- |
| **[+round]** | **[+high]** | `u` (u) | `U` (ʊ) |
| **[+round]** | **[-high]** | `o` (o) | `O` (ɔ) |
| **[-round]** | **[+high]** | `i` (i) | *(Absent)* |
| **[-round]** | **[-high]** | `e` (e) | `a` (a) |
This table gives both the XSAMPA (code block) and IPA (parentheses) values for each vowel, but we will care about the XSAMPA values for this challenge.
## Harmony
### [ATR] harmony
Every word in Mongolian is either **[+ATR]** or **[-ATR]** with only vowels of the corresponding **[ATR]** value appearing in that word. The once exception is `i` which is "transparent" for **[ATR]** harmony. Meaning it acts as if it is in *both* categories even though it is **[+ATR]**. Meaning that `i` can appear in words with any vowels.
This can be illustrated with the following Venn diagram:
![Venn diagram with two circles. In the left part are the letters u, e, and o. In the right part are the letters capital U, a, capital O. In the intersection is the letter i.](https://i.stack.imgur.com/hXX3x.png)
Modified with permission from [Mongolian vowel harmony Venn diagram](https://commons.wikimedia.org/wiki/File:Mongolian_vowel_harmony_Venn_diagram.svg) by wikimedia commons user AquitaneHungerForce. Liscensed under the Creative Commons Attribution-Share Alike 4.0 International license. See link for full attribution.
### [round] harmony
Mongolian also has **[round]** harmony which only affects **[-high]** vowels. At its simplest under **[round]** harmony if a **[-high]** vowel is the next vowel after a **[-high]** vowel, it must have the same **[round]** value.
`i` is also transparent for roundness harmony so we skip over `i` when looking for the last vowel.
## Task
Given a sequence of Mongolian vowels determine if they follow the rules of vowel harmony. Input can be either a list of XSAMPA vowels, or a string of XSAMPA vowels. Input will always be non-empty.
You should output one of two distinct values, the first if it is a valid sequence the second if it is not.
This is [code-golf](/questions/tagged/code-golf "show questions tagged 'code-golf'") so your goal is to minimize the size of your source code as measured in bytes.
## Test cases
### Valid
```
iiiiiii
oi
Oi
ui
Ui
ei
ai
UO
oiou
uie
ouie
OUia
```
### Invalid
```
uU
oO
eia
oiie
OUie
eieio
```
# Reading
This post is based off of the description from:
* Godfrey, Ross (2012). "Opaque Intervention in Khalkha Mongolian Vowel Harmony: A Contrastive Account" [pdf](https://www.mcgill.ca/mcgwpl/files/mcgwpl/godfrey2012_0.pdf).
  
[Answer]
# [JavaScript (Node.js)](https://nodejs.org), 45 bytes
*Saved 1 byte thanks to @dingledooper*
Expects a string. Returns *false* for valid or *true* for invalid.
```
s=>Buffer(s).some(n=>n%7&&s*((s^(s=n))%18%8))
```
[Try it online!](https://tio.run/##ddCxDoIwEAbg3ccgAVoTSZxkqYObUydcUJMGrqYG7wxnef2qcdF4dPx6vfuvVzc57sZwf6yQekjeJDbbXfQeRsW6YrqBQrPFfFMUvFSKz4oNap2v67zWOnWETANUA11U2R7cEPpTqRff7FUWPifTfzckoZUwSthICBI68bkVI1EU54NULLNtgnvzj5dHbPc4zfxRbKT2VtzPibHnkoDYAwK9PD0B "JavaScript (Node.js) – Try It Online")
### How?
In this version, we work directly on the ASCII codes of the characters:
```
 char |  O  |  U  |  a  |  e  |  i  |  o  |  u
------+-----+-----+-----+-----+-----+-----+-----
 code |  79 |  85 |  97 | 101 | 105 | 111 | 117
```
Conveniently, `i` is the only character whose ASCII code is congruent to \$0\$ modulo \$7\$.
Once the `i`'s have been filtered out using the above property, a pair of consecutive ASCII codes \$(x,y)\$ is valid if and only if:
$$\big((x\operatorname{XOR}y)\bmod 18\big)\bmod 8 = 0$$
Below is a summary of all possible outcomes (using the same order as in my first version for easier comparison):
```
   | e u O o U a
---+-------------
 e | 0 0 6 2 4 4
 u | 0 0 4 0 6 2
 O | 6 4 0 6 0 2
 o | 2 0 6 0 4 6
 U | 4 6 0 4 0 0
 a | 4 2 2 6 0 0
```
---
# JavaScript (ES6), 54 bytes
Expects an array of characters. Returns *false* for valid or *true* for invalid.
```
a=>a.some(c=>(n="ieuOoUa".search(c))&&a*((a^(a=n))%3))
```
[Try it online!](https://tio.run/##fdDBDsIgDAbgu49B4gYmcvHM7p444WXOpGGdYiY1Q/b6GPWiiV2PfKH92yvMkPwU7o9tpB7LYAqYBnSiG0pvGhmNCJgtORA6IUz@Ir1SVQUbKeEkwUSl1juliqeYaEQ90lnW7QHG0He1Wn0/D7LVWovwKdGpv0ysWFYyK44VZAX4bpZPTZlPh@y3BbMuwNt@sD7Gdh/nhftmx06z/DGA32wxIvItMdALyxM "JavaScript (Node.js) – Try It Online")
### How?
We map `"ieuOoUa"` to \$[0,1,2,3,4,5,6]\$.
We ignore the `i`'s and consider each pair of indices \$(x,y)\$ for the remaining consecutive characters.
The pair is valid if and only if:
$$(x\operatorname{XOR}y)\bmod 3=0$$
Below is a summary of all possible outcomes:
```
   | e u O o U a
---+-------------
 e | 0 0 2 2 1 1
 u | 0 0 1 0 1 1
 O | 2 1 0 1 0 2
 o | 2 0 1 0 1 2
 U | 1 1 0 1 0 0
 a | 1 1 2 2 0 0
```
[Answer]
# [Jelly](https://github.com/DennisMitchell/jelly), 19 [bytes](https://github.com/DennisMitchell/jelly/wiki/Code-page)
```
ḟ”iOµPƝ+⁽D§ẒẸ^+4ẒEƊ
```
A monadic Link that accepts a list of characters and yields `1` if valid and `0` otherwise.
**[Try it online!](https://tio.run/##y0rNyan8///hjvmPGuZm@h/aGnBsrvajxr0uh5Y/3DXp4a4dcdomQIbrsa7///@HZmb6Z2YmZmYCGQA "Jelly – Try It Online")** Or see the [test-suite](https://tio.run/##y0rNyan8///hjvmPGuZm@h/aGnBsrvajxr0uh5Y/3DXp4a4dcdomQIbrsa7/D3dvOdz@qGnN//@ZEMCVn8nln8lVmskVmsmVmsmVCGT4AwXzS4FiqVz5IMI/NDORqzSUK98fqCIRKAkRSwUA "Jelly – Try It Online").
### How?
Removes the `i`s and then takes the exclusive-or of these two checks using the remaining character's ordinal values:
1. (`PƝ+⁽D§ẒẸ`) whether any neighbouring pair are from the same **[ATR]**, both **[-high]**, but with different **[round]** values. That is `oe`, `eo`, `Oa`, or `aO`.
2. (`+4ẒE`) whether all are from the same **[ATR]**
```
ḟ”iOµPƝ+⁽D§ẒẸ^+4ẒEƊ - Link: list of characters, S (from 'aeoiuOU')
 ”i                 - an 'i' character
ḟ                   - S filter-discard 'i's
   O                - ordinal values
    µ               - start a new monadic chain, call that V
      Ɲ             - for neighbours in V:
     P              -   product
        ⁽D§         - 18226
       +            - add (vectorises)
           Ẓ        - is prime? -> 1 if the neighbours are oe, eo, Oa, or aO
                                     else 0
            Ẹ       - any? -> X1
                  Ɗ - last three links as a monad - f(V):
               4    -   four
              +     -   V add 4 (vectorises)
                Ẓ   -   is prime?
                 E  -   all equal? -> X2
             ^      - X1 XOR X2
```
That is:
1. Identifying those neighbours with the same **[ATR]** where both are **[-high]** but with different **[round]** values makes use of this table of ordinal products plus \$18226\$ with primes in bold:
| x×y+18226 | u | e | o | U | a | O |
| --- | --- | --- | --- | --- | --- | --- |
| **u** | 31915 | 30043 | 31213 | 28171 | 29575 | 27469 |
| **e** | 30043 | 28427 | **29437** | 26811 | 28023 | 26205 |
| **o** | 31213 | **29437** | 30547 | 27661 | 28993 | 26995 |
| **U** | 28171 | 26811 | 27661 | 25451 | 26471 | 24941 |
| **a** | 29575 | 28023 | 28993 | 26471 | 27635 | **25889** |
| **O** | 27469 | 26205 | 26995 | 24941 | **25889** | 24467 |
2. **[ATR]** groups identified by whether four more than the ordinal is a prime:
|  | ordinal+4 | prime? |
| --- | --- | --- |
| u | 121 | no |
| e | 105 | no |
| o | 115 | no |
| U | 89 | yes |
| a | 101 | yes |
| O | 83 | yes |
 
[Answer]
# [Retina](https://github.com/m-ender/retina/wiki/The-Language), 32 bytes
```
A`[oue]i*[OUa]|oi*e|Oi*a
2}V`
.+
```
[Try it online!](https://tio.run/##K0otycxLNPz/3zEhOr80NTZTK9o/NDG2Jj9TK7XGP1MrkcuoNiyBS0/7///8zPxSAA "Retina – Try It Online") or [Verify all test cases](https://tio.run/##JYqxCsMwEEN3/UfAOFBo/iBTx5ucJQR8ww1aelDqzfl294o1PMSTPvblW59jSelVx15Pb3Yxn1L06s5sXZgV231UPNYxOAMnhGhEIYzQKBLSWziD/yGFilbgEg@NcTr7AQ)
### Explanation
```
A`[oue]i*[OUa]|oi*e|Oi*a
2}V`
```
Do the following twice:
* `A`ntiGrep -- Remove lines containing a match for the regex `[oue]i*[OUa]|oi*e|Oi*a`
* Re`V`erse the input string
If the input contains any of the invalid combinations, this process will result in an empty string.
```
.+
```
Count matches of the regex `.+`. Outputs 1 if the line is non-empty, 0 otherwise.
[Answer]
# [Vyxal](https://github.com/Vyxal/Vyxal), 15 bytes
Returns `0` for valid or `1` for invalid.
```
\ioC¨pλ꘍∑8%7%;a
```
[Try it Online!](https://vyxal.pythonanywhere.com/#WyIiLCIiLCJcXGlvQ8KocM676piN4oiROCU3JTthIiwiIiwiZWllaW8iXQ==)
This currently fails for the case `iiiiiii` [due to a bug](https://github.com/Vyxal/Vyxal/issues/1095), but it should work after that gets fixed.
## How?
Ignoring `i`s, the digit sum of the xor of pairwise codepoints is good if it's one of `0`, `7`, or `8`.
```
   O  U  a  e  o  u
O  0  8 10  6  5 13
U  8  0  7 12 13  5
a 10  7  0  4  5  2
e  6 12  4  0  1  7
o  5 13  5  1  0  8
u 13  5  2  7  8  0
```
```
\ioC¨pλ꘍∑8%7%;a
\i              # literal i
  o             # remove is
   C            # to codepoints
    ¨pλ      ;  # pairwise map
       ꘍        # xor
        ∑       # digit sum
         %8     # mod 8
           %7   # mod 7
              a # are any truthy?
```
[Answer]
# [Python](https://www.python.org), 239 bytes
```
def f(x):
 x=x.replace('i','')
 if not x:return 1
 a='ueoi';b='UaOi';m='oOea';v='oO'
 for c in x:
  if(x[0] in a)^(c in a):return-1
 r=2
 for c in x:
  if r!=2:
   if(c in m)&r^(c in v):return-1
   else:r=2
  elif c in m:r=c in v
 return 1
```
[Attempt This Online!](https://ato.pxeger.com/run?1=ZVAxbsMgFN05xY-HAlIcNZkqR6xZbanyFKUSTUBGcgzCJnXv0a1LlvZOOUWv0A_20CoMj_f5730efH6796Gx3fX6FQadP91-TkqDZiMvCIxiXHnlWnlUjBq6pJQTMBo6O8BYeDUE38GagBQ0KGvo9lXQWpZIzoLaUkm6vURCCWjr4QimQx8BnMHG_eMh1pK_sONE5ok5TvRic-8BvxCbSKM_Nc78wc_2y187gGp7VaQpSNE6yfFkEuMNc_r52R9vjWkVMNM5KARqXBgY57AQkOXrrEjvxlNMLQStaIzhvOkGlj03NrQncLLvC8iWqjuJ9FEpwp1MS9P-k01tHW9e9a41KIWM79cHzqds11tTgZkWqcBGKCOECHUEFUGmskwSG1JfxWLaytpIsoNQI9gSQaXaGuzuYleRfP6MXw)
A very boring solution, using the most obvious application of the rules.
[Answer]
# Python3, 177 bytes:
```
lambda s:(S:=set)()in[S(T:=s.replace('i',''))-S('uoe'),S(T)-S('UOa')]and all(T[i:i+2]in'oo,oO,Oo,OO,ee,ea,ae,aa'.split(',')for i in range(len(T)-1)if S()==S(T[i:i+2])-S('oOea'))
```
[Try it online!](https://tio.run/##ZY6xTgMxEER7vsLd2uBEEBp00n3DFs5VIcVC9shKxrZ8dwVffzhBCths90azM5O@5nMMzy8pr2P/unr6fDuRmjrtun7i2Wgj4eD0vtA2c/L0zhoELIAxG6dhiQzGFsOVBiQwRwonRd7r/UE6edgdJUCMNqLFaBEts2WyxJYItlPyMuuSZ8aYlSgJKlP4YO05XEKfjIzKadP37pZ3rYrIpcusKUuY9VhG/VzR7m5arBFrXGocauQaqTFjUxSXJptrQyvgIPQrwAbud49//4f6HZtt1NT/T78I6zc)
[Answer]
# [Jelly](https://github.com/DennisMitchell/jelly), 15 [bytes](https://github.com/DennisMitchell/jelly/wiki/Code-page)
```
ḟ”iṢƝ“E“¬(’ḥⱮḂẸ
```
A monadic Link that accepts a list of characters from `aeiouOU` and yields `0` if valid and `1` otherwise.
**[Try it online!](https://tio.run/##y0rNyan8///hjvmPGuZmPty56NjcRw1zXIH40BqNRw0zH@5Y@mjjuoc7mh7u2vH/4e4th9sfNa35/z8TArjyM7n8M7lKM7lCM7lSM7kSgQx/oGB@KVAslSsfRPiHZiZylYZy5fsDVSQCJSFiqQA "Jelly – Try It Online")**
### How?
Beats my other one, but I kind of prefer that one :)
```
ḟ”iṢƝ“E“¬(’ḥⱮḂẸ - Link: list of characters, S
 ”i             - an 'i' character
ḟ               - S filter-discard 'i's
    Ɲ           - for each neighbouring pair:
   Ṣ            -   sort
            Ɱ   - for each sorted pair of neighbours:
           ḥ    -   hash using [salt, domain]:
     “E“¬(’     -     list of base 250 integers [70, 2041]
             Ḃ  - modulo two
              Ẹ - any?
```
[Answer]
# [Charcoal](https://github.com/somebody1234/Charcoal), 28 bytes
```
≔⁺c⁻Ｓiθ›№⁺⌊θ⌈θc⊙⪪oeOa²№⁺⮌θθι
```
[Try it online!](https://tio.run/##TY09CsMwDIX3niJokiFdumYKHUqH0NCcwBiRChw78U9oT@8oIUMFkp4e70Pmo4Px2pbSxsijw97miGCgrjp2Ip9uzmlIgd2Iqq6AQclaVHPpxUv4CKQTBbz7LNdBC8hTnnCRYKe/p95hAzJb98NhtpwQPL20fLqJ@8e/aaUQ6eD3ZiXVlEJM7Mt1tRs "Charcoal – Try It Online") Link is to verbose version of code. Outputs a Charcoal boolean, i.e. `-` if the input is harmonic, nothing if not. Explanation:
```
≔⁺c⁻Ｓiθ
```
Remove `i`s from the input, but also prefix a `c`.
```
›№⁺⌊θ⌈θc...
```
Check that `c` is now either the minimum or maximum of the string, and...
```
⊙⪪oeOa²№⁺⮌θθι
```
... that neither the string nor its reversal contain either `oe` or `Oa`.
[Answer]
# [R](https://www.r-project.org), 83 bytes
```
\(x,g=\(p)grepl(p,x,perl=TRUE))!(g('[ueo]')&g('[UaO]'))&!(g('oi*e|ei*o|Oi*a|ai*O'))
```
[Attempt This Online!](https://ato.pxeger.com/run?1=TY-_DsIgEId3n6J2KH-Cu0tHZxKVyRpDzLW5pAmElqRDn8PFpQ6-hS_St5FCE2X47gf3hYPny03zo3cebj10fZeV2Z3mmFYustxEykgfqSIhUqcTmUzjkwVxu1apUOdsU-u2-59BvCKCGBkAqJeICKEEHUjQg_T2fb3bz6eKDqIpK2pZ48C21IpBWHBteT6qA2Nb2lBy8WCuhBVLVFqGyIrYMMhhBORmlMj1qJHL0EtXf2r6-3mYSf8euSrTlOoX)
Very straightforward regular expression solution - can't have both sets of vowels, can't have o/e or O/a adjacent with any number of intervening is. (The other combinations are ruled out by [ATR] harmony.)
[Answer]
# [Python](https://www.python.org), ~~98~~ ~~97~~ 96 bytes
```
lambda s:len(t:=s.replace("i",""))>1and(y:=ord(t[0]))and any((y^(y:=ord(x)))%18%8for x in t[1:])
```
[Attempt This Online!](https://ato.pxeger.com/run?1=rZDBSsQwFEVxab4iPhjIg1rsrgTq3lVXdTMdNU5TfBKT0qQy_RY3BdF_0q8xnagbt2aRhPvIPYe8vA9zeHB2ee2r9m0K_Xn5cWfU032nuJdGWxFk5fNRD0bttQCCDADxslC2E7Os3NiJsL3YIcaAKzsLMd_8DA6IuCnKTdm7kR84WR62hdxh4nyenA0j2SDgWhnqAFnQPtzuldeeVxwAKC3miNXEJmINMU1MxUsdQzfFTDO3bnVDKr7I_WAoNrY21q3UtfII_q2W7DRh1yg3j5MPosyAA2bQBsh4f5wgsm-71l7Z5z-GVaRNDXN1NFJRJjnof3ZIP7Us6fwC)
Port of @Arnauld's [Node.js answer](https://codegolf.stackexchange.com/a/248305/110555).  
-1 byte from @Arnauld altering their answer  
-1 byte thanks to @mathjunkie
---
# [Python](https://www.python.org), ~~116~~ 115 bytes
```
lambda s:4in[len({*s}|{*g})for g in["ueoi","UaOi"]]and not any(t in s.replace("i","")for t in["eo","oe","aO","Oa"])
```
[Attempt This Online!](https://ato.pxeger.com/run?1=pVDNSsQwEMaTOE8R5tQudU8eZKEP4CmnerFFxu10HahJaVJhWfdJvCyIvpM-jdNG8ODRHL4kX-b7Ia8fwz4-end668r6fYrd5fVn6OnpoSUTNlfi7np22WEVji-H1e6Yd340O6M0TuwFC6zICjYNudY4Hw25fRb13YT1yENPW85wHsNFGRcleyU8K5BVsIRNnqK_zs6HUVzM8JZ6aTGHyCHebylwMKVBREkLvIAVmAQqARYgPVgl_aQcg5_BVkKqWIehF3WsndotJdRybvhrvYGLFDtTBdYRC9MtlzyHn0K1u3HPf0qVGjBV4K2WIM1Psfz_2PQfp1PavwE)
Checks if the set of the characters in the string matches the two valid options (from the Venn diagram) and that none of the illegal combinations (`"eo","oe","aO","Oa"`) are in the `"i"`-stripped string.
-1 byte thanks to @mathjunkie
[Answer]
# [Retina 0.8.2](https://github.com/m-ender/retina/wiki/The-Language/a950ad7d925ec9316e3e2fb2cf5d49fd15d23e3d), 38 bytes
```
i
1`[oue][OUa]|[OUa][oue]|oe|eo|Oa|aO
```
[Try it online!](https://tio.run/##JYohDsAwDAO5/zFpdK8YDCqqKjUgwGSRppXl713WGVj22bc9vHRu@9kngaNXH9aqFG2xfPVwC/MQDZU5@QtOCDGIQhihGSShj2QG/0wKFaPAJR@a48/sBQ "Retina 0.8.2 – Try It Online") Link includes test cases. Outputs `0` for harmonic, `1` if not. Explanation: Retina 0.8.2 doesn't have any handy reversal operations so I have to test for all six non-harmonic vowel combinations.
[Answer]
# [05AB1E](https://github.com/Adriandmen/05AB1E/wiki/Commands), 12 [bytes](https://github.com/Adriandmen/05AB1E/wiki/Codepage)
```
'iKÇü^18%8ÖP
```
Port of [*@Arnauld*'s first JavaScript answer](https://codegolf.stackexchange.com/a/248305/52210).  
I tried to find a -1 byte alternative for the `18%8Ö` using any of 05AB1E's single-byte builtins and constants, but was unable to.
[Try it online](https://tio.run/##yy9OTMpM/f9fPdP7cPvhPXGGFqoWh6cF/P/vH5qZCAA) or [verify all test cases](https://tio.run/##yy9OTMpM/V9Waa@k8KhtkoKSfeV/9Uzvw@2H98QZWqhaHJ4W8F/nf7RSJgQo6Sgo5YNJfzBZCiZDwWQqmEyEiPhDVOaXQlSlgrlQ2j80MxEsHAoW9YdoToTogCtJhQinZuYrxQIA).
**Explanation:**
```
'iK          '# Remove all "i" from the (implicit) input-string
   Ç          # Convert the string to a list of codepoint-integers
    ü         # For each overlapping pair:
     ^        #  Bitwise-XOR them together
      18%     # Modulo-18 each integer
         8Ö   # Check for each integer whether it's divisible by 8
            P # Check if all are truthy by taking the product of the list
              # (after which the result is output implicitly)
```
]