.i xu .e'o lo zunsnagri cu drani loka jboge'a

Given an input of a string consisting of two characters, output whether it is a valid consonant cluster in Lojban.

Here is a quote from CLL 3.6 detailing the rules for a valid consonant cluster pair (or rather, an invalid one):

1) It is forbidden for both consonants to be the same, as this would
   violate the rule against double consonants.

2) It is forbidden for one consonant to be voiced and the other unvoiced.
   The consonants “l”, “m”, “n”, and “r” are exempt from this restriction.
   As a result, “bf” is forbidden, and so is “sd”, but both “fl” and “vl”,
   and both “ls” and “lz”, are permitted.

3) It is forbidden for both consonants to be drawn from the set “c”, “j”,
   “s”, “z”.

4) The specific pairs “cx”, “kx”, “xc”, “xk”, and “mz” are forbidden.

The quote references "voiced" and "unvoiced" consonants. Here is a table of the unvoiced consonants and their voiced counterparts (also from CLL 3.6):

   p          b
   t          d
   k          g
   f          v
   c          j
   s          z
   x          -

Note that {x} has no voiced counterpart. For completeness, the remaining consonants that are not on this list (which can be either voiced or unvoiced for the purposes of the quote) are lmnr. (y is a vowel, and the letters hqw are not used.)

The input must be a single string, but you may assume that it will always consist of exactly two consonants, with optional trailing newline if you wish. The output may be any truthy or falsy value.

This is , so the shortest code in bytes wins.

Test cases (these are all possible input strings placed in the proper categories):

Valid consonant clusters:
 bd bg bj bl bm bn br bv bz cf ck cl cm cn cp cr ct db dg dj dl dm dn dr dv
 dz fc fk fl fm fn fp fr fs ft fx gb gd gj gl gm gn gr gv gz jb jd jg jl jm
 jn jr jv kc kf kl km kn kp kr ks kt lb lc ld lf lg lj lk lm ln lp lr ls lt
 lv lx lz mb mc md mf mg mj mk ml mn mp mr ms mt mv mx nb nc nd nf ng nj nk
 nl nm np nr ns nt nv nx nz pc pf pk pl pm pn pr ps pt px rb rc rd rf rg rj
 rk rl rm rn rp rs rt rv rx rz sf sk sl sm sn sp sr st sx tc tf tk tl tm tn
 tp tr ts tx vb vd vg vj vl vm vn vr vz xf xl xm xn xp xr xs xt zb zd zg zl
 zm zn zr zv

Invalid consonant clusters:
 bb bc bf bk bp bs bt bx cb cc cd cg cj cs cv cx cz dc dd df dk dp ds dt dx
 fb fd ff fg fj fv fz gc gf gg gk gp gs gt gx jc jf jj jk jp js jt jx jz kb
 kd kg kj kk kv kx kz ll mm mz nn pb pd pg pj pp pv pz rr sb sc sd sg sj ss
 sv sz tb td tg tj tt tv tz vc vf vk vp vs vt vv vx xb xc xd xg xj xk xv xx
 xz zc zf zj zk zp zs zt zx zz
  • \$\begingroup\$ Doorknob, this is very close to codegolf.stackexchange.com/q/66053/15599 I think about half my code might be reusable. \$\endgroup\$ Commented Jan 19, 2016 at 23:27
  • \$\begingroup\$ @steveverrill Right, I found that question, and I thought this would be sufficiently different given that you only get two characters as input and you don't have to handle vowels and such. \$\endgroup\$
    – Doorknob
    Commented Jan 19, 2016 at 23:30
  • 2
    \$\begingroup\$ @steveverrill ... but now I'm reconsidering, after taking a closer look at the answers. Do you think it would be better if I just left out the initial consonant pair part, and made the challenge simply "is this a valid consonant pair"? \$\endgroup\$
    – Doorknob
    Commented Jan 19, 2016 at 23:32
  • \$\begingroup\$ I think that would both increase the difference between the challenges and simplify this one, both of which would be a good thing. \$\endgroup\$ Commented Jan 19, 2016 at 23:38
  • \$\begingroup\$ @steveverrill Yeah, I agree now. Thanks! \$\endgroup\$
    – Doorknob
    Commented Jan 19, 2016 at 23:39

5 Answers 5


Retina, 59 57 54 53 52 bytes



The trailing linefeed is significant. For valid clusters, this outputs a non-empty string; for invalid ones the output is empty.

Try it online! This tests all clusters at once (removing all the invalid ones and leaving all the valid ones intact). To make that possible I've had to replace the ^ anchor with a \b word boundary.

Another solution for the same byte count:




