20
\$\begingroup\$

The setup:

A social network reports the number of votes a post has in two ways: the number of net upvotes (total upvotes - total downvotes), and the % of votes that were upvotes, rounded to the nearest integer (.5 rounds up). The number of net upvotes is an integer (not necessarily positive), and the second is guaranteed to be an integer between 0 and +100 inclusive. The number of upvotes and the number of downvotes are both either zero or positive 32-bit integers (you can specify signed or unsigned). Assume that if there are zero total votes, the percentage upvoted is reported as zero.

The challenge:

Given these two integers (net upvotes and % upvoted), what is the shortest program you can write which determines the lowest number of total upvotes the post received, with all the constraints above satisfied?

Input constraints are guaranteed. If the input does not satisfy the constraints above, the program behavior is up to you. Bonus kudos if it doesn't enter an infinite loop or otherwise crash. Consider returning a negative number if you want more guidance.

General rules:

  • This is , so the shortest valid solution (measured in bytes) wins.
  • Don't let code-golf languages discourage you from posting answers with non-codegolfing languages. Try to come up with an as short as possible answer for 'any' programming language. Bonus kudos for a client-side Web language like Javascript.
  • If you have interesting solutions in multiple languages, post them separately.
  • Standard rules apply for your answer, so you are allowed to use STDIN/STDOUT, functions/method with the proper parameters and return-type, or full programs. Your call.
  • Default loopholes are forbidden.
  • If possible, please add a link with a test for your code.
  • Also, please add an explanation of how the code works.
  • Keep in mind that if you are doing an integer division operation that truncates (e.g. 20/3=6) rather than rounds, that might not be fully correct.
  • Additional test cases that explore the edge cases in the above constraints are welcome.
  • While the expected return type is numeric, boolean "false" can be used in place of 0.

Example test cases:

The first column is just a reference number included to facilitate discussion.

ref net  %up    answer
1   0    0   => 0    
2   -5   0   => 0    
3   -4   17  => 1    
4   -3   29  => 2    
5   -2   38  => 3    
6   -1   44  => 4    
7   0    50  => 1    
8   5    100 => 5    
9   4    83  => 5    
10  3    71  => 5    
11  2    63  => 5    
12  1    56  => 5    
13  1234 100 => 1234
14  800  90  => 894  (tip: don't refer to this as the "last test case;" others may be added.)
\$\endgroup\$
5
  • \$\begingroup\$ That zero total vote special case is pretty finicky. If there's an equal number of upvotes and downvotes, the percentage upvotes is 50%, except it's 0% when there's no votes, breaking the upvote-downvote symmetry. \$\endgroup\$
    – xnor
    May 12, 2018 at 0:18
  • 2
    \$\begingroup\$ @xnor 0/0 is generally undefined, so an assumption must be made. With this choice, you get an automatic "answer = second input" if the second input is 0, and an automatic "answer = first input" if the second input is 100. \$\endgroup\$
    – WBT
    May 12, 2018 at 0:30
  • 1
    \$\begingroup\$ Suggested test case borrowed from @nwellnhof: 1000, 100. Can you confirm that the expected answer is 1000? \$\endgroup\$
    – Arnauld
    May 12, 2018 at 13:46
  • 1
    \$\begingroup\$ Downvoted, because haters gotta hate :) \$\endgroup\$
    – user10766
    May 12, 2018 at 20:42
  • \$\begingroup\$ @Arnauld and nwellnhof: as noted in the comment just before yours, if the second input = 100, the answer = first input. If the 100 was really a rounded slightly lower percent, more than the first input # of upvotes would be required to get net votes = first input, and this challenge seeks the lowest number of total upvotes. \$\endgroup\$
    – WBT
    May 12, 2018 at 23:45

5 Answers 5

10
\$\begingroup\$

JavaScript (ES6), 47 bytes

Takes input in currying syntax (n)(p), where n is the number of net upvotes and p is the percentage of upvotes. May return false for 0.

n=>p=>(g=u=>u/(u-n/2)*50+.5^p?g(u+1):u)(n>0&&n)

Try it online!

Commented

n => p => (          // given n and p
  g = u =>           // g = recursive function taking u = number of upvotes
    u / (u - n / 2)  //   compute u / (total_votes / 2)
    * 50 + .5        //   turn it into a percentage, add 1/2
    ^ p ?            //   XOR it with p, which gives 0 if the integer parts are matching
                     //   if the result is not equal to 0:
      g(u + 1)       //     try again with u + 1
    :                //   else:
      u              //     stop recursion and return u
)(n > 0 && n)        // initial call to g() with u = max(0, n)

Edge cases

Let Fn(u) = u / (u - n / 2) * 50 + 0.5

  • If u = 0 and n = 0, then Fn(u) = NaN and Fn(u) XOR p = p. So, we return u = 0 if n = p = 0 (first iteration of the first test case) or continue with the recursion if p != 0 (first iteration of the 7th test case).

  • If u > 0 and u = n / 2, then Fn(u) = +Infinity and -- again -- Fn(u) XOR p = p. Unless p = 0, we just go on with the next iteration. (This happens in the 9th and 11th test cases.)

