Data validation depending on condition Google Spreadsheets - validation

In Google Spreadsheets, is it possible to make a cell data validation for a range of numbers depending on the result of a condition?
Example:
column1 column2
a if column1=a then show dropdown of (1,0) if b then (2,3) so [1,0]
a if column1=a then show dropdown of (1,0) if b then (2,3) so [1,0]
b if column1=a then show dropdown of (1,0) if b then (2,3) so [2,0]
a if column1=a then show dropdown of (1,0) if b then (2,3) so [1,0]

I am not exactly clear on what you want, but you can
have different dropdowns based on different values in
a cell. This code assumes the value to evaluate is in
cell A1 ('a', 'b', or another value). If A1=a then the
dropdown list comes from C1:C2. If A1=b then the dropdown
list is from D1:D2. Any other value in A1 removes the
valadation in B1. (Entering a or b in A1 will put the
valadation back).
function onEdit(e)
{
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var sheetName= sheet.getSheetName();
if(sheetName=="Sheet1"){ //Only Sheet1
var editRange = sheet.getActiveRange();
var editRow = editRange.getRow();
var editCol = editRange.getColumn();
var lr = sheet.getLastRow()
var range = sheet.getRange("A1"); // Only apply on edit to cell Sheet1!A1
var rangeRowStart = range.getRow();
var rangeRowEnd = rangeRowStart + range.getHeight()-1;
var rangeColStart = range.getColumn();
var rangeColEnd = rangeColStart + range.getWidth()-1;
if (editRow >= rangeRowStart && editRow <= rangeRowEnd
&& editCol >= rangeColStart && editCol <= rangeColEnd)
{
drop()
}}}
function drop(){
var cell = SpreadsheetApp.getActive().getRange('B1');//set validation in B1
var ocell=SpreadsheetApp.getActive().getRange('B1').offset(0, -1).getValue()//evaluate value in A1
var cellVal=cell.getValue()
if(ocell=="a"){ //If A1=a dropdown list is C1:C2
var range = SpreadsheetApp.getActive().getRange('C1:C2');
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(range).build();
cell.setDataValidation(rule)}
else if(ocell=="b"){ //if A1=b dropdown list = D1:D2
var range = SpreadsheetApp.getActive().getRange('D1:D2');
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(range).build();
cell.setDataValidation(rule);}
else{ // If A1 is not a or b remove validation from B1
cell.clearDataValidations()}
}
If this is not what you are looking for explain and I can adjust.

Related

Google Apps Script - Copy Paste values on same page and in same row based on cell criteria

I have a table with data in columns A:Ak. Some of the data contains formulas and when a row of data is complete (i.e. the status in column W is "Y") I would like to copy that row in place and only past the values.
This is what I have tried:
function Optimize() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
// This searches all cells in columns W copies and pastes values in row if cell has value 'Y'
if (row[22] == 'Y') {
sheet.getDataRange(sheet.getCurrentCell().getRow() - 0, 1, 1, sheet.getMaxColumns()).activate();
sheet.getActiveRange().copyTo(sheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
rowsCopiedpasted++;
}
}
}
When I am in the sheet and click on the cell in column A of a row with a "Y" value this works, but I need it to go through the whole sheet and copy/paste values of all rows with "Y" in column W.
Try this:
function Optimize() {
const sh = SpreadsheetApp.getActiveSheet();
const rg = sh.getDataRange();
const vs = rg.getValues();
const lc = sh.getLastColumn();
let o = vs.forEach((r,i) => {
if(r[22] == "Y") {
sh.getRange(i + 1, lc + 1,1,lc).setValues([r])
}
});
}

How to insert two rows for every three rows of number in google sheet using apps script