The goal is to remove all invalid pairs completely. We can do with the valid pairs whatever we want as long as at least one character remains.


This takes care of three rules: (.)\1 matches any pair violating rule 1. [cjsz]{2} matches any pair violating 3. mz matches the specifically disallowed pair from rule 4.

That leaves only rule two and the other specific pairs xk, kx, xc and cx. We can save a bunch of bytes by doing some preprocessing so we have to handle fewer cases:


The idea is to collapse all voiced consonants into one, as well k and c. I'm also turning f into s out of necessity. This is a transliteration stage which will substitute individual characters for other characters. To see the actual mapping we need to expand the range and remember that the last character of the target list is repeated indefinitely:


The initial f => s is necessary, because it overrides the later f => v which would turn f into a voiced consonant. We also see that c is turned into k. And all the voiced consontants bdgjz are turned into v. That leaves ehi... luckily these are either vowels or unused in Lojban. The same could also have been achieved with


Alternatively, check out the other solution I posted above that uses a very different transliteration (with a reverse range, and it also turns k into c instead).

Now the remaining invalid combinations can be checked much more easily:


cx and cx have become kx and xk so we only need to check two cases now. For rule 2, we try to match the entire pair, starting from the beginning with an optional voiced consonant (reduced to v), a mandatory unvoiced consonant (where we don't need to check for f and c separately) and another optional voiced consonant. If the pair is a mix of voiced and unvoiced one of the two optional vs will match and the entire pair is removed. Otherwise, this can only match if the pair starts with a voiced consonant (and has anything else second) - in that case only the first character will be removed, and the other one will remain, still giving a truthy result.


Pyth, 53 48 47 bytes


This generates a list of all invalid pairs based on the rules above, then checks if the input is one of them.

! }                        A not in B
    z                      input
      "mz"                  "mz"
      s                    flattened
        .pM                permutations of each:
            s [               flatten the three-element array:
                +V              Alphabet vectorized concat with itself.
                   G            That is, duplicate letters
                c"xcxkcsjz"2     That string chopped every 2
                *               outer product of
                  "ptkfcsx"      voiced letters
                  "bdgvjz        and unvoiced letters

Try it here.


TypeScript Compiler,  427 417 bytes

type U="p"|"t"|"k"|"f"|"c"|"s"|"x"
type V="b"|"d"|"g"|"v"|"j"|"z"
type S="c"|"s"|"j"|"z"
type G<T,W>=T extends W?0:T extends"l"|"r"|"m"|"n"?1:T extends U?W extends V?0:1:W extends U?0:1
type H<T,W>=T extends"x"?W extends"k"|"c"?0:G<T,W>:T extends"k"|"c"?W extends"x"?0:G<T,W>:T extends"m"?W extends"z"?0:G<T,W>:G<T,W>
type L<T>=T extends`${infer A}${infer B}`?A extends S?B extends S?0:H<A,B>:H<A,B>:0
let y:L<"bd">=1

Playground link

If you replace the bd on the last line with an illegal pair of consonants, the code will not compile. I may well have missed some optimizations, but I'm just content with the fact that the `${infer A}${infer B}` works as intended. Interestingly, any single character is considered valid.


Pip, 49 bytes


Outputs with the truthy and falsey values switched (i.e. truthy if the consonant cluster is not valid, falsey if it is valid). If that's not considered acceptable for this challenge because it predates the linked consensus, then tack !: on the front for +2 bytes.

Verify all test cases: Try it online!


The input a is an invalid cluster if...

$Qa |

... its two characters are string-equal; or...

`[ptkfcsx][bdgvjz]|[ck]x` N PZa |

... the input PalindromiZed (e.g. xc -> xcx) contains an unvoiced consonant [ptkfcsx] followed by a voiced consonant [bdgvjz] or one of [ck] followed by x; or...


... the input consists entirely of one or more (in this case, two) characters from the set [cjsz], or consists entirely of the sequence mz.


Lexurgy, 128 bytes

Class U {p,t,k,f,c,s,x}
Class V {b,d,g,v,j,z}
Class J {c,j,s,z}
{{@U,@V,l,m,n,r}$1 $1,@V @U,@U @V,@J @J,{cx,kx,xc,xk,mz}}=>*

Replaces invalid clusters with an empty string.


Class U {p,t,k,f,c,s,x}
Class V {b,d,g,v,j,z}
Class C {@U,@V,l,m,n,r}
Class J {c,j,s,z}

@C$1 $1 => * # no dups
@V @U => * # no UV/VU pairs
@U @V => *
@J @J => * # no both cjsz
{cx,kx,xc,xk,mz} => * # specific pairs

Your Answer

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

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