# Is the writer a man or woman?

One problem on a website like this is that you often don't know if you are talking to a male or female. However, you have come up with a simple NLP technique you can use to determine the gender of the writer of a piece of text.

# Theory

About 38.1% of letters used in English are vowels [a,e,i,o,u] (see References below, y is NOT a vowel in this case). Therefore, we will define any word that is at least 40% vowels as a feminine word, and any word that is less than 40% vowels as a masculine word.

Beyond this definition we can also find the masculinity or femininity of a word. Let C be the number of consonants in the word, and V be the number of vowels:

• If a word is feminine, it's femininity is 1.5*V/(C+1).
• If a word is masculine, it's masculinity is C/(1.5*V+1).

For example, the word catch is masculine. Its masculinity is 4/(1.5*1+1) = 1.6. The word phone is feminine. Its femininity is 1.5*2/(3+1) = .75.

# Algorithm

To figure out the gender of the writer of a piece of text, we take the sum of the masculinity of all the masculine words (ΣM), and the sum of the femininity of all the feminine words (ΣF). If ΣM > ΣF, we have determined that the writer is a male. Otherwise, we have determined that the writer is a female.

# Confidence Level

Finally, we need a confidence level. If you have determined that the writer is female, your confidence level is 2*ΣF/(ΣF+ΣM)-1. If you have determined that the writer is male, the confidence level is 2*ΣM/(ΣF+ΣM)-1.

# Input