picture of add rows example
As the picture shows, there are 11 rows of numbers in Column A,
I want to add rows to it and make it shows as the numbers in column D
You see for every 3 rows of number in A, I want to add a new row to its up and bottom, the new row in the top with value "0", and the bottom row with the sum value of these three rows of numbers
for example
the first three rows are 1,2,3, and their sum is "1+2+3=6"
the second three rows are 4,5,6, and the sum is "4+5+6=15"
the third three rows are 7,8,9, and the sum is "7+8+9=24" as picture shows
and you see the last part of the column are "10 and 12", which are two rows
(less than three rows), but in this case, it still need to add two new rows, one row in top with"0" and one row in bottom with its sum "10+12=22"
Right now, I wrote two for loop function to insert the rows
function insertRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("sheet1");
var numRows = SpreadsheetApp.getActiveSpreadsheet().getSheetByName ("sheet1").getLastRow();
for (var i=1; i <= numRows+2; i+=4) {
var addrow = sheet.insertRowsBefore(i,1);
}
}
function insertRows2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("sheet1");
var numRows = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet1").getLastRow();
for (var j=4; j <= numRows+2; j+=5) {
var rowadd= sheet.insertRowsAfter(j,1)
}
}
"insertRows()" is used to insert the head row, "insertRows2()" is used to insert the bottom row,
so far these two functions are working good to insert the rows in right place, but I don't know how to fill in the top rows with "0"s in the loop and how to fill the sum value of each three rows of numbers in the bottom rows.
Do you guys know how to fill in the rows with the right values?
How about this sample script? I think that there are several answers for your situation. So please think of this as one of them.
Flow :
Retrieve values from "A1:A11" of sheet1.
Using the retrieved values, it creates an array for the result.
At first, it separates the values, while inserts 0 to the top and the sum of the end.
As an important point, the array is created to put the spreadsheet as 2 dimensional array.
Put the result values to "D1:19" of sheet1.
Sample script :
function insertRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("sheet1");
var value = sheet.getRange(1, 1, sheet.getLastRow(), 1).getValues().filter(Number);
var result = [];
var temp = [[0]];
var sum = 0;
for (var i = 0; i < value.length; i++) {
temp.push(value[i]);
sum += value[i][0];
if ((i + 1) % 3 == 0 || i == value.length - 1) {
temp.push([sum]);
result = result.concat(temp);
temp = [[0]];
sum = 0;
}
}
sheet.getRange(1, 4, result.length, 1).setValues(result);
}
Note :
When run this sample script, the lastrow of the sheet is changed. So I used filter() to var value = sheet.getRange(1, 1, sheet.getLastRow(), 1).getValues().
If this was not what you want, please tell me. I would like to modify my answer.

Simple nested for loop problems

I am very new to programming and I am trying to create a short macro on google sheets. I want to copy and paste a list of cells a certain number of times based on variables see here. On the first column you have a list of locations and the second column the number of times they should be pasted into a 3rd column (column F). For example i would like to paste A4 fives times into column F4. Then A5 twice into F8 (F4 + 5 rows) and so one until the end of my list.
I came up with the below code but it currently copies each locations of my list 5 times on the same rows (F4-F8) then repeat the same process on the following 5 rows, and so on. I think the issue is with the order of the loops but i can't figure it out really.
Another problem i have is that It only copies each location 5 times (B1), but i am not sure how to make my variable numShift an array for (B4-B16) so that each locations is copied the correct number of times
Any help would be really appreciated !
function myFunction() {
var app = SpreadsheetApp;
var ss = app.getActiveSpreadsheet()
var activeSheet = ss.getActiveSheet()
var numShift = activeSheet.getRange(4,2).getValue()
for (var k=0;k<65;k=k+5){
for (var indexNumLocation=0;indexNumLocation<15;indexNumLocation++){
for (var indexNumShift=0;indexNumShift<numShift;indexNumShift++)
{var locationRange = activeSheet.getRange(4+indexNumLocation,1).getValue();
activeSheet.getRange(4+k+indexNumShift,6).setValue(locationRange);
}
}
}
}
Try this
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lastRow = sheet.getLastRow();
var data = [];
var i, j;
// get times count for each row from B4 to last row
var times = sheet.getRange(4, 2, lastRow - 4, 1).getValues();
// get the data that needs to be copied for each row from A4 to last row
var values = sheet.getRange(4, 1, lastRow - 4, 1).getValues();
// loop over each row
for (i = 4; i <= lastRow; i++) {
// run loop number of times for that row
for (j = 1; j <= times[i]; j++) {
// push the value that needs to be copied in a temporary array
data.push([values[i][0]]);
}
}
// finally put that array data in sheet starting from F4
sheet.getRange(4, 6, data.length, 1).setValues(data);
}

LINQ filtering on a Dictionary<string, IList<string>>

