Furthest Building You Can Reach Algorithm - JavaScript - javascript
I'm trying to create a solution for the problem below in JavaScript.
You are given an integer array heights representing the heights of
buildings, some bricks, and some ladders.
You start your journey from building 0 and move to the next building
by possibly using bricks or ladders.
While moving from building i to building i+1 (0-indexed),
If the current building's height is greater than or equal to the next
building's height, you do not need a ladder or bricks. If the current
building's height is less than the next building's height, you can
either use one ladder or (h[i+1] - h[i]) bricks. Return the furthest
building index (0-indexed) you can reach if you use the given ladders
and bricks optimally.
Input: heights = [4,2,7,6,9,14,12], bricks = 5, ladders = 1
Output: 4
Explanation: Starting at building 0, you can follow these steps:
- Go to building 1 without using ladders nor bricks since 4 >= 2.
- Go to building 2 using 5 bricks. You must use either bricks or ladders because 2 < 7.
- Go to building 3 without using ladders nor bricks since 7 >= 6.
- Go to building 4 using your only ladder. You must use either bricks or ladders because 6 < 9.
It is impossible to go beyond building 4 because you do not have any more bricks or ladders.
My naive solution:
var furthestBuilding = function(heights, bricks, ladders) {
let pos = 0;
while(pos < heights.length) {
if(pos === heights.length - 1) break;
if(heights[pos+1] > heights[pos]) {
const distance = heights[pos+1] - heights[pos];
if(distance <= bricks) bricks -= distance;
else if(ladders > 0) ladders--;
else break;
}
pos++;
}
return pos;
};
It does return the expected result for the example above. However, it fails for the input below.
heights = [1,5,1,2,3,4,10000]
bricks = 4
ladders = 1
My output is 3, but the solution should be 5.
Input that crashes for the recursive solution.
heights = [253710,459585,71981,223232,977918,148680,123527,250812,260416,554767,473621,88538,966794,644116,865416,590993,550004,374573,105036,568303,460987,24602,757598,519047,263800,315868,963895,266638,598245,713310,489802,364169,742274,973483,807739,253747,564636,472387,598445,675408,626061,527760,922748,244691,41163,108095,953208,54400,191957,182321,801110,526756,11220,560896,782344,565351,570890,931781,511665,108738,357367,853555,674526,388790,686349,554731,102668,335287,461231,496065,489980,525209,693696,140598,784402,564477,743153,156379,370768,94810,121932,338323,972441,553422,865236,627884,673412,16147,858309,802780,150410,657225,761430,916149,644587,364929,661236,207648,507409,209803,663553,296241,51843,758342,448408,310536,733500,390516,580506,313748,729366,961156,766804,752158,713426,946971,433800,611365,806559,950149,831368,871881,132092,644626,150762,487527,365094,316637,684249,740162,605893,272845,416251,905202,984909,602362,424697,686193,566240,159584,600277,767037,211677,441897,586509,965864,393340,497044,881539,145921,159055,866123,603476,657682,284714,85306,470415,534245,641462,472616,159434,421500,843442,634366,625668,444943,657933,129173,914540,215272,598415,457087,437568,490742,172811,212016,435680,599042,789308,279873,689943,369130,618428,524390,877649,118759,60586,37618,20797,492854,946585,583767,944693,62988,358292,708659,23496,966718,557539,131703,358231,215464,771609,375770,855917,147866,543477,786910,760512,468183,542081,373398,979543,126508,361409,842847,872593,746682,893518,457222,978730,161753,697245,205997,363180,807952,795175,808090,462585,658667,186220,858457,923762,700792,294201,584816,514737,261038,327627,205592,221896,444108,979369,129394,44001,790354,353917,72772,330118,360651,635275,849492,966042,843108,158554,406317,995111,147752,121006,486157,678653,217657,4288,573547,820817,164534,921608,308037,373838,385901,343399,813472,58859,346176,68090,539503,322652,958331,832724,585003,75794,228299,31211,302603,601041,362034,300803,347024,650585,172193,876895,603734,165956,796982,786231,738823,562729,158032,364908,988395,775023,671485,424571,572157,623273,772919,914302,661979,920229,614760,934156,511607,889533,382154,82654,973121,549095,639792,412821,305216,74071,571794,969979,932469,335153,898442,938912,729489,872970,874332,8390,345366,901364,245104,315592,895028,533836,427909,737421,161915,510434,768573,179267,237370,562023,650593,869876,544314,464374,701215,789191,746271,871247,385836,788092,890101,286938,367130,635751,295576,607054,913206,556383,512305,253121,461980,951444,192012,897432,140517,842228,924286,268918,765459,344159,347853,592899,247814,379693,421908,295638,672994,774285,78096,886320,998456,10915,581642,549650,905526,186991,586693,320053,829130,465779,191060,238711,415584,273709,35854,55818,305798,667280,334370,121051,665390,230729,51662,904228,971349,7088,567705,265941,380847,760602,280222,351148,518112,609328,381795,46766,301829,886537,338310,130937,813816,446885,126867,578861,996302,56516,316900,648733,457604,903338,974707,336231,878687,776626,583241,353383,591761,438716,892530,231901,959454,915103,50735,453313,519651,940657,68380,38339,339705,19207,844122,483005,582959,592635,870233,208322,862826,598864,989646,583679,219396,371194,111781,493739,313465,383867,545219,171577,761747,992356,973874,497603,976481,136374,138311,918066,787696,929197,589326,801358,944697,28038,211029,752621,210197,491050,939207,254024,145811,767376,922553,796100,15858,899164,950319,728378,563113,532136,705190,290216,359946,214594,327241,641000,385347,786200,700340,576438,227606,498337,451637,425192,286305,472053,335562,587556,683468,290205,997253,868480,320419,392391,128015,674737,410783,136490,46713,154232,574917,904387,99900,490640,268209,994867,135705,390652,412028,404195,490553,184029,624391,836288,619242,570500,367786,908994,934572,226481,281181,469810,376226,354931,55711,43299,487568,853741,556475,842100,133451,371270,820314,735709,859169,992745,981261,506744,573542,544798,335063,71332,345306,551165,522500,148531,323820,525891,571989,109699,540927,391815,383139,528328,941384,577084,148432,537377,589708,613443,589827,688798,501198,304829,719726,181892,891384,237429,447803,49953,555945,69576,765896,194628,866362,533750,798399,369884,258270,964160,796047,420697,486470,781692,825420,689886,392317,278581,151823,184594,295461,723312,604322,248126,43623,91154,600821,55136,709242,990838,263827,564093,735641,174057,932157,750399,807534,338221,830644,171022,156968,351523,814982,403402,975555,955973,400091,523040,382185,577810,257717,544345,243199,509472,450948,839442,387377,553239,145202,822954,478559,487143,514465,587609,575770,547307,386320,410846,81519,599793,874316,730403,913822,800625,96868,913119,843783,699,767204,432828,496436,348230,767865,455134,266270,324004,863226,758456,66451,431182,641607,514915,522399,164590,335706,829719,724524,981933,812770,192582,880771,71867,704720,691726,761694,868674,964177,287148,124076,155241,535262,856554,108951,453851,597675,592745,32413,774791,750298,66826,876820,567338,699491,336474,60148,776819,430070,546456,564666,776689,886534,68830,749993,157504,933346,39836,417088,481438,30183,515310,764031,876787,321614,765291,466180,941767,877507,658149,60699,413225,849839,376668,689777,491763,712459,5768,608757,161358,554199,132368,464770,89566,309794,430979,979239,62376,354441,582188,947427,569030,430121,826059,562654,461350,901008,191328,484599,615686,859104,366550,140695,229053,282037,289028,296120,883539,980557,365526,143257,658629,730361,683520,101817,442395,50455,199765,137552,653983,47041,102020,308470,523274,447051,345263,967056,525031,506873,170405,995568,977216,83193,279492,376521,946443,847471,845107,321145,866307,523882,135730,824806,927733,605908,580895,177233,443804,914175,905847,661407,483093,518439,789231,66585,447439,14824,861841,89137,913636,194682,166773,212398,259259,160638,435374,941416,140851,311224,54813,155003,595354,742575,668942,77310,96783,217826,211522,116834,391751,922905,730508,225636,265187,995541,329461,244649,951125,322487,140958,608238,511144,410963,335698,228967,487748,382037,261094,363854,557078,539851,519352,364988,444038,284404,730251,828294,608545,188095,466810,46659,673970,142329,93794,167913,30119,116528,592075,810599,14144,445947,51745,236481,878706,838520,310352,112640,612690,663852,546444,818881,868195,573845,390221,254379]
bricks = 33671263
ladders = 108
Let's say an "ascent" is a height that is either the first in our list or greater than the previous height, and that the magnitude of an ascent is the delta.
Use a min_heap to represent ascents via ladder. Keep track of bricks_left and ladders_left, initialized to your inputs.
We will maintain the following as we parse the heights array. The ladders will be used for the steepest ascents we've seen up to the number possible, and the bricks will be used for all other ascents.
I.e., as long as ladders_left > 0, we add new ascents to the min_heap and subtract one from ladders_left.
If ladder_left = 0, we compare the ascent to the top of our min heap. If the smaller of the two is <= bricks_left we can continue, otherwise we're done.
Assuming we can continue:
if the smaller of the two is the new ascent, subtract it from bricks_left and continue.
If the smaller of the two is the top of the min_heap, remove it, add the new ascent to the min_heap, and subtract the former top of the min_heap from bricks_left.
-- edit -- The above algorithm is O(log(ladders) * (ascents - ladders)). If we start with not very many ladders and don't like the complexity of a min_heap for some reason, we can sove this in O(ladders * (ascents - ladders)) by replacing the min_heap with an array, and finding the min by scanning the array when we need to.
Approach by using all ladders first and replace the ladders with the smallest delta first to save bricks.
/**
* #param {number[]} heights
* #param {number} bricks
* #param {number} ladders
* #return {number}
*/
var furthestBuilding = function (heights, bricks = 0, ladders = 0) {
function insert(array, value) {
// part for searching the index, taken from
// https://stackoverflow.com/a/21822316/1447675
var low = 0,
high = array.length;
while (low < high) {
const mid = (low + high) >>> 1;
if (array[mid] < value) low = mid + 1;
else high = mid;
}
array.splice(low, 0, value);
}
const ladderSizes = [];
for (let i = 0, l = heights.length - 1; i < l; i++) {
const delta = heights[i + 1] - heights[i];
if (delta <= 0) continue;
if (ladders) {
insert(ladderSizes, delta);
ladders--;
continue;
}
const small = ladderSizes[0];
if (small <= delta && bricks >= small) {
bricks -= ladderSizes.shift();
insert(ladderSizes, delta);
continue;
}
if (bricks >= delta) {
bricks -= delta;
continue;
}
return i;
}
return heights.length - 1;
};
console.log(furthestBuilding([4, 2, 7, 6, 9, 14, 12], 5, 1));
console.log(furthestBuilding([1, 5, 1, 2, 3, 4, 10000], 4, 1));
Related
How do you avoid the "RangeError: Maximum call stack size exceeded" error?
I'm currently working on a maze generating algorithm called recursive division. The algorithm is quite simple to understand: Step 1: if the height of your chamber is smaller than the width, divide your grid/chamber with a vertical line. If the height is greater than the width, then divide your chamber with a horizontal line. Step 2: Repeat step 1 with the sub-chambers that were created by the lines. You want to repeat these steps until you get a maze (until the width or height equals 1 unit). The problem that I have with this algorithm is that JavaScript prints out a RangeError, meaning that I called the function that creates the maze too many times (I'm trying to implement this algorithm with a recursive function). Is there any way to avoid/prevent this from happening? Or am I missing something important in my code that makes the algorithm not work properly? I have tried to implement a trampoline function, but since I'm a beginner I just don't understand it well enough to implement my self. I have also restarted my entire project ruffly 3 times with some hope that I will come up with a different approach to this problem, but I get the same error every time. My code here: //leftCord = the left most x coordinate of my chamber/grid, upCord = the upmost y coordinate of my grid etc. //(0, 0) IS POSITIONED IN THE LEFT TOP NODE OF MY GRID function createMaze(leftCord, rightCord, upCord, downCord) { var height = Math.abs(downCord - upCord); var width = Math.abs(rightCord - leftCord); if (height < 2 || width < 2) { //The maze is completed! return; } else { if (height < width) { //cut the chamber/grid vertically //Getting a random number that's EVEN and drawing the function x = 'random number' on the grid var x = randomNum(leftCord / 2, rightCord / 2) * 2; var lineX = []; for (i = upCord; i < downCord; i++) { lineX.push(grid[i][x]); } //Making a random door/passage and making sure it's ODD var randomDoor = randomNum(0, lineX.length / 2) * 2 + 1; lineX.splice(randomDoor, 1); //Drawing the line for (i = 0; i < lineX.length; i++) { lineX[i].className = "wall"; } //Making the same thing again, but with the left and right sub-chambers that were created by the line createMaze(leftCord, x, upCord, downCord); createMaze(x, rightCord, upCord, downCord); } else { //cut the chamber/grid horizontally //Getting a random number that's EVEN and drawing the function y = 'random number' on the grid var y = randomNum(0, downCord / 2) * 2; var lineY = []; for (i = leftCord; i < rightCord; i++) { lineY.push(grid[y][i]); } //Making a random door/passage and making sure it's ODD var randomDoor = randomNum(0, lineY.length / 2) * 2 + 1; lineY.splice(randomDoor, 1); //Drawing the line for(i = 0; i < lineY.length; i++){ lineY[i].className = "wall"; } //Making the same thing again, but with the upper and lower-chambers that were created by the line createMaze(leftCord, rightCord, upCord, y); createMaze(leftCord, rightCord, y, downCord); } } }
This is happening because you never initialize i with var- it is sent into the global scope and is overwritten each function call.
Javascript: Set variable to either 1 or -1
I'm trying to get an object that moves in a different direction when you click on it, and each time you click on it it goes faster. I have it almost functioning, but I can't get the program to exclude 0 or do only -1 or 1; I can only do a random number between -1 and 1. This means that if it hits zero, it can't progress. (The following code is built with a Javascript engine called "Crafty". Non-javascript parts are commented as best as I can.) Crafty.init(400,320, document.getElementById('game')); // Creates canvas // Create variables var speed = 10; var min = -1; var max = 1; // Create a 32px by 32px red box var square = Crafty.e('2D, Canvas, Color, Mouse, Motion') .attr({x: 50, y: 50, w: 32, h: 32}) .color('red') // When the red box is clicked, move it in a random direction. Make it go faster each time. .bind('Click', function(MouseEvent){ speed *= 2; var vel = square.velocity(); var direction = ((Math.random() * (max - min)) + min); vel.x; vel.y; vel.x = (speed *= direction); vel.y = (speed *= direction); });
Change to this line var direction = (Math.random()) > .5 ? 1 : -1;
It really comes down to this line: var direction = ((Math.random() * (max - min)) + min); If you store the acceptable values (-1 and 1) in an array, you can make the random choose one of those based on the length of the array. By storing the values in an array, you not only make the process simpler, but it is extensible because you can always add new values later, if desired. function getRandom(){ var acceptable = [-1, 1]; // Get a random number from 0 to 1 (the length of the array, 2, will never be reached) var direction = Math.floor(Math.random() * acceptable.length); console.log(acceptable[direction]); // Choose either array element 0 or element 1 } // Run this code snippet a few times and you'll see that you only get -1 and 1 getRandom(); getRandom(); getRandom(); getRandom(); You can also remove the two lines declaring the max and min variables as they are no longer needed.
Algorithm to convert a 2d voxel map to line sometimes gets stuck
I have a 2D voxel map for a game, which is a 2D array where 1 means ground and 0 means sky. Example: all 1's in the array (ground) are green boxes The algorithm starts at the leftmost ground voxel that touches the sky (red box in picture). It will explore 8 neighbours of the current position to check if one of them is a ground voxel and also touches a sky voxel. This means it should be added to the groundline. Example of the algorithm working (it's able to go in 'caves' too) On this map it figured it out and returned a line across the ground. In some situations it suddenly stops though, like on this map: After about 10 loops it stopped creating the line. Here's the code, with some explanatory comments in there: voxelToLine() { let voxels = this.voxels.length,//this.voxels is the 2d array lineGround = [], checkedVoxels = [], nowChecking, toCheck = [], otherPaths = [], done = false; for (let y = 1; y < voxels - 1; y++)//sets first coordinate for line if (this.voxels[0][y] && (!this.voxels[0][y - 1] || !this.voxels[1][y] || !this.voxels[0][y + 1])) { lineGround[0] = [0, y / voxels]; nowChecking = [1, y];//search starts from this point } let looped = 0; while (!done) {//continues search untill right side is located, or it got stuk (max 10*voxelmap width loops) toCheck = nowChecking.neighbours(8, (n) => n[0] > 0 && n[0] < voxels - 1);//gets 8 neighbour points around current point, neighbours between 1 and (voxelwidth -1) get returned let foundNew = false; for (let i = 0; i < toCheck.length; i++) {//check every neighbour let x = toCheck[i][0], y = toCheck[i][1], index = y * voxels + x; if (!checkedVoxels.includes(index)) { if (this.voxels[x][y] && (!this.voxels[x][y - 1] || !this.voxels[x + 1][y] || !this.voxels[x - 1][y] || !this.voxels[x][y + 1])) { //if the neighbour is a floor voxel, and touches a skyvoxel this neighbour is added to the line checkedVoxels.push(index); if (foundNew) {//if a valid neighbour is already found, this means there are 2 possible paths from the current point otherPaths.push([x, y]); } else { lineGround.push([x / voxels, y / voxels]); nowChecking = [x, y]; //valid point gets added to the line and currently explored point get updated foundNew = true; } if (x >= voxels) done = true; } } else if (i == toCheck.length - 1 && !foundNew) { if (otherPaths.length > 0) { nowChecking = otherPaths.pop(); //if none of the neighbours are correct an alternative path gets explored foundNew = true; } } } if (!foundNew || looped++ > voxels * 10) { //if it never found a valid neighbour, or it's looped too often break from the whileloop console.log('loops: ', looped); break; } } if (lineGround[0][0] !== 0) lineGround.splice(0, 0, [0, lineGround[0][1]]); if (lineGround[lineGround.length - 1][0] !== 1) lineGround.push([1, lineGround[lineGround.length - 1][1]]); //x=0 and x=1 have to exist, so if they don't exist yet, add them return lineGround; } You can also test it here: game. If you click you remove (set to 0) a few voxels within a radius of where you clicked. Also the line gets recalculated. I'm stuck on this, because I have no idea why the line stops in some situations. All code is here. The relevant file is js/Level.js
There are more problems than the one you raised. I played a bit on your site and there are many patterns where things go wrong. I tried to follow the logic of your code, but got lost in details. So I rewrote most of the code. The main idea is that you should keep record of which direction (slope) you are travelling along the ground in order to know in which order you should look among the neighbours for one that is part of the ground. Let's say the neighbours are numbered as follows, from 0 to 7: +---+---+---+ | 7 | 0 | 1 | +---+---+---+ | 6 | * | 2 | +---+---+---+ | 5 | 4 | 3 | +---+---+---+ The cell marked with * is the last cell you found to be on ground level. Now let's say the previous one found was at 6, then the search among the neighbours should start at 7, then 0, 1, 2, ... 5. The first one that is found to be solid, should be the next cell added to ground level. Another example: if the previous one found was at 4 (we're going upward), then the neighbours should be searched starting at 5, then 6, 7, 0, 1, 2 and 3. The first neighbour that is found to be solid (ground) is the one you want to add to your ground line. This way you will follow every curve, into "caves", upward or downward, left or right. Of course, things can still go weird if you start on an island. But I did not attempt to solve that particular case. I've implemented the above idea in the following version of your method: voxelToLine() { let voxels = this.voxels.length, x, y, i; // neighbors' relative coordinates listed in clockwise order const neighbor = [ [0,-1], [1,-1], [1,0], [1,1], [0,1], [-1,1], [-1,0], [-1,-1] ]; for (y = 0; y < voxels; y++) //sets first coordinate for line. if (this.voxels[0][y]) break; // found ground, don't look further down let lineGround = [[0, y / voxels]]; let [curX, curY] = [0, y]; //search starts here let direction = 0; // upward let looped = 0; do {// Continues search until right side is located, // or it got stuk (max 10*voxelmap width loops) for (i = 0; i < 8; i++) {//check every neighbour, starting at `direction` [x, y] = [curX + neighbor[direction][0], curY + neighbor[direction][1]]; // if we found ground, then pick that cell as the next one on the line if (x>=0 && x<voxels && y>=0 && y<voxels && this.voxels[x][y]) break; direction = (direction + 1) % 8; // turn clockwise to get next neighbour } //if it never found a valid neighbour if (i === 8) break; lineGround.push([x / voxels, y / voxels]); // prepare for next round [curX, curY] = [x, y]; direction = (direction + 5) % 8; } while (looped++ <= voxels*10 && curX < voxels - 1); //x=0 and x=1 have to exist, so if they don't exist yet, add them if (lineGround[0][0] !== 0) lineGround.splice(0, 0, [0, lineGround[0][1]]); if (lineGround[lineGround.length - 1][0] !== 1) lineGround.push([1, lineGround[lineGround.length - 1][1]]); return lineGround; }
Looks like it's skipping over the voxel right below the last legitimate ground voxel because it's already been "checked" (added to the checkedVoxels array). Interestingly, this would prevent your ground path to ever turn 90 degrees (you'll notice your example picture doesn't have such a voxel pattern).
Diamond Square Algorithm fixed size
I am trying to figure out a way to have a fixed scale for the: https://en.wikipedia.org/wiki/Diamond-square_algorithm I see that the algorithm requires a power of 2 (+1) size of the array. The problem I am having is that I would like to have the same heightmap produced regardless of the resolution. So if I have a resolution of 512 it would look the same as with the resolution 256 but just have less detail. I just can't figure out how to do this with. My initial thought was to always create the heightmap in a certain dimension e.g. 1024 and downsample to the res I would like. Problem is I would like the upper resolution to be quite high (say 4096) and this severely reduces the performance at lower resolutions as we have to run the algo at the highest possible resolution. Currently the algorithm is in javascript here is a snippet: function Advanced() { var adv = {}, res, max, heightmap, roughness; adv.heightmap = function() { // heightmap has one extra pixel this is ot remove it. var hm = create2DArray(res-1, res-1); for(var x = 0;x< res-1;x++) { for(var y = 0;y< res-1;y++) { hm[x][y] = heightmap[x][y]; } } return hm; } adv.get = function(x,y) { if (x < 0 || x > max || y < 0 || y > max) return -1; return heightmap[x][y]; } adv.set = function(x,y,val) { if(val < 0) { val = 0; } heightmap[x][y] = val; } adv.divide = function(size) { var x, y, half = size / 2; var scale = roughness * size; if (half < 1) return; for (y = half; y < max; y += size) { for (x = half; x < max; x += size) { adv.square(x, y, half, Math.random() * scale * 2 - scale); } } for (y = 0; y <= max; y += half) { for (x = (y + half) % size; x <= max; x += size) { adv.diamond(x, y, half, Math.random() * scale * 2 - scale); } } adv.divide(size / 2); } adv.average = function(values) { var valid = values.filter(function(val) { return val !== -1; }); var total = valid.reduce(function(sum, val) { return sum + val; }, 0); return total / valid.length; } adv.square = function(x, y, size, offset) { var ave = adv.average([ adv.get(x - size, y - size), // upper left adv.get(x + size, y - size), // upper right adv.get(x + size, y + size), // lower right adv.get(x - size, y + size) // lower left ]); adv.set(x, y, ave + offset); } adv.diamond = function(x, y, size, offset) { var ave = adv.average([ adv.get(x, y - size), // top adv.get(x + size, y), // right adv.get(x, y + size), // bottom adv.get(x - size, y) // left ]); adv.set(x, y, Math.abs(ave + offset)); } adv.generate = function(properties, resolution) { Math.seedrandom(properties.seed); res = resolution + 1; max = res - 1; heightmap = create2DArray(res, res); roughness = properties.roughness; adv.set(0, 0, max); adv.set(max, 0, max / 2); adv.set(max, max, 0); adv.set(0, max, max / 2); adv.divide(max); } function create2DArray(d1, d2) { var x = new Array(d1), i = 0, j = 0; for (i = 0; i < d1; i += 1) { x[i] = new Array(d2); } for (i=0; i < d1; i += 1) { for (j = 0; j < d2; j += 1) { x[i][j] = 0; } } return x; } return adv; } Anyone ever done this before ?
Not quite sure if I understand your question yet but I'll provide further clarification if I can. You've described a case where you want a diamond-square heightmap with a resolution of 256 to be used at a size of 512 without scaling it up. I'll go through an example using a 2x2 heightmap to a "size" of 4x4. A diamond-square heightmap is really a set of vertices rather than tiles or squares, so a heightmap with a size of 2x2 is really a set of 3x3 vertices as shown: You could either render this using the heights of the corners, or you might turn it into a 2x2 set of squares by taking the average of the four surrounding points - really this is just the "square" step of the algorithm without the displacement step. So in this case the "height" of the top-left square would be the average of the (0, 0), (0, 1), (1, 1) and (1, 0) points. If you wanted to draw this at a higher resolution, you could split each square up into a smaller set of 4 squares, adjusting the average based on how close it is to each point. So now the value of the top-left-most square would be a sample of the 4 sub-points around it or a sample of its position relative to the points around it. But really this is just the diamond square algorithm applied again without any displacement (no roughness) so you may as well apply the algorithm again and go to the larger size. You've said that going to the size you wish to go to would be too much for the processor to handle, so you may want to go with this sampling approach on the smaller size. An efficient way would be to render the heightmap to a texture and sample from it and the position required.
Properly implemented diamond & square algorithm has the same first N steps regardless of map resolution so the only thing to ensure the same look is use of some specified seed for pseudo random generator. To make this work you need: set seed allocate arrays and set base randomness magnitude Diamond Square lower base randomness magnitude loop #3 until lowest resolution hit If you are not lowering the randomness magnitude properly then the lower recursion/iteration layers can override the shape of the result of the upper layers making this not work. Here see how I do it just add the seed: Diamond-square algorithm not working see the line: r=(r*220)>>8; if (r<2) r=2; The r is the base randomness magnitude. The way you are lowering it will determine the shape of the result as you can see I am not dividing it by two but multiplying by 220/256 instead so the lower resolution has bigger bumps which suite my needs. Now if you want to use non 2^x+1 resolutions then choose the closer bigger resolution and then scale down to make this work for them too. The scaling down should be done carefully to preserve them main grid points of the first few recursion/iteration steps or use bi-cubic ... If you're interested take a look on more up to date generator based on the linked one: Diamond&Square Island generator
Smoothing out values of an array
If I had an array of numbers such as [3, 5, 0, 8, 4, 2, 6], is there a way to “smooth out” the values so they’re closer to each other and display less variance? I’ve looked into windowing the data using something called the Gaussian function for a 1-dimensional case, which is my array, but am having trouble implementing it. This thread seems to solve exactly what I need but I don’t understand how user naschilling (second post) came up with the Gaussian matrix values. Context: I’m working on a music waveform generator (borrowing from SoundCloud’s design) that maps the amplitude of the song at time t to a corresponding bar height. Unfortunately there’s a lot of noise, and it looks particularly ugly when the program maps a tiny amplitude which results in a sudden decrease in height. I basically want to smooth out the bar heights so they aren’t so varied. The language I'm using is Javascript. EDIT: Sorry, let me be more specific about "smoothing out" the values. According to the thread linked above, a user took an array [10.00, 13.00, 7.00, 11.00, 12.00, 9.00, 6.00, 5.00] and used a Gaussian function to map it to [ 8.35, 9.35, 8.59, 8.98, 9.63, 7.94, 5.78, 7.32] Notice how the numbers are much closer to each other. EDIT 2: It worked! Thanks to user Awal Garg's algorithm, here are the results: No smoothing Some smoothing Maximum smoothing EDIT 3: Here's my final code in JS. I tweaked it so that the first and last elements of the array were able to find its neighbors by wrapping around the array, rather than calling itself. var array = [10, 13, 7, 11, 12, 9, 6, 5]; function smooth(values, alpha) { var weighted = average(values) * alpha; var smoothed = []; for (var i in values) { var curr = values[i]; var prev = smoothed[i - 1] || values[values.length - 1]; var next = curr || values[0]; var improved = Number(this.average([weighted, prev, curr, next]).toFixed(2)); smoothed.push(improved); } return smoothed; } function average(data) { var sum = data.reduce(function(sum, value) { return sum + value; }, 0); var avg = sum / data.length; return avg; } smooth(array, 0.85);
Interesting question! The algorithm to smooth out the values obviously could vary a lot, but here is my take: "use strict"; var array = [10, 13, 7, 11, 12, 9, 6, 5]; function avg (v) { return v.reduce((a,b) => a+b, 0)/v.length; } function smoothOut (vector, variance) { var t_avg = avg(vector)*variance; var ret = Array(vector.length); for (var i = 0; i < vector.length; i++) { (function () { var prev = i>0 ? ret[i-1] : vector[i]; var next = i<vector.length ? vector[i] : vector[i-1]; ret[i] = avg([t_avg, avg([prev, vector[i], next])]); })(); } return ret; } function display (x, y) { console.clear(); console.assert(x.length === y.length); x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`)); } display(array, smoothOut(array, 0.85)); NOTE: It uses some ES6 features like fat-arrow functions and template strings. Firefox 35+ and Chrome 45+ should work fine. Please use the babel repl otherwise. My method basically computes the average of all the elements in the array in advance, and uses that as a major factor to compute the new value along with the current element value, the one prior to it, and the one after it. I am also using the prior value as the one newly computed and not the one from the original array. Feel free to experiment and modify according to your needs. You can also pass in a "variance" parameter to control the difference between the elements. Lowering it will bring the elements much closer to each other since it decreases the value of the average. A slight variation to loosen out the smoothing would be this: "use strict"; var array = [10, 13, 7, 11, 12, 9, 6, 5]; function avg (v) { return v.reduce((a,b) => a+b, 0)/v.length; } function smoothOut (vector, variance) { var t_avg = avg(vector)*variance; var ret = Array(vector.length); for (var i = 0; i < vector.length; i++) { (function () { var prev = i>0 ? ret[i-1] : vector[i]; var next = i<vector.length ? vector[i] : vector[i-1]; ret[i] = avg([t_avg, prev, vector[i], next]); })(); } return ret; } function display (x, y) { console.clear(); console.assert(x.length === y.length); x.forEach((el, i) => console.log(`${el}\t\t${y[i]}`)); } display(array, smoothOut(array, 0.85)); which doesn't take the averaged value as a major factor. Feel free to experiment, hope that helps!
The technique you describe sounds like a 1D version of a Gaussian blur. Multiply the values of the 1D Gaussian array times the given window within the array and sum the result. For example Assuming a Gaussian array {.242, .399, .242} To calculate the new value at position n of the input array - multiply the values at n-1, n, and n+1 of the input array by those in (1) and sum the result. eg for [3, 5, 0, 8, 4, 2, 6], n = 1: n1 = 0.242 * 3 + 0.399 * 5 + 0.242 * 0 = 2.721 You can alter the variance of the Gaussian to increase or reduce the affect of the blur.
i stumbled upon this post having the same problem with trying to achieve smooth circular waves from fft averages. i've tried normalizing, smoothing and wildest math to spread the dynamic of an array of averages between 0 and 1. it is of course possible but the sharp increases in averaged values remain a bother that basically makes these values unfeasable for direct display. instead i use the fft average to increase amplitude, frequency and wavelength of a separately structured clean sine. imagine a sine curve across the screen that moves right to left at a given speed(frequency) times the current average and has an amplitude of current average times whatever will then be mapped to 0,1 in order to eventually determine 'the wave's' z. the function for calculating size, color, shift of elements or whatever visualizes 'the wave' will have to be based on distance from center and some array that holds values for each distance, e.g. a certain number of average values. that very same array can instead be fed with values from a sine - that is influenced by the fft averages - which themselves thus need no smoothing and can remain unaltered. the effect is pleasingly clean sine waves appearing to be driven by the 'energy' of the sound. like this - where 'rings' is an array that a distance function uses to read 'z' values of 'the wave's x,y positions. const wave = { y: height / 2, length: 0.02, amplitude: 30, frequency: 0.5 } //var increment = wave.frequency; var increment = 0; function sinewave(length,amplitude,frequency) { ctx.strokeStyle = 'red'; ctx.beginPath(); ctx.moveTo(0, height / 2); for (let i = 0; i < width; i+=cellSize) { //ctx.lineTo(i, wave.y + Math.sin(i * wave.length + increment) * wave.amplitude) ctx.lineTo(i, wave.y + Math.sin(i * length + increment) * amplitude); rings.push( map( Math.sin(i * length + increment) * amplitude,0,20,0.1,1) ); rings.shift(); } ctx.stroke(); increment += frequency; } the function is called each frame (from draw) with the current average fft value driving the sine function like this - assuming that value is mapped to 0,1: sinewave(0.006,averg*20,averg*0.3) allowing fluctuating values to determine wavelength or frequency can have some visually appealing effect. however, the movement of 'the wave' will never seem natural. i've accomplished a near enough result in my case. for making the sine appear to be driven by each 'beat' you'd need beat detection to determine the exact tempo of 'the sound' that 'the wave' is supposed to visualize. continuous averaging of distance between larger peaks in the lower range of fft spectrum might work there with setting a semi fixed frequency - with edm... i know, the question was about smoothing array values. forgive me for changing the subject. i just thought that the objective 'sound wave' is an interesting one that could be achieved differently. and just so this is complete here's a bit that simply draws circles for each fft and assign colour according to volume. with linewidths relative to total radius and sum of volumes this is quite nice: //col generator function getCol(n,m,f){ var a = (PIx5*n)/(3*m) + PIdiv2; var r = map(sin(a),-1,1,0,255); var g = map(sin(a - PIx2/3),-1,1,0,255); var b = map(sin(a - PIx4/3),-1,1,0,255); return ("rgba(" + r + "," + g + "," + b + "," + f + ")"); } //draw circles for each fft with linewidth and colour relative to value function drawCircles(arr){ var nC = 20; //number of elem from array we want to use var cAv = 0; var cAvsum = 0; //get the sum of all values so we can map a single value with regard to this for(var i = 0; i< nC; i++){ cAvsum += arr[i]; } cAv = cAvsum/nC; var lastwidth = 0; //draw a circle for each elem from array //compute linewith a fraction of width relative to value of elem vs. sum of elems for(var i = 0; i< nC; i++){ ctx.beginPath(); var radius = lastwidth;//map(arr[i]*2,0,255,0,i*300); //use a small col generator to assign col - map value to spectrum ctx.strokeStyle = getCol(map(arr[i],0,255,0,1280),1280,0.05); //map elem value as fraction of elem sum to linewidth/total width of outer circle ctx.lineWidth = map(arr[i],0,cAvsum,0,width); //draw ctx.arc(centerX, centerY, radius, 0, Math.PI*2, false); ctx.stroke(); //add current radius and linewidth to lastwidth var lastwidth = radius + ctx.lineWidth/2; } } codepen here: https://codepen.io/sumoclub/full/QWBwzaZ always happy about suggestions.