# 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. – Dennis Jul 22 '17 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! – sporklpony Jul 17 '17 at 23:42 • @ComradeSparklePony thanks! – wrymug Jul 17 '17 at 23:45 • map(e,s.split()) instead of [e(x)for x in s.split()] – Value Ink Jul 18 '17 at 0:08 • Also, it's better to return'FM'[h>S],2*max(S,h)/(S+h)-1 at the end – Value Ink Jul 18 '17 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 – Value Ink Jul 18 '17 at 0:32 # Ruby, 154+1 = 155 bytes Uses the -n flag. m=f=0 gsub(/\S+/){s=$&.gsub(/[^a-z]/i){}.upcase;k=s.size;v=s.count'AEIOU';v<k*0.4?m+=(k-v)/(1.5*v+1):f+=1.5*v/(k-v+1)}
puts m>f ??M:?F,2*[m,f].max/(m+f)-1


Try it online!

# 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. – Value Ink Jul 18 '17 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 – Coty Johnathan Saxman Jul 18 '17 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 – Coty Johnathan Saxman Jul 18 '17 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!] – Coty Johnathan Saxman Jul 18 '17 at 6:57

# 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!

# 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!

# 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);
}