Structure to run x first vs y first on 2d array - javascript

I want to have a flag passed to a function that runs an algorithm by either col-scanning or row-scanning:
if run-on-x
for 1..x
for 1..y
do something with ary[x][y]
else
for 1..y
for 1..x
do something with ary[x][y]
But I don't want to duplicate all the loops and logic.
I've come up with this:
let numPx = width * height;
for (let px = 0; px < numPx; px++) {
let [x, y] = yAxis ? [px % width, 0 | px / width] : [0 | px / height, px % height];
But I think all the math is rather heavy, especially when I'm running it on fairly large arrays.
Is there a better way to do this?

Perhaps by simply passing them in as parameters like so?:
function colRowScan(1stAxis,2ndAxis)
for 1.. 1stAxis
for 1.. 2ndAxis
do something with ary[x][y]
Without seeing what the "do something" is I don't know if there is any unforeseen reasons why this couldn't work but given what you posted it should do the trick.
I am not entirely sure what you are trying to do here:
let numPx = width * height;
for (let px = 0; px < numPx; px++) {
let [x, y] = yAxis ? [px % width, 0 | px / width] : [0 | px / height, px % height];

function f(x, y, on_x) {
var a, b;
if (on_x) {
a = x;
b = y;
}
else {
a = y;
b = x;
}
for (var ia = 0; ia < a.length; ia++) {
for (var ib = 0; ib = b.length; ib++) {
// ...
}
}
}

Keep the two sets of inner and outer loops, but change the body of the inner loop to a single function call. Then there's not much code duplication.

In your solution,
let numPx = width * height;
for (let px = 0; px < numPx; px++) {
let [x, y] = yAxis ? [px % width, 0 | px / width] : [0 | px / height, px % height];
Number of comparison is numPx times while earlier it was only once, leave out the heavy math involved.
I think the simple and best solution is to use a separate function.
OR you can try this
var a, b, fAry;
if (run-on-x) {
a = x;
b = y;
fAry = ary;
} else {
a = y;
b = x;
fAry = transpose of(ary);
}
for (var i = 0; i < a; i++) {
for (var j = 0; j < b; j++) {
do something with fAry[i][j];
}
}

for 1..x
for 1..y {
var a = run-on-x ? ary[x][y] : ary[y][x];
do something with a
}

Create helper functions for row major and column major iteration, taking the array and a function to apply to the array members.
var rowMajor = function (a, op) {
var maxi = a.length;
var maxj = a[0].length;
for(var i = 0; i < maxi; ++i) {
var row = a[i];
for(var j = 0; j < maxj; ++j)
op(row[j],i,j);
}
};
var colMajor = function (a, op) {
var maxi = a.length;
if(maxi === 0) return;
var maxj = a[0].length;
for(var j = 0; j < maxj; ++j) {
for(var i = 0; i < maxi; ++i) {
op(a[i][j],i,j);
}
}
};
// example use (with jQuery)
var array = [[11,12,13],[21,22,23]];
var div = $('<div></div>');
var append = function(value) {
div.append($('<span></span>').text(value + ' '));
};
rowMajor(array,append);
div.append('<br/>');
colMajor(array, append);
$('body').append(div);

Related

Why does a tetris piece fall all at once instead of one at a time?

I am making tetris in JS. When making a block fall, it makes the block reach the bottom of the screen in one draw instead of slowly approaching the bottom. I tried creating a variable that stores the changes to be made so that it only looks at the current board, but no luck. After checking whether the output variable is == to the board, it seems like the board is changing after all, as it returns true. What's going on?
EDIT: I have successfully made a shallow copy of the array. It still falls to the bottom immediately, though. What's going on?
var data = [];
function array(x, text) {
var y = [];
for (var i = 0; i < x-1; i++) {y.push(text);}
return y;
}
for (var i=0; i<20; i++){data.push(array(10, "b"));}
function draw(){
var j;
var i;
var dataOut = [...data];
for (i = 0; i < data.length - 1; i++){
for (j = 0; j < data[i].length; j++){
if (data[i][j] == "a" && data[i + 1][j] == "b" && i < data.length - 1) {
dataOut[i][j] = "b";
dataOut[i + 1][j] = "a";
}
}
}
data = dataOut;
}
data[0][4] = 'a';
draw();
console.log(data);
In JavaScript, Arrays and Objects are passed by reference. So when you do this:
var dataOut = data;
Both of these references point to the same Array. You could clone the Array every time:
var dataOut = JSON.parse(JSON.stringify(data));
Or simply revert your loop, to go from the bottom to the top. I took the liberty of renaming the variables to make this more clear. Try it below:
var chars = {empty: '.', block: '#'},
grid = createEmptyGrid(10, 20);
function createEmptyGrid(width, height) {
var result = [], x, y;
for (y = 0; y < height; y++) {
var row = [];
for (x = 0; x < width; x++) {
row.push(chars.empty);
}
result.push(row);
}
return result;
}
function draw() {
var x, y;
for (y = grid.length - 1; y > 0; y--) {
for (x = 0; x < grid[y].length; x++) {
if (grid[y][x] === chars.empty && grid[y - 1][x] === chars.block) {
grid[y][x] = chars.block;
grid[y - 1][x] = chars.empty;
}
}
}
}
// Just for the demo
var t = 0, loop = setInterval(function () {
draw();
if (grid[0].includes(chars.block)) {
clearInterval(loop);
grid[9] = 'GAME OVER!'.split('');
}
document.body.innerHTML = '<pre style="font-size:.6em">'
+ grid.map(row => row.join(' ')).join('\n')
+ '</pre>';
if (t % 20 === 0) {
grid[0][Math.floor(Math.random() * 10)] = chars.block;
}
t++;
}, 20);

I'm trying to raise numbers to their consecutive powers and my code isn't working

https://codepen.io/aholston/pen/ZJbrjd
The codepen link has commented code as well as actual instructions in HTML
Otherwise.... what I ultimately have to do is write a function that takes two params(a and b) and takes all the numbers between those two params (a-b) and put every number that can be added to the consecutive fowers and be equal to that number into a new array. Ex: 89 = 8^1 + 9^2 = 89 or 135 = 1^1 + 3^2 + 5^3 = 135
function sumDigPow(a, b) {
// Your code here
var numbers = [];
var checkNum = [];
var finalNum = [];
var total = 0;
for (var i = 1; i <= b; i++) {
if (i >= a && i <= b) {
numbers.push(i);
}
}
for (var x = 0; x < numbers.length; x++) {
var checkNum = numbers[x].toString().split('');
if (checkNum.length == 1) {
var together = parseInt(checkNum);
finalNum.push(together);
} else if (checkNum.length > 1) {
var together = checkNum.join('');
var togNumber = parseInt(together);
for (var y = checkNum.length; y > 0; y--) {
total += Math.pow(checkNum[y - 1], y);
}
if (total == togNumber) {
finalNum.push(togNumber);
}
}
}
return finalNum;
}
try this:
function listnum(a, b) {
var finalNum = [];
for (var i = a; i <= b; i++) {
var x = i;
var y = i;
var tot = 0;
j = i.toString().length;
while (y) {
tot += Math.pow((y%10), j--);
y = Math.floor(y/10);
}
if (tot == x)
finalNum.push(i);
}
return finalNum;
}
console.log(listnum(1, 200));
Okay, after debugging this is what I learned.
for (var y = checkNum.length; y > 0; y--) {
total += Math.pow(checkNum[y - 1], y);
}
if (total == togNumber) {
finalNum.push(togNumber);
}
}
}
return finalNum;
}
Everytime this loop happened, I neglected to reset the 'total' variable back to 0. So I was never getting the right answer for my Math.pow() because my answer was always adding to the previous value of total. In order to fix this, I added var total = 0; after i decided whether or not to push 'togNumber' into 'finalNum.' So my code looks like this..
for (var y = checkNum.length; y > 0; y--) {
total += Math.pow(checkNum[y - 1], y);
}
if (total == togNumber) {
finalNum.push(togNumber);}
}
var total = 0;
}
return finalNum;
}

