Javascript: Adding final element to array after for loop completes - javascript

Explanation of Desired Results
My source is over 30,000 lines of very structured text with incrementing front numbers followed by incrementing back numbers and separated by a colon. Stripping out the non-essentials, I am left with the following sourceArray, truncated for convenience :)
sourceArray = ["001:001", "001:002", "001:003",
"002:001", "002:002",
"003:001", "003:002"];
I am trying to count how many back numbers for each front number and push that to an array. In pseudocode, my final results should look like this:
myArray[totalNumberOf_001_Items, totalNumberOf_002_Items, totalNumberOf_003_Items]
Which in my simple example should give me a final value of:
[3, 2, 2]
Problem and Question
My for loop ends at the end of my data and I am therefore one element short in my array.
How do I make an "extra pass" through the loop or is there another way to get the final element pushed to my array?
var sourceArray = ["001:001", "001:002", "001:003",
"002:001", "002:002",
"003:001", "003:002"
];
var myArray = [];
var frontCounter = 1;
var backCounter = 1;
for (var i = 0; i < sourceArray.length; i++) {
var text = sourceArray[i];
var front = text.substr(0, 3);
front = Number(front);
var back = text.substr(4, 3);
back = Number(back);
if (front == frontCounter) {
backCounter++;
} else {
myArray.push(backCounter - 1);
backCounter = 2;
frontCounter++;
}
}
console.log(myArray); // result [3, 2]

You could use an object like below to keep track of how many times the the piece of text appear, the text would be the keys and as value the number of times they appear. From that you can build you array
var sourceArray = ["001:001", "001:002", "001:003",
"002:001", "002:002",
"003:001", "003:002"];
var frontEncounters = {};
function updateFrontEncounters(frontEncounters, front){
var keys = Object.keys(frontEncounters);
if(keys.indexOf(front) == -1)
{
frontEncounters[front] = 1;
}
else
{
frontEncounters[front] += 1;
}
}
for(var item in sourceArray){
var text = sourceArray[item];
var front = text.substr(0, 3);
var back = text.substr(4, 3);
updateFrontEncounters(frontEncounters, front);
}
var keys = Object.keys(frontEncounters);
var myArr = [];
for(var key in keys)
{
myArr.push(frontEncounters[keys[key]])
}
console.log(myArr);

Use an object to store the "front" numbers along with their count.
for (var i = 0; i < sourceArray.length; i++) {
var num = sourceArray[i].slice(0,3);
counts[num] = counts[num] ? counts[num]+1 : 1;
}
Once done, you can very easily convert that to an array:
var result = Object.keys(counts).map(function (key) {
return counts[key];
});
With ES-2017, it is even easier:
var result = Object.values(counts)
Working Snippet:
var sourceArray = ["001:001", "001:002", "001:003",
"002:001", "002:002",
"003:001", "003:002"];
var counts = {};
for (var i = 0; i < sourceArray.length; i++) {
var num = sourceArray[i].slice(0,3);
counts[num] = counts[num] ? counts[num]+1 : 1;
}
console.log(counts);
var result = Object.keys(counts).map(function (key) {
return counts[key];
});
console.log(result);
// ES-2017
//console.log(Object.values(counts));

Here's an alternative that you can use so that you don't have to go through the entire source of lines (30,000) . Use a while loop so that you can break as soon as you reach a 0; use Map to store the unique number by making the index/frontnumber the key and make its value an object that serves as a counter to keep track of it's total. If the key exists, update the total; if it doesn't, create a new counter object. Then just return the Map by transforming it into the desired array by map'ing it to an array with only the totals.
var sourceArray = ["001:001", "001:002", "001:003",
"002:001", "002:002",
"003:001", "003:002"
];
function getTotal(sourceArray) {
let count = new Map();
let update = item => count.get(item).total++;
let create = item => count.set(item, {total: 1});
const getItem = index => {
let text = sourceArray[index];
return text.substr(0, 3);
}
let index = -1;
let start = 0;
while (index != 0 && start < sourceArray.length) {
index = getItem(start++);
count.has(index) ? update(index) : create(index);
}
return [...count.values()].map(item => item.total);
}
console.log(getTotal(sourceArray));

Related

Google Sheets Random Copy and Sort with Scripts

