Skip to content

Instantly share code, notes, and snippets.

@MrBlenny
Last active November 18, 2019 03:38
Show Gist options
  • Save MrBlenny/5dd5cb8370d86310ab5d31e7a18e8f47 to your computer and use it in GitHub Desktop.
Save MrBlenny/5dd5cb8370d86310ab5d31e7a18e8f47 to your computer and use it in GitHub Desktop.
import { orderBy, sum } from 'lodash';
export interface ILatLon {
/** 0 - 180 */
latitude: number
/** 0 - 360 */
longitude: number
}
export const distribute = (sites: ILatLon[], center: ILatLon, goalPercentages: number[]) => {
// Round sumOfGoal to 2dp to allow for fractions in goalPercentages
const sumOfGoals = Math.round(sum(goalPercentages) * 100) / 100;
if (sumOfGoals !== 100) {
throw new Error(`Goal percentages must sum to 100. Got ${sumOfGoals}`)
}
// Order sites based on their distance from the center point
const orderedSites = orderBy(sites, [site => {
const latDiff = center.latitude - site.latitude;
const lonDiff = center.longitude - site.longitude;
const latLonMagntitude = Math.pow(Math.pow(latDiff, 2) + Math.pow(lonDiff, 2), 0.5);
return latLonMagntitude
}], ['asc']);
// Initialise empty buckets for every goalPercentage
const buckets: ILatLon[][] = goalPercentages.map(() => [])
// Iterate over sites and push them into a bucket based on the goalPercentage */
orderedSites.forEach((site, siteIdx) => {
// Abuse array.some to iterate until we return true
goalPercentages.some((goalPercentage, bucketIdx) => {
let breakLoop = false;
const bucket = buckets[bucketIdx];
const percentageCurrentlyInBucket = (bucket.length / (siteIdx + 1)) * 100;
if (percentageCurrentlyInBucket < goalPercentage) {
bucket.push(site)
breakLoop = true
}
return breakLoop
})
})
return buckets
}
////////////////////
describe('distribute', () => {
const sites = [
{
latitude: 90,
longitude: 180,
},
{
latitude: 90,
longitude: 179,
},
{
latitude: 90,
longitude: 178,
},
{
latitude: 90,
longitude: 177,
},
{
latitude: 90,
longitude: 176,
},
{
latitude: 90,
longitude: 175,
},
{
latitude: 90,
longitude: 174,
},
{
latitude: 90,
longitude: 173,
},
{
latitude: 90,
longitude: 172,
},
{
latitude: 90,
longitude: 171,
},
{
latitude: 90,
longitude: 170,
},
];
it('it distributes among 2 media evenly', () => {
const center = {
latitude: 0,
longitude: 0,
};
const goalPercentages = [50, 50];
const result = distribute(sites, center, goalPercentages);
expect(result).toMatchInlineSnapshot(`
Array [
Array [
Object {
"latitude": 90,
"longitude": 170,
},
Object {
"latitude": 90,
"longitude": 172,
},
Object {
"latitude": 90,
"longitude": 174,
},
Object {
"latitude": 90,
"longitude": 176,
},
Object {
"latitude": 90,
"longitude": 178,
},
Object {
"latitude": 90,
"longitude": 180,
},
],
Array [
Object {
"latitude": 90,
"longitude": 171,
},
Object {
"latitude": 90,
"longitude": 173,
},
Object {
"latitude": 90,
"longitude": 175,
},
Object {
"latitude": 90,
"longitude": 177,
},
Object {
"latitude": 90,
"longitude": 179,
},
],
]
`);
});
it('it distributes among 2 media but will assign none to the second if too few sites', () => {
const center = {
latitude: 0,
longitude: 0,
};
const goalPercentages = [99, 1];
const result = distribute(sites, center, goalPercentages);
expect(result).toMatchInlineSnapshot(`
Array [
Array [
Object {
"latitude": 90,
"longitude": 170,
},
Object {
"latitude": 90,
"longitude": 171,
},
Object {
"latitude": 90,
"longitude": 172,
},
Object {
"latitude": 90,
"longitude": 173,
},
Object {
"latitude": 90,
"longitude": 174,
},
Object {
"latitude": 90,
"longitude": 175,
},
Object {
"latitude": 90,
"longitude": 176,
},
Object {
"latitude": 90,
"longitude": 177,
},
Object {
"latitude": 90,
"longitude": 178,
},
Object {
"latitude": 90,
"longitude": 179,
},
Object {
"latitude": 90,
"longitude": 180,
},
],
Array [],
]
`);
});
it('it distributes among 3 media unevenly', () => {
const center = {
latitude: 0,
longitude: 0,
};
const goalPercentages = [50, 10, 40];
const result = distribute(sites, center, goalPercentages);
expect(result).toMatchInlineSnapshot(`
Array [
Array [
Object {
"latitude": 90,
"longitude": 170,
},
Object {
"latitude": 90,
"longitude": 172,
},
Object {
"latitude": 90,
"longitude": 174,
},
Object {
"latitude": 90,
"longitude": 176,
},
Object {
"latitude": 90,
"longitude": 178,
},
Object {
"latitude": 90,
"longitude": 180,
},
],
Array [
Object {
"latitude": 90,
"longitude": 171,
},
],
Array [
Object {
"latitude": 90,
"longitude": 173,
},
Object {
"latitude": 90,
"longitude": 175,
},
Object {
"latitude": 90,
"longitude": 177,
},
Object {
"latitude": 90,
"longitude": 179,
},
],
]
`);
});
it('it distributes among 3 media evenly', () => {
const center = {
latitude: 0,
longitude: 0,
};
const goalPercentages = [33.333, 33.333, 33.333];
const result = distribute(sites, center, goalPercentages);
expect(result).toMatchInlineSnapshot(`
Array [
Array [
Object {
"latitude": 90,
"longitude": 170,
},
Object {
"latitude": 90,
"longitude": 173,
},
Object {
"latitude": 90,
"longitude": 176,
},
Object {
"latitude": 90,
"longitude": 179,
},
],
Array [
Object {
"latitude": 90,
"longitude": 171,
},
Object {
"latitude": 90,
"longitude": 174,
},
Object {
"latitude": 90,
"longitude": 177,
},
Object {
"latitude": 90,
"longitude": 180,
},
],
Array [
Object {
"latitude": 90,
"longitude": 172,
},
Object {
"latitude": 90,
"longitude": 175,
},
Object {
"latitude": 90,
"longitude": 178,
},
],
]
`);
});
it('if there are more goal percentages than sites, the last will be empty', () => {
const center = {
latitude: 0,
longitude: 0,
};
const goalPercentages = new Array(12).fill(100 / 12);
const result = distribute(sites, center, goalPercentages);
expect(result).toMatchInlineSnapshot(`
Array [
Array [
Object {
"latitude": 90,
"longitude": 170,
},
],
Array [
Object {
"latitude": 90,
"longitude": 171,
},
],
Array [
Object {
"latitude": 90,
"longitude": 172,
},
],
Array [
Object {
"latitude": 90,
"longitude": 173,
},
],
Array [
Object {
"latitude": 90,
"longitude": 174,
},
],
Array [
Object {
"latitude": 90,
"longitude": 175,
},
],
Array [
Object {
"latitude": 90,
"longitude": 176,
},
],
Array [
Object {
"latitude": 90,
"longitude": 177,
},
],
Array [
Object {
"latitude": 90,
"longitude": 178,
},
],
Array [
Object {
"latitude": 90,
"longitude": 179,
},
],
Array [
Object {
"latitude": 90,
"longitude": 180,
},
],
Array [],
]
`);
});
it('throws an error if percentages do not equal 100 (rounded to 2 DP)', () => {
const center = {
latitude: 0,
longitude: 0,
};
const goalPercentages = [33.333, 33.333, 33];
expect(() => distribute(sites, center, goalPercentages)).toThrowError();
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment