Intersection of circles – How can I reduce this golf code to 127 bytes?

11

0

I started a CodeWars kata in Python, just two days ago, related to code golf.

Task: Given two congruent circles a and b of radius r, return the area of their intersection rounded down to the nearest integer, in less than 128 chars.

  • Has to be a one-liner.

  • Version: Python 3.6

  • External libraries: You may import NumPy as an external library.

  • Function name: circleIntersection

  • Input example: circleIntersection([0,0],[0,10], 10)

This is the closest I could come (129):

from math import*;circleIntersection=lambda a,b,r:r*r*(lambda h:h<1and acos(h)-h*(1-h*h)**.5)(hypot(b[0]-a[0],b[1]-a[1])/r/2)//.5

Leo oeL

Posted 2020-02-05T17:26:46.657

Reputation: 113

You mention that 3.6 is the latest version provided -- is Python 2 an option? If so, I have a way to save bytes with that. It could also help to link the site's problem page if we can view that without login. – xnor – 2020-02-05T23:27:42.310

Yes, Python 2 is an option. You need to login in order to view the problem :( – Leo oeL – 2020-02-06T00:07:38.177

Answers

4

Direct translation to NumPy: 127 bytes

from numpy import*
circleIntersection=lambda a,b,r:r*r*(lambda h:h<1and arccos(h)-h*(1-h*h)**.5)(hypot(*subtract(b,a))/r/2)//.5

Try it online!

Converting your code to numpy seems to save the required bytes. While using hypot is still ugly, being able to splat the result of subtract is still enough shorter that the loss of converting to arccos doesn't matter.

It is possible this can save more since it doesn't error on h<1 and will return nan in the case that the circles do not intersect. I don't know if that is permissible though since nan is not equal to zero.

I'm not convinced this is optimal, in particular I didn't look at improving the algorithm at all.

FryAmTheEggman

Posted 2020-02-05T17:26:46.657

Reputation: 16 206

I can't thank you enough! I tried NaN in one of my desperate tries and it was not acceptable. However, your piece of code did the job and it is successfully passing all tests! It is amazing how 2 bytes kept me awake all night haha! – Leo oeL – 2020-02-05T20:27:37.010

Subtract did the magic and I will be honest with you, this is the first time I see it. Need to dig into it and see how it works... once again, thanks for your effort and your time! – Leo oeL – 2020-02-05T20:45:25.543

@LeooeL No problem! I found subtract in this list of numpy functions, for reference.

– FryAmTheEggman – 2020-02-05T20:56:53.903

7

Use Python 2's arg unpacking: 124 bytes

from math import*;circleIntersection=lambda(v,w),(x,y),r:r*r*(lambda h:h<1and acos(h)-h*(1-h*h)**.5)(hypot(v-x,w-y)/r/2)//.5

Try it online!

Python 2 has parameter unpacking, allowing the input point arguments to be taken directly as pairs (v,w) and (x,y), where the input lists like [0,0] and [0,10] will be unpacked into the respective variables. This feature was removed in Python 3 -- a pity, in my opinion, since the old syntax seems more readable. But, the site has a Python 2 option, and nothing in your solution relies on Python 3.

xnor

Posted 2020-02-05T17:26:46.657

Reputation: 115 687

Works like a charm for now as sooner or later they plan to remove Python 2 from their platform. Thank you once again ^_^ – Leo oeL – 2020-02-06T00:36:53.547

This was actually the first thing I tried, but I assumed when I got a syntax error that I had just misremembered where you were allowed to do that. Thanks for finding it and explaining what had happened! – FryAmTheEggman – 2020-02-06T06:03:34.083

5

Use def: 125 bytes

You have a classic dilemma of wanting to assign to a variable without losing the brevity of a lambda function. Your method of using an inner lambda for assignment is one solution. The problem site not having Python 3.8 means the walrus operator is unfortunately off limits.

But, it's shorter here to just use def and suck up the bytes of writing out return. This is generically a 7-byte cost according to this reference. Within the def, we can just write the statement h= to do the assignment. We put everything on one line separated with ; to avoid needing to indent.

125 bytes

from math import*
def circleIntersection(a,b,r):h=hypot(b[0]-a[0],b[1]-a[1])/r/2;return(h<1and acos(h)-h*(1-h*h)**.5)*r*r//.5

Try it online! (vs original)

I've changed the multiplication order from return r*r*(...)//.5 to return(...)*r*r//.5 to cut the space after return.

There are likely other byte saves, including FryAmTheEggman's switch to numpy, but refactoring from lambda to def is already enough to get below 128 bytes.

xnor

Posted 2020-02-05T17:26:46.657

Reputation: 115 687

Hello, @xnor and thank you for your time. Your idea is brilliant and so well thought, however, I do apologise as I didn't mention that another requirement is that it has to be a one-liner. Will update the description asap. – Leo oeL – 2020-02-06T00:04:19.530

2@LeooeL Ah, too bad. When you say one line, do you mean literally that the code needs to not contain any newlines? Like, if there weren't an import, would a def like here work? – xnor – 2020-02-06T00:23:41.990