# Averages of Angles

## Story, or why we are doing this.

None. This exercise is completely pointless ... unless you are Stephen Hawking.

# The Challenge

Given a list of angles, find the average of those angles. For example the average of 91 degrees and -91 degrees is 180 degrees. You can use a program or function to do this.

# Input

A list of degree values representing angle measures. You may assume that they will be integers. They can be inputted in any convenient format or provided as function arguments.

# Output

The average of the inputted values. If there are more than one value found for the average, only one should be outputted. The average is defined as the value for which

is minimized. The output must be within the range of (-180, 180] and be accurate to at least two places behind the decimal point.

Examples:

> 1 3
2
> 90 -90
0 or 180
> 0 -120 120
0 or -120 or 120
> 0 810
45
> 1 3 3
2.33
> 180 60 -60
180 or 60 or -60
> 0 15 45 460
40
> 91 -91
180
> -89 89
0


As usual with , the submission with the least bytes wins.

Here is a Stack Snippet to generate both a regular leaderboard and an overview of winners by language.

var QUESTION_ID=60538,OVERRIDE_USER=32700;function answersUrl(e){return"http://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function commentUrl(e,s){return"http://api.stackexchange.com/2.2/answers/"+s.join(";")+"/comments?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+COMMENT_FILTER}function getAnswers(){jQuery.ajax({url:answersUrl(answer_page++),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){answers.push.apply(answers,e.items),answers_hash=[],answer_ids=[],e.items.forEach(function(e){e.comments=[];var s=+e.share_link.match(/\d+/);answer_ids.push(s),answers_hash[s]=e}),e.has_more||(more_answers=!1),comment_page=1,getComments()}})}function getComments(){jQuery.ajax({url:commentUrl(comment_page++,answer_ids),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){e.items.forEach(function(e){e.owner.user_id===OVERRIDE_USER&&answers_hash[e.post_id].comments.push(e)}),e.has_more?getComments():more_answers?getAnswers():process()}})}function getAuthorName(e){return e.owner.display_name}function process(){var e=[];answers.forEach(function(s){var r=s.body;s.comments.forEach(function(e){OVERRIDE_REG.test(e.body)&&(r="<h1>"+e.body.replace(OVERRIDE_REG,"")+"</h1>")});var a=r.match(SCORE_REG);a&&e.push({user:getAuthorName(s),size:+a[2],language:a[1],link:s.share_link})}),e.sort(function(e,s){var r=e.size,a=s.size;return r-a});var s={},r=1,a=null,n=1;e.forEach(function(e){e.size!=a&&(n=r),a=e.size,++r;var t=jQuery("#answer-template").html();t=t.replace("{{PLACE}}",n+".").replace("{{NAME}}",e.user).replace("{{LANGUAGE}}",e.language).replace("{{SIZE}}",e.size).replace("{{LINK}}",e.link),t=jQuery(t),jQuery("#answers").append(t);var o=e.language;/<a/.test(o)&&(o=jQuery(o).text()),s[o]=s[o]||{lang:e.language,user:e.user,size:e.size,link:e.link}});var t=[];for(var o in s)s.hasOwnProperty(o)&&t.push(s[o]);t.sort(function(e,s){return e.lang>s.lang?1:e.lang<s.lang?-1:0});for(var c=0;c<t.length;++c){var i=jQuery("#language-template").html(),o=t[c];i=i.replace("{{LANGUAGE}}",o.lang).replace("{{NAME}}",o.user).replace("{{SIZE}}",o.size).replace("{{LINK}}",o.link),i=jQuery(i),jQuery("#languages").append(i)}}var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",COMMENT_FILTER="!)Q2B_A2kjfAiU78X(md6BoYk",answers=[],answers_hash,answer_ids,answer_page=1,more_answers=!0,comment_page;getAnswers();var SCORE_REG=/<h\d>\s*([^\n,]*[^\s,]),.*?(\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)/,OVERRIDE_REG=/^Override\s*header:\s*/i;
body{text-align:left!important}#answer-list,#language-list{padding:10px;width:290px;float:left}table thead{font-weight:700}table td{padding:5px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b"> <div id="answer-list"> <h2>Leaderboard</h2> <table class="answer-list"> <thead> <tr><td></td><td>Author</td><td>Language</td><td>Size</td></tr></thead> <tbody id="answers"> </tbody> </table> </div><div id="language-list"> <h2>Winners by Language</h2> <table class="language-list"> <thead> <tr><td>Language</td><td>User</td><td>Score</td></tr></thead> <tbody id="languages"> </tbody> </table> </div><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody> </table> <table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody> </table>

## Language Name, N bytes


where N is the size of your submission. If you improve your score, you can keep old scores in the headline, by striking them through. For instance:

## Ruby, <s>104</s> <s>101</s> 96 bytes


If there you want to include multiple numbers in your header (e.g. because your score is the sum of two files or you want to list interpreter flag penalties separately), make sure that the actual score is the last number in the header:

## Perl, 43 + 2 (-p flag) = 45 bytes


You can also make the language name a link which will then show up in the leaderboard snippet:

## [><>](http://esolangs.org/wiki/Fish), 121 bytes


Here is a chatroom for any questions about the problem: http://chat.stackexchange.com/rooms/30175/room-for-average-of-angles

• Shouldn't 90,-90 give 180 if 91,-91 gives 180? – Blue Oct 12 '15 at 20:58
• Intuitively the average of -91 and 91 is 0, not 180. Using your definition we have: (180-91)^2+(180- -91)^2 => 81362, while (0-91)^2+(0- -91)^2 => 16562. So 180 surely can not be the average. What am I missing here? – edc65 Oct 12 '15 at 20:59
• 91%360=91;-91%360=269;(269+91)/2=180. Never mind, misread. Maybe? I'm not sure now. – Blue Oct 12 '15 at 21:01
• Ok thanks. Still no idea about how finding it – edc65 Oct 12 '15 at 21:52
• None of your test cases so far break the incorrect algorithm of simply taking all the angles mod 360°, taking their average, and then subtracting 360° if the result is greater than 180°. You should add a case like [89°, −89°], which should return 0°. – Anders Kaseorg Oct 13 '15 at 4:25

# Python 3, 129 bytes

lambda l:min([sum(((b-a)%360)**2for b in l)*len(l)-s*s,180-(180-a-s/len(l))%360]for a in l for s in[sum((b-a)%360for b in l)])[1]


This problem seems to have generated quite a lot of confusion. Intuitively, the idea is to cut the circle of angles at some point, unwrap the circle to a line, compute the arithmetic mean on that line, and then wrap the result back to the circle. But there are many different points where you could choose to cut the circle. It is not enough to arbitrarily pick one, such as 0° or 180°. You need to try them all and see which one results in the smallest sum of squared distances. If your solution is significantly less complicated than this, it is probably wrong.

• @AndreasKaseorg I think you can save one byte by changing s**2 to s*s – Ioannes Oct 13 '15 at 7:55
• See my comment on the question. – msh210 May 13 '18 at 16:27
• @msh210 Not sure why you’re directing this comment at me specifically. My solution already works that way. – Anders Kaseorg May 13 '18 at 17:27
• It was partially in reply to the last sentence of this answer post. – msh210 May 13 '18 at 18:13

## Python 3, 85 bytes

lambda l:180-min(range(72000),key=lambda x:sum((180-(x/200+i)%360)**2for i in l))/200


Takes advantage of the the answer only needing to be accurate to two decimal points by trying all possible angles with increments of 1/200 of a degree. This takes less than a second on my machine.

Because Python doesn't let us conveniently list arithmetic progressions of floats, we represent the possible angles as whole number [0,72000), which convert to an angle as (-180,180] as x -> 180 - x/200. We find the one of these that gives the minimum sum of squared angular differences.

For two angles with an angular displacement of d, the squared angular distance is found by transforming to an equivalent angle in (-180,180] as 180-(d+180)%360, then squaring. Conveniently, the angle given by x/200 is already offset by 180 degrees.

# Octave, 97 95 bytes

p=pi;x=p:-1e-5:-p;z=@(L)sum((p-abs(abs(x-mod(L,360)*p/180)-p)).^2);@(L)x(z(L)==min(z(L)))*180/p


This produces an anonymous function that just searches the minimum of the given function on a grid that is just fine enough. As input the function accepts column vectors, e.g. [180; 60; -60]. For testing you need to give the function a name. So you could e.g. run the code above and then use ans([180, 60; -60]).

• Yes, it returns 180. – flawr Oct 16 '15 at 15:13

# CJam,  44  40 bytes

Ie3_2*,f-:e-2{ea:~f{-P*180/mcmC2#}:+}$0=  Try it online in the CJam interpreter. ### Test cases $ for i in 1\ 3 90\ -90 0\ -120\ 120 0\ 810 1\ 3\ 3 180\ 60\ -60 0\ 15\ 45\ 460 91\ -91 -89\ 89
> do cjam <(echo 'Ie3_2*,f-:e-2{ea:~f{-P*180/mcmC2#}:+}$0=')$i
> echo
> done
2.0
180.0
0.0
45.0
2.33
60.0
40.0
180.0
0.0


### Idea

We compute the deviation for all potential averages from -179.99 to 180.00 with steps of size 0.01, and select the one with the lowest deviation.

For this purpose, it doesn't matter if we take the angular distances degrees or radians. Rather than mapping the differences δ of angles from input and potential averages in [0,360°) and conditionally subtracting the result from 180°, we can simply calculate arccos(cos(πδ÷180°)), since cos is both periodic and even, and arccos always yields a value in [0,π).

### Code

Ie3        e# Push 18e3 = 18,000.
_2*        e# Copy and multiply by 2. Pushes 36,000.
,          e# Push the range [0 ... 35,999].
f-         e# Subtract each element from 18,000. Pushes [18,000 ... -17,999].
:e-2       e# Divide each element by 100. Pushes [180.00 ... -179.99].
{          e# Sort; for each element A of that array:
ea:~     e#   Push and evaluate the array of command-line arguments.
f{       e#   For each input angle, push A and the angle; then:
-      e#     Subtract the angle from A.
P*180/ e#     Convert from degrees to radians.
mcmC   e#     Apply cos, then arccos to the result.
2#     e#     Square.
}        e#
:+       e#   Add the squares. This calculates the deviation.
}\$         e# A's with lower deviations come first.
0=         e# Select the first element of the sorted array.


# Javascript ES6, 87 bytes

with(Math)f=(...n)=>(t=>180/PI*atan(t(sin)/t(cos)))(f=>n.reduce((p,c)=>p+=f(c*PI/180)))


Example runs (Tested in Firefox):

f(-91,91)     // -0
f(-90,90)     // 0
f(0,-120,120) // 0
f(0,810)      // 44.999999999999936


Work in progress

This version takes a slightly different approach than the average-everything-then-do-modular-math. Rather, the angles are converted to vectors, the vectors are added and the angle of the resulting vector is then computed. Unfortunately, this version is very unstable with the trig and I'll be working on a modular-math version.

• f(-91,91) should return 180. – TheNumberOne Oct 13 '15 at 0:08
• Even if it were implemented correctly, a vector addition approach cannot compute the specified result. Vector addition maximizes the sum of cosines of angular differences, rather than minimizing the sum of squares of angular differences. – Anders Kaseorg Oct 13 '15 at 3:48

# MATLAB, 151

p=360;n=mod(input(''),p);a=0:0.01:p;m=[];for b=a e=b-n;f=mod([e;-e],p);c=min(f);d=c.^2;m=[m sum(d)];end;[~,i]=min(m);a=a(i);if a>180 a=a-p;end;disp(a);


Ok, so until I can actually understand what the methodology is, this is what I have come up with. It is a little bit of a hack, but as the question states that the answer must be correct to 2.d.p it should work.

I basically check every angle between 0 and 360 (in 0.01 increments) and then solve the formula in the question for each of those angles. Then the angle with the smallest sum is picked and converted into -180 to 180 range.

The code should with Octave. You can try it with the online interpreter

• 1°, 183° should result in −88°, not 92°. – Anders Kaseorg Oct 13 '15 at 4:09
• @AndersKaseorg try again now. – Tom Carpenter Oct 13 '15 at 4:25
• Nope, never mind. Back to the drawing board again... – Tom Carpenter Oct 13 '15 at 4:27

# JavaScript (ES6) 138

Not having the faintest idea of an algorithm, this tries all possibile values with 2 digits precision (-179.99 to 180.00). Quite fast with the test cases anyway.

Test running the snippet below in an EcmaScript 6 compliant browser (implementing arrow functions and default parameters - AFAIK Firefox)

A=l=>(D=(a,b,z=a>b?a-b:b-a)=>z>180?360-z:z,m=>{for(i=-18000;i++<18000;)l.some(v=>(t+=(d=D(v%360,i/100))*d)>m,t=0)||(m=t,r=i)})(1/0)||r/100

// Test
console.log=x=>O.innerHTML+=x+'\n'

;[[1,3],[89,-89],[90,-90],[91,-91],[0,120,-120],[0,810],[1,3,3],[180,60,-60],[0,15,45,460],[1,183]]
.forEach(t=>console.log(t+' -> '+A(t)))

// Less golfed

A=l=>{
D=(a,b,z=a>b?a-b:b-a) => z>180?360-z:z; // angular distance
m=1/0;
for(i=-18000;i++<18000;) // try all from -179.99 to 180
{
t = 0;
if (!l.some(v => (t+=(d=D(v%360,i/100))*d) > m))
{
m = t;
r = i;
}
}
return r/100;
}
<pre id=O></pre>