17
\$\begingroup\$

You run a political website, and have determined that people have a better intuitive understanding when the chance of winning or losing an election is expressed as a ratio ("5 in 7") than when it is expressed as a percentage ("71%").

But you also don't want to display confusing ratios like "58 in 82", you'd like them to be more easily understood, even if they aren't quite as precise.

So, given a percentage between 0.1% and 99.9%, return the closest "easy-to-understand" ratio "x in y", using the following rules:

  1. Most values (see exceptions below) should return the closest ratio out of 10 or lower. 55% should return "5 in 9", not "11 in 20".
  2. Ratios should be reduced to their lowest terms. 65% should return "2 in 3", not "4 in 6".
  3. Values under 10% should return the closest ratio of the form "1 in n" where n is one of (10,12,15,20,30,40,50,60,70,80,90,100). For example, 6% should return "1 in 15".
  4. Values over 90% should return the closest ratio of the form "n-1 in n" where n is one of (10,12,15,20,30,40,50,60,70,80,90,100). For example, 98.7% should return "79 in 80".
  5. Values under 1% should return "<1 in 100"
  6. Values over 99% should return ">99 in 100"

Or, to think about it another way, your program should return the closest ratio from the following possible outputs (I've included their approximate values for your convenience):

<1 in 100
 1 in 100  = 1.00%
 1 in 90   = 1.11%
 1 in 80   = 1.25%
 1 in 70   = 1.43%
 1 in 60   = 1.67%
 1 in 50   = 2.00%
 1 in 40   = 2.50%
 1 in 30   = 3.33%
 1 in 20   = 5.00%
 1 in 15   = 6.67%
 1 in 12   = 8.33%
 1 in 10   = 10.00%
 1 in 9    = 11.11%
 1 in 8    = 12.50%
 1 in 7    = 14.29%
 1 in 6    = 16.67%
 1 in 5    = 20.00%
 2 in 9    = 22.22%
 1 in 4    = 25.00%
 2 in 7    = 28.57%
 3 in 10   = 30.00%
 1 in 3    = 33.33%
 3 in 8    = 37.50%
 2 in 5    = 40.00%
 3 in 7    = 42.86%
 4 in 9    = 44.44%
 1 in 2    = 50.00%
 5 in 9    = 55.56%
 4 in 7    = 57.14%
 3 in 5    = 60.00%
 5 in 8    = 62.50%
 2 in 3    = 66.67%
 7 in 10   = 70.00%
 5 in 7    = 71.43%
 3 in 4    = 75.00%
 7 in 9    = 77.78%
 4 in 5    = 80.00%
 5 in 6    = 83.33%
 6 in 7    = 85.71%
 7 in 8    = 87.50%
 8 in 9    = 88.89%
 9 in 10   = 90.00%
 11 in 12  = 91.67%
 14 in 15  = 93.33%
 19 in 20  = 95.00%
 29 in 30  = 96.67%
 39 in 40  = 97.50%
 49 in 50  = 98.00%
 59 in 60  = 98.33%
 69 in 70  = 98.57%
 79 in 80  = 98.75%
 89 in 90  = 98.89%
 99 in 100 = 99.00%
>99 in 100

Other stipulations:

  • Numeric input can be in the range of 0.1 to 99.9 or in the range of 0.001 to 0.999, whichever is more convenient. You must handle at least 3 significant digits.
  • You must output a ratio ("3 in 4"), not the equivalent fraction ("3/4").
  • If there are two ratios equally close to the input, your program can return either one. 7.5% could return "1 in 12" or "1 in 15".
  • Leading/trailing white space and/or new lines are fine

Examples:

Input  :   Output
 0.5   :  <1 in 100
 1.0   :   1 in 100
 1.5   :   1 in 70
 7.5   :   1 in 15  or  1 in 12 (either is acceptable)
 9.2   :   1 in 10
13.1   :   1 in 8
29.2   :   2 in 7
29.3   :   3 in 10
52.7   :   1 in 2
52.8   :   5 in 9
72.0   :   5 in 7
73.9   :   3 in 4
88.8   :   8 in 9
90.8   :   9 in 10
94.2   :  19 in 20
98.7   :  79 in 80
98.9   :  89 in 90
99.0   :  99 in 100
99.1   : >99 in 100

This is a challenge, shortest code in each language wins.

(Similar to, but not duplicate of: Convert a decimal to a fraction, Closest fraction, Approximate floating point number with n-digit precision)

\$\endgroup\$
5
  • \$\begingroup\$ If there are two ratios equally close to the input, your program can return either one. 7.5% could return "1 in 12" or "1 in 15" Does that mean that we can return 7 in 100 as well? Btw, 1 in 14 is closer to the input in this case. \$\endgroup\$
    – DimChtz
    Aug 29, 2018 at 14:48
  • \$\begingroup\$ @DimChtz No, as that violates rule 3 (values under 10% should be expressed as "1 in n", for specific possible values of n). \$\endgroup\$
    – BradC
    Aug 29, 2018 at 14:50
  • \$\begingroup\$ Ohh, I didn't notice this. Okay. \$\endgroup\$
    – DimChtz
    Aug 29, 2018 at 14:51
  • 2
    \$\begingroup\$ I'd like it if we could just output the numerator and denominator as any format like a tuple/list or something, but there are already competing answers so I suppose it's too late for this challenge. For future challenges though, I'd consider a more flexible I/O format because some languages lose more competitiveness than others when you require string handling. \$\endgroup\$
    – hyper-neutrino
    Aug 29, 2018 at 21:12
  • 1
    \$\begingroup\$ @BradC - LOL. I was just at 538, and I was all "Wow! I gotta make a golfing challenge out of this!" \$\endgroup\$
    – Chas Brown
    Aug 29, 2018 at 21:23

8 Answers 8

6
\$\begingroup\$

T-SQL, 385 bytes

SELECT TOP 1IIF(i>.99,'>',IIF(i<.01,'<',''))+n+' in '+d
FROM t,(SELECT ISNULL(PARSENAME(value,2),'1')n,PARSENAME(value,1)d FROM
STRING_SPLIT('100,90,80,70,60,50,40,30,20,15,12,10,9,8,7,6,5,2.9,4,2.7,3.10,3,3.8,2.5,3.7,4.9,2,5.9,4.7,3.5,5.8,2.3,7.10,5.7,3.4,7.9,4.5,5.6,6.7,7.8,8.9,9.10,11.12,14.15,19.20,29.30,39.40,49.50,59.60,69.70,79.80,89.90,99.100',','))m
ORDER BY ABS(i-ABS(n)/d)

Input is via pre-existing table t with numeric field i, per our IO standards.

That input table is joined with an in-memory table parsed from a string via STRING_SPLIT (which separates rows) and PARSENAME (which separates numerator and denominator via .).

The table is sorted by distance from the input value i, and returns the top row, formatted appropriately.

\$\endgroup\$
5
\$\begingroup\$

Charcoal, 84 bytes

NθF¹¹«F⊖ι⊞υ⟦⊕κι⟧≔⎇⊖ι∨×χι¹²¦¹⁵ιF²⊞υ⟦∨κ⊖ιι⟧»≔Eυ↔⁻θ∕§ι⁰§ι¹η≔⌕η⌊ηη×<‹θ·⁰¹×>›θ·⁹⁹⪫§υη in 

Try it online! Link is to verbose version of code. Takes input as a decimal rather than a percentage. Explanation:

Nθ

Input the fraction.

F¹¹«

Run from \$ n = 0 \$ to \$ n = 10 \$.

F⊖ι⊞υ⟦⊕κι⟧

Generate ratios for \$ \frac 1 n \$ to \$ \frac {n - 1} n \$.

≔⎇⊖ι∨×χι¹²¦¹⁵ι

Get the \$ n^{th} \$ element of the list \$ 12, 15, 20 ... 100 \$ into \$ n \$.

F²⊞υ⟦∨κ⊖ιι⟧»

Generate ratios for \$ \frac {n - 1} n \$ and \$ \frac 1 n \$.

≔Eυ↔⁻θ∕§ι⁰§ι¹η

Calculate the decimal values of all the ratios and take the absolute difference with the original input.

≔⌕η⌊ηη

Find the index of the least absolute difference. In case of a tie (e.g. \$ \frac 1 2 \$ and \$ \frac 2 4 \$), take the ratio generated first.

×<‹θ·⁰¹

Print a < if the input is less than \$ 0.01 \$.

×>›θ·⁹⁹

Print a > if the input is greater than \$ 0.99 \$.

⪫§υη in 

Join the numerator and denominator of the appropriate ratio with in and print.

\$\endgroup\$
5
\$\begingroup\$

JavaScript (ES7), 164 159 144 bytes

Expects an input ratio in \$]0,1[\$.

r=>(g=m=>--n+11?g((q=n>1?n*10:n+10-~'13'[n],d=((p=r<.1?1:r>.9?q-1:n<0&&r*q+.5|0)/q-r)**2)>m?m:(o=p+' in '+q,d)):r<.01?'<'+o:r>.99?'>'+o:o)(n=11)

Try it online!

How?

We try all possible ratios \$p/q\$. For each of them, we compute:

$$d=(p/q-r)^2$$

We update the best score \$m\$ (the lower, the better) each time \$d\$ is lower than or equal to \$m\$.

We go from the highest value of \$q\$ to the lowest one, so that a smaller denominator is preferred in case of a tie.

Commented

r => (g = m =>               // r = input; g() = recursive function, taking m = best score
  --n + 11 ?                 // decrement n; if n is still greater than or equal to -10:
    g(                       //   do a recursive call to g():
      ( q =                  //     compute q = denominator:
        n > 1 ?              //       if n is greater than 1:
          n * 10             //         q = n * 10 (20, 30, ..., 100)
        :                    //       else:
          n + 10 - ~'13'[n], //         q = 12 if n = 0, 15 if n = 1, n + 11 if n < 0
        d = ((               //     compute d = (p / q - r)²:
          p =                //       compute p = numerator:
          r < .1 ?           //         if r is less than 0.01:
            1                //           p = 1
          :                  //         else:
            r > .9 ?         //           if r is greater than 0.90:
              q - 1          //             p = q - 1
            :                //           else:
              n < 0 &&       //             if n is negative (i.e. q is in [1,10]):
              r * q + .5 | 0 //               p = round(r * q)
                             //             otherwise: p = 0 (which will be ignored)
          ) / q - r          //       compute p / q - r
        ) ** 2               //       and square the result (cheaper than absolute value)
      ) > m ?                //     if d is greater than m:
        m                    //       leave m unchanged
      : (                    //     else:
        o = p + ' in ' + q,  //       update the output string o
        d                    //       and update m to d
    ))                       //   end of recursive call
  :                          // else (all possible ratios have been tried out):
    r < .01 ? '<' + o :      //   if r is less than 0.01, prefix with '<'
    r > .99 ? '>' + o :      //   if r is greater than 0.99, prefix with '>'
    o                        //   otherwise, just return o
)(n = 11)                    // initial call to g() with m = n = 11
\$\endgroup\$
4
\$\begingroup\$

Python 2, 261 278 261 237 177 bytes

lambda n:' <>'[(n<.01)-(n>.99)]+'%d in %d'%min([(a,b)for b in[[12,15]+r(10,110,10),r(1,11)][.1<n<.9]for a in r([1,b-1][n>.9],[b,2][n<.1])],key=lambda(a,b):abs(1.*a/b-n))
r=range

Try it online!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Doesn't Python support semicolons? You could replace '\n ' with ';'... unless I'm wrong. \$\endgroup\$
    – Dev
    Aug 30, 2018 at 2:12
  • \$\begingroup\$ @BradC Fixed :) \$\endgroup\$
    – TFeld
    Aug 30, 2018 at 6:49
