I'm looking to clone a row 3x, but only keeping data from one column.
So essentially I have the following [Name / Time / Booking], and each row is populated with all 3 properties, I'm trying to create 3 blank rows underneath each current row which is populated with only the persons name.
Can't work how to do it in scripting and can't find a plugin to do this. My data set is over 10,000 big so doing it manually isn't an option.
What I have:
What I want:
UPDATED code:
function duplicateRows() {
var sh, v, arr, c, b;
sh = SpreadsheetApp.getActive()
.getSheetByName('Blad1')
v = sh.getRange(1, 1, sh.getLastRow(), 40)
.getValues();
arr = [v[0]];
v.splice(1)
.forEach(function (r, i) {
arr.push(r)
c = 0
while (c < 3) {
dup = makeEmptyArrayXEl(40)
dup[0] = r[0];
arr.push(dup)
c += 1;
}
})
sh.getRange(1, 1, arr.length, arr[0].length)
.setValues(arr);
}
function makeEmptyArrayXEl(num) {
var arr = [];
for (var i = 0; i < num; i++) {
arr.push("")
}
return arr;
}
Would this work for you? It requires a free column to the left of Booking in the original data set. The formula below is a new sheet.
=ArrayFormula(sort({A2:A4,B2:B4,C2:C4;A2:A4,D2:D4,D2:D4;A2:A4,D2:D4,D2:D4;A2:A4,D2:D4,D2:D4},1,FALSE))
Related
Sorry in advanced for bad English. I have 15+ rows and 15+ columns. I need to merge cells in a spreadsheet but data will be unclear after merging. Is there a formula/function I can use to clarify my merge? Thank you in advance.
Please view example spreadsheet.
This is not possible using only formula. Formulas can't do anything to formatting like merging cells and applying colors to text. But here's a script you can use to begin with. You may modify it to apply font color.
Try:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("Sheet1");
var destSheet = ss.getSheetByName("Sheet2");
var sourceData = sourceSheet.getRange(2, 1, sourceSheet.getLastRow() - 1, sourceSheet.getLastColumn()).getValues();
//Add name to each column, add here if you have more columns
sourceData.forEach(function (x) {
x[2] = x[1] + ":\n" + x[2]
x[3] = x[1] + ":\n" + x[3]
});
var array = sourceData,
hash = {},
i, j,
result,
item,
key;
for (i = 0; i < array.length; i++) {
item = array[i];
key = item[0].toString();
if (!hash[key]) {
hash[key] = item.slice();
continue;
}
for (j = 1; j < item.length; j++) hash[key][j] = hash[key][j] + "\n" + item[j];
}
result = Object.values(hash);
destSheet.getRange(2, 1, result.length, result[0].length).setValues(result);
}
Result:
Reference:
How to merge an array of arrays with duplicate at index 0 into one array javascript
Hope this helps!
You can try with this formula, adapt the column range in the MAP function if necessary:
={UNIQUE('raw data'!A2:A),
SCAN(,UNIQUE('raw data'!A2:A),
LAMBDA(x,city, BYCOL(IFNA(FILTER ({'raw data'!B2:B,
MAP('raw data'!C2:G,LAMBDA(a,IF(a="","",INDEX('raw data'!B:B,ROW(a))&":"&CHAR(10)&a)))},'raw data'!A2:A=city,'raw data'!A2:A<>"")),LAMBDA(col,JOIN(CHAR(10)&CHAR(10),col)))))}
I'm currently using this formula to highlight duplicates in my spreadsheet.
=ARRAYFORMULA(COUNTIF(A$2:$A2,$A2)>1)
Quite simple, it allows me to skip the first occurrence and only highlight 2nd, 3rd, ... occurrences.
I would like the formula to go a bit further and highlight near duplicates as well.
Meaning if there is only one character difference between 2 cells, then it should be considered as a duplicate.
For instance: "Marketing", "Marketng", "Marketingg" and "Market ing" would all be considered the same.
I've made a sample sheet in case my requirement is not straightforward to understand.
Thanks in advance.
Answer
Unfortunately, it is not possible to do this only through Formulas. Apps Scripts are need as well. The process for achieving your desired results is described below.
In Google Sheets, go to Extensions > Apps Script, paste the following code1 and save.
function TypoFinder(range, word) { // created by https://stackoverflow.com/users/19361936
if (!Array.isArray(range) || word == "") {
return false;
}
distances = range.map(row => row.map(cell => Levenshtein(cell, word))) // Iterate over range and check Levenshtein distance.
var accumulator = 0;
for (var i = 0; i < distances.length; i++) {
if (distances[i] < 2) {
accumulator++
} // Keep track of how many times there's a Levenshtein distance of 0 or 1.
}
return accumulator > 1;
}
function Levenshtein(a, b) { // created by https://stackoverflow.com/users/4269081
if (a.length == 0) return b.length;
if (b.length == 0) return a.length;
// swap to save some memory O(min(a,b)) instead of O(a)
if (a.length > b.length) {
var tmp = a;
a = b;
b = tmp;
}
var row = [];
// init the row
for (var i = 0; i <= a.length; i++) {
row[i] = i;
}
// fill in the rest
for (var i = 0; i < b.length; i++) {
var prev = i;
for (var j = 0; j < a.length; j++) {
var val;
if (b.charAt(i) == a.charAt(j)) {
val = row[j]; // match
} else {
val = Math.min(row[j] + 1, // substitution
prev + 1, // insertion
row[j + 1] + 1); // deletion
}
row[j] = prev;
prev = val;
}
row[a.length] = prev;
}
return row[a.length];
}
In cell B1, enter =TypoFinder($A$2:$A2,$A2). Autofill that formula down the column by draggin.
Create a conditional formatting rule for column A. Using Format Rules > Custom Formula, enter =B2:B.
At this point, you might wish to hide column B. To do so, right click on the column and press Hide Column.
The above explanation assumes the column you wish to highlight is Column A and the helper column is column B. Adjust appropriately.
Note that I have assumed you do not wish to highlight repeated blank columns as duplicate. If I am incorrect, remove || word == "" from line 2 of the provided snippet.
Explanation
The concept you have described is called Levenshtein Distance, which is a measure of how close together two strings are. There is no built-in way for Google Sheets to process this, so the Levenshtein() portion of the snippet above implements a custom function to do so instead. Then the TypoFinder() function is built on top of it, providing a method for evaluating a range of data against a specified "correct" word (looking for typos anywhere in the range).
Next, a helper column is used because Sheets has difficulties parsing custom formulas as part of a conditional formatting rule. Finally, the rule itself is implemented to check the helper column's determination of whether the row should be highlighted or not. Altogether, this highlights near-duplicate results in a specified column.
1 Adapted from duality's answer to a related question.
Is it possible to fasten the hide of many rows when range.length > 300 rows ?
I also can't succeed moving the focus to the top of the sheet once the rows are hidden, I can only get the focus on another sheet.
Here is my code (french parameters), I'm not sure I need to show my spreadsheet. Thank you very much.
var LastRow = sheet.getLastRow()-1;
var ToHide = [];
for (var i=1 ; i < LastRow +1 ; i++){
if ( sheet.getRange(i,1).isChecked() == null){ ToHide.push(i); }
}
for (var j=0 ; j<ToHide.length ; j++){ MyActiveSheet.hideRows(ToHide[j+1],1); }
ToHide.forEach(function (d){ FeuilleActive.hideRows(d); }); // also tried .hideRows(d,1)
SpreadsheetApp.getActive().getSheets()[5].getRange(1,1).activate(); //
To lessen the number of loops, you can group it by determining the series of consecutive numbers in your Array. Which you can use to determine the index and number of rows to hide. The number of .hideRows() execution will be determined by the number of series in your array. Thus, lesser runtime.
Example Code:
function myFunction() {
var a = [1,2,3,4,6,7,8,9,11,13,14];
const result = a.reduce((r, n) => {
const lastSubArray = r[r.length - 1];
if(!lastSubArray || lastSubArray[lastSubArray.length - 1] !== n - 1) {
r.push([]);
}
r[r.length - 1].push(n);
return r;
}, []);
//result output: [[1.0, 2.0, 3.0, 4.0], [6.0, 7.0, 8.0, 9.0], [11.0], [13.0, 14.0]]
result.forEach(e => {
var index = e[0];
var numRows = e.length;
Logger.log("Index: "+ index);
Logger.log("numRows: "+numRows);
// MyActiveSheet.hideRows(index,numRows);
})
}
Output:
References:
Ori Drori answer on how to group series of consecutive numbers in an Array.
hideRows(rowIndex, numRows)
I'm not sure if this is even possible, and to be quite honest, I haven't tried many things because I wasn't sure where to even start. I'm using the Script Editor from Google Sheets, btw. I know there are SpreadsheetApp.getRange() and another to get the values or something like that. But what I want is a bit specific.
Is there a way to grab all the cell data in a given row and put it into an array? The rows will vary in size, that's why I can't do an exact range.
So for example, if I were to have rows have these values:
abc | 123 | 987 | efg
blah| cat | 654
I want to be able to grab those values and place them into an array like ["abc", "123", "987, "efg"]. And then if I run the function on the next row, it'd be ["blah", "cat", "654"].
Actually, it can be placed into any data type as long as there's a delimiter I'd be able to use.
Thank you in advance!
This is easier to achieve without a script, with the formula =filter(1:1, len(1:1)) returning all values in nonempty cells in row 1, etc.
From a script, you can do something like this:
function flat_nonempty() {
var range = SpreadsheetApp.getActiveSheet().getRange("A:A"); // range here
var values = range.getValues();
var flat = values.reduce(function(acc, row) {
return acc.concat(row.filter(function(x) {
return x != "";
}));
}, []);
Logger.log(flat); // flat list of values, no blanks
}
The range here can have one row or multiple rows.
Is this useful for you?
When there are abc | 123 | 987 | efg, blah| cat | 654 and abc | | 987 | efg at row 1, row 2 and row 3, myFunction(1), myFunction(2) and myFunction(3) return [abc, 123.0, 987.0, efg], [blah, cat, 654.0] and [abc, , 987.0, efg].
function myFunction(row){
var ss = SpreadsheetApp.getActiveSheet();
var values = ss.getRange(row, 1, 1, ss.getLastColumn()).getValues();
var c = 0;
for (var c = values[0].length - 1; c >= 0; c--){
if (values[0][c] != "") break;
}
values[0].splice(c + 1, values[0].length - c - 1);
return values[0];
}
Adapted from https://developers.google.com/apps-script/reference/spreadsheet/sheet#getDataRange()
You can loop and create your array directly if you do want an array. Here I am illustrating creating for all the rows, but if you know the row you want, you could do without the outer loop (and just set i to the desired row).
function rowArr() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
// This represents ALL the rows
var range = sheet.getDataRange();
var values = range.getValues();
for (var i = 0; i < values.length; i++) {
var row = [];
for (var j = 0; j < values[i].length; j++) {
if (values[i][j]) {
row.push(values[i][j]);
}
}
Logger.log(row);
}
}
I have 30 columns and 1000 rows, I would like to compare column1 with another column. IF the value dont match then I would like to colour it red. Below is a small dataset in my spreadsheet:
A B C D E F ...
1 name sName email
2
3
.
n
Because I have a large dataset and I want to storing my columns in a array, the first row is heading. This is what I have done, however when testing I get empty result, can someone correct me what I am doing wrong?
var index = [];
var sheet = SpreadsheetApp.getActiveSheet();
function col(){
var data = sheet.getDataRange().getValues();
for (var i = 1; i <= data.length; i++) {
te = index[i] = data[1];
Logger.log(columnIndex[i])
if (data[3] != data[7]){
// column_id.setFontColor('red'); <--- I can set the background like this
}
}
}
From the code you can see I am scanning whole spreadsheet data[1] get the heading and in if loop (data[3] != data[7]) compare two columns. I do have to work on my colour variable but that can be done once I get the data that I need.
Try to check this tutorial if it can help you with your problem. This tutorial use a Google AppsScript to compare the two columns. If differences are found, the script should point these out. If no differences are found at all, the script should put out the text "[id]". Just customize this code for your own function.
Here is the code used to achieve this kind of comparison
function stringComparison(s1, s2) {
// lets test both variables are the same object type if not throw an error
if (Object.prototype.toString.call(s1) !== Object.prototype.toString.call(s2)){
throw("Both values need to be an array of cells or individual cells")
}
// if we are looking at two arrays of cells make sure the sizes match and only one column wide
if( Object.prototype.toString.call(s1) === '[object Array]' ) {
if (s1.length != s2.length || s1[0].length > 1 || s2[0].length > 1){
throw("Arrays of cells need to be same size and 1 column wide");
}
// since we are working with an array intialise the return
var out = [];
for (r in s1){ // loop over the rows and find differences using diff sub function
out.push([diff(s1[r][0], s2[r][0])]);
}
return out; // return response
} else { // we are working with two cells so return diff
return diff(s1, s2)
}
}
function diff (s1, s2){
var out = "[ ";
var notid = false;
// loop to match each character
for (var n = 0; n < s1.length; n++){
if (s1.charAt(n) == s2.charAt(n)){
out += "–";
} else {
out += s2.charAt(n);
notid = true;
}
out += " ";
}
out += " ]"
return (notid) ? out : "[ id. ]"; // if notid(entical) return output or [id.]
}
For more information, just check the tutorial link above and this SO question on how to compare two Spreadsheets.