New Algorithm, will explain if it actually works this time.
const {performance} = require('perf_hooks');
class Connection{
constructor(left,index,length,right){
if(typeof right === 'undefined'){
this._distance = 0;
} else {
this._distance = typeof left === 'undefined' ? 0 :
Math.abs(right - left);
}
var half = Math.floor(length/2);
if(length % 2 < 1){
this._magnitude = half - Math.abs(index - half + 1);
} else {
var temp = index - half;
this._magnitude = half - Math.abs(temp >= 0 ?temp:temp + 1);
}
this._value = this.distance * this.magnitude;
}
get distance(){return this._distance;};
get magnitude(){return this._magnitude;};
set magnitude(value){
this._magnitude = value;
this._value = this.distance * this.magnitude;
};
valueOf(){return this._value};
}
class Group{
constructor(...connections){
this._connections = connections;
this._max = Math.max(...connections); //uses the ValueOf to get the highest Distance to the Left
}
get connections(){return this._connections;};
get max(){return this._max;};
cutLeft(index){
for(let i=1,j=index-1;;i++){
if(typeof this.connections[j] === 'undefined' || this.connections[j].magnitude <= i){
break;
}
this.connections[j].magnitude = i;
j--;
}
}
cutRight(index){
for(let i=0,j=index;;i++){
if(typeof this.connections[j] === 'undefined' || this.connections[j].magnitude <= i){
break;
}
this.connections[j].magnitude = i;
j++;
}
}
static of(...connections){
return new Group(...connections.map((c,i)=>new Connection(c.distance,i,connections.length)));
}
split(){
var index = this.connections.findIndex(c=>c.valueOf() == this.max);
if(index < 0){
return;
}
var length = this.connections.length;
var magnitude = this.connections[index].magnitude;
this.cutLeft(index);
this.cutRight(index);
this._max = Math.max(...this.connections);
}
center(){
if(typeof this._center === 'undefined'){
this._center = this.connections.reduce((a,b)=>a==0?b.valueOf():a.valueOf()+b.valueOf(),0);
}
return this._center;
}
valueOf(){return this._max;};
toString(){
var index = this.connections.findIndex(c=>c.valueOf() == this.max);
var value = this.connections[index].magnitude;
var ret = '';
for(let i = 0;i<value;i++){
ret += this.connections.map(c=>{return (i<c.magnitude)?c.distance:' ';}).reduce((a,b)=>a==''?b:a+'-'+b,'') + '\n';
}
return ret;
};
}
function crunch(plants, cities){
var found = [];
var size = cities.length;
cities = cities.map((city,i,arr)=> new Connection(city,i,size,arr[i+1])).slice(0,cities.length-1);
var group = new Group(...cities);
for(;plants>1;plants--){
group.split();
}
console.log(`Wire Length Needed: ${group.center()}`);
}
function biggestGroup(groups){
return groups.find(g => g[g.length-1].orig - g[0].orig);
}
function* range (start, end, limit) {
while (start < end || typeof limit !== 'undefined' && limit-- > 0) {
yield start
start += 1 + Math.floor(Math.random()*100);
}
}
function* cities (score){
let n = Math.floor(Math.pow(2,score/5));
var start = 0;
while (n-- > 0 && start <= (1000 * n)) {
yield start;
start += 1 + Math.floor(Math.random()*100);
}
}
if(typeof process.argv[3] === 'undefined'){
crunch(1,[0, 2, 4, 6, 8]);
console.log("Correct Answer: 12");
crunch(3,[0, 1, 10, 11, 20, 21, 22, 30, 32]);
console.log("Correct Answer: 23");
crunch(5,[0, 1, 3, 6, 8, 11, 14]);
console.log("Correct Answer: 3");
crunch(6,[0, 1, 3, 6, 8, 14, 15, 18, 29, 30, 38, 41, 45, 46, 49, 58, 66, 72, 83, 84]);
console.log("Correct Answer: 49");
crunch(2, [0, 21, 31, 45, 49, 54]);
console.log("Correct Answer: 40");
crunch(2, [0, 4, 7, 9, 10]);
console.log("Correct Answer: 7");
crunch(2, [0, 1, 3, 4, 9]);
console.log("Correct Answer: 6");
var max = 0;
var min = Number.POSITIVE_INFINITY;
var avg = [];
var score = typeof process.argv[2] === 'undefined' ? 60 : process.argv[2];
for(j = 0; j<10; j++){
var arr = []; for(let i of cities(score)) arr.push(i);
var plants = Math.floor(1 + Math.random() * arr.length);
console.log(`Running: Test:${j} Plants: ${plants}, Cities ${arr.length}, Score: ${score}`);
// console.log(`City Array: [${arr}]`);
var t0 = performance.now();
crunch(plants,arr);
var t1 = performance.now();
time = (t1-t0)/1000;
console.log(`Time Taken = ${time} seconds`);
avg.push(time);
max = Math.max(time,max);
min = Math.min(time,min);
}
console.log(`Bench: ${avg.reduce((a,b)=>a+b,0)/avg.length} Max: ${max} Min: ${min} Total: ${avg.reduce((a,b)=>a+b,0)}`);
} else {
var plants = process.argv[2];
var arr = process.argv.slice(3);
console.log(`Running: Plants: ${plants}, Cities ${arr.length}`);
var t0 = performance.now();
crunch(plants,arr);
var t1 = performance.now();
time = (t1-t0)/1000;
console.log(`Time Taken = ${time} seconds`);
}
Try it online!
Run the same way as the other one.
Outline of the algorithm:
The program first maps the Data into the City Class, which maps a few data points:
- city - the absolute distance of the city
- left - the distance of the closest city to the left
- right - distance of the closest city to the right
- index - (deprecated) index in original city array
the array is then thrown into the Group class, which has the following:
- cities - the city array
- dist - the distance spanning the group
- max - the largest left connection in the group
- split()
- returns an array containing sub groups split along the largest connection connected to the center city in the group
- if there are 2 center nodes (an even length group) it chooses from those 3 connections
- (*note*: this will drop any groups with less than two cities)
- center()
- returns the best wire value for the group
- working on a solution to skip iterating over each city left for this step
- now with 50% less mapping
Now the algorithm proceeds to split the groups as long as it has 2 or more
power plants to place.
Finally it maps the groups to the it's centers, and sums them all up.
How to run:
Run using Node.js (v9.2.0 is what was used for creation)
running the program using generated test cases for score 70:
node program.js 70
running the program using 1 power plant and cities [0,3,5]:
node program.js 1 0 3 5
Code:
const {performance} = require('perf_hooks');
class City{
constructor(city, left, right, i){
this._city = city;
this._index = i;
this._left = typeof left === 'undefined' ? 0 : city - left;
this._right = typeof right === 'undefined' ? 0 : right - city;
}
get city(){return this._city;};
get index(){return this._index;};
get left(){return this._left;};
get right(){return this._right;};
set left(left){this._left = left};
set right(right){this._right = right};
valueOf(){return this._left;};
}
class Group{
constructor(...cities){
this._cities = cities;
// console.log(cities.map(a=>a.city).reduce((a,b)=>a===''?a+(b<10?' '+b:b):a+'-'+(b<10?' '+b:b),""));
// console.log(cities.map(a=>a.left).reduce((a,b)=>a===''?a+(b<10?' '+b:b):a+'-'+(b<10?' '+b:b),""));
// console.log(cities.map(a=>a.right).reduce((a,b)=>a===''?a+(b<10?' '+b:b):a+'-'+(b<10?' '+b:b),""));
// console.log("+==+==+==+==+==+==+==+==+==+==+==+==")
this._dist = cities[cities.length-1].city - cities[0].city;
this._max = Math.max(...cities); //uses the ValueOf to get the highest Distance to the Left
}
get dist(){return this._dist;};
get cities(){return this._cities;};
get max(){return this._max;};
split(){
//var index = this.cities.findIndex(city=>city.left == this.max);
//this.cities[index].left = 0;
// console.log(`Slicing-${this.max}-${index}------`)
var centerIndex = this.cities.length / 2;
var splitIndex = Math.floor(centerIndex);
if(centerIndex%1 > 0){
var center = this.cities[splitIndex];
if(center.right > center.left){
splitIndex++;
}
} else {
var right = this.cities[splitIndex];
var left = this.cities[splitIndex-1];
if(left.left > Math.max(right.right,right.left)){
splitIndex--;
} else if(right.right > Math.max(left.left,left.right)){
splitIndex++;
}
}
// console.log(splitIndex);
this.cities[splitIndex].left = 0;
this.cities[splitIndex-1].right = 0;
var leftCities = [...this.cities.slice(0,splitIndex)];
var rightCities = [...this.cities.slice(splitIndex)];
// var center = this.cities[]
if(leftCities.length <= 1){
if(rightCities.length <= 1){
return [];
}
return [new Group(...rightCities)]
}
if(rightCities.length <= 1){
return [new Group(...leftCities)];
}
return [new Group(...leftCities), new Group(...rightCities)];
}
center(){
if(typeof this._center === 'undefined'){
if(this.cities.length == 1){
return [0];
}
if(this.cities.length == 2){
return this.cities[1].left;
}
var index = Math.floor(this.cities.length/2);
this._center = this.cities.reduce((a,b)=> {
// console.log(`${a} + (${b.city} - ${city.city})`);
return a + Math.abs(b.city - this.cities[index].city);
},0);
// console.log(this._center);
}
return this._center;
}
valueOf(){return this._max;};
}
function crunch(plants, cities){
var found = [];
var size = cities.length;
cities = cities.map((city,i,arr)=> new City(city,arr[i-1],arr[i+1],i));
var groups = [new Group(...cities)];
// console.log(groups);
for(;plants>1;plants--){
var mapped = groups.map(g=>g.center()-g.max);
var largest = Math.max(...groups);
// console.log('Largest:',largest)
// console.log(...mapped);
var index = groups.findIndex((g,i)=> mapped[i] == g.center() - g.max && g.max == largest);
// console.log(index);
groups = index == 0 ?
[...groups[index].split(),...groups.slice(index+1)]:
[...groups.slice(0,index),...groups[index].split(),...groups.slice(index+1)];
}
// console.log(`=Cities=${size}================`);
// console.log(groups);
size = groups.map(g=>g.cities.length).reduce((a,b)=>a+b,0);
// console.log(`=Cities=${size}================`);
var wires = groups.map(g=>g.center());
// console.log(...wires);
// console.log(`=Cities=${size}================`);
console.log(`Wire Length Needed: ${wires.reduce((a,b)=>a + b,0)}`);
}
function biggestGroup(groups){
return groups.find(g => g[g.length-1].orig - g[0].orig);
}
function* range (start, end, limit) {
while (start < end || typeof limit !== 'undefined' && limit-- > 0) {
yield start
start += 1 + Math.floor(Math.random()*100);
}
}
function* cities (score){
let n = Math.floor(Math.pow(2,score/5));
var start = 0;
while (n-- > 0 && start <= (1000 * n)) {
yield start;
start += 1 + Math.floor(Math.random()*100);
}
}
if(typeof process.argv[3] === 'undefined'){
crunch(1,[0, 2, 4, 6, 8]);
console.log("Correct Answer: 12");
crunch(3,[0, 1, 10, 11, 20, 21, 22, 30, 32]);
console.log("Correct Answer: 23");
crunch(5,[0, 1, 3, 6, 8, 11, 14]);
console.log("Correct Answer: 3");
crunch(6,[0, 1, 3, 6, 8, 14, 15, 18, 29, 30, 38, 41, 45, 46, 49, 58, 66, 72, 83, 84]);
console.log("Correct Answer: 49");
crunch(2, [0, 21, 31, 45, 49, 54]);
console.log("Correct Answer: 40");
crunch(2, [0, 4, 7, 9, 10]);
console.log("Correct Answer: 7");
var max = 0;
var min = Number.POSITIVE_INFINITY;
var avg = [];
var score = typeof process.argv[2] === 'undefined' ? 60 : process.argv[2];
for(j = 0; j<10; j++){
var arr = []; for(let i of cities(score)) arr.push(i);
var plants = Math.floor(1 + Math.random() * arr.length);
console.log(`Running: Test:${j} Plants: ${plants}, Cities ${arr.length}, Score: ${score}`);
var t0 = performance.now();
crunch(plants,arr);
var t1 = performance.now();
time = (t1-t0)/1000;
console.log(`Time Taken = ${time} seconds`);
avg.push(time);
max = Math.max(time,max);
min = Math.min(time,min);
}
console.log(`Bench: ${avg.reduce((a,b)=>a+b,0)/avg.length} Max: ${max} Min: ${min} Total: ${avg.reduce((a,b)=>a+b,0)}`);
} else {
var plants = process.argv[2];
var arr = process.argv.slice(3);
console.log(`Running: Plants: ${plants}, Cities ${arr.length}`);
var t0 = performance.now();
crunch(plants,arr);
var t1 = performance.now();
time = (t1-t0)/1000;
console.log(`Time Taken = ${time} seconds`);
}
Try it online!
I will clean up the commented out code over the next couple of days as I am still working on this, just wanted to see if this was passing more than just the small cases.
2^^(x/5)
: what is the meaning ? can you just provide an upper bound for N ? \$\endgroup\$N=21( = floor(2^(22/5)) )
in 10 seconds, but cannot deal withN=24( = floor(2^(23/5)) )
, then 23 will be the score. I didn't use an upper bound, since the differences between different algorithms are too great. For example, if I set N <= 40, there will be little difference betweenO(KN^2)
andO(KN^3)
, howeverO(2^N)
will not even finish in resonable time. \$\endgroup\$