My title might be terrible, but I can explain exactly what I want here. First of all, I am challenging myself to something very specific. Even if you guess what I'm trying to accomplish, please keep your answers on topic to what I am asking here, and not the overall problem I am solving.
I have a string of 25 generated letters.
I get an index of a letter via str.indexOf(c) and let's say that index is 16.
Visualize that str is a linear representation of a 5x5 table, thus an index of 16 would be the 4th row, 2nd column of that table.
I'm trying to find a way to find the row and column using javascript without looping through like this:
var row = 1;
var index = str.indexOf(c) + 1;
while(index > 5) {
index = index - 5;
row++;
}
With the above code, if index starts as 16, my end result will be row 4, index 2 - which is what I want. It just feels like there should be a way to do this with an algorithm instead of a loop.
Any thoughts?
You are replicating division and modulus in this code sample, you can achieve the same thing by doing row = Math.floor(index / 5) and col = index % 5.
var index = parseInt(prompt('Numeric Index'));
var row = Math.floor(index / 5) + 1;
var col = index % 5;
alert('[ ' + col + ', ' + row + ' ]');
Why not just use maths?
var index = 16;
var columnsPerRow = 5;
row = parseInt(index / columnsPerRow) + 1;
column = index % columnsPerRow + 1;
Instead of using a loop, you can just create a function
function findPosition(index, cols) {
var rowIndex = parseInt(index/cols) + 1;
var colIndex = index % cols;
return {rowIndex: rowIndex, colIndex: colIndex}
}
So in your case string index is 17, and your matrix col is 5
you will be calling findPosition(17, 5) and it will return {rowIndex: 4, colIndex: 2}
try this, you can just use the remainder (or modulus) to find the column and the whole number to find the row.
function getPosition(str, character, gridWidth) {
var index = str.indexOf(character);
var row = Math.floor(index/gridWidth);
var col = index%gridWidth;
return {row:row, col:col};
}
Then use it like this
var str = "hello sir";
console.log(getPosition(str, "i", 3));
You can caluclate the position, this solution depend on Matrix start at 0, 0 not 1, 1 add 1 to row and column if you want to start from index 1:
array = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
number = 16
per_row = 5
index = array.indexOf(number)
row = Math.floor(index/ per_row )
column = index % per_row
Related
I need to concatenate x number of columns per project sometimes it's 3 columns others 7 or 5, it just depends
I am trying to this with an array of range column numbers ex [2,5,3]
columns 2,5 3 in that order with a delimiter here |
I have searched but found only static concatenating functions
I have a VBA Macro that works as I need in Excel so I am trying to write it in Google Script
The function runs without erroring but nothing is posted back
From Logger.log() I am kinda close to the proper structure
I get undefined|b|e|c
I want to post back to the last column + 1
I am not sure this is the best way to do this but it what I have
Any help is appreciated, Thanks
colA ColB ColC ColD ColE ColF ColG ColH
a b cc d e f g b|e|c
a2 b2 d2 e2 f2 g2 e2|c2
ect.
Here is what I have:
function TemplateA_n() {
Template_A("A", [2, 4, 6])
}
function Template_A(SshtName, sArr){
var sSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SshtName);
var sR = sSheet.getDataRange();
var sV = sR.getValues();
var sLC = sSheet.getLastColumn();
var sLR = sSheet.getLastRow();
var a = []
//Rows
for (var row = 2; row < sLR; row++){
a[row] =[]
//Columns
for (var col = 0; col < sArr.length ; col++){
if(sV[row][sArr[col]] !== "") {
if(sV[row][sArr[0]] == "") {
a[row][0] = a[row][0] + sV[row][sArr[col]];
Logger.log(a[row][0])
}
else {
a[row][0] = a[row][0] + "|" + sV[row][sArr[col]];
Logger.log(a[row][0])
}
}
}
}
sSheet.getRange(1,sLC + 1,sLR,1);
}
Here is the Macro
Sub ConCatA()
Dim rng As Range, r As Range, i As Long
On Error Resume Next
Set rng = Application.InputBox("Select column(s)", Type:=8)
'Set rng = Range("B1,A1,C1")
On Error GoTo 0
If rng Is Nothing Then Exit Sub
With ActiveSheet.UsedRange
ReDim a(1 To .Rows.Count, 1 To 1)
a(1, 1) = "Concat"
For i = 2 To .Rows.Count
For Each r In rng
If .Cells(i, r.Column) <> "" Then
a(i, 1) = a(i, 1) & IIf(a(i, 1) = "", "", "|") & .Cells(i, r.Column).value
End If
Next r
Next i
With .Offset(, .Columns.Count).Resize(, 1)
.value = a
End With
End With
End Sub
Part 1: Undefined value in output
The reason you get the following ouput: undefined|b|e|c is because the variable a[row][0] is undefined before you assign it any value. So when program runs the following line of code for the first time in the loop it concats the value of sV[row][sArr[col]]to undefined.
a[row][0] = a[row][0] + sV[row][sArr[col]]
All you need to do is assign an empty value to begin with, like so
for (var row = 2; row < sLR; row++){
a[row] =[]
a[row][0] = ""
... your code here
}
Also, since the assignment of values only start from index 2 in the loop, we need to assign index 0 and 1.
a[0] = [""]
a[1] = [""]
This will enable us to input blank values in the sheet when we use setvalues function with this array.
Part 2: Append values to sheet (lastColumn + 1)You define the range to append your data and then set its values, as follows:
var appRange = Sheet.getRange(2,sLC+1,a.length,1)
appRange.setValues(a)
Your final code would look like this:
function Template_A(SshtName, sArr){
var sSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SshtName);
var sR = sSheet.getDataRange();
var sV = sR.getValues();
var sLC = sSheet.getLastColumn();
var sLR = sSheet.getLastRow();
var a = []
//Rows
a[0]= [""] // Since you are start from index 1, you need to assign a value to index 0
for (var row = 1; row < sLR; row++){ //If you intended to start from 2nd row
// var row = 1 and not 2
a[row] = []
a[row][0] = "" //Empty value for each row
//Columns
for (var col = 0; col < sArr.length ; col++){
if(sV[row][sArr[col]-1] !== "") {
if(a[row][0] == "") { //Check to see if the array is empty
// If yes donot add "|"
a[row][0] = a[row][0] + sV[row][sArr[col]-1];
Logger.log(a[row][0])
}
else {
a[row][0] = a[row][0] + "|" + sV[row][sArr[col]-1];
Logger.log(a[row][0])
}
}
}
}
Logger.log(a)
var appRange = sSheet.getRange(1,sLC+1,a.length,1)
appRange.setValues(a)
}
Final Note: If you intend to skip the first row in your sheet your loop should start with counter 1. Since array index starts from 0 but row numbering in sheet start from 1.
So I'm trying to just make a ".replace" loop, but something mysterious is happening.
var cell = "r1c1";
for (i = 0; i <= 4; i++){
cell = cell.replace(cell[3],i+1);
My expected output:
cell = "r1c1"
cell = "r1c2"
cell = "r1c3"
cell = "r1c4"
cell = "r1c5"
The actual output:
cell = "r1c2"
cell = "r2c1"
cell = "r2c3"
cell = "r2c4"
cell = "r2c5"
As you can see, it runs normal EXCEPT for the second iteration.
What in the world am I doing so wrong?
cell.replace(cell[3], i+1) is going to replace the first instance of the '1' character it finds in the string 'r1c1'. In this case it is the '1' at position 1 that gets replaced instead of the '1' at position 3.
To get the results you want, try
var cell = "r1c1";
for (i = 0; i <= 4; i++){
cell = cell.substring(0, cell.length-1)+(i+1);
}
You can use a regular expression: /^r(\d+)c(\d+)/:
var row_col = 'r1c1';
var match = /^r(\d+)c(\d+)/.exec(row_col); // match string
var row = +match[1]; // extract row
var col = +match[2]; // extract column
// edit row and col as needed
row_col = 'r' + row + 'c' + col; // "reassemble" string
This will take care of bigger row/column numbers than 9. If that is not to be expected, then read about String.prototype.substring():
var row_col = 'r1c1';
var row = +row_col.substring(1,2);
var col = +row_col.substring(3,4)
I don't like this, but the fix I ended up using looks like this:
cell = "r"+(j)+cell.substr(2,2);
Since I'm inevitably answering my own question, here, I still want to ask for comments:
How elegant would you say a solution like this is in the long run?
I'm trying to get a function to loop through data in two tables and put them into an object. The first for-loop that goes through the first table does this fine, but the second for-loop that goes through the second table spits out an empty object when there's only 1 row to go through.
However, when there is more than 1 row, then all of the objects have the values as the second row.
I really don't understand what I'm doing wrong here:
function foo(){
var rows = $('#table1').find('tr');
var array1 = [];
for(var i = 1; i < rows.length-1; i++){
var row = $(rows[i]).find('input');
var o = {
name : $(row[0]).val(),
bday : $(row[1]).val(),
parent : $(row[2]).val(),
parentBDay : $(row[3]).val(),
parentNumber : $(row[4]).val()
};
array1.push(o);
}
var array2 = [];
rows = $('#table2').find('tr');
for(var j = 1; j < rows.length-1; j++){
var row = $(rows[i]).find('input');
var o = {
name : $(row[0]).val(),
bday : $(row[1]).val(),
phoneNumber : $(row[2]).val()
};
console.log('wtf: ' + JSON.stringify(o));
array2.push(o);
}
}
Your problem is that your indexes in the for cycle are not looping well. You start the for cycle from 1, which, in case you want to use the very first element, then it is wrong. Note, that indexing begins from 0. If you miss the very first element on purpose, because it is a header row or something, then this is not a problem. However, the end sign, like
< rows.length - 1
is clearly wrong. Let's suppose, the number of rows is 50. Then its last index is 50 - 1 = 49. If you loop from one until you reach the number below 50 - 1, then your last index will be 48 and you will miss the very last element. Your condition should be either < rows.length or <= rows.length - 1.
I'm working on adding better visualization to data tables that I have so that the highest numbers in that column will have a green CSS background, and the lowest values in that column will have a red CSS background.
I've come pretty far, I am basically down to the last bit. I'm a native PHP dev, so I may be messing up the integer comparison as well as not doing the final jQuery selector correctly. The code selects all the elements in the same column, finds the min and max, calculates the step value, and calculates how many steps above the minimum the current element is. All I need to do now is apply a css class based on the steps. It will be something like values in the 0-5% range will have css group 0, 5-10 will have css group 1, 10-15 group 2, 95-100 group 20. All that css is on the fiddle. I am successfully applying a CSS class, but not to a single cell, it does it for the whole column
$(document).on('click', '#dvData td.color', function() {
var ndx = $(this).index() + 1;
//alert('Val of ndx: ' + ndx);
var thisCol = $('#dvData td:nth-child(' +ndx+ ')');
var arr = thisCol.slice(1, thisCol.length);
var columnDataArr = new Array();
alert("Number of rows: " + arr.length);
//alert("First Row: " + arr[0].innerHTML);
for(var i = 0, x = arr.length; i < x; i++){
columnDataArr[i] = arr[i].innerHTML;
}
var colorsArray = ["63BE7B","72C27B","82C77C","91CB7D","A1D07E","B1D47F","C0D980","D0DD81","DFE282","EFE683","FFEB84","FFDE82","FED280","FDC47D","FDB87B","FCAA78","FB9D75","FB9073","FA8370","F9776E","F8696B"];
var max = Math.max.apply(Math, columnDataArr),
min = Math.min.apply(Math, columnDataArr),
range = max - min,
step_val = range/100;
alert("Step Value:" + step_val);
for(var i = 0, x = arr.length; i < x; i++){
var thisPercentile = parseInt((columnDataArr[i] - min) / step_val);
alert("Percentile:" + thisPercentile);
switch ( thisPercentile ) {
// yes this looks terrible, but i can't seem to get the case to work
// with: case(thisPercentile) <= 5:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
alert("Below 10th Percentile");
break;
case parseInt(90):
alert("90th Percentile");
//arr[2].addClass('group10') // doesn't work
break;
}
}
arr.addClass('group20');
});
So the two issues are how to deal with passing a Range to a switch statement (or giving up and using ifs and else ifs), and what the correct selector is to target the current table cell. I have my code on jsfiddle.
Since your value range goes from 0-100 and your group names go from group0-group20, you can do a bit of math and forego the switch/if statement entirely.
If you get the floor of ( value / 5 ), you will end up with 0 for 0-4, 1 for 5-9, ... 19 for 95-99, 20 for 100.
After getting the floor value, you can concatenate the result with the group name and add the result clsas like below:
Note: You created a vanilla JS array with splice, so you'll need to wrap arr[i] with $( ) to turn it into a jQuery object.
jsfiddle: http://jsfiddle.net/7Luwyyxr/2/
for(var i = 0, x = arr.length; i < x; i++){
var thisPercentile = parseInt((columnDataArr[i] - min) / step_val);
alert("Percentile:" + thisPercentile);
// added this stuff
var gnum = Math.floor( thisPercentile/5 ); // returns 0 for 0-4, 1 for 5-9, ...
//alert("Group Num: " + gnum);
$(arr[i]).addClass('group'+gnum); // appends class to array index
}
This solution will distinguish between 20 different numbers before assigning 2 numbers the same colors. It works with percentile (e.g. the percent of items it is greater than or equal to) to assign a color. The top value would always get the same class, and the lowest would always get the same class. The numbers in between would depend on each other to get a class assigned.
for(var i = 0, x = columnDataArr.length; i < x; i++){
var greaterThan = 0;
var curNum = columnDataArr[i];
for(var j = 0, x = columnDataArr.length; j < x; j++){
if(curNum <= columnDataArr[j]){
greaterThan += 1;
}
}
var percentile = Math.round((greaterThan*100)/columnDataArr.length);
var group = Math.round(percentile/5);
$(arr[i]).addClass('group'+group);
}
And heres a fiddle : http://jsfiddle.net/7Luwyyxr/4/
I am really struggling with this, so maybe some of you have some beautiful hints?
What I want to do
I want to create a table with two columns ("name" and "rating"). I've got 5 rows.
2 of these rows should have a random "rating" between 6-10
2 other rows should have a random "rating" between 1-5
1 row (the last one) should have a random rating between 7-10.
Until this point there is no problem: This is my jsFiddle so far: http://jsfiddle.net/HT89v/2/
BUT:
I want to shuffle the "ratings" of the first 4 rows, so that the first two rows don't always have a very high "rating" and the 3rd and 4th row don't always have a very low "rating". I want to randomly shuffle them.
The 5th row should not be affected. .
I tried to implement the .shuffle plugin in the jsfiddle but I have no idea what I am doing wrong.
function noNorm(){
document.getElementById("rating4").innerHTML = [Math.floor(Math.random()*5) + 6];
for (var i = 0; i <= 1; i++){
document.getElementById("rating"+i).innerHTML = [Math.floor(Math.random()*5) + 6];
};
for (var i = 2; i <= 3; i++){
document.getElementById("rating"+i).innerHTML = [Math.floor(Math.random()*5) + 1];
};
};
noNorm();
jQuery(function($){
$('#rating0', '#rating1', '#rating2', '#rating3').shuffle();
});
(function($){
$.fn.shuffle = function() {
return this.each(function(){
var items = $(this).children().clone(true);
return (items.length) ? $(this).html($.shuffle(items)) : this;
});
}
$.shuffle = function(arr) {
for(var j, x, i = arr.length; i; j = parseInt(Math.random() * i), x = arr[--i], arr[i] = arr[j], arr[j] = x);
return arr;
}
})(jQuery);
Thank you very very much!
There are many solutions to shuffle arrays with javascript.
If I understand your problem correcty, this should do the trick: http://jsfiddle.net/HT89v/6/
I shuffle the whole ratings[] array before giving a value to ratings[4] so that the 5th row is not affected.