This is an optimization/algorithms challenge. Given two sets of points X and Y, which are to be thought of as points on a circle of circumference T, the challenge is to find the rotation which minimizes the distance from the set X to the set Y. Here the distance is just the sum of the absolute distance from each point in X to its closest point in Y. Here closest is really just the minimum distance you need to travel along the circle to reach a point in Y (remembering that you can go clockwise or anti-clockwise). Note that this measure of distance is not symmetric.
In order to be able to create the test data, you will need to be able to run Python, but your answer can be in any language of your choosing that is available without cost to run on Linux. You can also use any libraries you choose as long as they are easy to install on Linux and available without cost.
The test data will be created using the following Python code. It outputs two lines to standard out. The first is circle X and the second is circle Y.
import numpy as np
#Set T to 1000, 10000, 100000, 1000000, 10000000, 1000000000
T = 1000
N = int(T/5)
#Set the seed to make data reproducible
np.random.seed(seed = 10)
timesY = np.sort(np.random.uniform(low = 0.0, high = T, size = N))
indices = np.sort(np.random.randint(0,N, N/10))
rotation = np.random.randint(0,T, 1)[0]
timesX = [(t + rotation + np.random.random()) % T for t in timesY[indices]]
for tX in timesX:
print tX,
print
for tY in timesY:
print tY,
For those who don't run python, I have also uploaded some test files. Each file has three lines. The first line is the points for circle X, the second is the points for circle Y, and the last is the score you get from the generic optimizer in the python code below. This score will be in no way optimal and you should do much better.
http://wikisend.com/download/359660/1000.txt.lzma
http://wikisend.com/download/707350/10000.txt.lzma
http://wikisend.com/download/582950/100000.txt.lzma
http://wikisend.com/download/654002/1000000.txt.lzma
http://wikisend.com/download/416516/10000000.txt.lzma
Here is also a toy worked example of the distance between two sets of point to make the distance function clear.
Let timesX = [2, 4, 98]
and timesY = [1, 7, 9, 99]
. Let us assume the circumference of the circle is 100
. As it stands the distance is (2-1)+(7-4)+(99-98) = 5
. Now if we rotate timesX
by 3
we get [5, 7, 1]
which has total absolute distance 2
to timesY
. In this case this appears to be the optimal solution.
Here is some sample code which assumes that the circles are called timesX
and timesY
, and outputs the minimum distance it finds. It doesn't, however, do a very good job, as you will see.
from bisect import bisect_left
from scipy.optimize import minimize_scalar
def takeClosest(myList, myNumber, T):
"""
Assumes myList is sorted. Returns closest value to myNumber in a circle of circumference T.
"""
pos = bisect_left(myList, myNumber)
if (pos == 0 and myList[pos] != myNumber):
before = myList[pos - 1] - T
after = myList[0]
elif (pos == len(myList)):
before = myList[pos-1]
after = myList[0] + T
else:
before = myList[pos - 1]
after = myList[pos]
if after - myNumber < myNumber - before:
return after
else:
return before
def circle_dist(timesX, timesY):
dist = 0
for t in timesX:
closest_number = takeClosest(timesY, t, T)
dist += np.abs(closest_number - t)
return dist
def dist(x):
timesX_rotated = [(tX+x) % T for tX in timesX]
return circle_dist(timesX_rotated, timesY)
#Now perform the optimization
res = minimize_scalar(dist, bounds=(0,T), method='bounded', options={ 'disp':True})
print res.fun
Scoring
You should run your code for T = 1000, 10000, 100000, 1000000, 10000000. Your code should output both the optimum rotation and the distance you get for it so that it can be checked. The scoring criterion is as follows.
- You should calculate the sum of the distances you find for the five values of T. Low is better and the best possible is 0. You aren't allowed to cheat by looking at what rotation was used to create the data in the first place.
- Then you should calculate
551431.326508571/(the sum you just calculated)
and report that as your score.
Edit 1. Changed score so that high is better and removed time restriction so you can just run it on your own computer.
Edit 2 Note that the function dist
can be used as a verifier. Just put the points for circle X in timesX
and the points for circle Y in timesY
and set x
to be the rotation you want to test. It will return a score which you can compare to what you get.
Edit 3 Fixed the data creation code which wasn't exactly the code I had used to create the data.