I'm a little confused with my array creation. I have a list of items with x:y co ordinates and also their dimensions (in grid sizes) ... for example:
x: 1
y: 7
width: 2 tiles
height: 2 tiles
So the idea im trying to do is create an array of x:y grids that are "occupied". But looping such data so the occupied tiles would there for be:
x: 1
y: 7
//
x: 2
y: 7
//
x: 1
y: 8
//
x: 2
y: 8
Because in the above example the item is 2 by 2 tiles ( a square ).
My object is structured like this (shown in console.log(sdata);)
7: Array[4]
0: "1"
1: "7"
2: "2"
3: "2"
So yeah im trying to make an array to store this kind of grid reference..Hope I explained what I trying to get at, but i can't work out how to structure a loop to create the array.
http://jsfiddle.net/rlemon/JeeV2/ Here is an example of how you could produce this type of 'grid' occupied data..
var chords = [ // instead of arrays we'll use objects.. they're nicer.
{
x: 1,
y: 7,
h: 2, // height
w: 2}, // width
{ // ohh look a second plot point.
x: 4,
y: 1,
h: 3,
w: 2},
];
var occupied = { // will be your cells points occupied
x: [],
y: []
};
chords.forEach(function(chord) { // now lets loop the chords and produce the occupied array
occupied.x.push( chord.x );
occupied.x.push( chord.x + (chord.w-1) ); // expand the width of the cell - initial point
occupied.y.push( chord.y );
occupied.y.push( chord.y + (chord.h-1) ); // expand the height of the cell - initial point
});
console.log( occupied );
// outputs
Object
x: Array[4]
0: 1
1: 2
2: 4
3: 5
y: Array[4]
0: 7
1: 8
2: 1
3: 3
The resulting outputArray array is a collection of objects in the form { x: value, y: value}.
var inputArray = [ [1,7,2,2], ... ];
var outputArray = [];
for(var i = 0; i < inputArray.length; i++) {
var item = {
x: inputArray[i][0],
y: inputArray[i][1],
width: inputArray[i][2],
height: inputArray[i][3]
};
for(var xx = 0; xx < item.width; xx++) {
for(var yy = 0; yy < item.height; yy++) {
outputArray.push({
x: item.x + xx,
y: item.y + yy
});
}
}
}
I added the x, y, width and height attributes to make it more understandable.
function range(a, b) {
/*
range(5) -> [0,1,2,3,4]
range(1,4) -> [1,2,3]
*/
if (b===undefined)
var start=0, stop=a;
else
var start=a, stop=b;
var R = [];
for(var i=start; i<stop; i++)
R.push(i);
return R;
}
Then it's a two-liner:
range(x, x+width).map(function(x) {
return range(y, y+height).map(function(y) {
return {x:x, y:y}; // anything you'd like
})
})
Result (obtained via JSON.stringify):
[
[ {"x":1,"y":7}, {"x":1,"y":8} ],
[ {"x":2,"y":7}, {"x":2,"y":8} ]
]
Related
This question already has answers here:
What is 'Currying'?
(23 answers)
Closed 1 year ago.
so I'm struggling to understand this code. Can someone please explain how does function move works? Like I watched a lecture about it, but I still don't understand it.
const parse = (point) => {
return typeof point === "object" ? point : JSON.parse(point);
};
const move = (offset) => (point) =>{
point.x += offset.x;
point.y += offset.y;
return point
};
const polyline = [
{ x: 0, y: 0 },
{ x: 10, y: 10 },
'{ "x": 20, "y": 20 }',
{ x: 30, y: 30 },
];
const offset = move({ x: 10, y: -5 });
const path = polyline.map(parse).map(offset);
console.log({ path });
Here's how it was done previously, and this code is an optimised version of that code:
const shift = (offset, points) => {
let modifiedPoints = [];
points.forEach((point) => {
point = parse(point);
point.x += offset.x;
point.y += offset.y;
modifiedPoints.push(point);
});
return modifiedPoints;
};
It is called currying
The passed created function will in your case add 10 to x and subtract 5 from y from each of the polylines in the array
If you call it with
move({ x: 5, y: -15 }); it will offset each line by 5,-15
const parse = (point) => {
return typeof point === "object" ? point : JSON.parse(point); // did we get an object or a string? if the latter parse it
};
// move takes an offset and returns a function that uses that offset (closure)
const move = (offset) => (point) =>{
point.x += offset.x;
point.y += offset.y;
return point
};
// an array of objects or valid JSON strings
const polyline = [
{ x: 0, y: 0 },
{ x: 10, y: 10 },
'{ "x": 20, "y": 20 }', // this will be parsed
{ x: 30, y: 30 },
];
const offset = move({ x: 10, y: -5 }); // offset is returning a function to be used in the map
const path = polyline.map(parse).map(offset); // call the function for each parsed entry in the polyline
console.log({ path });
i have written an algorithm that finds each line of a hexagon in a huge object-array structure.
the array consits of about 80.000 - 100.000 elements (line coordinates from start to end).
A hexagon cosists of 6 lines points. So the array has the information of about 15.000 hexagons.
The structure of the object (UNSORTED!!!) looks like this:
const stamps = [
{
vertices: [
{x: 114.5116411118, y: 342.9815785601},
{x: 115.6663416502, y: 344.9815785601}
]
},
{
vertices: [
{x: 115.6663416502, y: 340.9815785601},
{x: 114.5116411118, y: 342.9815785601}
]
},
{
vertices: [
{x: 122.6663416502, y: 364.9815785601},
{x: 147.9757427269, y: 314.9815785601},
]
},
{
vertices: [
{x: 117.9757427269, y: 340.9815785601},
{x: 115.6663416502, y: 340.9815785601},
]
},
{
vertices: [
{x: 119.1304432653, y: 342.9815785601},
{x: 117.9757427269, y: 340.9815785601},
]
},
{
vertices: [
{x: 117.9757427269, y: 344.9815785601},
{x: 119.1304432653, y: 342.9815785601},
]
},
{
vertices: [
{x: 115.6663416502, y: 344.9815785601},
{x: 117.9757427269, y: 344.9815785601},
]
},
];
To find each line hexagon, my idea was that there has to be 2 elements that have to same coordinate. If this is the case, i'm jumping to the index of this element and repeat that process untill i have all 6 lines of the hexagon.
It works like this, but its really, really slow. For an array with 80.000 elements its about 3 minutes.
The algorithm:
function findHexPolyPoints() {
const hexCoordinates = [];
let activeArrayPos = 0;
let position = 0;
while (1) {
let foundPair = false;
if (stamps.length < 6) break;
for (let k = 0; k < stamps.length; ++k) {
if (k === position) continue;
if (stamps[position].vertices[0].x === stamps[k].vertices[1].x && stamps[position].vertices[0].y === stamps[k].vertices[1].y) {
if (hexCoordinates[activeArrayPos]) {
hexCoordinates[activeArrayPos].push(stamps[k].vertices[0].x, stamps[k].vertices[0].y);
} else {
hexCoordinates.push([stamps[position].vertices[0].x, stamps[position].vertices[0].y, stamps[k].vertices[0].x, stamps[k].vertices[0].y]);
}
foundPair = true;
} else if (stamps[position].vertices[1].x === stamps[k].vertices[0].x && stamps[position].vertices[1].y === stamps[k].vertices[0].y) {
if (hexCoordinates[activeArrayPos]) {
hexCoordinates[activeArrayPos].push(stamps[k].vertices[1].x, stamps[k].vertices[1].y);
} else {
hexCoordinates.push([stamps[position].vertices[1].x, stamps[position].vertices[1].y, stamps[k].vertices[1].x, stamps[k].vertices[1].y]);
}
foundPair = true;
}
if (foundPair) {
stamps.splice(position, 1);
if (k > position) {
position = k - 1;
} else {
position = k;
}
if (hexCoordinates[activeArrayPos].length < 12) break;
}
if (hexCoordinates[activeArrayPos] && hexCoordinates[activeArrayPos].length === 12) {
if (k > position) stamps.splice(k - 1, 1);
else stamps.splice(k, 1);
activeArrayPos += 1;
position = 0;
break;
}
if (k === stamps.length - 1) {
stamps.splice(position, 1);
break;
}
}
}
sortHexagons(hexCoordinates);
}
Is there any way to speed up my algorithm? I have read that a simple for loop is still faster that some js sort functions like .map .filter or similar.
The following O(n) algorithm assumes
two different hexagons do not have overlapping vertices
there are no more than two same vertices in the array (meaning, there could be an orphan that doesn't belong to any hexagon, but its coordinates should not equal any of the hexagons vertices)
there is no floating point inaccuracy in coordinates (meaning two vertices that are supposed to be equal, are === exactly equal)
6 connected vertices are assumed to form an hexagon... (there is no barycenter calculation and checks to ensure it's actually an hexagon)
(if the points 1. and 2. are not true, the algo would need to be worked more, to try all possibilities (in overt[x_y] array, see below) in order to avoid non-hexagon or overlapping coords, depending on the expectancy to find overlapping hexagons, or orphans, the complexity could go beyond O(n))
Using the concept of map (get an object value from a key), which is considered O(1).
In order to conveniently use the vertices coordinates, we can concatenate x and y into one string
x:123.456, y:789.321
gives
x_y = "123.456_789.321"
Let's create 3 variables avert = [], overt = {}, hexas = []
avert is an array of all vertices, avert[index] is an array(2) of the x_y vertices coordinates
overt is an object that, for each x_y coordinates gives an array of indexes in avert (the size should not become more than 2, as assumed above (and there is no >2 check))
hexas is an array of array(6), the list of hexagons found (each coordinate is formatted x_y)
In the first forEach, avert and overt are created.
The next forEach processes all avert vertices [x_y1, x_y2]
starting from the first vertex, tries to find the 6 points to form an hexagon
adds each vertex to one hexagon array hexa, starting from the next (after the first)
assume coordinates are not sorted, thus ensure we're not going back to previous vertice
skips the used vertices (in hexagons)
ensure the last vertex found has the same coordinates as the origin (first)
Initialization
let avert = [], overt = {}, hexas = [];
stamps.forEach(function(e, i){
let xy1 = e['vertices'][0]['x']+'_'+e['vertices'][0]['y'];
let xy2 = e['vertices'][1]['x']+'_'+e['vertices'][1]['y'];
// overt[XY] (array) should have two elements at most (no overlapping),
// one if orphan
if ( ! overt[xy1]) overt[xy1] = [];
overt[xy1].push( i );
if ( ! overt[xy2]) overt[xy2] = [];
overt[xy2].push( i );
avert.push([ xy1, xy2 ]);
});
Processing
avert.forEach(function (e){
let j,coord = e[0]; // first coords x_y
let origin = coord;
let hexa = [];
let lastindex = -1; // avoid going back!
// try to find 5 connected vertices + origin
for(j=0 ; j<6 ; j++) {
let o = overt[coord];
if ( o === undefined || o.length < 2 ) {
break; // not found(already processed), or orphan!
}
let index = o[0] == lastindex ? o[1] : o[0]; // no turn back!
lastindex = index;
coord = avert[index][0] === coord ? avert[index][1] : avert[index][0];
hexa.push(coord);
}
if (j >= 6) { // found all vertices
// check that the last coord is the starting point
if (hexa[5] === origin) { // got it
hexas.push( hexa );
hexa.forEach(function(h){ // delete used vertices
delete overt[h];
});
}
}
});
All hexagons should be in hexas
You can avoid the nested loop over all data by using a hash map. Key the individual vertices with a hash, for instance their JSON representation, and store the corresponding x, y coordinate together with a list of neigbor objects.
Once you have that, it is easy to walk through that graph and identify the hexagons.
Runnable snippet using the sample data you provided:
function findHexPolyPoints(stamps) {
// Create graph
let map = new Map;
for (let {vertices} of stamps) {
// Get unique identifier for each vertex (its JSON notation)
let keys = vertices.map(JSON.stringify);
// Create "nodes" for each vertex, keyed by their key
let nodes = keys.map(function (key, i) {
let {x, y} = vertices[i];
let node = map.get(key);
if (!node) map.set(key, node = { key, x, y, neighbors: [] });
return node;
});
// Link these two nodes in both directions
nodes[0].neighbors.push(nodes[1]);
nodes[1].neighbors.push(nodes[0]);
}
// Walk through the graph to detect and collect hexagons
let hexagons = [];
for (let [key, vertex] of map) {
let hexagon = [];
while (vertex && hexagon.push(vertex) < 6) {
vertex = vertex.neighbors.find(neighbor => !hexagon.includes(neighbor));
}
// Remove collected nodes so they don't get visited a second time
for (let {key} of hexagon) map.delete(key);
// Verify that they form indeed a hexagon:
if (vertex && vertex.neighbors.includes(hexagon[0])) {
// Simplify the hexagon to only coordinates (12 coordinates)
hexagons.push(hexagon.flatMap(({x, y}) => [x, y]));
}
}
return hexagons;
}
// Demo. Just replace `stamps` with your actual data.
const stamps = [{vertices: [{x: 114.5116411118, y: 342.9815785601},{x: 115.6663416502, y: 344.9815785601}]},{vertices: [{x: 115.6663416502, y: 340.9815785601},{x: 114.5116411118, y: 342.9815785601}]},{vertices: [{x: 122.6663416502, y: 364.9815785601},{x: 147.9757427269, y: 314.9815785601},]},{vertices: [{x: 117.9757427269, y: 340.9815785601},{x: 115.6663416502, y: 340.9815785601},]},{vertices: [{x: 119.1304432653, y: 342.9815785601},{x: 117.9757427269, y: 340.9815785601},]},{vertices: [{x: 117.9757427269, y: 344.9815785601},{x: 119.1304432653, y: 342.9815785601},]},{vertices: [{x: 115.6663416502, y: 344.9815785601},{x: 117.9757427269, y: 344.9815785601},]},];
let hexagons = findHexPolyPoints(stamps);
console.log(hexagons);
It is true that plain old for loops are somewhat faster than .map, .forEach, .reduce, .find and the likes, but here I kept using them, as the main speed up is really coming from using a hash map.
I have an array which looks like this
arcAxis:
0:{x: 1.2858791391047208e-15, y: 21}
1:{x: -21, y: 21.000000000000004}
2:{x: -35.8492424049175, y: 6.150757595082504}
3:{x: -39.40038395815852, y: -14.546812157640753}
4:{x: -32.12697787933814, y: -34.24700413672001}
5:{x: -16.811252024253655, y: -48.61462542668643}
6:{x: 3.0355856977321465, y: -55.47779032614515}
Now I have a function which draws elements using x and y of arcAxis.
What I want to do is to call that function to draw an element for each of arcAxis's index value something like this
function test() {
plot(0. x, 0. y)
}
.....
function test() {
plot(6. x, 6. y)
}
So, that I have 6 new elements made on different x,y values respective to their indexes
my approach is printing each element 6 times then printing next element 6 times
function test() {
const arcAxis = this.spiral();
for (var z in arcAxis) {
plot(arcAxis[z].x, arcAxis[z].x)
}
}
Anyway, can I print each element only 1 time with only 1 indexes value?
let data= {
arcAxis:[
{x: 1.2858791391047208e-15, y: 21},
{x: -21, y: 21.000000000000004},
{x: -35.8492424049175, y: 6.150757595082504},
{x: -39.40038395815852, y: -14.546812157640753},
{x: -32.12697787933814, y: -34.24700413672001},
{x: -16.811252024253655, y: -48.61462542668643},
{x: 3.0355856977321465, y: -55.47779032614515}
]
}
data.arcAxis.forEach(({x, y})=>{
plot(x,y);
})
function plot(x,y){
console.log("X: ", x,"Y: ", y );
}
If you are using a pre EcmaScript 6 version:
arcAxis.forEach(function(element) {
plot(element.x, element.y);
});
And for EcmaScript 6 and onwards:
arcAxis.forEach(element => plot(element.x, element.y));
I'm trying to make a match-3 game (candy crush like). I have an object level which has tiles property which is a 2d array. After I do some manipulations I want to change the type of a specific element to -1 using this simple line (I'll be using for, but for now I've made it simple for demonstrative purposes)
level.tiles[1][0].type = -1;
Here is the code
var level = {
x: 250, // X position
y: 113, // Y position
columns: 8, // Number of tile columns
rows: 8, // Number of tile rows
tilewidth: 40, // Visual width of a tile
tileheight: 40, // Visual height of a tile
tiles: [], // The two-dimensional tile array
selectedtile: {selected: false, column: 0, row: 0}
};
var tileTypes = [
{
type: "red",
colors: [255, 128, 128]
},
{
type: "green",
colors: [128, 255, 128]
},
{
type: "darkBlue",
colors: [128, 128, 255]
},
{
type: "yellow",
colors: [255, 255, 128]
}
];
function createLevel() {
for (var i = 0; i < level.columns; i++) {
level.tiles[i] = [];
}
for (var i = 0; i < level.columns; i++) {
for (var j = 0; j < level.rows; j++) {
level.tiles[i][j] = getRandomTile();
}
}
}
function getRandomTile() {
return tileTypes[Math.floor(Math.random() * tileTypes.length)];
}
createLevel();
level.tiles[1][0].type = -1;
Unfortunately not only tiles[1][0] is modified, but multiple cells. The interesting part is that every time random cells are affected
This occurs because getRandomTile() returns a reference to a tile type, not a copy of it.
I.e. to simplify this case:
var a = {x: 1};
var b = [a, a, a, a];
b[0].x = 2;
console.log(a, b);
will output
{x: 2} [{x: 2}, {x: 2}, {x: 2}, {x: 2}]
If you want the tiles to be modifiable, have getRandomTile return a copy – a shallow copy in this case, so colors is still a reference, not a copy – of the randomly chosen tile type.
function getRandomTile() {
const tileType = tileTypes[Math.floor(Math.random() * tileTypes.length)];
// Idiom for shallow copy, i.e. assign all properties of tileType
// into a new, unique object.
return Object.assign({}, tileType);
}
The problem is you modify the type object, instead of linking to another type. A solution would be to clone it when creating the tiles:
function getRandomTile() {
var srcType = tileTypes[Math.floor(Math.random() * tileTypes.length)];
return {type:srcType.type, colors:srcType.color};
}
Another one (depending on your goal) would be to have Tile objects, each one having a reference to a Type object (not just an integer). At this point some classes might be helpful:
class TileType {
constructor(colors){
this.colors = colors;
}
}
let tileTypes = [...]
class Tile {
constructor(){
this.type = tileTypes[Math.random()*tileTypes.length|0];
}
setNewType(type){
this.type = type;
}
}
etc.
This is caused by getRandomTile which returns the same reference of the object defined in tileTypes if the index passed in is the same. You can print tileTypes to help your understand what happens.
I am trying to change my variables inside my items variable and not sure how to do it.
var items = [
{name: '#builder', x: 0, y:15},
{name: '#image', x: 0, y:15},
{name: '#button', x: -100, y: -55}
];
//I only want to change builder and image but not button...How to do this?
if(i>5){
items = [
{name: '#builder', x: 50, y:105},
{name: '#image', x: 110, y:115}
];
}
Thanks for the help.
Have you considered using an object hash table instead?
var items = {
'#builder' : { x: 0, y:15},
'#image': { x: 0, y:15},
'#button': {x: -100, y: -55}
};
Then you can change them like so:
items['#builder'].x = 50;
If it's not an option, that's cool, but it seems like if you want to look things up by name, this might be a better route for you.
Outside of that, you'd have to loop through each record to find the name you wanted and set the values appropriately.
var items = [
{name: '#builder', x: 0, y:15},
{name: '#image', x: 0, y:15},
{name: '#button', x: -100, y: -55}
];
function updateValues(name, x, y) {
for(var i = 0; i < items.length; i++) {
var item = items[i];
if(item.name == name) {
item.x = x;
item.y = y;
return;
}
}
}
updateValues('#builder', 50, 105);
If you know the position, then you could do:
if (i > 5) {
items[0].x = 50;
items[0].y = 105;
items[1].x = 110;
items[1].y = 115;
}