Google Sheets Random Copy and Sort with Scripts - javascript

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:

Related

How to loop through all columns in spreadsheet?

I have a function that change the background color of cells.
Function works well and do the thing I want, but I met one problem that I don't really know how to solve.
I want this function to loop through all used columns in spreadsheet. (for now it is from G till TP column will increase)
As you can see the function I have now do the thing only with G column.
How to make it loop till the last used column?
function insertColor2() {
const sheetName = "結果1"; // set the sheet name.
// 1. Retrieve values from sheet.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(sheetName);
const values1 = sheet.getRange(3, 7, sheet.getLastRow() - 2,1).getValues();
const values = [];
values.push(values1);
// 2. Create an array for modifying the background colors.
const backgroundColors = values.map(([,,c,,,...g]) =>
g.reduce((o, e) => {
if (e.toString() != "") {
o.total += e;
o.colors.push(c >= o.total ? null : "red");
} else {
o.colors.push(null);
}
return o;
}, {colors: [], total: 0}).colors
);
const flatten = [].concat.apply([], backgroundColors);
const newArr = [];
while(flatten.length) newArr.push(flatten.splice(0,1));
Logger.log(newArr);
// 3. Modify the background colors of cells.
sheet.getRange(8, 7, newArr.length, 1).setBackgrounds(newArr);
}
So I found a solution to solve this problem. It is not the best solution but works well. I just simply transposed array twice at the beginning and before inputting result to the sheet.
function transpose(a) {
return Object.keys(a[0]).map(function(c) {
return a.map(function(r) { return r[c]; });
});
}
function transpose1(original) {
var copy = [];
for (var i = 0; i < original.length; ++i) {
for (var j = 0; j < original[i].length; ++j) {
// skip undefined values to preserve sparse array
if (original[i][j] === undefined) continue;
// create row if it doesn't exist yet
if (copy[j] === undefined) copy[j] = [];
// swap the x and y coords for the copy
copy[j][i] = original[i][j];
}
}
return copy;
}
function insertColor5() {
const sheetName = "結果1"; // Please set the sheet name.
// 1. Retrieve values from sheet.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(sheetName);
const values1 = sheet.getRange(3, 7, sheet.getLastRow() - 2, sheet.getLastColumn()).getValues();
const values = transpose(values1);
//Logger.log(values);
// 2. Create an array for modifying the background colors.
const backgroundColors = values.map(([,,c,,,...g]) =>
g.reduce((o, e) => {
if (e.toString() != "") {
o.total += e;
o.colors.push(c >= o.total ? null : "red");
} else {
o.colors.push(null);
}
return o;
}, {colors: [], total: 0}).colors
);
const kolorki = transpose1(backgroundColors);
//Logger.log(transpose1(backgroundColors));
// 3. Modify the background colors of cells.
sheet.getRange(8, 7, kolorki.length, kolorki[0].length).setBackgrounds(kolorki);
}

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
}
});
}

How to add a row to a 3D array Array Google App scripts

I have some data with three columns, (PO, SKU, Units). I've sorted this array by SKU. The data looks like the image below
I'd like to add a blank row in between each set of SKUs to make it easier to read, and perhaps add totals in later. For example, after the last AL-STSTCHOPPER record, and before the first CE-12-SLOT-WOOD-WATCH-DARK record, there needs to be a gap. I've tried using a for loop to do this, with no luck. I think there's an easier way, but not sure what it could be. Code I have for this is as follows:
var activeSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SheetName");
var orderLog = SpreadsheetApp.openByUrl("URL").getSheetByName("Order Details");
var orderSort = orderLog.getRange(3, 1, orderLog.getLastRow(), 3).getValues().
sort(function(r1,r2){
var a = r1[1];
var b = r2[1];
if(a>b){
return 1;
}else if (a<b){
return -1;}
return 0;
});
Logger.log(orderSort);
You can add blank row with a loop:
function whitespace() {
var ss = SpreadsheetApp.getActive(); // SpreadsheetApp.open...
var sheet = ss.getActiveSheet(); //ss.getSheetByName('sheetName');
var lastRow = sheet.getLastRow();
sheet
.getRange(1, 2, lastRow)
.getValues()
.reverse()
.forEach(function(row, i, all){
if(all.length > (i+1) && row[0] && all[i+1][0] && row[0] != all[i+1][0]) sheet.insertRowBefore(lastRow - i);
});
}
You can do it by reading and writing an array:
function whitespace2() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getActiveSheet();
var lastRow = sheet.getLastRow();
var arr = [];
sheet
.getRange(1, 1, lastRow, 3)
.getValues()
.forEach(function(row, i, all){
if(all.length > (i+1) && row[1] && all[i+1][1] && row[1] != all[i+1][1]) {
arr.push(row);
arr.push(['','','']);
}
else arr.push(row);
});
sheet
.getRange(1, 1, arr.length, 3)
.setValues(arr);
}

how to make multiple instances in JavaScript

i was trying to generate random numbers to pick random items from array to update collage of pictures, i noticed that sometimes generated number is same to a previously generated number causing one picture to update 2-3 times so to check this i thought it would be great if i store last generated random number into an variable and make a function that checks if last generated value is equal to
current generated value, it continue to generate numbers till a non-equal value is generated.
it is working fine until i wanted multiple instances ... as variable that stores last value is same, i am getting same numbers.
var bgs = document.getElementById('collage').getElementsByClassName('bg');
var images = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var lv = null;
function get_random(length) {
arr = [];
while (arr.length < 1) {
var cv = Math.floor(Math.random() * length);
if (lv === cv) {
arr = [];
continue;
}
else if (lv !== cv) {
arr.push(cv);
break;
}
}
lv = arr[0];
return arr[0];
}
function update_collage() {
var img = images[get_random(images.length)];
var target = bgs[get_random(bgs.length)];
target.style.background = "url('assets/img/thumbs/" + img + ".jpg') no-repeat center";
target.style.backgroundSize = "cover";
}
window.setInterval(function(){
update_collage();
}, 1200);
how can i correct this, how can i create multiple instances of function or variable
I suggest using a generator function and instantiating it as often as you need:
// Generate pairwise unique random numbers < max:
function* generate_random(max) {
let prev;
while (true) {
let next = Math.floor(Math.random() * max);
if (prev === next) continue;
yield prev = next;
}
}
// Example:
let generators = [generate_random(3), generate_random(3)];
for (let i = 0; i < 10; ++i) {
// Both generators run independently:
console.log(generators[0].next().value, generators[1].next().value);
}
In your code, you could e.g. replace
var img = images[get_random(images.length)];
var target = bgs[get_random(bgs.length)];
with
var img = images[generators[0].next().value];
var target = bgs[generators[1].next().value];
where
var generators = [generate_random(images.length), generate_random(bgs.length)];

Javascript: Adding final element to array after for loop completes

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));

Categories

Resources