Google App Script Data Validation inserts too many dropdowns - validation

I have a script that works mostly the way want. It looks at a cell then compares that to a column in another tab, finds the like items and returns that, and creates a dropdown on the cell. This moves down the column until it reaches the end. The problem is that it continues past the last row for about 20 rows. The starting row is row24.
function getInventoryItems() {
var jobSummaryInventoryItems = jobSummary.getRange(24, 8, jobSummary.getLastRow(), 1);
var jobSummaryInventoryItemsValues = jobSummaryInventoryItems.getValues();
var inventoryItems = inventory.getRange(4, 3, inventory.getLastRow(), 1);
var inventoryItemsValues = inventoryItems.getValues();
jobSummary.getRange(24, 8, jobSummary.getLastRow(), 1).setDataValidation(null);
for (z = 0; z < jobSummaryInventoryItemsValues.length; z++) {
if (jobSummaryInventoryItemsValues[z].toString().length > 1) {
var listOfInventory = [];
for (i = 0; i < inventoryItems.getLastRow() - 4; i++) {
if (inventoryItemsValues[i].toString() == jobSummaryInventoryItemsValues[z]) {
break;
}
var w = jobSummaryInventoryItemsValues[z];
if (inventoryItemsValues[i].toString().includes(jobSummaryInventoryItemsValues[z])) {
listOfInventory.push(inventoryItemsValues[i].toString());
}
}
}
if (listOfInventory.length > 0) {
var rangeRule = SpreadsheetApp.newDataValidation().requireValueInList(listOfInventory).build();
jobSummary.getRange(z + 24, 8).setDataValidation(rangeRule);
}
}

Get Inventory Items
function getInventoryItems() {
const ss = SpreadsheetApp.getActive();
const jobSummary = ss.getSheetByName('Job Summary');
const inventory = ss.getSheetByName('Inventory');
const jobSummaryInventoryItems = jobSummary.getRange(24, 8, jobSummary.getLastRow() - 23, 1);
const jobSummaryInventoryItemsValues = jobSummaryInventoryItems.getValues();
const inventoryItems = inventory.getRange(4, 3, inventory.getLastRow() - 3, 1);
const inventoryItemsValues = inventoryItems.getValues();
jobSummary.getRange(24, 8, jobSummary.getLastRow() -23, 1).setDataValidation(null);
for (z = 0; z < jobSummaryInventoryItemsValues.length; z++) {
if (jobSummaryInventoryItemsValues[z].toString().length > 1) {
let listOfInventory = [];
for (i = 0; i < inventoryItems.length; i++) {
if (inventoryItemsValues[i].toString() == jobSummaryInventoryItemsValues[z]) {
break;
}
let w = jobSummaryInventoryItemsValues[z];
if (inventoryItemsValues[i].toString().includes(jobSummaryInventoryItemsValues[z])) {
listOfInventory.push(inventoryItemsValues[i].toString());
}
}
}
if (listOfInventory.length > 0) {
let rangeRule = SpreadsheetApp.newDataValidation().requireValueInList(listOfInventory).build();
jobSummary.getRange(z + 24, 8).setDataValidation(rangeRule);
}
}
}
Sheet.getRange(row,column,number of rows, number of columns)

Related

handling objects offscreen for a view frustum

I have a view frustum that works great when looking at stuff from a distance. But for example, when i stand in the middle of a square, my current system struggles to place vertices which are behind me.
For example the image below demonstrates looking at a tile from a distance vs standing on top of it ...
My proccess for putting things onto screen can be described in 5 steps
cross product the co-ordinates of the object with my camera matrix
cross product the result of 1 with my projection_matrix
normalize the result of 2 by dividing through the 4th dimension of my co-ordinates (my w co-ordinate)
cull results of 3 (its not causing the problem, i tried without culling)
cross product 4 with to_screen_matrix
Basically the problem i have, is this procces is great at putting things on the screen but sometimes an object isnt on the screen... what co-ordinates should be used then?
Below is a drawing of what i think the problem is
below is my screen_projection function
return_screen_projection(dont_cull = false){
var position = cross_product(this.position , player.camera_matrix())
position = cross_product(position , projection.projection_matrix) // does this just convert the position to cameras reference frame.
for (let i = 0; i < position.length; i++) {
position[i] = position[i]/position[3]
}
if (dont_cull == false){
for (let i = 0; i < position.length; i++) {
if (i != 1){
if (this.is_this_object_behind_player()){for (let ii = 0; ii < position.length; ii++) {position[ii] = -9999;} console.log("culling1")}
if (position[i] > 2){for (let ii = 0; ii < position.length; ii++) {position[ii] = -9999;} console.log("culling2")}
if (position[i] < -2){for (let ii = 0; ii < position.length; ii++) {position[ii] = -9999;} console.log("culling3")}
}
} // also all examples say set position = 0 if culling
}
position = cross_product(position , projection.to_screen_matrix)
return [position[0],position[1]]
}
how could i better handle the position of a vertex offscreen, when some vertexes of the object im dealing with are on screen?
################## extra info below
below is a blob of 300 lines of code (sorry , i cant make it more minimal and reproducable with just a copy n paste)
running the below in a web browser will give you an example of the problem (w,a,s,d) to move (you start mired in the middle of an object, you may wish to step back to see better the first time)
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/11.3.3/math.js"></script>
<script async src="https://unpkg.com/es-module-shims#1.3.6/dist/es-module-shims.js"></script>
</head>
<body>
<div id="canvas div" style = "position: relative; left: 0px; float:left; top: 0px;" >
<h1> first person below </h1>
<canvas id="mi_canvas" width="300" height="300" style="border-style: solid;"></canvas> <br>
</div>
<script>
var floor_y_pos = 9
canvas = document.getElementById("mi_canvas");
ctx = canvas.getContext("2d");
render_distance = 1000;
fov = math.pi / 2
class Projection{
constructor(){
var NEAR = player.near_plane
var FAR = player.far_plane
var RIGHT = Math.tan(player.h_fov/2)
var LEFT = - RIGHT
var TOP = Math.tan(player.v_fov /2)
var BOTTOM = -TOP
var m00 = 2*NEAR / (RIGHT - LEFT)
var m02 = (RIGHT + LEFT)/(RIGHT - LEFT)
var m11 = 2*NEAR / (TOP - BOTTOM)
var m12 = (TOP + BOTTOM) /(TOP - BOTTOM)
var m22 = (FAR * NEAR) / (FAR - NEAR)
var m23 = -2 * NEAR * FAR / (FAR-NEAR)
this.projection_matrix = [
[-m00,0,m02,0],
[0,m11,0,0],
[m02,m12,-m22,-1],
[0,0,m23,0]
]
var HW=player.H_WIDTH
var HH = player.H_HEIGHT
this.to_screen_matrix = [
[HW,0,0,0],
[0,HH,0,0],
[0,0,1,0],
[HW,HH,0,1]
]
}
}
function multiply(a, b) {
var aNumRows = a.length, aNumCols = a[0].length,
bNumRows = b.length, bNumCols = b[0].length,
m = new Array(aNumRows); // initialize array of rows
for (var r = 0; r < aNumRows; ++r) {
m[r] = new Array(bNumCols); // initialize the current row
for (var c = 0; c < bNumCols; ++c) {
m[r][c] = 0; // initialize the current cell
for (var i = 0; i < aNumCols; ++i) {
m[r][c] += a[r][i] * b[i][c];
}
}
}
return m;
}
function mi_position_matrix_multiplier(A, B)
{
var new_matrix = []
for (var new_num_ind = 0; new_num_ind < A.length; ++new_num_ind)
{
this_num = 0;
for (var a_ind = 0; a_ind < A.length; ++a_ind)
{
this_num += (A[a_ind] * B[a_ind][new_num_ind])
}
new_matrix.push(this_num)
}
return new_matrix;
}
function pythagoras(thing1, thing2)
{
dist = (((thing1[0]-thing2[0])**2)+((thing1[1]-thing2[1])**2))**0.5
return dist
}
class vertex{
constructor(x, y,z , id){
this.id = id
this.position = [x,y,z,1]
this.min_dist = 1.5 // minimum possible distance between player and object
}
is_this_object_behind_player(){
var arrow_length = 0.0001;
var pointing_position = [player.position[0]+(player.forward[0]*arrow_length) , player.position[2]-(player.forward[2]*arrow_length)]
var dist1 = pythagoras([this.position[0],this.position[2]], pointing_position)
var dist2 = pythagoras([this.position[0],this.position[2]], [player.position[0],player.position[2]])
if (dist1 < dist2){
return true;}
else if (dist1 > dist2){
return false;}
else{}
}
return_screen_projection(dont_cull = false){
var position = mi_position_matrix_multiplier(this.position , player.camera_matrix())
position = mi_position_matrix_multiplier(position , projection.projection_matrix) // does this just convert the position to cameras reference frame.
for (let i = 0; i < position.length; i++) {
position[i] = position[i]/position[3]
}
if (dont_cull == false){
for (let i = 0; i < position.length; i++) {
if (i != 1){
if (this.is_this_object_behind_player()){for (let ii = 0; ii < position.length; ii++) {position[ii] = -9999;} console.log("culling1")}
if (position[i] > 2){for (let ii = 0; ii < position.length; ii++) {position[ii] = -9999;} console.log("culling2")}
if (position[i] < -2){for (let ii = 0; ii < position.length; ii++) {position[ii] = -9999;} console.log("culling3")}
}
} // also all examples say set position = 0 if culling
}
position = mi_position_matrix_multiplier(position , projection.to_screen_matrix)
return [position[0],position[1]]
}
}
class player{
constructor(){
this.position =[0,0,0,1.0]
this.forward = [0,0,1,1]
this.up = [0,1,0,1]
this.right =[1,0,0,1]
this.h_fov = 3.1415926535/3
this.v_fov = this.h_fov * (canvas.height / canvas.width)
this.near_plane = 1
this.far_plane = 100
this.moving_speed = 0.2
this.rotation_speed = 0.1
this.H_WIDTH = canvas.width/2
this.H_HEIGHT = canvas.height/2
this.anglePitch = 0
this.angleYaw = 0
}
set_camera_angle(){
var rotate = multiply(rotate_x(this.anglePitch) , rotate_y(this.angleYaw))
this.forward = [0, 0, 1, 1]
this.up = [0, 1, 0, 1]
this.right = [1, 0, 0, 1]
this.forward = mi_position_matrix_multiplier(this.forward , rotate)
this.right = mi_position_matrix_multiplier(this.right , rotate)
this.up = mi_position_matrix_multiplier(this.up , rotate)
}
camera_yaw(angle){
this.angleYaw += angle}
translate_matrix(self){
var x = this.position[0];
var y = this.position[1];
var z = this.position[2];
var w = this.position[3];
return [
[1,0,0,0],
[0,1,0,1],
[0,0,1,0],
[-x,-y,z, 1]
]}
rotate_matrix(){
var rx = this.right[0]
var ry = this.right[1]
var rz = this.right[2]
var w = this.right[3]
var fx = this.forward[0]
var fy = this.forward[1]
var fz = this.forward[2]
var w = this.forward[3]
var ux = this.up[0]
var uy = this.up[1]
var uz = this.up[2]
var w = this.up[3]
return [
[rx,ux,fx,0],
[ry,uy,fy,0],
[rz,uz,fz,0],
[0,0,0,1]
]
}
camera_matrix(){
return multiply(this.translate_matrix(), this.rotate_matrix());
}
move(event)
{
var key_code = parseInt(event.keyCode)
if (key_code == 37 || key_code == 39 || key_code == 83 || key_code == 87 || key_code == 119|| key_code == 115)
{
var dx = Math.cos(this.angleYaw)*this.moving_speed
var dy = Math.sin(this.angleYaw)*this.moving_speed
// console.log("that were moving = dx , dy = "+dx.toString()+" , "+dy.toString())
if ( key_code == 37 || key_code == 87 || key_code == 119) {
this.position[0] += -dy
this.position[2] += dx
}
if (key_code == 39 || key_code == 83 || key_code == 115) {
for (let i = 0; i < this.position.length; i++) {
this.position[0] += dy
this.position[2] += -dx
}
}
}
else {
if ( key_code == 38 || key_code == 65 || key_code == 97) {
this.camera_yaw(-this.rotation_speed)
}
if (key_code == 40 || key_code == 68 || key_code == 100) {
this.camera_yaw(this.rotation_speed)
}
this.set_camera_angle()
}
}
}
function translate(pos){
tx,ty,tz=pos
return np.array([
[1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[tx,ty,tz,1]
])}
function rotate_x(angle){
return [
[1,0,0,0],
[0,Math.cos(angle),Math.sin(angle),0],
[0,-Math.sin(angle),Math.cos(angle),0],
[0,0,0,1]
]
}
function rotate_y(a){
return [
[math.cos(a),0, -math.sin(a),0],
[0,1,0,0],
[math.sin(a), 0 , math.cos(a),0],
[0,0,0,1]
]
}
function update_matrix_info_debug(matrix_name, matrix){
if (matrix[0].length > 1)
{
for (let x = 1; x < matrix.length+1; x++) {
for (let y = 1; y < matrix.length+1; y++) {
document.getElementById(matrix_name.toString()+"_"+x.toString()+y.toString()).innerHTML = matrix[x-1][y-1]
}
}
}
else {
for (let x = 1; x < matrix.length+1; x++) {document.getElementById(matrix_name.toString()+"_"+"1"+x.toString()).innerHTML = matrix[x-1]}
}
}
class two_d_surdace {
constructor(verex1,verex2,verex3,verex4 , colour){
this.vertices = [verex1,verex2,verex3,verex4]
this.colour = colour
}
draw_all_faces(){
var each_point = []
for (let i = 0; i < this.vertices.length; i++) {
each_point.push(this.vertices[i].return_screen_projection(true))
}
ctx.fillStyle = this.colour;
var moved_to_first_yet = false
for (let vertex = 0; vertex < this.vertices.length; vertex++)
{
if (moved_to_first_yet == false)
{
moved_to_first_yet = true
ctx.moveTo( each_point[vertex][0],each_point[vertex][1]);
}
else{ctx.lineTo( each_point[vertex][0],each_point[vertex][1]);}
}
ctx.closePath();
ctx.fill();
}
}
function if_off_screen(x, y)
{
if (x> canvas.width || x < 0){ return true;}
if (y > canvas.height || y < 0){ return true;}
return false;
}
function if_most_of_these_numbers_are_off_screen(numbers){
var threshold = 1; //Math.floor(numbers.length*0.49)
var counter = 0
for (let i = 0; i < numbers.length; i++) { if (if_off_screen(numbers[i][0], numbers[i][1])){ counter +=1} else{} }
if (counter >= threshold){return true}
return false;
}
player = new player();
projection = new Projection()
floor = new two_d_surdace(new vertex(50,floor_y_pos,50) , new vertex(-50,floor_y_pos,50) , new vertex(-50,floor_y_pos,-50) , new vertex(50,floor_y_pos,-50) , '#F90' )
$(document).on("keypress", function (event) {
player.move(event)
ctx.beginPath();
ctx.clearRect(0, 0, canvas.width, canvas.height);
floor.draw_all_faces()
});
</script>
</body>

Grouping multiple moving averages in dc.js

I have a dc.js example going where I'd like to calculate moving averages over two different windows of the dataset and group them. Ultimately, I'd like to also group the ratio between the two moving averages with them so I can access them using a key.
I've got a handle on how to do single moving avg using reductio, but I'm not sure how to do two of them concurrently, make a ratio, and show all three (MA1,MA2,Ratio) in the graph.
Here is how I'm doing each independent MA:
var date_array = [];
var mapped_date_array = [];
var activities_infinity = activityDistanceByDayGroup.top(Infinity);
var i = 0;
for (i=0; i < activities_infinity.length; i++) {
date_array.push(activities_infinity[i].key);
}
date_array.sort(function (date1, date2) {
if (date1 > date2) return 1;
if (date1 < date2) return -1;
})
mapped_date_array = date_array.map(function(e) { return e.toDateString();
});
// For Chronic Load
var cLoadMovingAvg = activityByDay.groupAll();
cReducer = reductio().groupAll(function(record) {
var idx = mapped_date_array.indexOf(record.dtg.toDateString());
if (record.dtg < date_array[9]) {
return [date_array[idx]];
} else {
var i = 0;
var return_array = [];
for (i = 9; i >= 0; i--) {
return_array.push(date_array[idx - i]);
}
return return_array;
}
}).count(true).sum(dc.pluck('Distance')).avg(true)(cLoadMovingAvg);
// For Acute Load
var aLoadMovingAvg = activityByDay.groupAll();
aReducer = reductio().groupAll(function(record) {
var idx = mapped_date_array.indexOf(record.dtg.toDateString());
if (record.dtg < date_array[3]) {
return [date_array[idx]];
} else {
var i = 0;
var return_array = [];
for (i = 3; i >= 0; i--) {
return_array.push(date_array[idx - i]);
}
return return_array;
}
}).count(true).sum(dc.pluck('Distance')).avg(true)(aLoadMovingAvg);
jsFiddle is here: http://jsfiddle.net/gasteps/hLh5frc8/2/
Thanks so much for any help on this!

{d3.js} migration v3 to v4: code work on v3 (d3.layout.stack()) error v4 (d3.stack())

I'm trying to migrate D3 from v3 to v4:
Read:
https://github.com/d3/d3/blob/master/CHANGES.md#shapes-d3-shape
See:
d3.layout.stack ↦ d3.stack
I changed my working code:
Code working on v3: (d3.layout.stack())
Code producing error in v4: (d3.stack())
V4:
var dvstack = d3.stack();
var layers = dvstack(d3.range(nLocal).map(function(d,i) { ...
console.log(dvstack);
function stack(data) {
var kz = keys.apply(this, arguments),
i,
m = data.length,
n = kz.length,
sz = new Array(n),
oz;
for (i = 0; i < n; ++i) {
for (var ki = kz[i], si = sz[i] = new Array(m), j = 0, sij; j < m;++j) {
si[j] = sij = [0, +value(data[j], ki, j, data)];
sij.data = data[j];
}
si.key = ki;
}
for (i = 0, oz = order(sz); i < n; ++i) {
sz[oz[i]].index = i;
}
offset(sz, oz);
return sz; }
layers[c].dvnum = c;
Error:
SCRIPT5007: Unable to set property 'dvnum' of undefined or null reference
V3:
var stack = d3.layout.stack();
var layers = stack(d3.range(nLocal).map(function(d,i) { ...
console.log(stack);
function stack(data, index) {
if (!(n = data.length)) return data;
var series = data.map(function(d, i) {
return values.call(stack, d, i);
});
var points = series.map(function(d) {
return d.map(function(v, i) {
return [ x.call(stack, v, i), y.call(stack, v, i) ];
});
});
var orders = order.call(stack, points, index);
series = d3.permute(series, orders);
points = d3.permute(points, orders);
var offsets = offset.call(stack, points, index);
var m = series[0].length, n, i, j, o;
for (j = 0; j < m; ++j) {
out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
for (i = 1; i < n; ++i) {
out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
}
}
return data; }
layers[c].dvnum = c;
Screenshot of the working code in v3:
Screenshot of the working code (D3 v3) console.log(layers)
Screenshot of the (D3 v4) console.log(layers)
Turns out, it's actually quite easy.
You simply want to transpose your matrix so that it's looks like somethings which is close to the array of objects the new stack function is waiting for:
var n = 4, // number of layers
m = 58, // number of samples per layer
stack = d3.stack().keys([0,1,2,3]);
stack.value(function (d, key) {
return d[key].y;
});
var layers = stack(d3.transpose(d3.range(n).map(function() { return bumpLayer(m, .1); }))),
Then it's a simple matter of modifying the names according to the new syntax.
I updated your fiddle so that it works for v4.
see:
https://jsfiddle.net/9y2g65qc/20/

Finding path from cell x to cell y in a grid so that all cells are parsed once

I am trying to code an algorithm so that it can start from any "start" cell of a grid ( eg.cell no. 4 in the pic) and parse through each cell of a grid once. The grid can be of any size 3x3, 4x4, 8x8 etc.
The following codes generates such paths. And works fine for many cells in 8x8 grid. But for many cells, it takes forever to search a path.
I was wondering if there is some better solution that can be referenced, so that the solution can be optimized.
package
{
import flash.display.MovieClip;
public class Main extends MovieClip
{
private var rand_Num;
public var node0_Mc:MovieClip ,node1_Mc:MovieClip,node2_Mc:MovieClip,node3_Mc:MovieClip,node4_Mc:MovieClip,node5_Mc:MovieClip,node6_Mc:MovieClip,node7_Mc:MovieClip,node8_Mc:MovieClip,node9_Mc:MovieClip,
node10_Mc:MovieClip,node11_Mc:MovieClip,node12_Mc:MovieClip,node13_Mc:MovieClip,node14_Mc:MovieClip,node15_Mc:MovieClip,node16_Mc:MovieClip,node17_Mc:MovieClip,node18_Mc:MovieClip,node19_Mc:MovieClip,
node20_Mc:MovieClip,node21_Mc:MovieClip,node22_Mc:MovieClip,node23_Mc:MovieClip,node24_Mc:MovieClip,node25_Mc:MovieClip,node26_Mc:MovieClip,node27_Mc:MovieClip,node28_Mc:MovieClip,node29_Mc:MovieClip,
node30_Mc:MovieClip,node31_Mc:MovieClip,node32_Mc:MovieClip,node33_Mc:MovieClip,node34_Mc:MovieClip,node35_Mc:MovieClip,node36_Mc:MovieClip,node37_Mc:MovieClip,node38_Mc:MovieClip,node39_Mc:MovieClip,
node40_Mc:MovieClip,node41_Mc:MovieClip,node42_Mc:MovieClip,node43_Mc:MovieClip,node44_Mc:MovieClip,node45_Mc:MovieClip,node46_Mc:MovieClip,node47_Mc:MovieClip,node48_Mc:MovieClip,node49_Mc:MovieClip,
node50_Mc:MovieClip,node51_Mc:MovieClip,node52_Mc:MovieClip,node53_Mc:MovieClip,node54_Mc:MovieClip,node55_Mc:MovieClip,node56_Mc:MovieClip,node57_Mc:MovieClip,node58_Mc:MovieClip,node59_Mc:MovieClip,
node60_Mc:MovieClip, node61_Mc:MovieClip, node62_Mc:MovieClip, node63_Mc:MovieClip;
public const NUM_COLS:Number = 8;
// 3 ;// 4 ;
public const NUM_ROWS:Number = 8;// 3 ;// 4 ;
var chain_Arr:Array = [];
var blockIndex_Arr:Array = [];
var nodearr:Array;
var adjacentNodeArray_Arr:Array = [];
var parsedNodeIndex_Arr:Array = [];
var validNextAdjacentNodeIndexArray_Arr:Array = [];
var validPreviousAdjacentNodeIndexArray_Arr:Array = [];
var savePair_Arr:Array = [];
var countChain_Num:Number = 0;
var saveParent_Arr:Array = [];
public function Main()
{
// constructor code
nodearr = [node0_Mc,node1_Mc,node2_Mc,node3_Mc,node4_Mc,node5_Mc,node6_Mc,node7_Mc,node8_Mc,node9_Mc,node10_Mc,node11_Mc,node12_Mc,node13_Mc,node14_Mc,node15_Mc,node16_Mc,node17_Mc,node18_Mc,node19_Mc,node20_Mc,node21_Mc,node22_Mc,node23_Mc,node24_Mc,node25_Mc,node26_Mc,node27_Mc,node28_Mc,node29_Mc,node30_Mc,node31_Mc,node32_Mc,node33_Mc,node34_Mc,node35_Mc,node36_Mc,node37_Mc,node38_Mc,node39_Mc,node40_Mc,node41_Mc,node42_Mc,node43_Mc,node44_Mc,node45_Mc,node46_Mc,node47_Mc,node48_Mc,node49_Mc,node50_Mc,node51_Mc,node52_Mc,node53_Mc,node54_Mc,node55_Mc,node56_Mc,node57_Mc,node58_Mc,node59_Mc,node60_Mc,node61_Mc,node62_Mc,node63_Mc];
var possibleAdjacentNodeIndex_Arr:Array = [];
initValidNextAdjacentNodeIndexArray();
initValidPreviousAdjacentNodeIndexArray();
savePair_Arr = [];
var startIndex_num:Number = 45;// 0 ;
rand_Num = 62;// randomRange(10, (NUM_COLS * NUM_ROWS) - 1);
getAllChainsFromParamNodeIndexParamParentChain(startIndex_num,[startIndex_num]);
}
function initValidNextAdjacentNodeIndexArray():void
{
for (var index_num = 0; index_num < NUM_COLS*NUM_ROWS; index_num++)
{
validNextAdjacentNodeIndexArray_Arr[index_num] = getValidNodeIndexArrayAdjacentToParamNodeIndex(index_num);
//trace(validNextAdjacentNodeIndexArray_Arr[index_num]);
}
}
function initValidPreviousAdjacentNodeIndexArray():void
{
for (var index_num = 0; index_num < NUM_COLS*NUM_ROWS; index_num++)
{
validPreviousAdjacentNodeIndexArray_Arr[index_num] = getValidNodeIndexArrayAdjacentToParamNodeIndex(index_num);
//trace(validNextAdjacentNodeIndexArray_Arr[index_num]);
}
}
//function getAllChainsFromParamNodeIndexParamParentChain( path_arr:Array,index_param_num:Number ):void
function getAllChainsFromParamNodeIndexParamParentChain( index_param_num:Number, parent_arr:Array ):void
{
var i;
if ( countChain_Num > 15 )
{
return;
}
reinitPath();
var adjacent_arr = getValidNodeIndexArrayAdjacentToParamNodeIndex(index_param_num);
for (i = 0; i < adjacent_arr.length; i++)
{
reinitPath();
if ( ( checkRepeat(chain_Arr)) )
{
continue;
}
chain_Arr.push(adjacent_arr[i]);
//trace("chain starts from", chain_Arr);
var nodeIndex_num:Number = adjacent_arr[i];
var nodeIndexExist_num = 0;
var parentNode_num:Number = parent_arr[parent_arr.length - 1];
var bool:Boolean;
while ( isNaN(nodeIndex_num) == false)
{
//var childNodeIndex_num = manageValidAdjacentIndexArray(parent_arr[parent_arr.length-1], parentNode_num , nodeIndex_num );
var childNodeIndex_num = manageValidAdjacentIndexArray(adjacent_arr[i],parentNode_num,nodeIndex_num);
//var childNodeIndex_num = manageValidAdjacentIndexArray(parentNode_num, parentNode_num , nodeIndex_num );
parentNode_num = nodeIndex_num;
nodeIndex_num = childNodeIndex_num;
if ( ( checkRepeat(chain_Arr)) )
{
break;
}
if ( isNaN(nodeIndex_num) == false)
{
chain_Arr.push(nodeIndex_num);
}
}
if (chain_Arr.length > NUM_COLS * NUM_ROWS - 1)
{
//if ( !( checkRepeat(chain_Arr)) )
{
trace(chain_Arr);
saveParent_Arr[saveParent_Arr.length ] = new Array();
for (var k = 0; k < rand_Num; k++)
{
saveParent_Arr[saveParent_Arr.length - 1].push( chain_Arr[k]);
}
countChain_Num++;
}
}
};
for (i = 0; i < adjacent_arr.length; i++)
{
var arr2:Array = parent_arr.slice();
arr2.push(adjacent_arr[i]);
getAllChainsFromParamNodeIndexParamParentChain( adjacent_arr[i], arr2 );
}
function reinitPath():void
{
chain_Arr = [];
chain_Arr = chain_Arr.concat(parent_arr);
}
}
private function checkRepeat(chain_arr:Array):Boolean
{
var bool:Boolean;
var num = rand_Num;
if (chain_arr.length >= num - 1)
{
for (var i = 0; i < saveParent_Arr.length; i++)
{
//trace( saveParent_Arr[i][0], saveParent_Arr[i][1]);
if (saveParent_Arr[i] != undefined)
{
var z = 0;
for (var j = 0; j < num; j++)
{
if (saveParent_Arr[i][j] == chain_arr[j])
{
z++;
if ( z >= num)
{
bool = true;
break;
}
}
else
{
break;
}
}
if (bool)
{
break;
}
}
}
}
return bool;
}
function randomRange( minNum:Number, maxNum:Number):Number
{
return (Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum);
}
function randomizeArray(arr:Array ):void
{
for (var i = 0; i < arr.length; i++)
{
var random = randomRange(0,arr.length - 1);
var temp = arr[i];
arr[i] = arr[random];
arr[random] = temp;
}
}
//will send NaN if no valid adjacent index array is possible
private function manageValidAdjacentIndexArray(chainStartIndex_num:Number, parentNodeIndex_param_num:Number, nodeIndex_num:Number):Number
{
var num_nan:Number;
var ret_num:Number;
var j;
//var tot:Number = validNextAdjacentNodeIndexArray_Arr[nodeIndex_num].length ;
j = 0;
var arr:Array = validNextAdjacentNodeIndexArray_Arr[nodeIndex_num];// getValidNodeIndexArrayAdjacentToParamNodeIndex(nodeIndex_num) ;
randomizeArray(arr);
while (arr.length > 0 )// && isNaN(ret_num))
{
ret_num = arr[j];//validNextAdjacentNodeIndexArray_Arr[nodeIndex_num][j] ;
//if this index is present in chain then remove it off
if (chain_Arr.indexOf(ret_num) >= 0)
{
//j++ ;
ret_num = num_nan;
}
j++;
if ( j >= arr.length )
{
ret_num = num_nan;
break;
}
}
return ret_num;
}
private function getValidAdjacentIndexToParamNodeIndex(nodeIndex_param_num:Number):Number
{
var adjacentNode_arr:Array = [];
var adjacentRow_arr:Array = [];
var adjacentCol_arr:Array = [];
var allIndex_arr:Array = [];
var r:Number;
var c:Number;
var row_num:Number = int(nodeIndex_param_num / NUM_COLS);
var col_num:Number = (nodeIndex_param_num % NUM_COLS);
allIndex_arr = getAllAdjacentNodeIndexArray(row_num,col_num);
validateIndices(allIndex_arr);
var ret_num:Number;
ret_num = allIndex_arr[0];
return ret_num;
}
function getValidNodeIndexArrayAdjacentToParamNodeIndex(nodeIndex_param_num:Number ):Array
{
var adjacentNode_arr = [];
for (var positionId_num = 0; positionId_num < 8; positionId_num++)
{
var num:Number = getNodeIndexAtParamPositionIdOfParamIndex(positionId_num,nodeIndex_param_num);
if (isNaN(num))
{
}
else
{
adjacentNode_arr.push(num);
}
}
return adjacentNode_arr;
}
private function getAllAdjacentNodeIndexArray(row_num:Number, col_num:Number,within_arr:Array=null):Array
{
var num:Number;
var index_arr:Array = [num,num,num,num,num,num,num,num];
var r:Number;
var c:Number;
if ( row_num > 0 )
{
r = row_num - 1;
c = col_num;
index_arr[0] = r * NUM_COLS + c;
}
if ( col_num < NUM_COLS-1)
{
r = row_num;
c = col_num + 1;
index_arr[1] = r * NUM_COLS + c;
}
if ( row_num < NUM_ROWS-1)
{
r = row_num + 1;
c = col_num;
index_arr[2] = r * NUM_COLS + c;
}
if ( col_num > 0 )
{
r = row_num;
c = col_num - 1;
index_arr[3] = r * NUM_COLS + c;
}
///////////////////////////////////////////
if ( row_num > 0 && col_num > 0)
{
r = row_num - 1;
c = col_num - 1;
index_arr[4] = r * NUM_COLS + c;
}
if ( row_num > 0 && col_num < NUM_COLS-1)
{
r = row_num - 1;
c = col_num + 1;
index_arr[5] = r * NUM_COLS + c;
}
if ( row_num < NUM_ROWS-1 && col_num < NUM_COLS-1)
{
r = row_num + 1;
c = col_num + 1;
index_arr[6] = r * NUM_COLS + c;
}
if ( row_num < NUM_ROWS-1 && col_num > 0 )
{
r = row_num + 1;
c = col_num - 1;
index_arr[7] = r * NUM_COLS + c;
}
return index_arr;
}
//the adjacent node must be present in within_arr, which we splice, one time for variation
function getNodeIndexAtParamPositionIdOfParamIndex( n:Number , nodeIndex_param_num:Number):Number
{
var adjacentNode_arr:Array = [];
var adjacentRow_arr:Array = [];
var adjacentCol_arr:Array = [];
var index_arr:Array = [];
var r:Number;
var c:Number;
var index_num:Number;
var row_num:Number = int(nodeIndex_param_num / NUM_COLS);
var col_num:Number = (nodeIndex_param_num % NUM_COLS);
index_arr = getAllAdjacentNodeIndexArray(row_num,col_num);
validateIndices(index_arr);
var ret_num:Number;
ret_num = index_arr[n];
return ret_num;
}
private function validateIndices(index_arr:Array):void
{
for (var i = 0; i < index_arr.length; i++)
{
if (chain_Arr.indexOf(index_arr[i]) == -1)
{
}
else
{
var num:Number;
index_arr[i] = num;//pushing NaN
}
}
}
}
}
It's a Hamiltonian path problem. It's NP complete and afaik there is still no efficient solution.
check this out: hamilton paths in grid graphs (pdf)
short randomized algorithm explanation: Princeton Hamiltonian path problem
or wikipedia: Hamiltonian path problem
edit:
I did some more research and for your special case of strongly connected square grids (nxn chessboards) a variation of the warndoffs rule for finding a knights tour might give you a solution in near linear time. See here: A method for finding hamiltonian paths and knight tours (pdf)

Why Is My Genetic Algorithm Terrible (Why Doesn't It Converge)?

I wrote a quick experiment with a genetic algorithm. It simply takes a grid of squares and tries to mutate their color to make them all yellow. It fails miserably and I can't seem to figure out why. I've included a link to JSFiddle that demonstrates working code, as well as a copy of the code in its entirety.
http://jsfiddle.net/mankyd/X6x9L/
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<div class="container">
<h1>The randomly flashing squares <i>should</i> be turning yellow</h1>
<div class="row">
<canvas id="input_canvas" width="100" height="100"></canvas>
<canvas id="output_canvas" width="100" height="100"></canvas>
</div>
<div class="row">
<span id="generation"></span>
<span id="best_fitness"></span>
<span id="avg_fitness"></span>
</div>
</div>
</body>
</html>
Note that the below javascript relies on jquery in a few places.
// A bit of code that draws several squares in a canvas
// and then attempts to use a genetic algorithm to slowly
// make those squares all yellow.
// Knobs that can be tweaked
var mutation_rate = 0.1; // how often should we mutate something
var crossover_rate = 0.6; // how often should we crossover two parents
var fitness_influence = 1; // affects the fitness's influence over mutation
var elitism = 1; // how many of the parent's generation to carry over
var num_offspring = 20; // how many spawn's per generation
var use_rank_selection = true; // false == roulette_selection
// Global variables for easy tracking
var children = []; // current generation
var best_spawn = null; // keeps track of our best so far
var best_fitness = null; // keeps track of our best so far
var generation = 0; // global generation counter
var clear_color = 'rgb(0,0,0)';
// used for output
var $gen_span = $('#generation');
var $best_fit = $('#best_fitness');
var $avg_fit = $('#avg_fitness');
var $input_canvas = $('#input_canvas');
var input_ctx = $input_canvas[0].getContext('2d');
var $output_canvas = $('#output_canvas');
var output_ctx = $output_canvas[0].getContext('2d');
// A spawn represents a genome - a collection of colored
// squares.
var Spawn = function(nodes) {
var _fitness = null; // a cache of our fitness
this.nodes = nodes; // the squares that make up our image
this.fitness = function() {
// fitness is simply a function of how close to yellow we are.
// This is defined through euclidian distance. Smaller fitnesses
// are better.
if (_fitness === null) {
_fitness = 0;
for (var i = 0; i < nodes.length; i++) {
_fitness += Math.pow(-nodes[i].color[0], 2) +
Math.pow(255 - nodes[i].color[1], 2) +
Math.pow(255 - nodes[i].color[2], 2);
}
_fitness /= 255*255*3*nodes.length; // divide by the worst possible distance
}
return _fitness;
};
this.mutate = function() {
// reset our cached fitness to unknown
_fitness = null;
var health = this.fitness() * fitness_influence;
var width = $output_canvas[0].width;
var height = $output_canvas[0].height;
for (var i = 0; i < nodes.length; i++) {
// Sometimes (most times) we don't mutate
if (Math.random() > mutation_rate) {
continue;
}
// Mutate the colors.
for (var j = 0; j < 3; j++) {
// colors can move by up to 32 in either direction
nodes[i].color[j] += 64 * (.5 - Math.random()) * health;
// make sure that our colors stay between 0 and 255
nodes[i].color[j] = Math.max(0, Math.min(255, nodes[i].color[j]));
}
}
};
this.draw = function(ctx) {
// This draw function is a little overly generic in that it supports
// arbitrary polygons.
ctx.save();
ctx.fillStyle = clear_color;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
for (var i = 0; i < nodes.length; i++) {
ctx.fillStyle = 'rgba(' + Math.floor(nodes[i].color[0]) + ',' + Math.floor(nodes[i].color[1]) + ',' + Math.floor(nodes[i].color[2]) + ',' + nodes[i].color[3] + ')';
ctx.beginPath();
ctx.moveTo(nodes[i].points[0][0], nodes[i].points[0][1]);
for (var j = 1; j < nodes[i].points.length; j++) {
ctx.lineTo(nodes[i].points[j][0], nodes[i].points[j][1]);
}
ctx.fill();
ctx.closePath();
}
ctx.restore();
};
};
Spawn.from_parents = function(parents) {
// Given two parents, mix them together to get another spawn
var nodes = [];
for (var i = 0; i < parents[0].nodes.length; i++) {
if (Math.random() > 0.5) {
nodes.push($.extend({}, parents[0].nodes[i]));
}
else {
nodes.push($.extend({}, parents[1].nodes[i]));
}
}
var s = new Spawn(nodes);
s.mutate();
return s;
};
Spawn.random = function(width, height) {
// Return a complete random spawn.
var nodes = [];
for (var i = 0; i < width * height; i += 10) {
var n = {
color: [Math.random() * 256, Math.random() * 256, Math.random() * 256, 1],
points: [
[i % width, Math.floor(i / width) * 10],
[(i % width) + 10, Math.floor(i / width) * 10],
[(i % width) + 10, Math.floor(i / width + 1) * 10],
[i % width, Math.floor(i / width + 1) * 10],
]
};
nodes.push(n);
}
return new Spawn(nodes);
};
var select_parents = function(gene_pool) {
if (use_rank_selection) {
return rank_selection(gene_pool);
}
return roulette_selection(gene_pool);
};
var roulette_selection = function(gene_pool) {
var mother = null;
var father = null;
gene_pool = gene_pool.slice(0);
var sum_fitness = 0;
var i = 0;
for (i = 0; i < gene_pool.length; i++) {
sum_fitness += gene_pool[i].fitness();
}
var choose = Math.floor(Math.random() * sum_fitness);
for (i = 0; i < gene_pool.length; i++) {
if (choose <= gene_pool[i].fitness()) {
mother = gene_pool[i];
break;
}
choose -= gene_pool[i].fitness();
}
// now remove the mother and repeat for the father
sum_fitness -= mother.fitness();
gene_pool.splice(i, 1);
choose = Math.floor(Math.random() * sum_fitness);
for (i = 0; i < gene_pool.length; i++) {
if (choose <= gene_pool[i].fitness()) {
father = gene_pool[i];
break;
}
choose -= gene_pool[i].fitness();
}
return [mother, father];
};
var rank_selection = function(gene_pool) {
gene_pool = gene_pool.slice(0);
gene_pool.sort(function(a, b) {
return b.fitness() - a.fitness();
});
var choose_one = function() {
var sum_fitness = (gene_pool.length + 1) * (gene_pool.length / 2);
var choose = Math.floor(Math.random() * sum_fitness);
for (var i = 0; i < gene_pool.length; i++) {
// figure out the sume of the records up to this point. if we exceed
// our chosen spot, we've found our spawn.
if ((i + 1) * (i / 2) >= choose) {
return gene_pool.splice(i, 1)[0];
}
}
return gene_pool.pop(); // last element, if for some reason we get here
};
var mother = choose_one();
var father = choose_one();
return [mother, father];
};
var start = function() {
// Initialize our first generation
var width = $output_canvas[0].width;
var height = $output_canvas[0].height;
generation = 0;
children = [];
for (var j = 0; j < num_offspring; j++) {
children.push(Spawn.random(width, height));
}
// sort by fitness so that our best comes first
children.sort(function(a, b) {
return a.fitness() - b.fitness();
});
best_spawn = children[0];
best_fitness = best_spawn.fitness();
best_spawn.draw(output_ctx);
};
var generate = function(spawn_pool) {
// generate a new set of offspring
var offspring = [];
for (var i = 0; i < num_offspring; i++) {
var parents = select_parents(spawn_pool);
// odds of crossover decrease as we get closer
if (Math.random() * best_fitness < crossover_rate) {
var s = Spawn.from_parents(parents);
}
else {
// quick hack to copy our mother, with possible mutation
var s = Spawn.from_parents([parents[0], parents[0]]);
}
offspring.push(s);
}
// select a number of best from the parent pool (elitism)
for (var i = 0; i < elitism; i++) {
offspring.push(spawn_pool[i]);
}
// sort our offspring by fitness (this includes the parents from elitism). Fittest first.
offspring.sort(function(a, b) {
return a.fitness() - b.fitness();
});
// pick off the number that we want
offspring = offspring.slice(0, num_offspring);
best_spawn = offspring[0];
best_fitness = best_spawn.fitness();
best_spawn.draw(output_ctx);
generation++;
return offspring;
};
var average_fitness = function(generation) {
debugger;
var a = 0;
for (var i = 0; i < generation.length; i++) {
a += generation[i].fitness();
}
return a / generation.length;
};
//Draw yellow and then initialize our first generation
input_ctx.fillStyle = 'yellow';
input_ctx.fillRect(0, 0, input_ctx.canvas.width, input_ctx.canvas.height);
start();
// Our loop function. Use setTimeout to prevent things from freezing
var gen = function() {
children = generate(children);
$gen_span.text('Generation: ' + generation);
$best_fit.text('Best Fitness: ' + best_fitness);
$avg_fit.text('Avg. Fitness: ' + average_fitness(children));
if (generation % 100 === 0) {
console.log('Generation', generation);
console.log('Fitness', best_fitness);
}
setTimeout(gen, 1);
};
gen();​
I've commented the code to try to make parsing it easy. The basic idea is quite simple:
Select 1 or 2 parents from the current generation
Mix those one or two parents together
Mutate the result slightly and add it to the next generation
Select the best few parents (1 in the example) and add them to the next generation
Sort and slice off N results and use them for the next generation (potentially a mix of parents and offspring)
Rinse and repeat
The output never gets anywhere near yellow. It quickly falls into a steady state of a sort that looks awful. Where have I gone wrong?
Solved it. It was in the "from_parents" method:
if (Math.random() > 0.5) {
nodes.push($.extend({}, parents[0].nodes[i]));
}
else {
nodes.push($.extend({}, parents[1].nodes[i]));
}
The $.extend() was doing a shallow copy. The obvious solution was to either put true as the first argument which causes a deep copy. This, however, is incredibly slow performance-wise. The better solution was to remove the $.extend() from that chunk of code entirely and instead to move it up to the mutate() method, where I call $.extend() only if a node is actually about to be changed. In other words, it becomes a copy-on-write.
Also, the color I put in the fitness function was wrong :P

Resources