4
\$\begingroup\$

Jelly, 58 bytes

⁵R×⁵;12,15µ’,1,€)Ẏ;⁵Œc¤ð÷/ạ¥ÞḢj“ in ”
”<”>“”>.99$?<.01$?;Ç

Try it online!

-16 bytes thanks to Arnauld (can just prepend the < and > instead of rewriting the whole phrase)
-6 bytes and bug-fixes thanks to Jonathan Allan

\$\endgroup\$
10
  • \$\begingroup\$ @Arnauld Oh you're right, never thought about that :P Thanks! \$\endgroup\$
    – hyper-neutrino
    Aug 30, 2018 at 0:18
  • \$\begingroup\$ 0.3 should result in 3 in 10 not 2 in 7 \$\endgroup\$ Aug 30, 2018 at 7:45
  • \$\begingroup\$ You should just remove the µµ, no? EDIT - and then golf ÐṂṂ to ÞḢ \$\endgroup\$ Aug 30, 2018 at 7:45
  • \$\begingroup\$ changing 9 to should resolve the bug I believe. \$\endgroup\$ Aug 30, 2018 at 8:09
  • \$\begingroup\$ @JonathanAllan Oh whoops, yep I wasn't using 10 as a valid denominator. Thanks. And no, removing the double mu doesn't work because then the "minimum" is attached to the right side of the dyadic link-min function which is definitely not what I want, but only putting one mu doesn't seem to fix it. Thanks for the golf though :D \$\endgroup\$
    – hyper-neutrino
    Aug 31, 2018 at 15:06
