8
\$\begingroup\$

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/(ΣFM)-1. If you have determined that the writer is male, the confidence level is 2*ΣM/(ΣFM)-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

  1. Percentage of vowels in English dictionary words (38.1%)
  2. Percentage of vowels in English texts (38.15%)
\$\endgroup\$
1
  • \$\begingroup\$ Comments are not for extended discussion; this conversation has been moved to chat. \$\endgroup\$
    – Dennis
    Commented Jul 22, 2017 at 6:09

8 Answers 8

5
\$\begingroup\$

Python 3, 320 317 307 286 253 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."))
\$\endgroup\$
6
  • 1
    \$\begingroup\$ You can save 4 bytes by using semicolons and putting all of the first function on one line. Try it online! \$\endgroup\$
    – sporkl
    Commented Jul 17, 2017 at 23:42
  • \$\begingroup\$ @ComradeSparklePony thanks! \$\endgroup\$
    – wrymug
    Commented Jul 17, 2017 at 23:45
  • 1
    \$\begingroup\$ map(e,s.split()) instead of [e(x)for x in s.split()] \$\endgroup\$
    – Value Ink
    Commented Jul 18, 2017 at 0:08
  • 1
    \$\begingroup\$ Also, it's better to return'FM'[h>S],2*max(S,h)/(S+h)-1 at the end \$\endgroup\$
    – Value Ink
    Commented Jul 18, 2017 at 0:10
  • 1
    \$\begingroup\$ I looked up a more efficient way to count vowels/consonants via sum(map(s.count,chars)), dropping your count to 253 bytes \$\endgroup\$
    – Value Ink
    Commented Jul 18, 2017 at 0:32
4
\$\begingroup\$

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!

\$\endgroup\$
4
  • 1
    \$\begingroup\$ 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. \$\endgroup\$
    – Value Ink
    Commented Jul 18, 2017 at 3:41
  • 1
    \$\begingroup\$ 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 \$\endgroup\$ Commented Jul 18, 2017 at 6:07
  • 1
    \$\begingroup\$ 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 \$\endgroup\$ Commented Jul 18, 2017 at 6:10
  • 1
    \$\begingroup\$ 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!] \$\endgroup\$ Commented Jul 18, 2017 at 6:57
4
\$\begingroup\$

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!

\$\endgroup\$
2
\$\begingroup\$

C (gcc), 237 229 222 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!

\$\endgroup\$
1
2
\$\begingroup\$

05AB1E, 61 bytes

ð¡εálžNžM‚δÀg©`3;*‚Â>/®¤sO/.4@©è®‚}0š.¡θ}€€нODZk„MFèsZ·sO/<‚

Try it online or verify all test cases.

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

Try the first four steps online.

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"

Try the first five steps online.

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

Try the first six steps online.

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)
\$\endgroup\$
2
\$\begingroup\$

JavaScript, 336 328 297 286 278 276 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);
}
\$\endgroup\$
1
\$\begingroup\$

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
\$\endgroup\$
1
\$\begingroup\$

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!

\$\endgroup\$

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.