What I'm attempting to do is copy a column over and re-sort it. Problem is, it captures all available cells and uses the same space to re-sort, causing blank spaces. The idea is to create tournament match pairings, with the first column being the Roster itself, and following columns being players they will be matched against.
I'd also like to add a line that verifies a name doesn't appear twice on the same row, reshuffling until the column is all unique along each row
This is the code I have so far. I attempted to filter the data by swapping
range2.setValues(shuffleArray(range.getValues()));
for
range2.setValues(shuffleArray(range.getValues().filter(String)));
but this results in a "Number of data rows is 10 when range is 41" error, not verbatim obviously. I'm trying to collapse the blank spaces that are shown in this Screenshot.
I'm sure I can figure out how to expand it by however many matches I wish to generate.
function shuffleRange() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SETUP');
var range = sheet.getRange('A31:A')
var range2 = sheet.getRange('C31:C');
range2.clearContents;
range2.setValues(shuffleArray(range.getValues()));
}
function shuffleArray(array) {
var i, j, temp;
for (i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i+1));
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
EDIT::::: Code has been moved to a test sheet hence different name and ranges, ive adjusted the samples when i moved them of course
function shuffleRange() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet4');
var range = sheet.getRange('A1:A40')
var v = range.getValues().filter(String);
//Match 1
var values = shuffleArray1(v);
while (v.length != [...new Set(values.map(([a]) => a))].length) {
values = shuffleArray1(v);
}
range.offset(0, 1, values.length).setValues(values);
//Match 2
var values2 = shuffleArray2(v);
while (v.length != [...new Set(values2.map(([a]) => a))].length) {
values = shuffleArray2(v);
}
range.offset(0, 2, values.length).setValues(values2);
}
function shuffleArray1(array) {
var i, j, temp;
for (i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i+1));
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
function shuffleArray2(array) {
var u, v, temp;
for (u = array.length - 3; u > 0; u--) {
v = Math.floor(Math.random() * (u+2));
temp = array[u];
array[u] = array[v];
array[v] = temp;
}
return array;
}
Modification points:
I think that range2.clearContents might be range2.clearContent().
In your script, by sheet.getRange('A31:A'), all rows in the sheet are retrieved.
When these points are reflected in your script, how about modifying shuffleRange() as follows?
Modified script:
function shuffleRange() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SETUP');
var lastRow = sheet.getLastRow();
var range = sheet.getRange('A31:A' + lastRow);
var range2 = sheet.getRange('C31:C' + lastRow);
range2.clearContent();
var values = shuffleArray(range.getValues()).filter(String);
range.offset(0, 2, values.length).setValues(values);
}
I'm not sure about the last row of your sheet. So, I proposed the above modification.
Added 1:
From your following new question,
essentially if the row contains a duplicate it has to reshuffle until each row contains a unique name from the original column, to create unique match pairings for tournaments, this will check the whole row, as some tournaments run only 2 matches, some up to 21
In this case, how about the following sample script?
Sample script:
function shuffleRange() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SETUP');
var lastRow = sheet.getLastRow();
var range = sheet.getRange('A31:A' + lastRow);
var range2 = sheet.getRange('C31:C' + lastRow);
range2.clearContent();
var v = range.getValues().filter(String);
var values = shuffleArray(v);
while (v.length != [...new Set(values.map(([a]) => a))].length) {
values = shuffleArray(v);
}
range.offset(0, 2, values.length).setValues(values);
}
In this case, when the duplicated values are included in values, shuffleArray function is run again.
Added 2:
From your following reply,
Unfortunately it produced duplicate lines almost immediately once i duplicate the functions to create a second set of results
I added a new sample so you can see how im trying to expand it across several columns of results, this will create a set number of matches. I will, when done, swap the counter for a cell check so a user can set the match number, but thats later
Sample script:
function shuffleRange() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SETUP');
var range = sheet.getRange('A1:A40');
var v = range.getValues().filter(String);
var createValues = v => {
SpreadsheetApp.flush(); // I'm not sure whether this line is required.
var temp = sheet.getRange(1, 1, 40, sheet.getLastColumn()).getValues();
var existValues = temp[0].map((_, c) => temp.map(r => r[c]).join("")).filter(String);
var values;
do {
values = shuffleArray1(v);
while (v.length != [...new Set(values.map(([a]) => a))].length) {
values = shuffleArray1(v);
}
var check = values.map(([a]) => a).join("");
} while (existValues.some(e => e == check));
return values;
}
var values1 = createValues(v);
range.offset(0, 1, values1.length).setValues(values1);
var values2 = createValues(v);
range.offset(0, 2, values2.length).setValues(values2);
}
In this modification, the new column values are created by checking all existing columns.
Adding to Tanaike's suggestion I've joined your two functions in order to be able to re-shuffle. I'm not as well-versed in coding, and probably there's a more-alike version of your code that also enables the re-shuffling. But you can try this:
function shuffleRange() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SETUP');
var lastRow = sheet.getLastRow()
var range = sheet.getRange('A31:A' + lastRow);
var range2 = sheet.getRange('C31:C' + lastRow);
range2.clearContents;
function shuffleArray() {
var i, j, temp;
var array = range.getValues()
var array2 = range.getValues()
var count= 1;
while (count>0){
count=0
for(i=array.length-1;i>0;i--){
j = Math.floor(Math.random() * (i+1));
temp = array2[i];
array2[i] = array2[j];
array2[j] = temp;
}
for(i=0;i<array.length;i++){
if(array[i].toString() == (array2[i]).toString()){count = count+1}
}}
return array2
}
range2.setValues(shuffleArray())
}
I've make it try tenths of times and never got a duplicate:

