I have some data in google sheet which I want to filter based on a certain criteria and return a corresponding value from another column. Lastly, count the number of elements in the returned column. Here is a sample data:
Sample data
A
B
1
Initials
Application Reference
2
MWB.KBB
1001
3
JET,JJB
1002
4
KBB
100,310,041,005
5
MKGC
1006
6
KBB
1007
Let's say I want to filter the data by searching for "KBB". I want to get all cells that contain the word "KBB" which should be three (3) cells. However, I am only getting two in return. The 1st row that contain two elements in a single cell is not included but it should be included. Lastly, count the elements in the returned column based on the criteria.
Here's the code I have tried:
function filter(){
//opened ss via url
const ws = ss.getSheetByName("Sample");
const range = ws.getRange(2,1,ws.getLastRow() - 1,2).getValues();
const initial = range.map(function(n){return n[0];});
const filtered = initial.filter(filterLogic);
Logger.log(initial); // [MWP, KBB, JET, JJB, KBB, MKGC, KBB]
Logger.log(filtered); // [KBB, KBB]
}
function filterLogic(name){
if(name == "KBB"){
return true;
} else {
return false;
}
}
The above code is only for the criteria. Not included is the counting of elements for the returned value from another column after the filter is applied.
What should I do so I can include the first row that contains the text "KBB" as well in my filtered data. Is there any other way around this?
Code:
function searchForKBB(n = "KBB") {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const osh = ss.getSheetByName("Sheet1");
let o = sh.getRange(1,1,sh.getLastRow(),sh.getLastColumn()).createTextFinder(n).matchEntireCell(false).findAll().map(rg => [rg.getA1Notation()]);
o.unshift(["Ranges"]);
osh.getRange(1,1,o.length,o[0].length).setValues(o)
}
Data:
A
B
1
Initials
Application Reference
2
MWB.KBB
1001
3
JET,JJB
1002
4
KBB
100,310,041,005
5
MKGC
1006
6
KBB
1007
Results:
Ranges
A2
A4
A6
Maybe you can do this:
```
function getAllKBBs(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ss1 = ss.getSheetByName("YOUR_SHEET_NAME");
var range = ss1.getRange(1,1,ss1.getLastRow(),4).getValues();
output = whenTextContains("KBB", range, 1, 1);
Logger.log(output.length);
} ```
where whenTextContains() function is in this repository
https://github.com/NikolaPlusEqual/GoogleAppsScriptFilters/blob/main/Functions
Or, you can copy this into you code and call above function:
function letterToColumn(letter){
var column = 0, length = letter.length;
for (var i = 0; i < length; i++)
{
column += (letter.charCodeAt(i) - 64) * Math.pow(26, length - i - 1);
}
return column;
}
////// source for letterToColumn() function :
////// https://stackoverflow.com/questions/21229180/convert-column-index-into-corresponding-column-letter
var setData = {}
function whenTextContains(txt, rng, col, targetCol = 0){
if (typeof col == "number" && typeof txt == "string"){
setData.col = col;
setData.txt = txt;
}
else{
return;
}
var output = rng.filter(wtc);
if(targetCol == 0){
return output;
}
else if(typeof targetCol == "number"){
var result = output.map(function (item) {
return item[targetCol-1];
});
return result;
}
else if(typeof targetCol == "string"){
var targetnum = letterToColumn(targetCol);
var result = output.map(function (item) {
return item[targetnum-1];
});
return result;
}
else{
return;
}
}
function wtc(ar){
var txt = setData.txt;
var col = setData.col - 1;
var str = ar[col].toString();
return str.includes(txt);
}
Related
I have a script that allows me to get the contents of a column from my Google Sheet and display it in my HTML form while removing any duplicates of the same name.
Example: red, red, yellow, yellow, blue, green would show in the dropdown menu as red, yellow, blue, green.
The thing is, I would like to get the column contents by name and not by number i.e 1.
Here is my script:
function getColors() {
var sheet = SpreadsheetApp.openById("1czFXXQAIbW9IlAPwHQ0D5S_a-Ew82p-obBEalJFNJTI").getSheetByName("Vinyl Costs");
var getLastRow = sheet.getLastRow();
var return_array = [];
for(var i = 2; i <= getLastRow; i++)
{
if(return_array.indexOf(sheet.getRange(i, 1).getValue()) === -1) {
return_array.push(sheet.getRange(i, 1).getValue());
}
}
return return_array;
}
I've found a similar question and the accepted answer was this:
function getByName(colName, row) {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var col = data[0].indexOf(colName);
if (col != -1) {
return data[row-1][col];
}
}
But I can't seem to make that work with mine? This is my first ever Google Script so I don't really understand it 100% yet.
I changed the functions a bit.
For one thing, getByName now gets not all values of the sheet, but only the first row.
function getColors() {
const sheet = SpreadsheetApp.openById("1czFXXQAIbW9IlAPwHQ0D5S_a-Ew82p-obBEalJFNJTI").getSheetByName("Vinyl Costs");
const colName = 'your_column_name';
const colNum = getColNumberByName(colName);
if (colNum === null) {
Logger.log('Column ' + colName + ' was not found!');
return [];
}
const firstRow = 2;
const lastRow = sheet.getLastRow();
// get all values from column
const columnData = sheet.getRange(firstRow, colNum, lastRow).getValues().flat();
// filter values on duplicates
return columnData.filter((el, i) => i === columnData.indexOf(el) && el !== '');
}
function getColNumByName(colName, row = 1) {
const sheet = SpreadsheetApp.openById("1czFXXQAIbW9IlAPwHQ0D5S_a-Ew82p-obBEalJFNJTI").getSheetByName("Vinyl Costs");
const [data] = sheet.getRange(row, 1, row, sheet.getLastColumn()).getValues();
const col = data.indexOf(colName);
// adding 1 because column nums starting from 1
return col === -1 ? null : col + 1;
}
I have created a Google script that pushes data every hour from the Capital Bikeshare API to a Google Sheet, but I have noticed that the way I am currently pulling the data doesn't maintain consistency over time. Here's the code I'm using:
function myFunction() {
// Set the active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var currentData = ss.getSheetByName("Current");
var historicData = ss.getSheetByName("Historic");
// Fetch API
var stationInfo = UrlFetchApp.fetch('https://gbfs.capitalbikeshare.com/gbfs/en/station_information.json');
var stationStatus = UrlFetchApp.fetch('https://gbfs.capitalbikeshare.com/gbfs/en/station_status.json');
// Get the current date and time
var today = new Date();
var date = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
var dateTime = date+' '+time;
// Parse the JSON reply
var jsonInfo = stationInfo.getContentText();
var dataInfo = JSON.parse(jsonInfo);
var jsonStatus = stationStatus.getContentText();
var dataStatus = JSON.parse(jsonStatus);
// Create the data frame for every BID station
var stationInfo72 = dataInfo["data"]["stations"][69];
var stationStatus72 = dataStatus["data"]["stations"][69];
var stationInfo87 = dataInfo["data"]["stations"][83];
var stationStatus87 = dataStatus["data"]["stations"][83];
var stationInfo330 = dataInfo["data"]["stations"][311];
var stationStatus330 = dataStatus["data"]["stations"][311];
var stationInfo153 = dataInfo["data"]["stations"][143];
var stationStatus153 = dataStatus["data"]["stations"][143];
var stationInfo226 = dataInfo["data"]["stations"][213];
var stationStatus226 = dataStatus["data"]["stations"][213];
var stationInfo365 = dataInfo["data"]["stations"][342];
var stationStatus365 = dataStatus["data"]["stations"][342];
var stationInfo473 = dataInfo["data"]["stations"][446];
var stationStatus473 = dataStatus["data"]["stations"][446];
var outputStationsInfo = [stationInfo72, stationInfo87, stationInfo330, stationInfo153, stationInfo226, stationInfo365, stationInfo473]
var outputStationsStatus = [stationStatus72, stationStatus87, stationStatus330, stationStatus153, stationStatus226, stationStatus365, stationStatus473]
Logger.log(outputStationsInfo, outputStationsStatus)
// Create lists of each element
var outputHead = [];
var outputTail = [];
outputStationsInfo.forEach(function(elem,i) {
outputHead.push([elem["station_id"],elem["name"],elem["capacity"], elem["lat"], elem["lon"]]);
});
outputStationsStatus.forEach(function(elem,i) {
outputTail.push([elem["num_bikes_available"], elem["num_ebikes_available"], dateTime]);
});
// Publish arrays in the Current sheet
currentData.getRange(2,1,7,5).setValues(outputHead);
currentData.getRange(2,6,7,3).setValues(outputTail);
// Publish arrays in the Historic sheet
historicData.getRange(historicData.getLastRow() + 1,1,7,5).setValues(outputHead);
historicData.getRange(historicData.getLastRow() - 6,6,7,3).setValues(outputTail);
}
Essentially, I am drilling into the 69th item in the indexes of the JSONs to get the data that I need from two different APIs, and then I merge them together to create a data frame of everything I need to push to the sheet. However, sometimes the API does not report them in the normal order and I end up getting bikeshare stations that aren't in my study area. For example, 99% of the time the 69th item in the array is station_id = 72, but occasionally it's station_id = 73 or something.
Is there a way to conditionally pull a specific array based on the station_id number within the array? I feel like the answer might allow me to do a loop as well to clean this up. Any advice is helpful, as I'm super new to this.
You have to check if the element's station_id is as expected. If not, check through the surrounding parts of the array using a custom iterator.
Snippet:
/**
* #return indexes of the surrounding ``i`` in batches of 5
*/
function* checkSurroundings(i, lastIndex) {
let j = i;
function* check(ct, border, reverse = true, limit = border < 5 ? border : 5) {
const margin = reverse ? ct - limit : ct + limit;
while (ct - margin !== 0) yield reverse ? --ct : ++ct;
return ct;
}
while (i !== 0 || j < lastIndex) {
if (i !== 0) i = yield* check(i, i);
if (j < lastIndex) j = yield* check(j, lastIndex - j, false);
//console.log({ i, j });
}
}
var stations = dataInfo["data"]["stations"];
var stationInfo72 = stations[69];
const iter = checkSurroundings(69,stations.length-1)
//if station_id is not 72, loop through the surrounding indexes
while(stationInfo72["station_id"] !== 72){
const next = iter.next();
if(next.done) {
console.error("station id 72 not found");
break;
}
stationInfo72 = stations[next.value]
}
Snippet showing how checkSurroundings iterates:
/**
* #return indexes of the surrounding ``i`` in batches of 5
*/
function* checkSurroundings(i, lastIndex) {
let j = i;
function* check(ct, border, reverse = true, limit = border < 5 ? border : 5) {
const margin = reverse ? ct - limit : ct + limit;
while (ct - margin !== 0) yield reverse ? --ct : ++ct;
return ct;
}
while (i !== 0 || j < lastIndex) {
if (i !== 0) i = yield* check(i, i);
if (j < lastIndex) j = yield* check(j, lastIndex - j, false);
console.log({ i, j });
}
}
console.log("Order of iteration",[...checkSurroundings(50, 100)])
Conditionally picking elements: filter
For conditionally picking elements from an array in JavaScript, Array.prototype.filter should always be a consideration.
Create a predicate function that matches the shape of your data and checks for certain station IDs.
Here is a function that returns a predicate function. You put in the IDs you want in an array, and it returns the required function for filter.
function byStationId(stationIds) {
return function (obj) {
return stationIds.indexOf(obj.station_id) > -1;
};
}
var myStationFilter = byStationId([72, 73, 74]);
var outputStationsInfo = dataInfo.data.stations.filter(myStationFilter);
Transforming data: map
The pattern
var newArray = [];
oldArray.forEach(function (item) {
newArray.push(/* something based on item */);
});
can usually be replaced with Array.prototype.map
var newArray = oldArray.map(function (item) { return /* something based on item */});
Think of this as the "adapter" from one data shape to another.
function cleanInfo(info) {
return [info.station_id, info.name, info.capacity, info.lat, info.lon];
}
var outputHead = outputStationsInfo.map(cleanInfo);
For the dateTime injection, just do the same trick demonstrated above with the station IDs: have a function that takes a date string and returns the appropriate adapter function.
(Also note the provided date formatting utility Apps Scripts provides, Utilities.formatDate())
var dateTime = Utilities.formatDate(
new Date(),
ss.getSpreadsheetTimeZone(),
"yyyy-MM-dd HH:mm:ss"
);
function cleanStatus(dateTime) {
return function (status) {
return [status.num_bikes_available, status.num_bikes_available, dateTime];
};
}
var outputTail = outputStationsStatus.map(cleanStatus(dateTime));
Here's everything together, untested, just for inspiration. You must at the very least update the line with the station IDs to match your desired station IDs. Note that the helper functions for map and filter are at the bottom, taking advantage of JavaScript's hoisting feature.
function myFunction() {
// Set the active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Get the current date and time
var dateTime = Utilities.formatDate(
new Date(),
ss.getSpreadsheetTimeZone(),
"yyyy-MM-dd HH:mm:ss"
);
// Fetch API
var stationInfo = UrlFetchApp.fetch(
"https://gbfs.capitalbikeshare.com/gbfs/en/station_information.json"
);
var stationStatus = UrlFetchApp.fetch(
"https://gbfs.capitalbikeshare.com/gbfs/en/station_status.json"
);
// Parse the JSON reply
var dataInfo = JSON.parse(stationInfo.getContentText());
var dataStatus = JSON.parse(stationStatus.getContentText());
// Create the data frame for every BID station
var myStationFilter = byStationId([72, 73, 74]); //!! UPDATE THESE NUMBERS
var outputStationsInfo = dataInfo.data.stations.filter(myStationFilter);
var outputStationsStatus = dataStatus.data.station.filter(myStationFilter);
// Create lists of each element
var outputHead = outputStationsInfo.map(cleanInfo);
var outputTail = outputStationsStatus.map(cleanStatus(dateTime));
// Publish arrays in the Current sheet
var currentData = ss.getSheetByName("Current");
currentData.getRange(2, 1, 7, 5).setValues(outputHead);
currentData.getRange(2, 6, 7, 3).setValues(outputTail);
// Publish arrays in the Historic sheet
var historicData = ss.getSheetByName("Historic");
historicData
.getRange(historicData.getLastRow() + 1, 1, 7, 5)
.setValues(outputHead);
historicData
.getRange(historicData.getLastRow() - 6, 6, 7, 3)
.setValues(outputTail);
//-------- helper functions ------------
function byStationId(stationIds) {
return function (obj) {
return stationIds.indexOf(obj.station_id) > -1;
};
}
function cleanInfo(info) {
return [info.station_id, info.name, info.capacity, info.lat, info.lon];
}
function cleanStatus(dateTime) {
return function (status) {
return [status.num_bikes_available, status.num_bikes_available, dateTime];
};
}
}
This is just a snippet of my code from Google App Script which iterates through each row in columns 1, 2, 3. If an edit is made in column 3, an incremental ID will be generated and a concatenation of the same row and different columns will also be generated - in this case Column D, E, and F. I am struggling with figuring out a way to change the formulas into values. What am I missing here?
// Location format = [sheet, ID Column, ID Column Row Start, Edit Column]
var locations = [
["Consolidated Media Plan",1,9,3]
];
function onEdit(e){
// Set a comment on the edited cell to indicate when it was changed.
//Entry data
var range = e.range;
var col = range.getColumn();
var row = range.getRow();
// Location Data
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
function getNewID(){
function IDrange(){
var dataRange = sheet.getDataRange();
var lastRow = dataRange.getLastRow();
return sheet.getRange(IDrowStart,IDcol,lastRow-IDrowStart).getValues();
};
//Get largest Value in range
function getLastID(range){
var sorted = range.sort();
var lastIDval = sorted[sorted.length-1][0];
return lastIDval;
};
//Stores leading letters and zeroes and trailing letters
function getLettersNzeroes(id){
//Get any letters or zeroes.
var re = new RegExp("^([a-zA-Z0])$");
var letterZero = [];
for(char = 0; char < id.length; char++){
if(re.test(id[char])){
letterZero.push([char,id[char]]);// [[position, letter or zero]]
};
};
// Categorize letters and zeroes into start and end blocks
var startLetterZero = "",
endLetter = "",
len = letterZero.length - 1;
for(j = 0; j < letterZero.length; j++){
if(letterZero[j][0] === j){
startLetterZero += letterZero[j][1];
}else if(letterZero[j][1] !== "0" && letterZero[len][0] - (len - j) == letterZero[j][0]){
endLetter += letterZero[j][1];
};
};
var startNend = {"start":startLetterZero,"end":endLetter};
return startNend;
};
//Gets last id number. Adds 1 an checks to set if its new length is greater than the lastNumber.
function getNewNumber(id){
var removeZero = false;
var lastNum = parseInt(id.replace(/\D/g,''),10);//Remove letters
var newNum = (lastNum+1).toString();
if(lastNum.toString().length !== newNum.length){
var removeZero = true;
};
var newNumSet = {"num":newNum, "removeZero": removeZero};
return newNumSet
};
var lastID = getLastID(IDrange());
var lettersNzeroes = getLettersNzeroes(lastID);
var newNumber = getNewNumber(lastID);
//If the number is 9,99,999,9999 etc we need to remove a zero if it exists.
if(newNumber.removeZero === true && lettersNzeroes.start.indexOf("0") !== -1.0){
lettersNzeroes.start = lettersNzeroes.start.slice(0,-1);
};
//Rejoin everything together
var newID = lettersNzeroes.start +
newNumber.num +
lettersNzeroes.end;
return newID;
};
for(i = 0; i < locations.length; i++){
var sheetID = locations[i][0],
IDcol = locations[i][1],
IDrowStart = locations[i][2],
EditCol = locations[i][3];
var offset = IDcol - EditCol;
var cell = sheet.getActiveCell();
if(sheetID === sheet.getName()){
if(EditCol === col){
//ID Already Exists the editing cell isn't blank.
if(cell.offset(0,offset).isBlank() && cell.isBlank() === false){
var newID = getNewID();
cell.offset(0,offset).setValue(newID);
cell.offset(0,-1).setFormulaR1C1('=concatenate(R[0]C[-1],"_",INDEX(Glossary!K:K,MATCH(R[0]C[2],Glossary!J:J,0)))');
};
};
};
};
};
EDIT:
This is my full code, I have been unsuccessful with trying to retrieve just the values of the formula within the same (i.e, If C9 gets edited, a formula with the values specific to the 9th row should be populated)
Also, I've tried to add an index/match formula to the concatenation formula at the bottom of the code - it works as expected on the google sheets, but when I run it with the script it pastes the correct formula but it returns a #NAME? error message. However, when I copy and paste the exact same formula in the cell, it works perfectly, any idea what could be causing this error?
This works for me. I know it's not exactly the same thing but I didn't have access to getNewId()
function onEdit(e) {
var sh=e.range.getSheet();
if(sh.getName()!='Sheet1')return;
//e.source.toast('flag1');
if(e.range.columnStart==3 && e.range.offset(0,1).isBlank() && e.value) {
//e.source.toast('flag2');
e.range.offset(0,1).setValue(e.value);
e.range.offset(0,2).setFormulaR1C1('=concatenate(R[0]C[-1],"_",R[0]C[-2],"_",R[0]C[-3],"_",R[0]C[-4])');
}
}
I am writing a script to copy and paste a range from one sheet to another. The pasted range size should be reduced by using two functions : one to delete rows with specific values and the other is an aggregate function.
I started getting this error after I introduced the aggregate function The function is basically reducing the array size using the reduce JS function.
I have replicated my problem here and the code is accessible in the script editor.
When I run the script I am getting the following error :
Incorrect range height was 28 but should be 7 (line 36, file "test")
I have no idea why am I getting this error. My aggregate function returns a properly formatted array with the right length.
function append_range(){
var origin_sheet = SpreadsheetApp.openById('1-2ZheMz1p01qwtwY3ghbNjJedYfGXeylnLEjDMCLpMw');//open the file
origin_sheet = origin_sheet.getSheetByName('test');
var rangeStart = 2;
var range = origin_sheet.getRange('A'+ (rangeStart.toString())+':T'+ (origin_sheet.getLastRow()).toString());
var dataFromRange = range.getValues();
var dataFromRangeLength = dataFromRange.length;
var destination_sheet = SpreadsheetApp.openById('1-2ZheMz1p01qwtwY3ghbNjJedYfGXeylnLEjDMCLpMw');
destination_sheet = destination_sheet.getSheetByName('append');
var rowLast = destination_sheet.getLastRow()+1;
Logger.log("row last" + rowLast);
var formattedRange = deleteRows(dataFromRange);
var groups = aggregate(formattedRange);
var aggregates = [];
for(var group in groups)
{
aggregates.push(groups[group]);
}
Logger.log(aggregates);
var formattedRangeLength = aggregates.length;
Logger.log("formattedRangeLength" + formattedRangeLength);
destination_sheet.getRange(rowLast,1,formattedRangeLength, 20).setValues(deleteRows(dataFromRange));
function isDate(sDate) {
if (isValidDate(sDate)) {
sDate = Utilities.formatDate(new Date(sDate), "PST", "yyyy-MM-dd");
}
return sDate;
}
function isValidDate(d) {
if ( Object.prototype.toString.call(d) !== "[object Date]" )
return false;
return !isNaN(d.getTime());
}
//
function deleteRows(dataRange){//just pass the range in an array and this method will return another array with filtered range
var formatted = dataRange.filter(function(e) {
return e[8]||e[9]||e[10]||e[11]||e[12]||e[13]||e[14]||e[15]||e[16]||e[17]||e[18]||e[19];
});
return formatted;
}
function aggregate(data)
{
var groups = data.reduce(
function(accumulator, previous){
{
var key = previous[1] + previous[3] + previous[5] + previous[6];
var group = accumulator[key];
if(group == null || typeof group == 'undefined')
{
accumulator[key] = previous;
}
else {
var startIndex = 8;
for(var i = startIndex; i < previous.length;i++)
{
group[i] += previous[i];
}
}
return accumulator;
}},
{});
return groups;
}
}
The .setValues() is not setting your aggregates array it is trying to set deleteRows(dataFromRange)
// Change the setValues() to your reduced array
destination_sheet.getRange(rowLast,1,formattedRangeLength, 20).setValues(aggregates);
I think this might work:
var output=deleteRows(dataFromRange));
destination_sheet.getRange(rowLast,1,output.length, output[0].length).setValues(deleteRows(output));
This assumes a non jagged array.
I have a spreadsheet of surveys, in which I need to see how particular users have varied over time. As such, I need to disregard all rows with unique values in a particular column. The data looks like this:
Response Date Response_ID Account_ID Q.1
10/20/2011 12:03:43 PM 23655956 1168161 8
10/20/2011 03:52:57 PM 23660161 1168152 0
10/21/2011 10:55:54 AM 23672903 1166121 7
10/23/2011 04:28:16 PM 23694471 1144756 9
10/25/2011 06:30:52 AM 23732674 1167449 7
10/25/2011 07:52:28 AM 23734597 1087618 5
I've found a way to do so in Excel VBA:
Sub Del_Unique()
Application.ScreenUpdating = False
Columns("B:B").Insert Shift:=xlToRight
Columns("A:A").Copy Destination:=Columns("B:B")
i = Application.CountIf(Range("A:A"), "<>") + 50
If i > 65536 Then i = 65536
Do
If Application.CountIf(Range("B:B"), Range("A" & i)) = 1 Then
Rows(i).Delete
End If
i = i - 1
Loop Until i = 0
Columns("B:B").Delete
Application.ScreenUpdating = True
End Sub
I'd like to do it in Google Spreadsheets with a script that won't have to be changed. Closest I can get is retrieving all duplicate user ids from the range, but can't associate that with the row. That code follows:
function findDuplicatesInSelection() {
var activeRange = SpreadsheetApp.getActiveRange();
var values = activeRange.getValues();
// values that appear at least once
var once = {};
// values that appear at least twice
var twice = {};
// values that appear at least twice, stored in a pretty fashion!
var final = [];
for (var i = 0; i < values.length; i++) {
var inner = values[i];
for (var j = 0; j < inner.length; j++) {
var cell = inner[j];
if (cell == "") continue;
if (once.hasOwnProperty(cell)) {
if (!twice.hasOwnProperty(cell)) {
final.push(cell);
}
twice[cell] = 1;
} else {
once[cell] = 1;
}
}
}
if (final.length == 0) {
Browser.msgBox("No duplicates found");
} else {
Browser.msgBox("Duplicates are: " + final);
}
}
This is maybe not very efficient, but I think it's what you want:
var ar=[1,3,3,5,6,8,6,6];
console.log("Before:");
display(ar);//1 3 3 5 6 8 6 6
var index=[];
var ar2=[];
for(var a=0;a<ar.length;a++)
{
var duplicate=false;
for(var b=0;b<ar.length;b++)
{
if(ar[a]==ar[b]&&a!=b)
{
duplicate=true;
}
}
if(!duplicate)
{
index.push(a);
}
}
for(var a=0;a<index.length;a++)
{
ar[index[a]]=null;
}
for(var a=0;a<ar.length;a++)
{
if(ar[a]!=null)ar2.push(ar[a]);
}
console.log("After:");
display(ar2);//3 3 6 6 6
function display(x)
{
for(var a=0;a<x.length;a++)console.log(x[a]);
}
The fiddle : http://jsfiddle.net/mageek/6AGQ4/
And a shorter version that is as a function :
var ar=[1,3,3,5,6,8,6,6];
function removeUnique(x)
{
var index=[];
var ar2=[];
for(var a=0;a<ar.length;a++)
{
var duplicate=0;
for(var b=0;b<ar.length;b++)if(ar[a]==ar[b]&&a!=b)duplicate=1;
if(!duplicate)index.push(a);
}
for(var a=0;a<index.length;a++)ar[index[a]]=null;
for(var a=0;a<ar.length;a++)if(ar[a]!=null)ar2.push(ar[a]);
return x;
}
ar=removeUnique(ar);
The fiddle : http://jsfiddle.net/mageek/6AGQ4/2
I'd suggest going for something simple.
Create a short script that flags duplicates
Write the formula directly into the cell "=flagDuplicate(C2,C$2:C$10)"
Copy the forumla down the column
Use Spreadsheet's built in QUERY formula to pull the information you need
"=QUERY(A1:E10; "SELECT * WHERE E = TRUE"; 1)"
Here is a simple function to flag duplicates
function flagDuplicate(value, array) {
var duplicateCounter = 0;
for (var i=0; i<array.length; i++){
if (array[i] == value){ // I avoid === in Spreadsheet functions
duplicateCounter++;
}
}
if (duplicateCounter > 1){
return true;
}else{
return false;
}
}
Too many functions on a large table can slow things down. If it becomes a problem, you can always copy and "paste values only" - that will retain the information but remove the functions.
Best of luck.
Note: When I tested this I noticed that can take a while before the spreadsheet recognizes the new custom function (gives error like can't find function FLAGDUPLICATE)
You could also do it using arrays to handle the whole sheet at once :
function removeUnique(){
var col = 2 ; // choose the column you want to check for unique elements
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data=ss.getDataRange().getValues();// get all data
data.sort(function(x,y){
// var xp = Number(x[col]);// use these to sort on numeric values
// var yp = Number(y[col]);
var xp = x[col];// use these for non-numeric values
var yp = y[col];
Logger.log(xp+' '+yp); // just to check the sort is OK
return xp == yp ? 0 : xp < yp ? -1 : 1;// sort on column col numeric ascending
});
var cc=0;
var newdata = new Array();
for(nn=0;nn<data.length-1;++nn){
if(data[nn+1][col]==data[nn][col]||cc>0){
newdata.push(data[nn]);
++cc;
if(cc>1){cc=0}}
}
ss.getDataRange().clearContent(); // clear the sheet
sh.getRange(1,1,newdata.length,newdata[0].length).setValues(newdata);// paste new values sorted and without unique elements
}
EDIT : here is the version that keeps all duplicates (the working one)
function removeUnique(){
var col = 2 ; // choose the column you want to check for unique elements
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data=ss.getDataRange().getValues();// get all data
data.sort(function(x,y){
// var xp = Number(x[col]);// use these to sort on numeric values
// var yp = Number(y[col]);
var xp = x[col];// use these for non-numeric values
var yp = y[col];
Logger.log(xp+' '+yp); // just to check the sort is OK
return xp == yp ? 0 : xp < yp ? -1 : 1;// sort on column col numeric ascending
});
var newdata = new Array();
for(nn=0;nn<data.length-1;++nn){
if(data[nn+1][col]==data[nn][col]){
newdata.push(data[nn]);
}
}
if(data[nn-1][col]==data[nn][col]){newdata.push(data[nn])}
ss.getDataRange().clearContent(); // clear the sheet
sh.getRange(1,1,newdata.length,newdata[0].length).setValues(newdata);// paste new values sorted and without unique elements
}