3
\$\begingroup\$

Clean, 224 198 197 bytes

import StdEnv,Data.List,Text
t=toReal
$p=if(p<1.0)"<"if(p>99.0)">"""+snd(minimum[(abs(p-t n*1E2/t d),n<+" in "<+d)\\i<-[10,12,15:[20,30..100]],(n,d)<-[(1,i),(i-1,i):diag2[1..10][1..10]]|gcd n d<2])

Try it online!

Explained:

t = toReal                              // give `toReal` a shorter name
$ p
 = if(p < 1.0)                          // if the percentage is less than 1%
  "<"                                   // prepend "<"
 if(p > 99.0)                           // if the percentage is > 99%
  ">"                                   // prepend ">"
  ""                                    // otherwise prepend nothing
 + snd (                                // to the second element of
  minimum [                             // the smallest item in a list composed of
   (                                    // pairs of
    abs (                               // the absolute value of
     p -                                // the difference between the percentage
     t n*1E2 / t d                      // and the ratio
    ) 
   ,                                    // associated with
    n <+ " in " <+ d                    // the string representation of the ratio
   )                                    // in the form of a tuple
   \\ i <- [10, 12, 15: [20, 30..100]]  // for every special denominator `i`
   , (n, d) <- [(1, i), (i - 1, i): diag2 [1..10] [1..10]]
                                        // for every ratio `n` : `d`
   | gcd n d < 2                        // where `n` / `d` cannot be further simplified
  ]
 )