How to compare an item in an array to all items in other array and add it into shpreadsheet?

I'm developing a tool and currently I'm stuck with a problem.
I'm writing a code in GoogleAppScript (JavaScript) and I have two columns where I collect data. As a result I've two arrays. Let's call them mainArray and checkArray
I need a code doing this logic:
getting the 1st value of the mainArray, i.e. mainArray[0]
chenking the value if it's equal to checkArray[0], then checkArray[1]... checkArray[i]
if there's a match, then toss it to the garbage bin, and swith to the mainArray[1]
Checking mainArray[1] with all of the values from checkArray, as we did it in p.2
If there's no match with any vals from the checkArray add these value into the 3rd array (finArray)
I've done exaclty the opposite.
for (var j=0; j<checkArr.length; j++) {
for(var i=0; i<mainArr.length; i++) {
if(mainArr[i][0]!==''){
if(checkArr[j][0]==mainArr[i][0])
{
Logger.log('not in the list'+mainArr[i][0])
finArr.push(mainArr[i][0])
break;
}}
But I don't know how to get the code working as I described above.
`
// The Arrays actually are one dimensional
// I prefer to create a one dimensional array
// GetDataArray function creates one dimensional array
function GetDataArray(sht,rng) {
var Data = [] var i = 0;
Logger.log(' Sht Name %s\n rng %s,', sht.getName(), rng)
sht.getRange(rng).getValues() .forEach(
function (row) {
row.forEach( function (cell) {
//Logger.log(cell);
Data[i++] = cell }); } );
return Data
} //
......
var sht = SpreadsheetApp.getActiveSheet()
var rngMain = ....// Provide the range
var rngCheck = ...
var checkArr = GetDataArray(sht, rngCheck)
var mainArr = GetDataArray(sht, rngMain)
var finArr = []
mainArr.forEach( function(cell) {
if (cell == '') continue
if (checkArr.indexOf(cell) != -1) finArr.push(cell)})
function thefunc() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet1');
let vs1 = sh.getRange(1,1,sh.getLastRow(),1).getValues().flat();
let vs2 = sh.getRange(1,2,sh.getLastRow(),1).getValues().flat();
let d = 0;
vs1.forEach((r,i) => {
if(~vs2.indexOf(r[0])) {
sh.deleteRow(i+1-d++);//delete row
}
});
}

Is there a way to store index of an array and get its value back?

here is the task: I am trying to build an anagram finder which receives a given string (simple word) from an input, then check in a dictionary (of 350k words) and then return all the anagrams of that word. Here is where I got so far:
const button = document.getElementById("findButton");
button.addEventListener("click", function() {
let typedText = document.getElementById("input").value;
let wordIn = typedText.toLowerCase().split("").sort().join("").trim();
let output = [];
for (let i = 0; i < dictionary.length; i++) {
if (dictionary[i].length === wordIn.length)
output.push(dictionary[i])
}
let sorted = [];
let result = [];
for (let i = 0; i < output.length; i++) {
sorted.push(output[i].toLowerCase().split("").sort().join("").trim())
if (wordIn === sorted[i]) {
result.push(sorted[i])
}
}
});
With the current approach I get as final output an array with words alphabeticaly sorted i.e.: cat ("act", "act", "act"...) , but I need them as they are in the dictionary. I was thinking if I could store the index of the words in the output array (which already reduces from 350k to only those with same length) and after temporary sort them by their alphabetic value, I could get only those which match and return them as a new array. i.e cat = "cat", "act", "tac".
You can use filter:
const
// Once, at the top of your script:
normalizeWord = w => w.toLowerCase().split("").sort().join("").trim(),
dictionary = ['foo', 'act', 'bar', 'cat', 'example', 'word'],
sortedDict = dictionary.map(normalizeWord),
// In your event handler, or its own named function:
wordIn = normalizeWord('tac'),
result = dictionary.filter((_, i) => sortedDict[i] === wordIn);
console.log(result);
You can first of all increase your performance by getting the desired result in the first loop itself, thus no requirement of extra array and extra loop saving space and time.
const button = document.getElementById("findButton");
button.addEventListener("click", function() {
let typedText = document.getElementById("input").value;
let wordIn = typedText.toLowerCase().split("").sort().join("").trim();
let output = []; //it will have your final anagram list
for (let i = 0; i < dictionary.length; i++) {
let sortedWord = dictionary[i].toLowerCase().split("").sort().join("").trim();
if (sortedWord === wordIn) {
output.push(dictionary[i]);
}
}
});
Instead of result.push(sorted[i])
You should result.push(output[i]) instead.

Javascript, comparing two arrays in order while skipping non-matching indexes

I have two arrays:
var arr1 = [1,2,3,4,5]
var arr2 = [7,1,8,2,12,3,4,28,5]
I need to go through arr2 looking for matches to arr1, but it has to be in order (1,2,3,4,5). As you can see in arr2, the order does exists, but there are some numbers in between.
[7,1,8,2,12,3,4,28,5]
I have about 50 arrays similar to arr2, so I need to look through each one, and when I find a match, push it out to a "results" object. Small issue though is that some arrays will not have the entire match, may only have 1,2,3 or any variation of the search. Also, if the array I'm searching in is NOT in order, (IE: starts at 2,3,4) skip over it entirely.
The idea is to loop through these arrays, and when I find a match, add a count to the results array.
For example, using arr1 as the search, go through these arrays:
[7,1,8,2,12,3,4,28,5],
[7,1,8,2,12,3,4],
[7,8,1,2],
[1,2,3]
and have a result that looks like this (a dictionary of what was searched for, and a count of what was found) :
{1:4, 2:4, 3:3, 4:2, 5:1}
I tried doing a bunch of for-loops, but I can't figure out how to skip over a number that I'm not looking for, and continue onto the next iteration, while saving the results into a dictionary object.
let list = [[7,1,8,2,12,3,4,28,5], [7,1,8,2,12,3,4], [7,8,1,2], [1,2,3]];
let search = [1, 2, 3, 4, 5];
// Initialize result with zeros:
let result = search.reduce((result, next) => {
result[next] = 0;
return result;
}, {});
// Increment result for items found:
list.forEach(array => {
for (let i = 0, j = 0; i < array.length && j < search.length; ++i) {
if (array[i] == search[j]) {
++result[search[j]];
++j;
}
}
});
console.log(result);
Essentially this:
var needle = [1,2,3,4,5]
var collection = [[7,1,8,2,12,3,4,28,5], [7,1,8,2,12,3,4], [7,8,1,2], [1,2,3]]
// start with an object
var results = {}
// populate object with zeros
needle.forEach(function (i) { results[i] = 0 })
// define an index to iterate through collection
var i = 0
// define an index to conditionally iterate through "arr1"
var j = 0
// define an index to iterate through collection arrays
var k = 0
// define surrogate for the arrays in the collection
var arr
while (i < collection.length) {
// get collection array
arr = collection[i]
// reset the indices
j = 0
k = 0
while (k < arr.length) {
// if same element on needle is in a collection array
if (needle[j] === arr[k]) {
// save it in an object starting at 1
results[needle[j]]++
j++ // increment needle
}
k++ // increment array in collection
}
i++ // increment collection
}
console.log(results) // {1:4, 2:4, 3:3, 4:2, 5:1}
I hope that helps!
var arr1 = [1,2,3,4,5];
var arr2 = [7,1,8,2,12,3,4,28,5];
function givenTwoArrays(a,b, obj){
var obj = obj || {};
var cond = true;
function otherMatch(indexFound,elementFound){
var indexOnA = a.indexOf(elementFound);
return a.some(function(ele, idx){
if(idx > indexOnA)
return b.some(function(bele,bidx){
return ele == bele && bidx < indexFound;
});
});
}
a.map(function(aele,idx){
if(cond){
var indexFound = b.findIndex(function(bele){
return aele == bele;
});
if(typeof indexFound !== 'undefined'){
if(!otherMatch(indexFound,aele)){
if(typeof obj[aele] !== 'undefined')
obj[aele]++;
else{
obj[aele] = 1;
}
} else {
cond = false;
}
}else
cond = false;
}
});
return obj;
}
console.log("first pass");
console.log(givenTwoArrays(arr1,arr2))
console.log("second pass");
console.log(givenTwoArrays(arr1,arr2,{
"1": 1,
"2": 1,
"3": 1,
"4": 1,
"5": 1
}));
I think this will work, just need to add a little recursion!
var orign = [1,2,3,4,5];
var arr = [[7,1,8,2,12,3,4,28,5], [7,1,8,2,12,3,4], [7,8,1,2], [1,2,3]];
//temp result
var arrTmp = [];
for (var x in arr){
var match = 0;
var mis = 1;
var curIndex = 0;
var cur = orign[curIndex];
var arrTmpX = [];
for(var y in arr[x]){
if(arr[x][y] !== cur){
mis=1;
}else{
//add match after mismatch
arrTmpX.push(cur);
curIndex++
cur = orign[curIndex];
}
}
arrTmp.push(arrTmpX);
}
//calc result
var result = {};
for (var x in orign){
result[orign[x]] = 0;
for(var y in arrTmp){
if(arrTmp[y].length>x)result[orign[x]]++;
}
}
console.log(result);
this works

Display number if each item in JavaScript array

I am trying to display the contents of an array in a more readable way, this is the array:
["malevolent", "pariah", "judicious", "pariah", "judicious"]
I'm simply adding that array to an HTML element to display it, but I want to display it like this:
malevolent, pariah x 2, judicious x2
How would I do this?
It's quite simple actually:
var myArray = new Array("a", "b", "c", "b", "a");
var newObject = {};
// Iterate over the array
for(var i = 0; i < myArray.length; i++){
// If the new object already contains the key (e.g. a, b, or c), increment value by one
if(myArray[i] in newObject){
newObject[myArray[i]]++;
} else {
// Otherwise add a key (e.g. a, b, or c) to the object and assign 1 to it (first occurence)
newObject[myArray[i]] = 1;
}
}
// Write the resulting object to console
window.console && console.log(newObject);
newObject contains a list of keys (a,b,c) and values (number of occurrences of each key). You can than use that data to output it in any format you like, but that's left up to you as an excercise.
You can try the following:
var myArray = ["malevolent", "pariah", "judicious", "pariah", "judicious"];
var resultArray = [];
var countArray = [];
for(index in myArray) {
var element = myArray[index];
var isInArray = resultArray.indexOf(element);
if(isInArray !== -1) {
var tmpCnt = countArray[isInArray];
tmpCnt++;
countArray[isInArray] = tmpCnt;
} else {
resultArray.push(element);
countArray.push(1);
}
}
console.log(resultArray);
console.log(countArray);
Felix Kling provided a Link to an answer on how to count your elements. I just shamelessly use the reduce method described there and then just iterate over the object to build a string.
var a = ["malevolent", "pariah", "judicious", "pariah", "judicious"].reduce(function (acc, curr) {
if (typeof acc[curr] == 'undefined') {
acc[curr] = 1;
} else {
acc[curr] += 1;
}
return acc;
}, {});
var out = "";
for (var k in a) {
out += k + " x " + a[k] + "; ";
}
console.log(out);
try this
var arr = new Array("malevolent", "pariah", "judicious", "pariah", "judicious");
var new_arr1 = new Array(); // for containing distinct values like pariah
var new_arr2 = new Array(); // for containing distinct values count like 2 for pariah
// both will be on same index in new_arr1 and new_arr2
for(var i=0; i<arr.length; i++)
{
// fetch every value of arr
var indx = $.inArray(arr[i], new_arr1); // check value is exists in new_arr1 and get index
if(indx > -1) // values exists in new_arr1
{
var v = new_arr2[indx]+1; // increment the previous count +1
new_arr2[indx] = v; // update it on the index of new_arr2
}
else
{
// if value not in new_arr1 means the value comes first time
var l = new_arr1.length;
new_arr1[l] = arr[i]; // insert value in new_arr1
new_arr2[l] = 1; // initate count 1 for the same index of new value in new_arr2
}
}
// now new_arr1 will contains the distinct values
// and new_arr2 contains the count for distinct values
// eg new_arr1[0] = 'malevolent';
// new_arr2[0] = 1;
// new_arr1[1] = 'pariah';
// new_arr2[1] = 2;
// now you can fetch distinct values and their count like given below
for(var i=0; i<new_arr1.length; i++)
{
var str = new_arr1[i]+" X "+new_arr2[i];
alert(str);
}
See FIDDLE

Categories

Resources