# [Haskell](https://www.haskell.org) + [hgl](https://gitlab.com/wheatwizard/haskell-golfing-library), 186 bytes ```haskell c="[^aeiou]" v="[aeiou]" ``` ``` cP$rXw(yS hdS)$fo["([mpbkgfvcsx][lr]|[cs][mnptkf]|[jz][mbdgv]|t[csr]|d[jzr])",v,c,v,"|",c,v,"([cjsz]{2}|x[kc]|[kc]x|mz|/p)!([ptkfcsxlmnr]{2}|[bdgvjzlmnr]{2})",v] ``` [Attempt This Online!](https://ato.pxeger.com/run?1=XVBBTsMwEBTXvGKxemilVkicECIvQKoq9QKyjOQ6duskjo1jhxDCiWdw6QXxCV7AD8prcNsYEAfbM7PrGXtf3ze0LnhZbneXUhltHdx7WkoheQYLy0uf8WQoLJLZDObaga6AOp08LiEFsYLPlzfvxOxi98FShO8ol9oTlDSBRCxSthjZm4dxuLPJlpOR0BiNsTKrYi0aVrcEl5b0mNUEq8q4QgSSd4GssnVDehcqoZ4FzZIJmjZTFhbq0fEcY5bXHXk6f-5bXLBwN2xtr7r-zExOx3hvGEJKVdlDE9675l3ke0Ny_MPXybXjtbuFNAHAgNayVh4FPAXEhDQRW1q1csDKm0pHXao1H3Cn65YO2HlVH_pJkuwD5jGA55aqoamVTrsBZ-6PKaM2mprW59GUcdf434AuPugw9QEXlOW_j2iLHx9fVT8-jBYxq9TORd0EOXoW3Ds9fEBRWaWZDsRYGIGAKzgM7b8wP850uz2e3w) # Without regex, 189 bytes ```haskell y=jzW(p2**).*wR v=xys W5 c=xys d d="ptkfcsxlmnr bdgvjzlmnr" ``` ``` cP$fo[asy$y"mpbkgfvcsx cs jz t d""lr mnptkf mbdgv csr jzr",v,c,v]++fo[c,v,asy$cX(y d d)$zW p2 d d<>y"cjsz x kc m""cjsz kc x z",v] ``` [Attempt This Online!](https://ato.pxeger.com/run?1=ZZDBjtMwEIbF1U8xsnrY7aZ7WAmJQ8KJG6iquBRU9eC1neIkjo09Dk5ehUsviMfgOZanwWnjXQQnf_5n_M8__v7zC_Ot7Lrzk1baGofwNbBO1UoK2DnZBSHJf4V3DNn9B-Uxl3Zks4GtQTA9MDQwSiTNtIcKmh7KsoRpT-wDRBiTdIjFePwRsN68efo1Vqnvxj6s17f3628fyVDF0cP-NeEXEERU1GJbcx873Tt4FKehmWakpK74blWbA_PjaqTaPranekiNwD00EyAISjsHup8NQM9PU8mlmqPFUPBiON7dpfcJitmDf7oZQYC4XaXoKW7C8u1IeeOnFL3loOn1kjDClDyWNX6_eo_S42eoCMAB6El5HWjiAiivlc3sWB_VwjrY3mRd6ZNceDI-soUxaH_pPxIyD9jmAbJxTC9NUaHBhQX-ZcqZy6Y2hiabcolDeBkw5UBMKpP1lvHmJURsn31C3z_7cNbmWZ1BzLpNcvZsZUCzLKCZ6ith0sU6WEENJVw-7V9he_3T8_l6_gE) ## Explanation First we define some helpers. `y=jzW(p2**).*wR` is the most complex. It's a weird synthetic operation that happens to be useful in this usecase, so it doesn't really have a simple explanation. But you can break it down part by part: * `(p2**)` takes two lists of characters and creates all strings formed by taking one character from the first and one from the second, so ``` >>> (p2**) "abc" "ae" ["aa","ae","ba","be","ca","ce"] ``` * `jzW` applies it pairwise to two lists. This allows us to get a bunch of pairs made from combinations of specific sets of characters. * `(.*wR)` makes it so it takes the lists as space separated strings. This is just a denser format than lists of strings. `v` is relatively simple. It's a parser that parses any vowel, `aeiou`. Similarly `c` parses any Lojban consonant (or a space). `d` is our list of all consonants, it contains a space and some repeats because this format is useful for other things. We can do this since `xys` basically treats its input as a set and we know the input will never have a space. Specifically the string is `<voiceless consonants><sonorants> <voiced consonants><sonorants>`. Now we get into the body, this has the form: ``` cP$fo[??,v,c,v]++fo[c,v,??,v] ``` with the two `??`s each being replaced with the clustering rules. ## Reflection The non-regex answer is *so close* to beating the regex answer. I have some improvements: * While `jzW(p2**).*wR` is overly synthetic `jzW(p2**)` and `zW p2` are both probably useful. * `"[aeiou]"` is shorter than `'[':W5<>"]"`. If there were a function, `ekQ`, to enclose a string in square brackets `ekQ W5` would be shorter than both. * There should probably be constants for consonants of the ISO alphabet as well as the ones for vowels. * Back-referencing would be useful in the regex answer. It's already a planned feature, there are just technical hurdles on the way to implementing it. * There could be more versatile ways to handle user input parsers. `v` and `c` could potentially have been user input parsers if there existed the ability to supply more than one. * Along the lines of that, I could make a way for the user to assign escape sequences in some sort of header string, like `rwh"v[aeiou];c[^aeiou]""/c/v/c/c/v"`.