Input is a piece of English text including punctuation. Words are all separated by spaces (You don't have to worry about new-lines or extra spaces). Some words have non-letter characters in them, which you need to ignore (such as "You're"). If you encounter a word that is all non-letters (like "5" or "!!!") just ignore it. Every input will contain at least one usable word.

# Output

You need to output an M or F depending on which gender you think the writer is, followed by your confidence level.

# Examples

1. There's a snake in my boot.

• Gender + masculinity/femininity of each word: [M1.0,F1.5,F.75,F.75,M2.0,F1.0]
• ΣM = 3.0, ΣF = 4.0
• CL: 2*4.0/(4.0+3.0)-1 = .143
• Output: F .143
2. Frankly, I don't give a ^$*. • [M2.4,F1.5,M1.2,F1.0,F1.5], ΣM = 3.6, ΣF = 4.0, CL: 2*4.0/(4.0+3.6)-1 = .053, Output: F .053 3. I'm 50 dollars from my goal! • [F.75,M1.25,M1.2,M2.0,F1.0], ΣM = 4.45, ΣF = 1.75, CL: 2*4.45/(4.45+1.75)-1 = .435, Output: M .435 # References • Comments are not for extended discussion; this conversation has been moved to chat. Commented Jul 22, 2017 at 6:09 ## 8 Answers # Python 3, 320317307286253 189 bytes h=S=0 for v in input().split():V=sum(map(v.count,'aeiouAEIOU'));C=sum(x.isalpha()for x in v);H=V<.4*C;C-=V;K=[1.5*V/(C+1),C/(1.5*V+1)][H];h+=K*H;S+=K-K*H print('FM'[h>S],2*max(S,h)/(S+h)-1)  Try it online! Ungolfed: def evaluateWord(s): V = len([*filter(lambda c: c in 'aeiou', s.lower())]) C = len([*filter(lambda c: c in 'bcdfghjklmnpqrstvxzwy', s.lower())]) isMasculine = V < 0.4*(V+C) return C/(1.5*V+1) if isMasculine else 1.5*V/(C+1), isMasculine def evaluatePhrase(s): scores = [] for word in s.split(): scores.append(evaluateWord(word)) masc = 0 fem = 0 for score in scores: if score[1]: masc += score[0] else: fem += score[0] return ('M', 2*masc/(fem+masc)-1) if masc > fem else ('F', 2*fem/(fem+masc)-1) print(evaluatePhrase("There's a snake in my boot."))  • You can save 4 bytes by using semicolons and putting all of the first function on one line. Try it online! Commented Jul 17, 2017 at 23:42 • @ComradeSparklePony thanks! Commented Jul 17, 2017 at 23:45 • map(e,s.split()) instead of [e(x)for x in s.split()] Commented Jul 18, 2017 at 0:08 • Also, it's better to return'FM'[h>S],2*max(S,h)/(S+h)-1 at the end Commented Jul 18, 2017 at 0:10 • I looked up a more efficient way to count vowels/consonants via sum(map(s.count,chars)), dropping your count to 253 bytes Commented Jul 18, 2017 at 0:32 # Python 3, 205 201 197 192 bytes -Thanks @Value Ink for 4 bytes: lower() beforehand -Thanks @Coty Johnathan Saxman for 9 bytes: Inverted condition .4*(v+c)>v and -~c for (c+1) bitshift-based consonant check instead of literal. # Python 3, 192 bytes M=F=0 for i in input().lower().split(): v=sum(j in'aeiou'for j in i);c=sum(33021815<<98>>ord(k)&1for k in i) if.4*(v+c)>v:M+=c/(1.5*v+1) else:F-=1.5*v/~c print('FM'[M>F],2*max(M,F)/(F+M)-1)  Try it online! • for i in input().lower().split(): so that you only need to look in 'aeiou' for the vowel count and cut the lower call in the consonant count. Commented Jul 18, 2017 at 3:41 • In your 'else', the divisor (c+1) can be shortened to -~c, with no parentheses, saving a byte. This negative can then, in turn, be carried to your +=, making it a -= and saving one more byte. F-=1.5*v/~c Commented Jul 18, 2017 at 6:07 • Switching the order of your inequality (in your if statement) saves you one more byte because you can delete the space. if.4*(v+c)>v Commented Jul 18, 2017 at 6:10 • This is a tricky one, but you can save 5 bytes by switching your consonant lookup for a hardcoded binary lookup table. k in'bcdfghjklmnpqrstvxzwy'for k... becomes 33021815<<98>>ord(k)&1for k... [tio.run/… Try it online!] Commented Jul 18, 2017 at 6:57 # Ruby-ap, 154 140 bytes -ap loops over input and splits on spaces, storing the result in $F, then outputs $_ after the program finishes. m=f=0$F.map{s=_1.upcase.gsub(/[^A-Z]/){}
k=s.size-v=s.count('AEIOU')
k>v*1.5?m+=k/(1.5*v+1):f+=1.5*v/-~k}
$_=m>f ??M:?F,2*[m,f].max/(m+f)-1  Attempt This Online! # C (gcc), 237229222 216 bytes Boy I though I could do this in a LOT LESS BYTES... v,c;float m,f;g(char*s){for(m=f=0;*s;v*1.0/(c+v)<.4?m+=c/(1.5*v+1):1?f+=1.5*v/(c+1):0,s+=*s!=0)for(v=c=0;*s&&*s^32;s++)isalpha(*s)?strchr("AaEeIiOoUu",*s)?++v:++c:0;printf("%c %.3f",m>f?77:70,(m>f?2*m:2*f)/(f+m)-1);}  Try it online! • 196 bytes Commented Jan 22, 2019 at 18:36 # 05AB1E, 61 bytes ð¡εálžNžM‚δÃ€g©3;*‚Â>/®¤sO/.4@©è®‚}0š.¡θ}€€нODZk„MFèsZ·sO/<‚  Explanation: Step 1: Split the input into words, and get the amount of consonants and vowels of each word: ð¡ # Split the (implicit) input-string by spaces # (NOTE: builtin # doesn't work if the input is just a single word) ε # Map over each word: á # Only leave its letters (removing any punctuation) l # And convert it to lowercase žN # Push the lowercase consonants: "bcdfghjklmnpqrstvwxyz" žM‚ # Pair it with the vowels: ["bcdfghjklmnpqrstvwxyz","aeiou"] δ # Map over both using the lowercase word: Ã # And only keep those characters € # Then map over the pair: g # And get the length of each # (we now have a pair [amount_of_consonants,amount_of_vowels], # let's call these [C,V] like in the challenge description) © # Store this pair in variable ® (without popping)  Try step one online. Step 2: Apply the formulas: $$\\left[\frac{C}{1.5V+1},\frac{1.5V}{C+1}\right]\$$   # Pop and push both values separated to the stack 3;* # Multiply V by 1.5 (3 halved) ‚ # And pair it back together with C Â # Bifurcate; short for Duplicate & Reverse copy > # Increase both values in the reversed duplicate by 1 / # Divide the pairs at the same positions: # [C/(V*1.5+1),V*1.5/(C+1)]  Try the first two steps online. Step 3: Check if the word was feminine or masculine by checking if the amount of vowels is at least 40%:  ® # Push the pair from variable ® again ¤ # Push its last item (without popping the pair): V s # Swap so the pair it at the top of the stack again O # Sum them together: C+V / # Divide them: V/(C+V) .4@ # Check if this is larger than or equal to 0.4 # (1 if >=0.4; 0 if <0.4) © # Store this boolean as new ® (without popping) è # 0-based index it into the [C/(V*1.5+1),V*1.5/(C+1)]-pair ®‚ # And pair it together with boolean ® } # Close the map  Try the first three steps online. Step 4: Group all masculine and feminine values together:  0š # Prepend a 0 to the list of pairs # (this is to ensure the first group are the falsey/masculine pairs; # and the second group the truthy/feminine pairs) .¡ # Group the pairs (and the leading 0) by: θ # Their last item, which is the boolean }€ # After the group-by, map over each group: € # Map over each inner pair: н # And only leave the first item  Step 5: Get the sums of both groups, and check if the masculine or feminine sum is larger: O # Take the sum of each inner group D # Duplicate this pair of sums Z # Get the maximum (without popping the pair itself) k # Get the index of this maximum in the pair (0 or 1) „MFè # Index it into the string "MF"  Step 6: Apply the confidence formula: $$\\frac{2\max\left(\sum{F},\sum{M}\right)}{\sum{F}+\sum{M}}-1\$$  s # Swap so the duplicated pair of sums is at the top again Z # Get the maximum again (without popping) · # Double this maximum s # Swap so the pair is at the top of the stack again O # Sum them together / # Divide the doubled maximum by this sum < # Decrease it by 1  Step 7: And finally pair the two results together, and output it as result:  ‚ # And pair it together with the "M"/"F" # (after which the result is output implicitly)  # JavaScript, 336328297286278276 260 bytes t=>['FM'[+(g=((m=(r=s=>t.split .map(c=>[p=(v=(c=[...c][u='filter'](a=>/[a-zA-Z]/.test(a)))[u](a=>/[aeiouAEIOU]/.test(a)).length)>=(q=c.length)*.4?0:1,p?(q-v)/(1.5*v+1):1.5*v/(q-v+1)])[u](e=>e[0]==s).reduce((a,c)=>a+c[1],0))(1))>(f=r(0))))],2*(g?m:f)/(f+m)-1]  This took me a really long time to golf but it still is 260 bytes long. -8 bytes thanks to my own efforts: removed return statement and curly braces -31 bytes thanks to my own efforts: replaced long alphabet string literal inclusion test with regular expression test: why did I not think of that before. Code is also becoming more and more unreadable as time goes by. -11 bytes thanks to my own efforts: come to think of it, why not replace the other includes as well? -8 bytes thanks to my own efforts: noticed similarity between possible confidence levels and refactored -2 bytes thanks to my own efforts: new q variable set to c[h] used multiple times. day 2 of golfing super-long answer: -16 bytes thanks to my own efforts Ungolfed: function determine(text) { let words = text.split(' '); words = words.map(cur => { cur = [...cur].filter(a=>"abcdefghijklmnopqrstuvwxyz".includes(a.toLowerCase())).join; let vowels = [...cur].filter(a=>"aeiou".includes(a.toLowerCase())).length; let output = [vowels>=cur.length/2.5?'f':'m']; output.push(output[0]=='m'?(cur.length-vowels)/(1.5*vowels+1):1.5*vowels/(cur.length-vowels+1)); return output; }); let femi = words.filter(e=>e[0]=='f').reduce((a,c)=>a+c[1],0); let masc = words.filter(e=>e[0]=='m').reduce((a,c)=>a+c[1],0); let gender = (masc>femi?'m':'f'); let confidence = (gender=='f'?2*femi/(femi+masc)-1:2*masc/(femi+masc)-1); console.log(gender , confidence); }  # Common Lisp, 404 bytes (defun f(x &aux(a 0)c(f 0)m v u)(labels((w(x &aux(p(position #\ x)))(cons(#1=subseq x 0 p)(and p(w(#1#x(1+ p)))))))(dolist(e(w(coerce x'list)))(setf v(#2=count-if(lambda(x)(member x(coerce"aeiouAEIOU"'list)))e)u(#2#'alpha-char-p e)c(- u v)m(and(> c 0)(<(/ v c)4/6)))(and(> u 0)(if m(incf a(/ c(1+(* v 3/2))))(incf f(/ v 2/3(1+ c))))))(format t"~:[F~;M~] ~4f~%"(> a f)(-(/(* 2(if(> a f)a f))(+ a f))1))))  Good old verbose lisp! Try it online! Ungolfed version: (defun f(x &aux (a 0) c (f 0) m v u) ; parameter & auxiliary variables (labels ((w (x &aux (p (position #\ x))) ; recursive function to split input into words (cons (subseq x 0 p) (and p (w (subseq x (1+ p))))))) (dolist (e (w (coerce x 'list))) ; for each word (setf v (count-if (lambda (x) (member x(coerce"aeiouAEIOU"'list))) e) ; count vowels u (count-if 'alpha-char-p e) ; count all alfabetic letters c (- u v) ; calculate consonants m (and (> c 0) (< (/ v c) 4/6))); is male or not? (and (> u 0) ; if non-empty word (if m (incf a (/ c (1+ (* v 3/2)))); increase masculinity (incf f (/ v 2/3 (1+ c)))))) ; increase femininity (format t "~:[F~;M~] ~4f" ; print (> a f) ; “gender” (-(/ (* 2 (if (> a f)a f)) (+ a f)) 1)))) ; and confidence  # Raku, 147 bytes {{"{<M F>[[<]$_]} {2*.max/.sum-1}"}(@([Z+] map {my \v=1.5*.comb(rx:i/<[aeiou]>/);my \c=.comb(/<:L>/)-2*v/3;(c>v)*c/(v+1),(v>=c)*v/(c+1)},.words))}


Try it online!