\$\endgroup\$
3
\$\begingroup\$

Jelly,  53  52 bytes

_.01,.99Ṡµ<0ịØ<ḣE⁵Ż×⁵+12,5Ṡ,’Ɗż€$Ẏ;⁵Œc¤÷/ạ¥Þ³Ḣj“ in 

A full program which prints the result.

Try it online!

Or see the test-suite

Note that the test-suite is altered to make the code a monadic link by:

  1. using the register keep a track of the current "program input", with ³ to ®; and
  2. closing the list of characters code for " in ", with “ in to “ in ”

How?

Starts with code that forces any necessary printing of the < or > sign and then code which constructs all the numerator-denominator pairs (with some redundant not simplified form versions, all after their simplified form) and prints the minimally different division-evaluated entry using a stable sort joined with in .

_.01,.99Ṡµ<0ịØ<ḣE⁵Ż×⁵+12,5Ṡ,’Ɗż€$Ẏ;⁵Œc¤÷/ạ¥Þ³Ḣj“ in  - Main Link: number in [0,1], n
 .01,.99                                             - literal pair = [0.01, 0.99]
_                                                    - subtract -> [n - 0.01, n - 0.99]
        Ṡ                                            - sign (vectorises) (-1 if <0; 1 if >0; else 0) 
         µ                                           - start a new monadic link
                                                     -   call that X
          <0                                         - less than zero? (vectorises)
             Ø<                                      - literal list of characters = "<>"
            ị                                        - index into (vectorises) ("<<" if n < 0.01; ">>" if n >= 0.99; else "><")
                E                                    - all (of X) equal? (1 if ((n < 0.01) OR (n > 0.99)) else 0
               ḣ                                     - head to index ("<" if n < 0.01; ">" if n > 0.99; else "")
                                                     -   (the following nilad forces a print of that)
                 ⁵                                   - literal 10
                  Ż                                  - zero-range -> [0,1,2,3,4,5,6,7,8,9,10]
                   ×⁵                                - multiply by 10 -> [0,10,20,30,40,50,60,70,80,90,100]
                      12,5                           - literal pair = [12,5]
                     +                               - add -> [12,15,20,30,40,50,60,70,80,90,100]
                                $                    - last two links as a monad
                             Ɗ                       -   last three links as a monad
                          Ṡ                          -     sign -> [1,1,1,1,1,1,1,1,1,1,1]
                            ’                        -     decrement -> [11,14,19,29,39,49,59,69,79,89,99]
                           ,                         -     pair -> [[1,1,1,1,1,1,1,1,1,1,1],[11,14,19,29,39,49,59,69,79,89,99]]
                              ż€                     -   zip with for €ach -> [[[1,12],[1,15],[1,20],[1,30],[1,40],[1,50],[1,60],[1,70],[1,80],[1,90],[1,100]],[[11,12],[14,15],[19,20],[29,30],[39,40],[49,50],[59,60],[69,70],[79,80],[89,90],[99,100]]]
                                 Ẏ                   - tighten -> [[1,12],[1,15],[1,20],[1,30],[1,40],[1,50],[1,60],[1,70],[1,80],[1,90],[1,100],[11,12],[14,15],[19,20],[29,30],[39,40],[49,50],[59,60],[69,70],[79,80],[89,90],[99,100]]
                                      ¤              - nilad followed by link(s) as a nilad:
                                   ⁵                 -   literal 10
                                    Œc               -   unordered pairs -> [[1,2],[1,3],[1,4],[1,5],[1,6],[1,7],[1,8],[1,9],[1,10],[2,3],[2,4],[2,5],[2,6],[2,7],[2,8],[2,9],[2,10],[3,4],[3,5],[3,6],[3,7],[3,8],[3,9],[3,10],[4,5],[4,6],[4,7],[4,8],[4,9],[4,10],[5,6],[5,7],[5,8],[5,9],[5,10],[6,7],[6,8],[6,9],[6,10],[7,8],[7,9],[7,10],[8,9],[8,10],[9,10]]
                                  ;                  - concatenate -> [[1,12],[1,15],[1,20],[1,30],[1,40],[1,50],[1,60],[1,70],[1,80],[1,90],[1,100],[11,12],[14,15],[19,20],[29,30],[39,40],[49,50],[59,60],[69,70],[79,80],[89,90],[99,100],[1,2],[1,3],[1,4],[1,5],[1,6],[1,7],[1,8],[1,9],[1,10],[2,3],[2,4],[2,5],[2,6],[2,7],[2,8],[2,9],[2,10],[3,4],[3,5],[3,6],[3,7],[3,8],[3,9],[3,10],[4,5],[4,6],[4,7],[4,8],[4,9],[4,10],[5,6],[5,7],[5,8],[5,9],[5,10],[6,7],[6,8],[6,9],[6,10],[7,8],[7,9],[7,10],[8,9],[8,10],[9,10]]
                                           Þ         - sort by:
                                          ¥          -   last two links as a dyad:
                                                     -       ...(with right argument of
                                            ³        -           the program input, n)
                                        /            -     reduce by:
                                       ÷             -       division
                                         ạ           -     absolute difference
                                             Ḣ       - head
                                               “ in  - literal list of characters " in "
                                              ;      - concatenate
                                                     - implicit print
\$\endgroup\$
2
\$\begingroup\$

Perl 6, 118 bytes

{'<'x(.01>$_)~'>'x($_>.99)~(|(1..9 X ^11),|map({|(1,$_-1 X$_)},12,15,|(^11 X*10))).min({abs $_-[/] @^a}).join(' in ')}

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.