Last active
November 18, 2019 03:38
-
-
Save MrBlenny/5dd5cb8370d86310ab5d31e7a18e8f47 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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