javascript canvas: draw moving average line with curves

So basically, I want to draw a curved average line over a certain amount of points of a time-series line chart. Like this:
I want it to span the entire length of the chart but I can't figure out how to calculate the start and end points because the average would (I think) be a point in the middle of each section. Looking at a stock chart with moving average you can see what I want to acheive:
I calculate the averages first by splitting the data array up into chunks based on a period of time. So if I start with:
[
{ time: 1, value: 2 },
{ time: 2, value: 4 },
{ time: 3, value: 5 },
{ time: 4, value: 7 },
]
I get to:
var averages = [
{
x: 1.5,
y: 3,
},
{
x: 3.5 (the average time)
y: 6 (the average value)
},
]
This is what I've tried where I end up with an incomplete line, one that doesnt start at the beginning of the chart and doesnt stop at the end, but stars and ends inside the chart at the first average time:
ctx.moveTo((averages[0].x), averages[0].y);
for(var i = 0; i < averages.length-1; i ++)
{
var x_mid = (averages[i].x + averages[i+1].x) / 2;
var y_mid = (averages[i].y + averages[i+1].y) / 2;
var cp_x1 = (x_mid + averages[i].x) / 2;
var cp_x2 = (x_mid + averages[i+1].x) / 2;
ctx.quadraticCurveTo(cp_x1, averages[i].y ,x_mid, y_mid);
ctx.quadraticCurveTo(cp_x2, averages[i+1].y ,averages[i+1].x, averages[i+1].y);
}
ctx.stroke();
How would you do this?
To get a moving mean you need to just get the mean of n points either side of the current sample.
For example
// array of data points
const movingMean = []; // the resulting means
const data = [12,345,123,53,134,...,219]; // data with index representing x axis
const sampleSize = 5;
for(var i = sampleSize; i < data.length - sampleSize; i++){
var total = 0;
for(var j = i- sampleSize; j < i + sampleSize; j++){
total += data[j];
}
movingMean[i] = total / (sampleSize * 2);
}
This method does not pull the mean forward giving the most accurate mean for each data point.
The problem with this method is that you do not get a mean for the first n and last n samples, where n is the number of samples either side of the mean.
You can do an alternative that will pull the mean forward a little but by applying a weighted mean you can reduce the bias a little
for(var i = sampleSize; i < data.length + Math.floor(sampleSize / 4); i++){
var total = 0;
var count = 0;
for(var j = sampleSize; j > 0; j --){
var index = i - (sampleSize - j);
if(index < data.length){
total += data[index] * j; // linear weighting
count += j;
}
}
movingMean[i-Math.floor(sampleSize / 4)] = total / count;
}
This method keeps that mean closer to the current sample end.
The example show a random data set and the two types of means plotted over it. Click to get a new plot. The red line is the moving mean and the blue is the weighted mean. Note how the blue line tends to follow the data a little slow.
The green line is a weighted mean that has a sample range 4 times greater than the other two.
// helper functions
const doFor = (count, callback) => {var i = 0; while (i < count) { callback(i ++) } };
const setOf = (count, callback) => {var a = [],i = 0; while (i < count) { a.push(callback(i ++)) } return a };
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
const randG = (dis, min, max) => {var r = 0; doFor(dis,()=>r+=rand(min,max)); return r / dis};
function getMinMax(data){
var min = data[0];
var max = data[0];
doFor(data.length - 1, i => {
min = Math.min(min,data[i+1]);
max = Math.max(max,data[i+1]);
});
var range = max-min;
return {min,max,range};
}
function plotData(data,minMax){
ctx.beginPath();
for(var i = 0; i < data.length; i++){
if(data[i] !== undefined){
var y = (data[i] - minMax.min) / minMax.range;
y = y *(ctx.canvas.height - 2) + 1;
ctx.lineTo(i/2,y);
}
}
ctx.stroke();
}
function getMovingMean(data,sampleSize){
const movingMean = []; // the resulting means
for(var i = sampleSize; i < data.length - sampleSize; i++){
var total = 0;
for(var j = i- sampleSize; j < i + sampleSize; j++){
total += data[j];
}
movingMean[i] = total / (sampleSize * 2);
}
return movingMean[i];
}
function getMovingMean(data,sampleSize){
const movingMean = []; // the resulting means
for(var i = sampleSize; i < data.length - sampleSize; i++){
var total = 0;
for(var j = i- sampleSize; j < i + sampleSize; j++){
total += data[j];
}
movingMean[i] = total / (sampleSize * 2);
}
return movingMean;
}
function getWeightedMean(data,sampleSize){
const weightedMean = [];
for(var i = sampleSize; i < data.length+Math.floor(sampleSize/4); i++){
var total = 0;
var count = 0;
for(var j = sampleSize; j > 0; j --){
var index = i - (sampleSize - j);
if(index < data.length){
total += data[index] * j; // linear weighting
count += j;
}
}
weightedMean[i-Math.floor(sampleSize/4)] = total / count;
}
return weightedMean;
}
const dataSize = 1000;
const sampleSize = 50;
canvas.width = dataSize/2;
canvas.height = 200;
const ctx = canvas.getContext("2d");
function displayData(){
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
var dataPoint = 100;
var distribution = Math.floor(rand(1,8));
var movement = rand(2,20);
const data = setOf(dataSize,i => dataPoint += randG(distribution, -movement, movement));
const movingMean = getMovingMean(data, sampleSize);
const weightedMean = getWeightedMean(data, sampleSize*2);
const weightedMean1 = getWeightedMean(data, sampleSize*8);
var minMax = getMinMax(data);
ctx.strokeStyle = "#ccc";
plotData(data,minMax);
ctx.strokeStyle = "#F50";
plotData(movingMean,minMax);
ctx.strokeStyle = "#08F";
plotData(weightedMean,minMax);
ctx.strokeStyle = "#4C0";
plotData(weightedMean1,minMax);
}
displayData();
document.onclick = displayData;
body { font-family : arial; }
.red { color : #F50; }
.blue { color : #0AF; }
.green { color : #4C0; }
canvas { position : absolute; top : 0px; left :130px; }
<canvas id="canvas"></canvas>
<div class="red">Moving mean</div>
<div class="blue">Weighted mean</div>
<div class="green">Wide weighted mean</div>
<div>Click for another sample</div>

Specific combination algorithm

If I have n balls and k containers then this -> ( (n+k-1)! / n!(k-1)! ) will work out how many combinations there are.
I am having difficulty changing this to produce a list of all combinations in javascript.
In a function taking an array of balls and some amount of containers.
combinations([1,2,3,4,5,6], 3)
Each container can have any number of balls and containers can be empty.
Here is something i attempted but im only getting one ball in each container.
function generateCombinations(array, r, callback) {
function equal(a, b) {
for (var i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
function values(i, a) {
var ret = [];
for (var j = 0; j < i.length; j++) ret.push(a[i[j]]);
return ret;
}
var n = array.length;
var indices = [];
for (var i = 0; i < r; i++) indices.push(i);
var final = [];
for (var i = n - r; i < n; i++) final.push(i);
while (!equal(indices, final)) {
callback(values(indices, array));
var i = r - 1;
while (indices[i] == n - r + i) i -= 1;
indices[i] += 1;
for (var j = i + 1; j < r; j++) indices[j] = indices[i] + j - i;
}
callback(values(indices, array));
}
count = 0
generateCombinations([1,2,3,4,5,6,7,8,9,1],3,function(first){
$("#hello").append(first+"<br />")
count = count +1
})
$("#hello").append(count)
You can do it in this way:
var containers = [];
// n - number of balls, k - number of containers
function dfs(n, k) {
// Ending point of recursion, all balls are placed
if(n == 0) {
var output = [];
for(var i = 0; i < k; i++) {
output.push('{' + containers[i].join(', ') + '}');
}
output = '[' + output.join(', ') + ']';
console.log(output);
return;
}
// Try to put ball #n
for(var i = 0; i < k; i++) {
containers[i].push(n);
// Now we have placed ball #n, so we have 1 .. n - 1 balls only
dfs(n - 1, k);
// Remove ball when back to use again
containers[i].pop();
}
}
var n = 4;
var k = 3;
for(var i = 0; i < k; i++) {
containers[i] = [];
}
dfs(n, k);
I initially thought you wanted all the combinations of k elements out of n, but your problem is different, it's partitioning n elements in k parts.
When going through the elements, at each steps, you may choose to put the current element in any container, that's k possibilities. In total, you will have kn possible solutions.
Therefore, it would be faster to iterate through all the solutions, rather than storing them in an array.
You can represent a solution as a unique number in base k, with n digits, and iterate through the solutions by incrementing that number.
In your example, the base is 3, and the number of digits is 6. The first solution is to put all the balls in container 0, ie.
000000
The next solution is to put all the balls in container 0, excepted the last which goes in container 1.
000001
...
000002
000010
000011
000020
Hopefully you should get the idea.

Randomly generate objects in canvas without duplicate or overlap

How do I generate objects on a map, without them occupying the same space or overlapping on a HTML5 Canvas?
X coordinate is randomly generated, to an extent. I thought checking inside the array to see if it's there already, and the next 20 values after that (to account for the width), with no luck.
var nrOfPlatforms = 14,
platforms = [],
platformWidth = 20,
platformHeight = 20;
var generatePlatforms = function(){
var positiony = 0, type;
for (var i = 0; i < nrOfPlatforms; i++) {
type = ~~(Math.random()*5);
if (type == 0) type = 1;
else type = 0;
var positionx = (Math.random() * 4000) + 500 - (points/100);
var duplicatetest = 21;
for (var d = 0; d < duplicatetest; d++) {
var duplicate = $(jQuery.inArray((positionx + d), platforms));
if (duplicate > 0) {
var duplicateconfirmed = true;
}
}
if (duplicateconfirmed) {
var positionx = positionx + 20;
}
var duplicateconfirmed = false;
platforms[i] = new Platform(positionx,positiony,type);
}
}();
I originally made a cheat fix by having them generate in an area roughly 4000 big, decreasing the odds, but I want to increase the difficulty as the game progresses, by making them appear more together, to make it harder. But then they overlap.
In crude picture form, I want this
....[]....[].....[]..[]..[][]...
not this
......[]...[[]]...[[]]....[]....
I hope that makes sense.
For reference, here is the code before the array check and difficulty, just the cheap distance hack.
var nrOfPlatforms = 14,
platforms = [],
platformWidth = 20,
platformHeight = 20;
var generatePlatforms = function(){
var position = 0, type;
for (var i = 0; i < nrOfPlatforms; i++) {
type = ~~(Math.random()*5);
if (type == 0) type = 1;
else type = 0;
platforms[i] = new Platform((Math.random() * 4000) + 500,position,type);
}
}();
EDIT 1
after some debugging, duplicate is returning as [object Object] instead of the index number, not sure why though
EDIT 2
the problem is the objects are in the array platforms, and x is in the array object, so how can I search inside again ? , that's why it was failing before.
Thanks to firebug and console.log(platforms);
platforms = [Object { image=img, x=1128, y=260, more...}, Object { image=img, x=1640, y=260, more...} etc
You could implement a while loop that tries to insert an object and silently fails if it collides. Then add a counter and exit the while loop after a desired number of successful objects have been placed. If the objects are close together this loop might run longer so you might also want to give it a maximum life span. Or you could implement a 'is it even possible to place z objects on a map of x and y' to prevent it from running forever.
Here is an example of this (demo):
//Fill an array with 20x20 points at random locations without overlap
var platforms = [],
platformSize = 20,
platformWidth = 200,
platformHeight = 200;
function generatePlatforms(k) {
var placed = 0,
maxAttempts = k*10;
while(placed < k && maxAttempts > 0) {
var x = Math.floor(Math.random()*platformWidth),
y = Math.floor(Math.random()*platformHeight),
available = true;
for(var point in platforms) {
if(Math.abs(point.x-x) < platformSize && Math.abs(point.y-y) < platformSize) {
available = false;
break;
}
}
if(available) {
platforms.push({
x: x,
y: y
});
placed += 1;
}
maxAttempts -= 1;
}
}
generatePlatforms(14);
console.log(platforms);
Here's how you would implement a grid-snapped hash: http://jsfiddle.net/tqFuy/1/
var can = document.getElementById("can"),
ctx = can.getContext('2d'),
wid = can.width,
hei = can.height,
numPlatforms = 14,
platWid = 20,
platHei = 20,
platforms = [],
hash = {};
for(var i = 0; i < numPlatforms; i++){
// get x/y values snapped to platform width/height increments
var posX = Math.floor(Math.random()*(wid-platWid)/platWid)*platWid,
posY = Math.floor(Math.random()*(hei-platHei)/platHei)*platHei;
while (hash[posX + 'x' + posY]){
posX = Math.floor(Math.random()*wid/platWid)*platWid;
posY = Math.floor(Math.random()*hei/platHei)*platHei;
}
hash[posX + 'x' + posY] = 1;
platforms.push(new Platform(/* your arguments */));
}
Note that I'm concatenating the x and y values and using that as the hash key. This is to simplify the check, and is only a feasible solution because we are snapping the x/y coordinates to specific increments. The collision check would be more complicated if we weren't snapping.
For large sets (seems unlikely from your criteria), it'd probably be better to use an exclusion method: Generate an array of all possible positions, then for each "platform", pick an item from the array at random, then remove it from the array. This is similar to how you might go about shuffling a deck of cards.
Edit — One thing to note is that numPlatforms <= (wid*hei)/(platWid*platHei) must evaluate to true, otherwise the while loop will never end.
I found the answer on another question ( Searching for objects in JavaScript arrays ) using this bit of code to search the objects in the array
function search(array, value){
var j, k;
for (j = 0; j < array.length; j++) {
for (k in array[j]) {
if (array[j][k] === value) return j;
}
}
}
I also ended up rewriting a bunch of the code to speed it up elsewhere and recycle platforms better.
it works, but downside is I have fewer platforms, as it really starts to slow down. In the end this is what I wanted, but its no longer feasible to do it this way.
var platforms = new Array();
var nrOfPlatforms = 7;
platformWidth = 20,
platformHeight = 20;
var positionx = 0;
var positiony = 0;
var arrayneedle = 0;
var duplicatetest = 21;
function search(array, value){
var j, k;
for (j = 0; j < array.length; j++) {
for (k in array[j]) {
if (array[j][k] === value) return j;
}
}
}
function generatePlatforms(ind){
roughx = Math.round((Math.random() * 2000) + 500);
type = ~~(Math.random()*5);
if (type == 0) type = 1;
else type = 0;
var duplicate = false;
for (var d = 0; d < duplicatetest; d++) {
arrayneedle = roughx + d;
var result = search(platforms, arrayneedle);
if (result >= 0) {
duplicate = true;
}
}
if (duplicate = true) {
positionx = roughx + 20;
}
if (duplicate = false) {
positionx = roughx;
}
platforms[ind] = new Platform(positionx,positiony,type);
}
var generatedplatforms = function(){
for (var i = 0; i < nrOfPlatforms; i++) {
generatePlatforms(i);
};
}();
you go big data, generate all possibilities, store each in an array, shuffle the array,
trim the first X items, this is your non heuristic algorithm.

Categories