The code below is not giving me the expected result.
It's to compare rows from two ranges and, although the second range's last row equals the one from the first range, it gives me false as the result.
var entryValuesCom = sheet.getRange(7, 1, LastRowSource, 9).getValues();
var dbDataCom = dbSheet.getRange(2, 1, dbSheet.getLastRow(), 9).getValues();
var entryVlArray = new Array();
var dbArray = new Array();
for (var r = 0; r < entryValuesCom.length; r++) {
if (entryValuesCom[r][0] != '' && entryValuesCom[r][5] != 'Daily Ledger Bal') {
entryVlArray.push(entryValuesCom[r]);
}
}
for (var a = 0; a < dbDataCom.length; a++) {
if (dbDataCom[a][1] != '' && dbDataCom[a][8] == bank) {
dbArray.push(dbDataCom[a]);
}
}
var duplicate = false;
loop1:
for (var x = 0; x < entryVlArray.length; x++) {
loop2:
for (var j = 0; j < dbArray.length; j++) {
if (JSON.stringify(entryVlArray) == JSON.stringify(dbArray)) {
duplicate = true;
break loop1;
}
}
}
Here's a snapshot of how the array is coming:
I've tried it using .join(), but still...
This is for thousands of rows, so is this going to do well performance wise?
I believe your goal as follows.
You want to compare the arrays of entryVlArray and dbArray using Google Apps Script.
When the duplicated rows are existing between entryVlArray and dbArray, you want to output duplicate = true.
Modification points:
When your script is modified, at if (JSON.stringify(entryVlArray) == JSON.stringify(dbArray)) {, all 2 dimensional arrays are compared. I think that this might be the reason of your issue. From your script, I think that it is required to compare each element in the 2 dimensional array.
When above points are reflected to your script, it becomes as follows.
Modified script:
From:
var duplicate = false;
loop1:
for (var x = 0; x < entryVlArray.length; x++) {
loop2:
for (var j = 0; j < dbArray.length; j++) {
if (JSON.stringify(entryVlArray) == JSON.stringify(dbArray)) {
duplicate = true;
break loop1;
}
}
}
To:
var duplicate = false;
for (var x = 0; x < entryVlArray.length; x++) {
for (var j = 0; j < dbArray.length; j++) {
if (JSON.stringify(entryVlArray[x]) == JSON.stringify(dbArray[j])) {
duplicate = true;
break;
}
}
}
console.log(duplicate)
By this modification, when each element (1 dimensional array) in the 2 dimensional array is the same, duplicate becomes true.
Note:
As other method, when an object for searching each row value is prepared, I think that the process cost might be able to be reduced a little. In this case, the script is as follows. Please modify as follows.
From:
var duplicate = false;
loop1:
for (var x = 0; x < entryVlArray.length; x++) {
loop2:
for (var j = 0; j < dbArray.length; j++) {
if (JSON.stringify(entryVlArray) == JSON.stringify(dbArray)) {
duplicate = true;
break loop1;
}
}
}
To:
var obj = entryVlArray.reduce((o, e) => Object.assign(o, {[JSON.stringify(e)]: true}), {});
var duplicate = dbArray.some(e => obj[JSON.stringify(e)]);
References:
reduce()
some()
Added:
About your following 2nd question,
AMAZING!!!! Would there be a way of capturing these duplicates in a pop up, using reduce() and some()?
When you want to retrieve the duplicated rows, how about the following script? In this case, I thought that filter() is useful instead of some().
Modified script:
var obj = entryVlArray.reduce((o, e) => Object.assign(o, {[JSON.stringify(e)]: true}), {});
// var duplicate = dbArray.some(e => obj[JSON.stringify(e)]);
var duplicatedRows = dbArray.filter(e => obj[JSON.stringify(e)]);
console.log(duplicatedRows)
In this modified script, you can see the duplicated rows at the log.
About a pop up you expected, if you want to open a dialog including the duplicated rows, how about adding the following script after the line of var duplicatedRows = dbArray.filter(e => obj[JSON.stringify(e)]);?
Browser.msgBox(JSON.stringify(duplicatedRows));
Related
I have a string "ABCDEFGHIJKLMN" that I need to shuffle in a specific manner. To do that, I write the characters sequentially in columns bottom -> top and then left -> right (4 chars per column for example) until all characters are done. If the last column is not complete, then the empty spaces need to be on the bottom (this is very important). Like so:
D H L N
C G K M
B F J
A E I
The shuffle is accomplished by producing a new string reading the block of letters as we read text, in rows left -> right:
"DHLNCGKMBFJAEI"
The cases where the columns are not complete (word.size % column_height !=0) complicate things considerably.
I came up with a few solutions, but I'm not sure if there is a simpler (ie, shorter OR easier to read) and more elegant way of coding this problem. My solutions either have an ugly, separate block of code to handle the final incomplete column or seem way too complicated.
My question is, could it be done better?
If you don't want any spoilers and decide to try and figure it out for yourself, stop reading now. If you want to work from what I fiddled so far, then a working piece of code is
var result = "";
var str = "ABCDEFGHIJKLMN";
var nr_rows = 4;
var current_row = 4;
var columns = Math.floor(str.length / nr_rows);
var modulus_table = str.length % nr_rows;
var modulus_position = -1;
for (var i = 0; i < nr_rows; i++) {
for (var j = 0; j < columns; j++) {
result += str[current_row + j * nr_rows - 1];
}
if (modulus_table > 0) {
result += str[str.length + modulus_position];
modulus_table--;
modulus_position--;
}
current_row--;
}
console.log(result);
Moving on to arrays, the next example would loop through each character, placing it correctly in a matrix-like array, but it doesn't work. The array needs to be created another way. For another example of this issue, see How to create empty 2d array in javascript?. This would also need an ugly hack to fix the last characters on the last incomplete column aligning to the bottom instead of the top.
var result = [[],[]];
var str = "ABCDEFGHIJKLMN";
var nr_rows = 4;
var row = nr_rows - 1;
var column = 0;
for (var i = 0; i < str.length; i++) {
result[row][column] = str[i];
row--;
if (row < 0) {
row = nr_rows;
column++;
}
}
console.log(result);
This last method goes full matrix array, but it quickly becomes complicated, since it needs to loop through the array in 3 different directions. First, create a dummy array with the characters in the wrong place, but where the 'undefined' positions correspond to those that should be left empty. That is acomplished by populating the array 'rotated 90º' from the reading orientation.
Without this first step, the empty positions would be stacked at the bottom instead of the top.
A second pass is required to re-write the caracters in the correct places, skipping any holes in the matrix using the 'undefined' value. This check is made for every position and there is no separate block of code to handle an incomplete last line.
A third pass then reads every character in order to form the final shuffled string. All this seems way too complicated and confusing.
// matrix populated top->bottom and left->right
// with the characters in the wrong place
// but the undefined postions in the correct place of the empty positions
var matrix = [];
var str = "ABCDEFGHIJKLMN";
var rows = 4;
var columns = Math.ceil(str.length / rows);
var k = 0;
for (var i = 0; i < rows; i++) {
matrix[i] = [];
for (var j = columns - 1; j >= 0; j--) {
matrix[i][j] = str[k];
k++;
}
}
// populate the matrix with the chars in the correct place and the 'undefined' positions left empty
var k = 0;
for (var i = 0; i < rows; i++) {
for (var j = 0; j < columns; j++) {
if (matrix[i][j] != undefined) {
matrix[i][j] = str[k];
k++;
}
}
}
// read matrix in correct direction and send to string, skipping empty positions
var result = "";
for (var j = columns - 1; j >= 0; j--) {
for (var i = 0; i < rows; i++) {
if (matrix[i][j] != undefined) {
result += matrix[i][j];
}
}
}
console.log(result);
What if you just split/reverse the array into column groups, and convert to rows?
const result = str.match(/.{1,4}/g) // split string into groups of 4
.map(i => i.split('').reverse()) // reverse each group (bottom to top, and solves the last col issue)
.reduce((res, col) => { // reduce the groups into rows
col.forEach((c, i) => res[i] += c) // concat each string char to the right row
return res
}, ['','','','']) // initialise empty strings per row
.join('') // join the rows up
Fiddle here
If you wish to return a string, I don't see why any intermediate result should use an array when it doesn't have to. The following could use one less array, but it's convenient to use split and an array to control the while loop rather than mutate the string.
The idea is to fill the strings from the bottom up until the column is full, then keep adding from the bottom of each column until it runs out of characters to assign. The row to start filling from is based on how many characters are left and how many rows there are.
Rather than building strings, it could build arrays but then generating a string requires multiple joins.
It can also produce results where there are insufficient slots for all the characters, so a result using 9 characters from 10 or more using a 3x3 "matrix" (see last example).
function verticalShuffle(s, rows, cols) {
var result = [''];
s = s.split('');
while (s.length && result[0].length < cols) {
for (var i = (rows < s.length? rows : s.length) -1 ; i>=0; i--) {
if (!result[i]) result[i] = '';
result[i] += s.splice(0,1)[0] || '';
}
}
return result.join('');
}
var s = 'ABCDEFGHIJKLMN';
console.log(verticalShuffle(s, 4, 4)); // DHLNCGKMBFJAEI
console.log(verticalShuffle(s, 6, 3)); // FLNEKMDJCIBHAG
// Only use 9 characters
console.log(verticalShuffle(s, 3, 3)); // CFIBEHADG
This uses plain ed3 functionality that will run in any browser. I don't see the point of restricting it to ECMAScript 2015 or later hosts.
If interpret Question correctly, you can use for loop, String.prototype.slice() to to populate arrays with characters of string. Use Array.prototype.pop() within recursive function to get last element of array until each array .length is 0.
To create array
[
["D","H","L","N"],
["C","G","K","M"],
["B","F","J"],
["A","E","I"]
]
from vertically inverted string you can use for loop, String.prototype.slice() to set array of arrays containing elements having .length 4, or 3 once .length of parent array is 2, having been set with two arrays containing four elements
var str = "ABCDEFGHIJKLMN";
function fnVerticalInvert(str, arr, res) {
if (!arr && !res) {
arr = []; res = "";
}
if (str) {
for (var i = 0; i < str.length; i += 4) {
arr.push([].slice.call(str.slice(i, i + 4)));
}
}
for (var i = 0; i < arr.length; i++) {
if (arr[i].length) {
res += arr[i].pop()
}
}
if (arr.some(function(curr) {return curr.length}))
return fnVerticalInvert(null, arr, res);
for (var i = 0, l = 4, j = 0, n = l - 1, k; i < l; i++, j += l) {
if (i === l / 2) k = j;
arr[i] = [].slice.call(res.slice(!k ? j : k, !k ? j + l : k + n));
if (k) k += n;
}
return {str: res, arr:arr};
};
var res = fnVerticalInvert(str);
console.log(res);
I'm writing a simple snakes game using JavaScript and HTML5 canvas.
I have a Multidimensional array that hold snake block like this:
snake=[[1,1],[1,2]];
and set it on arrayMap using (snake.indexOf([i],[j])!=-1) then draw arrayMap on canvas.
for (var i = 0; i < blocksHeightCount; i++) {
for (var j = 0; j < blocksWidthCount; j++) {
if ((snake.indexOf(i,j)!=-1)||
(walls.indexOf(i,j)!=-1)||
(foods.indexOf(i,j)!=-1)) {
arrayMap[i][j]=1;
} else {
arrayMap[i][j]=0;
}
}
}
for (var i = 0; i < blocksHeightCount; i++) {
for (var j = 0; j < blocksWidthCount; j++) {
Block = arrayMap[i][j];
if (Block!=0){
ctx.fillStyle = (Block != 9) ? colors[Block]
: "#bdc3c7";
ctx.fillRect(j * cubeWidth, i * cubeHeight
, cubeWidth-.4,cubeHeight-.4);
}
}
}
the problem is indexOf isn't working when I set array on it!
It works fine when I set indexOf("i,j") but i need it to be array.
please help, thx
First solution : using Array.map
Each element of your arrays snake, walls and foods is an array with 2 elements. So to check if an [x,y] exists in one of the arrays you need a simple way to
compare two arrays [x1, y1] and [x2, y2]. Comparing arrays directly using the operator == will compare their references and not values (Thanks #Elena for the remarque). A way to compare values
would be to affect a hash to each array and compare hashes. By hash I mean a number which is unique for each array of type [x, y]. That could be x * blocksWidthCount + y
in your case and the code will be :
function getHash(x, y){
return x * blocksWidthCount + y;
}
var blockHashes = snake.concat(walls).concat(foods).map(function(cell) {
return getHash(cell[0], cell[1]);
}); // hashes of all blocks in one array
for (var i = 0; i < blocksHeightCount; i++) {
for (var j = 0; j < blocksWidthCount; j++) {
if (blockHashes.indexOf(getHash(i, j)) != -1) {
arrayMap[i][j]=1;
} else {
arrayMap[i][j]=0;
}
}
}
Second Solution Changing the way we see things
Instead of looping over all cells and verifying if every single cell is a block which gives a complexity of O(N * M) (N number of cells and M number of blocks).
We can do better simply by supposing that there is no block and then loop over blocks and mark them as blocks which is in O(N + M) !
function markBlock(cell){
arrayMap[cell[0]][cell[1]] = 1;
}
for (var i = 0; i < blocksHeightCount; i++)
for (var j = 0; j < blocksWidthCount; j++)
arrayMap[i][j] = 0;
snake.forEach(markBlock);
walls.forEach(markBlock);
foods.forEach(markBlock);
I am confused about how to iterate on multiple values.
for example : values.categories[0].num[0].entry[0].label;
Do I need to write three for loops in order to iterate through categories, num and entry.
Because categories[0] will always identify the 1st position, but am looking for generic categories[i].
Can you please help me out whether to write three for loops or better option is there to achieve.?
This is what I have tried:
var result = [];
for (var i = 0; i < categories.length; i++) {
var abc = categories[i].num;
for (var j = 0; j < abc.length; j++){
var def = num[i].entry;
}
for(var k = 0; k < def.length; k++){
var ghi = entry[i].label;
result.push(ghi)
console.log(result);
}
}
you can use the each function of jquery.
$.each(categories, function(ci, num) {
// This set the index of the array in ci and the value in num = categories[ci]
$.each(num, function(ni, entry) {
// etc ...
});
});
if you want it to stop the iteration you can return false inside the callback function.
Hello there am trying to save news tweets into three different array which are dynamically created.
am finding trouble when i want to get the text from each one of those array and make another request to twitter.
news_tweets("reuters","1652541",3);
function news_tweets(query, user_id,count) {
news_array = [];
$.getJSON("https://api.twitter.com/1/statuses/user_timeline.json?include_entities=true&include_rts=false&user_id=" + user_id + "&count="+count+
"&callback=?",
function (data) {
for (var i = 0; i < count; i++) {
var user = data[i].user.name;
var date = data[i].created_at;
var profile_img = data[i].user.profile_image_url;
var text = data[i].text;
var url = (data[i].entities.urls.length > 0 ? data[i].entities.urls[0].url : '');
news_array[i] = [{user:user,date:date,profile_img:profile_img,text:text,url:url}];
}
for (var i = 0; i < news_array.length; i++) {
for (var x=0; x<i.length; x++){
console.log(news_array[i][x].user);
}
}
});
}
It doesn't show anything on the console.log.
thanks for the help!!!!!
First, make sure that your count is smaller than the data array's length, otherwise this could lead to some undefined values:
for (var i = 0; i < count && i < data.length; i++) …
Then, why are you creating all those one-element-arrays in the news_array? Just use only objects.
This would solve your actual issue: You are looping wrong over those inner arrays. The correct code would be
for (var i = 0; i < news_array.length; i++) {
for (var x = 0; x < news_array[i].length; x++){
console.log(news_array[i][x].user);
}
}
Also, you should indent your code properly. You have some odd braces around, which don't make the code readable.
The problem is the x<i.length in the for loop near the end. i is a number, so it doesn't have a length. You probably meant x < news_array[i].length.
You may try the following:
Use the push method to append elements / data in your array new_array
Use only 1 loop for to display the user value on console
So your code will be something like this:
news_tweets("reuters","1652541",3);
function news_tweets(query, user_id,count) {
news_array = [];
$.getJSON("https://api.twitter.com/1/statuses/user_timeline.json?include_entities=true&include_rts=false&user_id=" + user_id + "&count="+count+
"&callback=?",
function (data) {
for (var i = 0; i < count; i++) {
var user = data[i].user.name;
var date = data[i].created_at;
var profile_img = data[i].user.profile_image_url;
var text = data[i].text;
var url = (data[i].entities.urls.length > 0 ? data[i].entities.urls[0].url : '');
// Pushing your elements in your array, 1 by 1
news_array.push({user:user,date:date,profile_img:profile_img,text:text,url:url});
}
// Here you only need 1 loop!
for (var i = 0; i < news_array.length; i++) {
console.log(news_array[i][x].user);
}
});
}
First thing is i would loop the first one till data.length rather than count because its an api and it "might" or "might not" return all the data. So it will be fool proof to loop till data.length
And your problem is with i.length
for (var i = 0; i < news_array.length; i++) {
console.log(news_array[i].user);
}
this should work. not sure why you had to loop through a loop.
This question already has answers here:
Closed 11 years ago.
Possible Duplicates:
Simplest code for array intersection in javascript
How to merge two arrays in Javascript
There are three arrays:
var items = Array(523,3452,334,31,5346);
var items_used = Array(3452,31,4123);
var items_new = Array();
First one is general, second is the items currenly in use. Third one includes all the items from the first array, witch are not mentioned in second.
How do I remove from the first array items, witch are used in second, and write the result to the third array?
We should get items_new = Array(523, 334, 5346). 3452 and 31 are removed, because they are mentioned in second array.
You could do this:
var items = Array(523,3452,334,31,5346);
var items_used = Array(3452,31,4123);
var items_compared = Array();
$.each(items, function(i, val){
if($.inArray(val, items_used) < 0)
items_compared.push(val);
});
That's it
Why not a simple for loop?
for(var j = 0; j < items.length; j++)
{
var found = false;
for(var k = 0; k < items_used.length; k++)
{
if(items_used[k] == items[j])
{
found = true;
break;
}
}
if(!found)
items_compared.push(items[j]);
}
As a faster solution maybe :
var j, itemsHash = {};
for (j = 0; j < items.length; j++) {
itemsHash[items[j]] = true;
}
for (j = 0; j < itemsUsed.length; j++) {
itemsHash[itemsUsed[j]] = false;
}
for (j in itemsHash) {
if (itemsHash[j]) {
itemsCompared.push(j);
}
}
runs in O(n) time, with a little more memory.
Basically I would make the third have all elements in the first, then loop through the second array removing all of those elements found in the first.
var items_compared = items;
for(int i = 0; i < items_used.length; ++i)
{
var indx = $.inArray(items_used[i], items_compared);
if(indx != -1)
items_compared.splice(indx, 1);
}