I have code similar to this:
var dict = new Dictionary<string, IList<string>>();
dict.Add("A", new List<string>{"1","2","3"});
dict.Add("B", new List<string>{"2","4"});
dict.Add("C", new List<string>{"3","5","7"});
dict.Add("D", new List<string>{"8","5","7", "2"});
var categories = new List<string>{"A", "B"};
//This gives me categories and their items matching the category list
var result = dict.Where(x => categories.Contains(x.Key));
Key Value
A 1, 2, 3
B 2, 4
What I would like to get is this:
A 2
B 2
So the keys and just the values that are in both lists. Is there a way to do this in LINQ?
Thanks.
Easy peasy:
string key1 = "A";
string key2 = "B";
var intersection = dict[key1].Intersect(dict[key2]);
In general:
var intersection =
categories.Select(c => dict[c])
.Aggregate((s1, s2) => s1.Intersect(s2));
Here, I'm utilizing Enumerable.Intersect.
A somewhat dirty way of doing it...
var results = from c in categories
join d in dict on c equals d.Key
select d.Value;
//Get the limited intersections
IEnumerable<string> intersections = results.First();
foreach(var valueSet in results)
{
intersections = intersections.Intersect(valueSet);
}
var final = from c in categories
join i in intersections on 1 equals 1
select new {Category = c, Intersections = i};
Assuming we have 2 and 3 common to both lists, this will do the following:
A 2
A 3
B 2
B 3

LINQ Grouping: Is there a cleaner way to do this without a for loop

I am trying to create a very simple distribution chart and I want to display the counts of tests score percentages in their corresponding 10's ranges.
I thought about just doing the grouping on the Math.Round((d.Percentage/10-0.5),0)*10 which should give me the 10's value....but I wasn't sure the best way to do this given that I would probably have missing ranges and all ranges need to appear even if the count is zero. I also thought about doing an outer join on the ranges array but since I'm fairly new to Linq so for the sake of time I opted for the code below. I would however like to know what a better way might be.
Also note: As I tend to work with larger teams with varying experience levels, I'm not all that crazy about ultra compact code unless it remains very readable to the average developer.
Any suggestions?
public IEnumerable<TestDistribution> GetDistribution()
{
var distribution = new List<TestDistribution>();
var ranges = new int[] { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110 };
var labels = new string[] { "0%'s", "10%'s", "20%'s", "30%'s", "40%'s", "50%'s", "60%'s", "70%'s", "80%'s", "90%'s", "100%'s", ">110% "};
for (var n = 0; n < ranges.Count(); n++)
{
var count = 0;
var min = ranges[n];
var max = (n == ranges.Count() - 1) ? decimal.MaxValue : ranges[n+1];
count = (from d in Results
where d.Percentage>= min
&& d.Percentage<max
select d)
.Count();
distribution.Add(new TestDistribution() { Label = labels[n], Frequency = count });
}
return distribution;
}
// ranges and labels in a list of pairs of them
var rangesWithLabels = ranges.Zip(labels, (r,l) => new {Range = r, Label = l});
// create a list of intervals (ie. 0-10, 10-20, .. 110 - max value
var rangeMinMax = ranges.Zip(ranges.Skip(1), (min, max) => new {Min = min, Max = max})
.Union(new[] {new {Min = ranges.Last(), Max = Int32.MaxValue}});
//the grouping is made by the lower bound of the interval found for some Percentage
var resultsDistribution = from c in Results
group c by
rangeMinMax.FirstOrDefault(r=> r.Min <= c.Percentage && c.Percentage < r.Max).Min into g
select new {Percentage = g.Key, Frequency = g.Count() };
// left join betweem the labels and the results with frequencies
var distributionWithLabels =
from l in rangesWithLabels
join r in resultsDistribution on l.Range equals r.Percentage
into rd
from r in rd.DefaultIfEmpty()
select new TestDistribution{
Label = l.Label,
Frequency = r != null ? r.Frequency : 0
};
distribution = distributionWithLabels.ToList();
Another solution if the ranges and labels can be created in another way
var ranges = Enumerable.Range(0, 10)
.Select(c=> new {
Min = c * 10,
Max = (c +1 )* 10,
Label = (c * 10) + "%'s"})
.Union(new[] { new {
Min = 100,
Max = Int32.MaxValue,
Label = ">110% "
}});
var resultsDistribution = from c in Results
group c by ranges.FirstOrDefault(r=> r.Min <= c.Percentage && c.Percentage < r.Max).Min
into g
select new {Percentage = g.Key, Frequency = g.Count() };
var distributionWithLabels =
from l in ranges
join r in resultsDistribution on l.Min equals r.Percentage
into rd
from r in rd.DefaultIfEmpty()
select new TestDistribution{
Label = l.Label,
Frequency = r != null ? r.Frequency : 0
};
This works
public IEnumerable<TestDistribution> GetDistribution()
{
var range = 12;
return Enumerable.Range(0, range).Select(
n => new TestDistribution
{
Label = string.Format("{1}{0}%'s", n*10, n==range-1 ? ">" : ""),
Frequency =
Results.Count(
d =>
d.Percentage >= n*10
&& d.Percentage < ((n == range - 1) ? decimal.MaxValue : (n+1)*10))
});
}

Resources