\$\endgroup\$
1
  • \$\begingroup\$ Nice! You get bonus kudos for language choice, and for including an explanation + link to a live demo! \$\endgroup\$
    – WBT
    May 12, 2018 at 0:44
7
\$\begingroup\$

Stax, 17 bytes

ëI╩½• ╠☺Vì∞«S↑♠αS

Run and debug it

This is brute force. It starts with 0 for candidate upvotes, and increments until it satisfies the formula.

Unpacked, ungolfed, and commented, it looks like this.

0       push zero
{       start filter block...
        candidate upvotes is on the stack
  cHx-  calculate candidate downvotes for denominator (upvotes * 2 - net)
  c1?   if denominator is zero, replace it with 1
  :_    floating point division
  AJ*   multiply by 100
  j     round to integer
  ;=    is equal to second input?
        increment until a match is found
}gs

Run this one

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

Clean, 114 107 104 bytes

import StdEnv
? =toReal o toInt
$a d#e= ?d
= ?a+until(\c#b= ~c*e/(e-100.0)
= ?(?100*b/(?b+c))==e)inc 0.0

Try it online!

Defines the function $ :: Int Int -> Real, where the arguments are signed integers and the return value is a double-precision float exactly representable by a 32-bit signed integer.

It checks every value of c in the equation b=-cd/(d+1) to find a b satisfying a+c=b and b/(b+c)=d, since the smallest c results in the smallest b, take the first element of the set of all solutions.

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

05AB1E, 13 bytes [mildly broken]

*²·т-/ò²т;Qi1

Try it online!

Explanation:

To solve this, I assumed inputs a, b and expected result x. Given the info in the setup, that gave me the equation:

 2x         100x
———— - a = ——————
 a           b

Rearranging for x gives

        ab
x = ——————————
     2b - 100

The only test case this doesn't work for is 0, 50 - I simply hardcoded to check for this.

*²·т-/ò²т;Qi1     Implicit Inputs: a, b              STACK (bottom to top)
*                 Multiply the inputs together       [ab]
 ²·               Take the second input * 2          [ab, 2b]
   т-             Subtract 100                       [ab, 2b - 100]
     /ò           Divide and round                   [round(ab/(2b-100))]
       ²т;Qi1     If 2nd input = 50, push 1 to stack
                  { Implicitly output top item of stack [either 1, or round(...)] }
\$\endgroup\$
5
  • \$\begingroup\$ This doesn't work correctly for some inputs. 90% with 800 net votes can be done with 894 up votes. \$\endgroup\$
    – recursive
    May 12, 2018 at 16:28
  • \$\begingroup\$ @recursive I know what it is. It assumes 90% exactly, not 89.5%. \$\endgroup\$ May 12, 2018 at 16:44
  • \$\begingroup\$ Well, closer to 90.5% in this case but yes. \$\endgroup\$
    – recursive
    May 12, 2018 at 16:45
  • 1
    \$\begingroup\$ Now I realise it's trickier than I thought. I'll think about it, but for now I'll mark it as broken. \$\endgroup\$ May 12, 2018 at 17:10
  • \$\begingroup\$ @GenoRacklinAsher Now I realise it's trickier than I thought. I'll think about it... Those are the kinds of comments I like to read, seeing them as the hallmark of a good puzzle :-). \$\endgroup\$
    – WBT
    May 13, 2018 at 1:04
0
\$\begingroup\$

Go 1.10, 154 bytes

func h(n,u float64)float64{if u==50{return 1};r:=Round(n*u/(2*u-100));s:=Round(n*(u+.5)/(2*u-99));v:=s/(2*s-n);if v>1||Round(v*100)!=u{return r};return s}

Try it on Go Playground! (TIO runs Go 1.9, which doesn't have math.Round)

Ungolfed version

func haters(n, u float64) float64 {
    if u == 50 {
        return 1
    }
    r := Round(n * u / (2*u - 100))
    //Test the case where we were given a percentage that was rounded down (e.g. 90.4% given as 90%)
    //We test this by adding 0.5% to u. The denominator is just a simplified form of 2*(u+0.5) - 100
    s := Round(n * (u + .5) / (2*u - 99))
    //Check if s is a valid result
    v := s / (2*s - n)
    if v > 1 || Round(v*100) != u {
        return r
    }
    //s is strictly less than r, so we don't need to check the minimum.
    return s
}

In the interests of adding an explanation, the above formula for r can be derived by simultaneously solving n=v-d and u = 100 * v/(v + d) for v, where v and d are the number of upvotes and downvotes respectively. The derived formula is undefined for v = 50, so we have to handle that case (which we do with the first if statement).

\$\endgroup\$

Your Answer

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

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